Use Objective C Delegates in NativeScript for Angular iOS apps

Nathan Walker
InstructorNathan Walker

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 3 years ago

Objective C is a very interesting programming language which offers a lot of power as well as interesting paradigms. Let’s look at some Objective C api’s and how they translate to NativeScript. In particular, let’s learn how to wire up iOS delegates using Filestack's FSPickerDelegate as an example. We will also look at handling WeakRef’s and iterating an NSArray.

[00:00] Let's start by copying the name of this delegate, and above our class we're going to create a new class. We'll just call this filestackDelegate and we want to make sure that it extends NSObject.

[00:11] This is key, NSObject is the base class for most all Objective-C classes, and ensures all Objective-C classes adhere to a common base-level interface. It's often the case that if your class going to interact with common Objective-C functionality like delegates, then you will need to at least extend NSObject.

[00:31] Then when NativeScript constructs this delegate, it will look for an Objective-C protocols property, and this should be equal to a collection of the delegate classes that this adheres to. We can drop in our FSPicker delegate here, and then we'll just declare this at the top.

[00:47] Next, we can go back to our example and much like we did above, we can copy and paste this delegate code. Now we're going to convert and just implement these methods.

[00:56] This one becomes just a standard method call. For now, we're just going to simply log out. To the next one, this is another multi-parameter Objective-C call, so we're just going to take this parameter here, collapse it into this, and capitalize that first character.

[01:13] Then we're just going to pass in the arguments in the right order. This you can see was typed NSError, and since we have our declarations installed, we can actually use that type definition to type it.

[01:24] Again, we'll just log that result, and on to the next. Same multiparameter, we collapse in, and capitalize the first character and we pass in our arguments in the same order.

[01:34] We'll just log this result, and our last method, take our parameter, collapse, and capitalize, and pass the arguments. We can see that this second argument is an NSArray, so we can also type this as NSArray, since we have those types.

[01:49] For now, just log the result. We can see TypeScript helping us out, letting us know that we do need a type defined with this collection.

[01:57] Next, we want to construct this class and assign it to our delegate. We could just say new filestackDelegate, and away we go. However, it would be nice to have a reference back to our filestack class, so that we could actually pass the files that are picked back to our main class. In Objective-C you'll often see that relationship between the delegate to the main class referred to as the owner.

[02:21] In fact, there's a common method call that is used across Objective-C to create this relationship. It's worth noting that you can use any type of set up to actually hold a reference back, but for the sake of consistency with the language, we will actually use this common practice. We're going to name this initWithOwner, and we're going to pass in the owner which is going to be a reference back to our filestack class.

[02:47] However, because Objective-C handles its own garbage collection in a unique way, we want to make sure that this reference back to our main class uses what's known as a weak reference. So we'll type this as a WeakRef, and this will actually be our filestack main class.

[03:05] It will return an instance of this filestack delegate. Inside, we will just create an instance of itself using the new operator, and we'll ensure that this is properly type casted back to itself.

[03:17] Then we'll create a reference property called owner. This will be our weak reference back to our main filestack class.

[03:26] In our static method, we'll just set owner equal to the incoming owner, and just return that instance. Now we can use that to construct our class.

[03:36] We'll say filestackDelegate initWithOwner, and we'll construct a new WeakRef that holds an instance of this main class. Now that will allow us to actually get an instance of that main class to pass files back.

[03:51] We can call up the owner reference, and use WeakRef's API to get that instance. Then we can call up the API. Let's just add a method that will actually take the incoming files that were picked and handle them. We'll just say files, and for now let's just log the result.

[04:10] Back in these methods we can just call our main class here, and we'll pass back the file that was picked. We'll do the same thing here for when it picks multiple files, and this is plural.

[04:22] We are now ready to run and check out how things are wired up. You many notice a hard TypeScript error, this is become we're mixing some DOM APIs with our Objective-C APIs.

[04:32] Let's improve our build script to actually handle these types a little better. Let's go into our package, and instead of specifying the types directly here, let's actually reuse our references that we have set up, and let's try it again.

[04:45] We have one more pesky TypeScript warning. This is because TNS core modules list view actually uses the experimental decorators option, so let's just make that is also added to our build script. Right on the end here, we'll make sure experimental decorators is set to true.

[05:02] Let's try our build one last time. We are in business. Now we could choose maybe from a local album here, and we'll just choose an image.

[05:10] This time we can see the file upload. When it is complete, you will see it hit the logs in our delegate and in our main class here. You can see the delegate FSPicker pickedMediaWithBlob method being called here, and the single file that was picked with the URL to a CDN location that that file actually exists, which we can pull up here.

[05:35] We can see that is exactly the image we uploaded. Further down we can also see that FSPicker didFinishPickingMediaWithBlobs was also called.

[05:44] We can determine that whether a single file or multiple files are picked, both of these delegate methods get called. We have a choice to either use one or the other, and if a delegate passes back a collection, this will be a much more useful method to use, because we can grab all the files the user may have chosen. We may go back to our API and modify it a bit, and not use this delegate method, but instead rely on this one that passes back the collection.

[06:12] To process each one of these, we can actually iterate through an NSArray, but it is a little different than a standard JavaScript array, it actually has a couple different methods. To loop through an NSArray, let's make sure that our argument coming in is typed properly as NSArray. That way TypeScript can help us out. We can use a standard JavaScript loop, but instead of using length, we can use NSArray's count property.

[06:37] Then inside to grab a file, we can use object at index, and our iterator here to actually get a reference to that single file. Then we could print out each file in the loop. If we run that one more time.

[06:51] Now when we upload a file, we'll see it print that collection, and then we'll see the single file in our for loop be printed out here, and of course the file is now available at a cloud URL.

~ 5 years ago

Hey, I am getting this when trying to compile using npm start:

node_modules/typescript/lib/lib.es2015.collection.d.ts(31,11): error TS2300: Duplicate identifier 'MapConstructor'.
node_modules/typescript/lib/lib.es2015.collection.d.ts(68,11): error TS2300: Duplicate identifier 'SetConstructor'.
node_modules/typescript/lib/lib.es2015.iterable.d.ts(118,11): error TS2300: Duplicate identifier 'MapConstructor'.
node_modules/typescript/lib/lib.es2015.iterable.d.ts(135,11): error TS2300: Duplicate identifier 'SetConstructor'.
node_modules/tns-core-modules/declarations.d.ts(51,25): error TS2304: Cannot find name 'FormData'.
node_modules/tns-core-modules/declarations.d.ts(78,25): error TS2304: Cannot find name 'Blob'.
node_modules/tns-core-modules/declarations.d.ts(78,30): error TS2304: Cannot find name 'FormData'.
node_modules/tns-core-modules/declarations.d.ts(84,17): error TS2304: Cannot find name 'FormData'.
node_modules/tns-core-modules/es-collections.d.ts(15,5): error TS2687: All declarations of 'size' must have identical modifiers.
node_modules/tns-core-modules/es-collections.d.ts(19,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'Map' must be of type 'MapConstructor', but here has type '{ new <K, V>(): Map<K, V>; new <K, V>(m: Map<K, V>): Map<K, V>; new <K, V>(l: List<any>): Map<K, ...'.
node_modules/tns-core-modules/es-collections.d.ts(30,14): error TS2300: Duplicate identifier 'MapConstructor'.
node_modules/tns-core-modules/es-collections.d.ts(31,14): error TS2300: Duplicate identifier 'SetConstructor'.
node_modules/tns-core-modules/es-collections.d.ts(39,5): error TS2687: All declarations of 'size' must have identical modifiers.
node_modules/tns-core-modules/es-collections.d.ts(41,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'Set' must be of type 'SetConstructor', but here has type '{ new <T>(): Set<T>; new <T>(s: Set<T>): Set<T>; new <T>(l: List<T>): Set<T>; prototype: Set<any>...'.
node_modules/tns-core-modules/es-collections.d.ts(54,5): error TS2687: All declarations of 'value' must have identical modifiers.
node_modules/tns-core-modules/es6-promise.d.ts(98,5): error TS2687: All declarations of 'prototype' must have identical modifiers.
node_modules/tns-core-modules/es6.d.ts(7,5): error TS2687: All declarations of 'prototype' must have identical modifiers.
node_modules/tns-core-modules/es6.d.ts(9,5): error TS2687: All declarations of 'iterator' must have identical modifiers.
node_modules/tns-core-modules/http/http.d.ts(86,24): error TS2304: Cannot find name 'FormData'.
node_modules/tns-core-modules/weakmap.d.ts(11,13): error TS2403: Subsequent variable declarations must have the same type.  Variable 'WeakMap' must be of type 'WeakMapConstructor', but here has type 'new <K, V>() => WeakMap<K, V>'.

My references.d.ts file looks like this:

/// <reference path="./node_modules/tns-core-modules/tns-core-modules.d.ts" /> 

/// <reference path="./node_modules/tns-platform-declarations/android.d.ts" />
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />

and my build scripts look like this:

"scripts": {
    "build.plugin": "tsc nativescript-filestack/filestack.ios.ts references.d.ts --lib es2016 -d true --experimentalDecorators true && tns plugin remove nativescript-filestack && tns plugin add nativescript-filestack",
    "start": "npm run build.plugin && tns run ios"
~ 5 years ago

I fixed this by copying the references.d.ts file and the tsconfig file from your repo. THen I started getting this weird error: error TS2307: Cannot find module './app.module.ngfactory'. Which I fixed by deleting that file altogether.