Skip to content

Commit 4cc1235

Browse files
docs: initial content for Extending Classes and Conforming to Protoco… (#27)
Co-authored-by: Nathan Walker <[email protected]>
1 parent 9f62edb commit 4cc1235

File tree

2 files changed

+256
-13
lines changed

2 files changed

+256
-13
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
---
2+
title: iOS Subclassing and conforming to protocols
3+
---
4+
<!-- TODO: add Preview -->
5+
6+
## Extending iOS classes
7+
8+
The following example shows how to extend the `UIViewController`:
9+
10+
```js
11+
const MyViewController = UIViewController.extend({
12+
// Override an existing method from the base class.
13+
// We will obtain the method signature from the protocol.
14+
viewDidLoad: function () {
15+
// Call super using the prototype:
16+
UIViewController.prototype.viewDidLoad.apply(this, arguments);
17+
// or the super property:
18+
this.super.viewDidLoad();
19+
20+
// Add UI to the view here...
21+
},
22+
shouldAutorotate: function () { return false; },
23+
24+
// You can override existing properties
25+
get modalInPopover() { return this.super.modalInPopover; },
26+
set modalInPopover(x) { this.super.modalInPopover = x; },
27+
28+
// Additional JavaScript instance methods or properties that are not accessible from Objective-C code.
29+
myMethod: function() { },
30+
31+
get myProperty() { return true; },
32+
set myProperty(x) { },
33+
}, {
34+
name: "MyViewController"
35+
});
36+
```
37+
38+
The NativeScript runtime adds the `.extend` API, as an option, which is available on any platform native class which takes an object containing platform implementations (`classMembers`) for that class and an optional second argument object defining a `nativeSignature` explained below.
39+
40+
You can also use the `@NativeClass()` decorator with standard class `extends` which may feel a bit more natural.
41+
42+
When creating custom platform native classes which extend others, always make sure their name is unique to avoid class name collisions with others on the system.
43+
44+
```ts
45+
@NativeClass()
46+
class JSObject extends NSObject implements NSCoding {
47+
public encodeWithCoder(aCoder) { /* ... */ }
48+
49+
public initWithCoder(aDecoder) { /* ... */ }
50+
51+
public "selectorWithX:andY:"(x, y) { /* ... */ }
52+
53+
// An array of protocols to be implemented by the native class
54+
public static ObjCProtocols = [ NSCoding ];
55+
56+
// A selector will be exposed so it can be called from native.
57+
public static ObjCExposedMethods = {
58+
"selectorWithX:andY:": { returns: interop.types.void, params: [ interop.types.id, interop.types.id ] }
59+
};
60+
}
61+
```
62+
63+
:::warning Note
64+
65+
There should be no TypeScript constructor, because it will not be executed. Instead override one of the `init` methods.
66+
:::
67+
68+
#### Exposed Method Example
69+
70+
As shown above, extending native classes in NativeScript take the following form:
71+
72+
`const <DerivedClass> = <BaseClass>.extend(classMembers, nativeSignature);`
73+
74+
The `classMembers` object can contain three types of methods:
75+
76+
- base class overrides,
77+
- native visible methods, and
78+
- pure JavaScript methods
79+
80+
The pure JavaScript methods are not accessible to native libraries. If you want the method to be visible and callable from the native libraries, pass the `nativeSignature` parameter the needed additional metadata about the method signature to `extend` with needed additional metadata about the method signature.
81+
82+
83+
The `nativeSignature` argument is optional and has the following properties:
84+
85+
- `name` - optional, string with the derived class name
86+
- `protocols` - optional, array with the implemented protocols
87+
- `exposedMethods` - optional, dictionary with method `names` and `native method signature` objects
88+
89+
The `native method signature` object has two properties:
90+
91+
- `returns` - required, `type` object
92+
- `params` - required, an array of `type` objects
93+
94+
The type object in general is one of the `runtime types`:
95+
96+
- A constructor function, that identifies the Objective-C class
97+
- A primitive types in the `interop.types` object
98+
- In rare cases can be a reference type, struct type etc. described with the interop API
99+
100+
101+
The following example is how you can expose a pure JavaScript method to Objective-C APIs:
102+
103+
```js
104+
const MyViewController = UIViewController.extend({
105+
viewDidLoad: function () {
106+
// ...
107+
const aboutButton = UIButton.buttonWithType(UIButtonType.UIButtonTypeRoundedRect);
108+
// Pass this target and the aboutTap selector for touch up callback.
109+
aboutButton.addTargetActionForControlEvents(this, "aboutTap", UIControlEvents.UIControlEventTouchUpInside);
110+
// ...
111+
},
112+
// The aboutTap is a JavaScript method that will be accessible from Objective-C.
113+
aboutTap: function(sender) {
114+
const alertWindow = new UIAlertView();
115+
alertWindow.title = "About";
116+
alertWindow.addButtonWithTitle("OK");
117+
alertWindow.show();
118+
},
119+
}, {
120+
name: "MyViewController",
121+
exposedMethods: {
122+
// Declare the signature of the aboutTap. We can not infer it, since it is not inherited from base class or protocol.
123+
aboutTap: { returns: interop.types.void, params: [ UIControl ] }
124+
}
125+
});
126+
```
127+
128+
### Overriding Initializers
129+
130+
Initializers should always return a reference to the object itself, and if it cannot be initialized, it should return `null`. This is why we need to check if `self` exists before trying to use it.
131+
132+
```js
133+
const MyObject = NSObject.extend({
134+
init: function() {
135+
const self = this.super.init();
136+
if (self) {
137+
// The base class initialized successfully
138+
console.log("Initialized");
139+
}
140+
return self;
141+
}
142+
});
143+
144+
```
145+
146+
## Conforming to Objective-C/Swift protocols
147+
148+
The following example conforms to the `UIApplicationDelegate` protocol:
149+
150+
```js
151+
const MyAppDelegate = UIResponder.extend({
152+
// Implement a method from UIApplicationDelegate.
153+
// We will obtain the method signature from the protocol.
154+
applicationDidFinishLaunchingWithOptions: function (application, launchOptions) {
155+
this._window = new UIWindow(UIScreen.mainScreen.bounds);
156+
this._window.rootViewController = MyViewController.alloc().init();
157+
this._window.makeKeyAndVisible();
158+
return true;
159+
}
160+
}, {
161+
// The name for the registered Objective-C class.
162+
name: "MyAppDelegate",
163+
// Declare that the native Objective-C class will implement the UIApplicationDelegate Objective-C protocol.
164+
protocols: [UIApplicationDelegate]
165+
});
166+
```
167+
168+
Let's look how to declare a delegate in Typescript by setting one for the [Tesseract-OCR-iOS](https://github.com/gali8/Tesseract-OCR-iOS/wiki/Using-Tesseract-OCR-iOS/6510b29bbf18655f29a26f484b00a24cc66ed88b) API
169+
170+
```ts
171+
interface G8TesseractDelegate extends NSObjectProtocol {
172+
preprocessedImageForTesseractSourceImage?(tesseract: G8Tesseract, sourceImage: UIImage): UIImage;
173+
progressImageRecognitionForTesseract?(tesseract: G8Tesseract): void;
174+
shouldCancelImageRecognitionForTesseract?(tesseract: G8Tesseract): boolean;
175+
}
176+
```
177+
178+
Implementing the delegate:
179+
180+
```ts
181+
// native delegates often always extend NSObject
182+
// when in doubt, extend NSObject
183+
@NativeClass()
184+
class G8TesseractDelegateImpl extends NSObject
185+
implements G8TesseractDelegate {
186+
187+
static ObjCProtocols = [G8TesseractDelegate] // define our native protocols
188+
189+
static new(): G8TesseractDelegateImpl {
190+
return <G8TesseractDelegateImpl>super.new() // calls new() on the NSObject
191+
}
192+
193+
preprocessedImageForTesseractSourceImage(tesseract: G8Tesseract, sourceImage: UIImage): UIImage {
194+
console.info('preprocessedImageForTesseractSourceImage')
195+
return sourceImage
196+
}
197+
198+
progressImageRecognitionForTesseract(tesseract: G8Tesseract) {
199+
console.info('progressImageRecognitionForTesseract')
200+
}
201+
202+
shouldCancelImageRecognitionForTesseract(tesseract: G8Tesseract): boolean {
203+
console.info('shouldCancelImageRecognitionForTesseract')
204+
return false
205+
}
206+
207+
}
208+
```
209+
210+
Using the class conforming to the `G8TesseractDelegate`:
211+
212+
```ts
213+
let delegate: G8TesseractDelegateImpl;
214+
215+
function image2text(image: UIImage): string {
216+
let tess: G8Tesseract = G8Tesseract.new()
217+
218+
// The `tess.delegate` property is weak and won't be retained by the Objective-C runtime so you should manually keep the delegate JS object alive as long the tessaract instance is alive
219+
delegate = G8TesseractDelegateImpl.new()
220+
tess.delegate = delegate
221+
222+
tess.image = image
223+
let results: boolean = tess.recognize()
224+
if (results == true) {
225+
return tess.recognizedText
226+
} else {
227+
return 'ERROR'
228+
}
229+
}
230+
231+
```
232+
233+
## Limitations
234+
235+
- You should not extend an already extended class
236+
- You can't override static methods or properties
237+
- You can't expose static methods or properties

content/sidebar.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,15 @@ export default [
224224
text: 'Adding Native Code',
225225
link: '/guide/adding-native-code',
226226
},
227-
{
228-
text: 'Data Binding',
229-
link: '/guide/data-binding',
230-
},
231227
{
232228
text: 'Extending Native Classes',
233-
link: '/guide/subclassing',
229+
link: '/guide/subclassing/',
230+
items: [
231+
{
232+
text: 'iOS',
233+
link: '/guide/extending-classes-and-conforming-to-protocols-ios',
234+
}
235+
]
234236
},
235237
{
236238
text: 'Multithreading',
@@ -263,24 +265,28 @@ export default [
263265
],
264266
},
265267
{
266-
text: 'Property System',
267-
link: '/guide/property-system',
268+
text: 'Animations',
269+
link: '/guide/animations',
268270
},
269271
{
270-
text: 'Error Handling',
271-
link: '/guide/error-handling',
272+
text: 'Gestures',
273+
link: '/guide/gestures',
272274
},
273275
{
274276
text: 'Shared Element Transitions',
275277
link: '/guide/shared-element-transitions',
276278
},
277279
{
278-
text: 'Animations',
279-
link: '/guide/animations',
280+
text: 'Data Binding',
281+
link: '/guide/data-binding',
280282
},
281283
{
282-
text: 'Gestures',
283-
link: '/guide/gestures',
284+
text: 'Property System',
285+
link: '/guide/property-system',
286+
},
287+
{
288+
text: 'Error Handling',
289+
link: '/guide/error-handling',
284290
},
285291
],
286292
},

0 commit comments

Comments
 (0)