Build a custom NativeScript view component for iOS

Nathan Walker
InstructorNathan Walker

Share this video with your friends

Send Tweet
Published 5 years ago
Updated 3 years ago

Building a custom NativeScript XML view component for iOS entails several interesting notes, like overriding the onLoaded method of the base NativeScript View class. Let’s look at how to properly build a custom component based on an underlying native view widget with NativeScript XML which may come from a CocoaPod. We will learn several important aspects regarding Objective C properties and methods you will need to consider. In particular, we will implement this Objective C view component

[00:00] Let's set up this neat analog clock as a custom iOS view component. Let's grab the podline and get things set up. So we have our internal plug-in setup here, NativeScript Clock platforms iOS, and in the podfile, we'll drop the podline. And we already have a package set up here for NativeScript Clock, and let's just create 'clock.ios.typescript'. And for this view plug-in, we're just going to export a component named 'clock'. And unlike service-level plug-ins, for custom-view components, you want to extend the view class from the UI core view packet. And this provides many conveniences when setting up a custom, NativeScript XML component.

[00:38] First thing we want to do is set up a constructor and we'll call it 'super'. Then we want to create a reference that is going to represent the component on iOS, and then, we're going to create this iOS component with the native API. So if we look on the read-me of this CocoaPod, we see an example of how instantiate this clock view. Let's copy and convert this line. We remove the brackets, this becomes 'allocate' and this is 'emit with frame'. Then our CGRectMake, which is an Objective-C call, we can actually initialize this to zero across the board. This is the x, y, and width and height value. By using the NativeScript view as a super-class automatically handles the measuring of our Native view component here.

[01:20] So let's just declare our clock view here. And since our declarations are not in here, we'll just declare for CGRectMake. Then we want to add two critical getters to our view component. One is the iOS getter, which is just going to return the instance of that Native class. And then, this special '_Native view property' that's also going to return the iOS reference. This is used under the hood. And then, we're going to override at the unloaded method from the view class and ensure that we call it 'super onloaded'. This is very important. If this was omitted, you would not get loaded events dispatched on your custom XML component whenever that view loads.

[01:58] And then, secondly, we will allow the user to actually set explicit widths on the XML to size this at a specific width and height that they would like. And we'll use the iOS reference to the clock view, and we'll just set its frame equal to a CGRectMake, and we'll pass zero -- zero for x and y-- and then, the width and the height that the user might have entered inline on the XML component. Otherwise, we'll just set a default size. And we'll just set a reasonable default size at 300 by 300. At this point, this is actually all we need to create this custom view component. Let's give it a shot.

[02:35] So to use this view component with NativeScript for Angular, we need to make sure that we register this element, so it can be used in the view. We can first import from the NativeScript Clock internal plug-in, and we'll grab that clock class. Then we want to import from NativeScript Angular the register element method. This gives us the ability to register an element by name, and we're going to call it 'clock'. And then, this takes a resolver function that just points to the view class, and in this case, this is our clock class. This allows us to use the view as a standard view component like anything else, so we'll just say 'clock' here.

[03:10] And if we run that we see we have our clock here, but it's slightly askew, and this is because the NativeScript for Angular renderer measures slightly differently. So we're just going to set an explicit width and height on this component. And we could do it inline, which works perfectly well, or we could do it with CSS, and we'll add a clock class with width and height set to 300. And we can see that works just as well. So at this point, this was quite simple. This Native clock view offers many interesting options. Let's implement a few of them to see how easy it is to implement.

[03:42] One, for instance, is animating the clock time. We'll go back to our clock view component and implement a method for this, and we'll just call it 'update time animated'. And we can allow the user to pass in hours, minutes, and seconds, and we want to set our instance to those properties. So we'll take the iOS instance and set hours equal to our argument, and minutes and seconds equal as well. And then, we'll just call that 'instance method and pass true', so you can see it takes a bullion.

[04:09] Now if we go back to our view component here, let's wire up a method that we could tap that would actually cause our clock to animate. So to do this, we're going to add the loaded event here and this is going to call a method. And we'll just say 'clock loaded' and we'll pass along the event, and then our component will just implement that. And so, from this event, we can actually get a reference to our clock component. So we'll call this 'clock' and this is going to be the object key on the event that comes in. So let's keep a reference here to our clock, and it actually will be typed as 'clock'.

[04:40] Then we add a method we combine to a button, which will animate this time. Let's call it 'animate time'. And this, we'll call our clock's 'update time animated method'. And for fun, we'll just pass in random values here. Let's randomize for the hour, and for the minutes, and seconds. Then let's bind that to a button to a the label 'animate time and bind to our tap events'. And for good measure, we'll just put some margin above it, and let's check that out. So we click 'animate time' and we see our clock animate randomly to different times on the clock. Pretty neat.

[05:10] Let's implement a few more methods like, for instance, real time. It'd be great if this automatically updated in real-time to whatever the actual device time is. So let's go back to our view plug-in and let's add a setter for real-time. This will take a bullion, as we can see from the read-me, and we'll just set the iOS instance 'real-time equal to this value'. And let's implement another feature. This set time via touch is pretty neat. It allows you to touch anywhere along the clock to have the time follow your finger. So let's also add a setter for that property, and that also takes a bullion. And we'll just set the instance equal to our value. Now we can use these directly in our component.

[05:48] So let's create a binding to real-time to a property on our component called 'real-time', and we'll do the same for 'set time via touch'. Let's initialize these properties and let's add buttons to enable them. Let's add one to toggle the real-time and another to toggle the touch. This can be 'toggle real-time' and this one can be 'toggle touch'. So now, if we run that if we click 'toggle real-time', we'll notice that actually nothing happens. And if we try 'toggle touch', again, nothing happens. But if we do 'animate time', of course, this still works. So this is very important to understand.

[06:21] So if we go back to our code, we can see we were just using a view binding to this bullion value to toggle this. But our 'animate time' is actually calling a method on our iOS instance. So these view bindings are just setting a property right now in our plug-in. So we can see 'set real-time' is just setting this value to true or false, 'set time via touch' is the same. Now this is a case by case type of thing, depending on the view plug-in that you're implementing.

[06:49] But if we go back to the repo and we look in classes here, we can see this .n file for the implementation, and we can actually look at what's going on. And you'll notice that the real-time property is just a bullion property. So toggling this to 'true' or 'false' is not actually executing anything. We can see this layout subviews that goes through. It checks this bul here only when it's 'true'. And then, when we look right here is where if real-time were 'true', when it laid the subview out, it actually constructs this view, it sets this timer to actually handle the real-time.

[07:23] So we need to make sure that when we toggle that bullion value that we actually execute some type of function to cause our clock to do what we want it to do. The plug-in actually offers a 'start real-time' and 'stop real-time' method. So we'll use these in this case to aid our setter. So in our setter for real-time, we'll just make sure that we start the real-time when it's 'true', and otherwise, stop it. And then, for our 'set time via touch', there's really no other method to call except for reloading the clock. So we'll make sure that every time that this is changed that we actually reload the clock. And now, let's run that.

[07:59] And now, when we toggle real-time, we actually see the clock starting to tick along with time here. And without touch on, we can tap and drag around and see that it does not move. But if we toggle 'touch on', we can see that now we can drag the mouse around and get this smooth touch control of the clock, and our 'animate time' still works.