Learn how to construct sophisticated custom components using UIViewController. Understand how to construct UIButton's programmatically and attach tap events via NativeScript using the static ObjCExposedMethods property. Also learn how to save images to the user's iPhone Camera Roll using native api's. Specifically we will look at creating a custom {N} view component for SwiftyCam (a Snapchat inspired implementation: https://github.com/Awalz/SwiftyCam).
[00:00] To implement this Snapchat-inspired camera, let's first take our pod line and set up our plugin. We'll create a NativeScript SwiftyCam folder, with our platforms iOS folder, and in our pod file we'll drop the pod line. Then we'll create SwiftyCam iOS. I'll export a class SwiftyCam and we'll actually extend the stack layout.
[00:20] Looking back at the ReadMe, we can see there's a couple prerequisites for iOS10. Let's make sure we take care of these.
[00:25] Let's go to our app's app resources, and in iOS, and let's drop these property keys. Moving further down the example, we can see we need a custom view controller that extends SwiftyCam view controller. Before we go any further, let's actually see what classes are exposed that we can use.
[00:41] Let's build our plugin with this command, and let's make sure we have added the plugin to our project doing tns plugin add nativescript-swiftycam. Now we can generate the declarations using tns typescript-declarations-path, and we'll just set it to a local folder called typingz with a Z, and run tns build iOS, and we'll see an error right away.
[01:02] This is referring to the fact that we need to set our Swift version correctly.
[01:05] Let's go back to our pod file and add the post install hook to set the Swift version to 3.0Let's clean the platforms folder for good measure, and also make sure we remove and add our plugin back fresh, this ensures that it gets the proper updates that we just made to the pod file, and let's try that again.
[01:21] This time it succeeded, and it generated a typingz folder. If we look in either one of these and we scroll through the list, we'll see that it generated declarations for SwiftyCam.
[01:31] We can see we have SwiftyCam button exposed, and we also have SwiftyCam view controller. We can use these declarations right away. Let's first go to our ts.config and make sure that it excludes both of these folders when we run our build, then we can go to references and just add a line to reference that i386 folder for the SwiftyCam declarations.
[01:51] Now we can get some help when we create our custom view controller, we'll call it mySwifty, and have it extend the SwiftyCam view controller.
[01:59] We'll start by just overloading the viewDidLoad method of uiViewController. We'll make sure we call superViewDidLoad, and we can start setting things up. We'll start out by holding a reference to our custom view controller in our custom component here, and we'll set up a way to enable this custom view.
[02:18] We'll set up a local reference to track this value, and when it's enabled, we'll just construct our custom view controller using the new operator. For this implementation, we're just going to display this camera view full screen.
[02:30] We're going to use a reference to the root view controller, and we'll use that to present the view controller. In this case, it's our Swifty reference to our custom view controller. We'll enable animation, and we could add a callback hook for when it actually is presented, but for now we'll just pass null.
[02:45] Let's go to our app component now, and let's make sure we register a new element called SwiftyCam and require it from our SwifyCam plugin. Now we can just use that in our view.
[02:55] We've added a build plugin script to make sure it builds our plugin fresh and removes and adds the plugin back, and then this start script that makes sure that that gets run before it runs the app. Let's use our scripts. At this point our app runs, but of course it doesn't do anything. Let's add a button to actually enable our SwiftyCam.
[03:13] We'll create a local reference to our SwiftyCam component, which we can use to pass our enable set to true. Just so it looks a little better, we'll add some classing.
[03:23] Let's see if we can enable it. We can see that with the simulator, we can't capture media here, so we'll have to run this on our device only. On the device, if we choose open SwiftyCam, we can access the camera, we'll access the microphone, and we can see things running. Let's add some buttons to control our camera.
[03:41] SwiftyCam offers this nice button, which automatically sets up long-press gestures and tap gestures to engage the camera.
[03:47] Let's go back to our plugin and in our custom view controller, let's create a reference of the new SwiftyCam button. We'll pass in the frame param equal to the UI screen's main screen bounds. We'll just make sure we typecast this properly.
[04:00] We can see the delegate should be set to the SwiftyCam view controller, so we'll set the delegate equal to this. But we're seeing some red, so there's a potential issue here. In the ReadMe it mentions we need the SwiftyCam view controller delegate.
[04:13] If we look at our SwiftyCam declarations, we can see that there is actually no delegate exposed, and to get access to that we actually need to fork this repo and make a few modifications. Inside the source on that project, we can see that the delegate sure enough is not exposed. We want to make sure that we add this @Objective-C modifier, and let's commit and push that up.
[04:36] We want to modify our pod file to actually use our fork. Let's clear platforms for good measure, rebuild our plugin, and generate the typingz one more time.
[04:44] If we take a look, we actually do have more definitions in here, we actually have our SwiftyCam view controller delegate, and our SwiftyCam button delegate which provides the delegate property which now removes that red squiggly in our custom plugin here. To finish this out we can call up our view, and we can call addSubView to actually add that Swifty button.
[05:05] Looking back at the ReadMe we can see this SwiftyCam view controller delegate, and we need to make sure that the camera delegate property is set to an instance of that.
[05:13] Let's start by creating a class, we'll call this swiftyDelegate, making sure it extends nsobject, and then let's set the static Objective-C protocols to the SwiftyCam view controller delegate. Let's set up a standard Objective-C pattern, which sets up a weak ref back to our mySwifty view controller.
[05:29] We'll set up a static initWithOwner, which creates an instance of the delegate, assigning the owner relationship and handing back the delegate.
[05:38] We can go to the SwiftyCam view controller delegate interface in our declarations and actually take all the methods from here, drop them here, and actually implement them.
[05:47] For all the implementations we'll just simply log that that was called. It's worth mentioning that you could implement the Swifty view controller delegate since we have the declarations, and TypeScript would tell you that in fact a particular method was not implemented.
[06:00] Doing so is a nice way to determine what methods actually are available to implement. Let's go back to our custom view controller and make sure we set the camera delegate equal to our new swiftyDelegate class using the initWithOwner static method.
[06:13] When we tap on the screen, you can see we get the SwiftyCamDidTake, and it prints out the UI image of the photo. Let's do something with that photo.
[06:22] Let's actually write that photo to the camera roll. We can use UIImageWrite to save to photos album, and we can just pass along that UI image. For all the rest of the arguments we can just pass null for this example. Let's also grab our owner, get the reference, and let's add a method tookPhoto, where we can pass this photo that was taken to our custom view controller.
[06:44] From here it'd be nice to actually call up our SwiftyCam component, to perhaps send an event and even add to our stack layout.
[06:51] Let's add a reference back to our custom NativeScript view component, and provide a setter to set that. In our NativeScript view component, we'll just set that to our instance. Then we can provide a sendEvent method, perhaps, that could tap into NativeScript's notify API where we can send out the event by name with the reference back to this, and passing along the data.
[07:12] For fun, let's also add a showImage method that will just construct a NativeScript image, and we'll make sure we have that imported. It will set the source equal to that photo, then we'll add this image as a child to our stack layout.
[07:23] Lastly, let's just provide a close method, that will just set enable equal to false, so in our setter we can close the view controller with the rootViewController dismissViewController animated completed.
[07:35] Let's add two buttons to our camera overlay, and to help facilitate this we'll create a reusable function that will init the UI button with the frame we pass, and we'll set the tile to our label to the normal UI control state. We'll set titleColor for state so we can see it, to just the white color for the normal state, and we'll adjust the title label font using UIFontBold system font of size of 30.
[07:57] To add a tap event, we'll pass along our target to the addTargetAction for control events, and we'll pass our event name and this will be for the touchdown control event, ensuring we return our UI button.
[08:08] Let's use our utility method to create a close button, where the target will be this, and for the frame positioning, we'll set it 20 pixels from the right, 60 pixels down, and 50/50 on the width and height. Our label will just be an X character, and the event name is going to be closeTap.
[08:25] Let's implement that method, which is going to call our custom NativeScript component and close. Because these button events need to be exposed to Objective-C, we'll set our static Objective-CExposed methods, with the key to reference the name of that method, which will set its returns value to the global Interop Types of void.
[08:42] We'll add our close button to the view, and for fun we'll create another one for our switch camera.
[08:47] For the frame on this one, we'll set it to the right side, just 120 pixels off the right side of the screen, still 60 pixels down, and 100 width. The label will say switch, and the event name will be switchCam, and we'll add the button. The switchCam method will just call switchCamera from the Swifty view controller, just make sure we also expose switchCam.
[09:07] Let's make sure our custom view controller calls the showImage to add the photo to the stack layout, and also emits an event called tookPhoto, passing along that UI image.
[09:16] Because we are calling writeToSavedPhotosAlbum, we'll want to make sure that our app has one more property key in the P list, which is the NSPhotoLibraryUsageDsecription. We can now go to our app component template, and we can bind that tookPhoto event that we're emitting out now. We'll bind it to a method called tookPhoto, and we'll print out that it was handled in the app component.
[09:36] Now we will see our X and switch button, and when we tap switch, hello there, we can see it switches the camera back and forth. When we tap it will take the photo, we can see the image printed out, and the tookPhoto even from the app component. When we close the view with our X button, we can see the image in our custom view component.