From 6d7524803f9b3c94f9d972786594ebe82679f4a8 Mon Sep 17 00:00:00 2001 From: Ombuweb Date: Sun, 20 Nov 2022 19:01:47 +0100 Subject: [PATCH 1/4] docs: initial content for Extending Classes and Conforming to Protocols iOS --- ...classes-and-conforming-to-protocols-ios.md | 229 ++++++++++++++++++ content/sidebar.ts | 10 + 2 files changed, 239 insertions(+) create mode 100644 content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md diff --git a/content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md b/content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md new file mode 100644 index 00000000..c870fa7e --- /dev/null +++ b/content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md @@ -0,0 +1,229 @@ +--- +title: iOS Subclassing and conforming to protocols in NativeScript +--- + + +## Extending iOS classes in NativeScript + +The following example shows how to extend the `UIViewController`: + +```js +var MyViewController = UIViewController.extend({ + // Override an existing method from the base class. + // We will obtain the method signature from the protocol. + viewDidLoad: function () { + // Call super using the prototype: + UIViewController.prototype.viewDidLoad.apply(this, arguments); + // or the super property: + this.super.viewDidLoad(); + + // Add UI to the view here... + }, + shouldAutorotate: function () { return false; }, + + // You can override existing properties + get modalInPopover() { return this.super.modalInPopover; }, + set modalInPopover(x) { this.super.modalInPopover = x; }, + + // Additional JavaScript instance methods or properties that are not accessible from Objective-C code. + myMethod: function() { }, + + get myProperty() { return true; }, + set myProperty(x) { }, +}, { + name: "MyViewController" +}); + +``` + +```ts +// A native class with the name "JSObject" will be registered, so it should be unique +@NativeClass() +class JSObject extends NSObject implements NSCoding { + public encodeWithCoder(aCoder) { /* ... */ } + + public initWithCoder(aDecoder) { /* ... */ } + + public "selectorWithX:andY:"(x, y) { /* ... */ } + + // An array of protocols to be implemented by the native class + public static ObjCProtocols = [ NSCoding ]; + + // A selector will be exposed so it can be called from native. + public static ObjCExposedMethods = { + "selectorWithX:andY:": { returns: interop.types.void, params: [ interop.types.id, interop.types.id ] } + }; +} +``` + +:::warning Note + +There should be no TypeScript constructor, because it will not be executed. Instead override one of the `init` methods. +::: + +#### Exposed Method Example + +As shown above, extending native classes in NativeScript take the following form: + +`var = .extend(classMembers, nativeSignature);` + +The `classMembers` object can contain three types of methods: + +- base class overrides, +- native visible methods, and +- pure JavaScript methods + +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. + + +The `nativeSignature` argument is optional and has the following properties: + +- `name` - optional, string with the derived class name +- `protocols` - optional, array with the implemented protocols +- `exposedMethods` - optional, dictionary with method `names` and `native method signature` objects + +The `native method signature` object has two properties: + +- `returns` - required, `type` object +- `params` - required, an array of `type` objects + +The type object in general is one of the `runtime types`: + +- A constructor function, that identifies the Objective-C class +- A primitive types in the `interop.types` object +- In rare cases can be a reference type, struct type etc. described with the interop API + + +The following example is how you can expose a pure JavaScript method to Objective-C APIs: + +```js +var MyViewController = UIViewController.extend({ + viewDidLoad: function () { + // ... + var aboutButton = UIButton.buttonWithType(UIButtonType.UIButtonTypeRoundedRect); + // Pass this target and the aboutTap selector for touch up callback. + aboutButton.addTargetActionForControlEvents(this, "aboutTap", UIControlEvents.UIControlEventTouchUpInside); + // ... + }, + // The aboutTap is a JavaScript method that will be accessible from Objective-C. + aboutTap: function(sender) { + var alertWindow = new UIAlertView(); + alertWindow.title = "About"; + alertWindow.addButtonWithTitle("OK"); + alertWindow.show(); + }, +}, { + name: "MyViewController", + exposedMethods: { + // Declare the signature of the aboutTap. We can not infer it, since it is not inherited from base class or protocol. + aboutTap: { returns: interop.types.void, params: [ UIControl ] } + } +}); +``` + +### Overriding Initializers +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. + +```js +var MyObject = NSObject.extend({ + init: function() { + var self = this.super.init(); + if (self) { + // The base class initialized successfully + console.log("Initialized"); + } + return self; + } +}); + +``` + +## Conforming to Objective-C/Swift protocols in NativeScript + +The following example conforms to the `UIApplicationDelegate` protocol: + +```js +var MyAppDelegate = UIResponder.extend({ + // Implement a method from UIApplicationDelegate. + // We will obtain the method signature from the protocol. + applicationDidFinishLaunchingWithOptions: function (application, launchOptions) { + this._window = new UIWindow(UIScreen.mainScreen.bounds); + this._window.rootViewController = MyViewController.alloc().init(); + this._window.makeKeyAndVisible(); + return true; + } +}, { + // The name for the registered Objective-C class. + name: "MyAppDelegate", + // Declare that the native Objective-C class will implement the UIApplicationDelegate Objective-C protocol. + protocols: [UIApplicationDelegate] +}); +``` + +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 + +```ts +interface G8TesseractDelegate extends NSObjectProtocol { + preprocessedImageForTesseractSourceImage?(tesseract: G8Tesseract, sourceImage: UIImage): UIImage; + progressImageRecognitionForTesseract?(tesseract: G8Tesseract): void; + shouldCancelImageRecognitionForTesseract?(tesseract: G8Tesseract): boolean; +} +``` + +Implementing the delegate: + +```ts +@NativeClass() +class G8TesseractDelegateImpl + extends NSObject // native delegates mostly always extend NSObject + implements G8TesseractDelegate { + + static ObjCProtocols = [G8TesseractDelegate] // define our native protocols + + static new(): G8TesseractDelegateImpl { + return super.new() // calls new() on the NSObject + } + + preprocessedImageForTesseractSourceImage(tesseract: G8Tesseract, sourceImage: UIImage): UIImage { + console.info('preprocessedImageForTesseractSourceImage') + return sourceImage + } + + progressImageRecognitionForTesseract(tesseract: G8Tesseract) { + console.info('progressImageRecognitionForTesseract') + } + + shouldCancelImageRecognitionForTesseract(tesseract: G8Tesseract): boolean { + console.info('shouldCancelImageRecognitionForTesseract') + return false + } + +} +``` + +Using the class conforming to the `G8TesseractDelegate`: + +```ts +function image2text(image: UIImage): string { + let delegate: G8TesseractDelegateImpl = G8TesseractDelegateImpl.new() + let tess: G8Tesseract = G8Tesseract.new() + tess.delegate = delegate + /*============================= + = NOTES = + =============================*/ + // 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 + /*===== End of NOTES ======*/ + tess.image = image + let results: boolean = tess.recognize() + if (results == true) { + return tess.recognizedText + } else { + return 'ERROR' + } +} + +``` +## Limitations +- You shouldn't extend an already extended class +- You can't override static methods or properties +- You can't expose static methods or properties diff --git a/content/sidebar.ts b/content/sidebar.ts index c95688b9..68f82d45 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -99,6 +99,16 @@ export default [ { text: 'Advanced Concepts', items: [ + { + text: 'Extending Classes and interfaces', + link: '/guide/subclassing/', + items: [ + { + text: 'iOS', + link: '/guide/subclassing/extending-classes-and-conforming-to-protocols-ios', + } + ] + }, { text: 'Multithreading', link: '/guide/multithreading', From 5655731a0c95f9e662508c3a2454636118903e3b Mon Sep 17 00:00:00 2001 From: Ombuweb Date: Mon, 21 Nov 2022 18:51:25 +0100 Subject: [PATCH 2/4] fix: shortened link --- .../extending-classes-and-conforming-to-protocols-ios.md | 6 +++--- content/sidebar.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename content/guide/{subclassing => }/extending-classes-and-conforming-to-protocols-ios.md (97%) diff --git a/content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md b/content/guide/extending-classes-and-conforming-to-protocols-ios.md similarity index 97% rename from content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md rename to content/guide/extending-classes-and-conforming-to-protocols-ios.md index c870fa7e..64b4d55b 100644 --- a/content/guide/subclassing/extending-classes-and-conforming-to-protocols-ios.md +++ b/content/guide/extending-classes-and-conforming-to-protocols-ios.md @@ -1,9 +1,9 @@ --- -title: iOS Subclassing and conforming to protocols in NativeScript +title: iOS Subclassing and conforming to protocols --- -## Extending iOS classes in NativeScript +## Extending iOS classes The following example shows how to extend the `UIViewController`: @@ -138,7 +138,7 @@ var MyObject = NSObject.extend({ ``` -## Conforming to Objective-C/Swift protocols in NativeScript +## Conforming to Objective-C/Swift protocols The following example conforms to the `UIApplicationDelegate` protocol: diff --git a/content/sidebar.ts b/content/sidebar.ts index 68f82d45..85b6226f 100644 --- a/content/sidebar.ts +++ b/content/sidebar.ts @@ -105,7 +105,7 @@ export default [ items: [ { text: 'iOS', - link: '/guide/subclassing/extending-classes-and-conforming-to-protocols-ios', + link: '/guide/extending-classes-and-conforming-to-protocols-ios', } ] }, From 0160fdc04167bdf9d901d481f3cc131300e078a0 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 2 Sep 2023 09:26:33 -0700 Subject: [PATCH 3/4] Update content/guide/extending-classes-and-conforming-to-protocols-ios.md --- .../guide/extending-classes-and-conforming-to-protocols-ios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/guide/extending-classes-and-conforming-to-protocols-ios.md b/content/guide/extending-classes-and-conforming-to-protocols-ios.md index 64b4d55b..3c64cdd2 100644 --- a/content/guide/extending-classes-and-conforming-to-protocols-ios.md +++ b/content/guide/extending-classes-and-conforming-to-protocols-ios.md @@ -224,6 +224,6 @@ function image2text(image: UIImage): string { ``` ## Limitations -- You shouldn't extend an already extended class +- You should not extend an already extended class - You can't override static methods or properties - You can't expose static methods or properties From 0d2fc7edcffa254749844a27423dad8f0862001f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sat, 2 Sep 2023 11:35:23 -0700 Subject: [PATCH 4/4] chore: cleanup --- ...classes-and-conforming-to-protocols-ios.md | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/content/guide/extending-classes-and-conforming-to-protocols-ios.md b/content/guide/extending-classes-and-conforming-to-protocols-ios.md index 3c64cdd2..68a985c0 100644 --- a/content/guide/extending-classes-and-conforming-to-protocols-ios.md +++ b/content/guide/extending-classes-and-conforming-to-protocols-ios.md @@ -8,7 +8,7 @@ title: iOS Subclassing and conforming to protocols The following example shows how to extend the `UIViewController`: ```js -var MyViewController = UIViewController.extend({ +const MyViewController = UIViewController.extend({ // Override an existing method from the base class. // We will obtain the method signature from the protocol. viewDidLoad: function () { @@ -33,11 +33,15 @@ var MyViewController = UIViewController.extend({ }, { name: "MyViewController" }); - ``` +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. + +You can also use the `@NativeClass()` decorator with standard class `extends` which may feel a bit more natural. + +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. + ```ts -// A native class with the name "JSObject" will be registered, so it should be unique @NativeClass() class JSObject extends NSObject implements NSCoding { public encodeWithCoder(aCoder) { /* ... */ } @@ -65,7 +69,7 @@ There should be no TypeScript constructor, because it will not be executed. Inst As shown above, extending native classes in NativeScript take the following form: -`var = .extend(classMembers, nativeSignature);` +`const = .extend(classMembers, nativeSignature);` The `classMembers` object can contain three types of methods: @@ -97,17 +101,17 @@ The type object in general is one of the `runtime types`: The following example is how you can expose a pure JavaScript method to Objective-C APIs: ```js -var MyViewController = UIViewController.extend({ +const MyViewController = UIViewController.extend({ viewDidLoad: function () { // ... - var aboutButton = UIButton.buttonWithType(UIButtonType.UIButtonTypeRoundedRect); + const aboutButton = UIButton.buttonWithType(UIButtonType.UIButtonTypeRoundedRect); // Pass this target and the aboutTap selector for touch up callback. aboutButton.addTargetActionForControlEvents(this, "aboutTap", UIControlEvents.UIControlEventTouchUpInside); // ... }, // The aboutTap is a JavaScript method that will be accessible from Objective-C. aboutTap: function(sender) { - var alertWindow = new UIAlertView(); + const alertWindow = new UIAlertView(); alertWindow.title = "About"; alertWindow.addButtonWithTitle("OK"); alertWindow.show(); @@ -122,12 +126,13 @@ var MyViewController = UIViewController.extend({ ``` ### Overriding Initializers + 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. ```js -var MyObject = NSObject.extend({ +const MyObject = NSObject.extend({ init: function() { - var self = this.super.init(); + const self = this.super.init(); if (self) { // The base class initialized successfully console.log("Initialized"); @@ -143,7 +148,7 @@ var MyObject = NSObject.extend({ The following example conforms to the `UIApplicationDelegate` protocol: ```js -var MyAppDelegate = UIResponder.extend({ +const MyAppDelegate = UIResponder.extend({ // Implement a method from UIApplicationDelegate. // We will obtain the method signature from the protocol. applicationDidFinishLaunchingWithOptions: function (application, launchOptions) { @@ -173,9 +178,10 @@ interface G8TesseractDelegate extends NSObjectProtocol { Implementing the delegate: ```ts +// native delegates often always extend NSObject +// when in doubt, extend NSObject @NativeClass() -class G8TesseractDelegateImpl - extends NSObject // native delegates mostly always extend NSObject +class G8TesseractDelegateImpl extends NSObject implements G8TesseractDelegate { static ObjCProtocols = [G8TesseractDelegate] // define our native protocols @@ -204,15 +210,15 @@ class G8TesseractDelegateImpl Using the class conforming to the `G8TesseractDelegate`: ```ts +let delegate: G8TesseractDelegateImpl; + function image2text(image: UIImage): string { - let delegate: G8TesseractDelegateImpl = G8TesseractDelegateImpl.new() let tess: G8Tesseract = G8Tesseract.new() - tess.delegate = delegate - /*============================= - = NOTES = - =============================*/ + // 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 - /*===== End of NOTES ======*/ + delegate = G8TesseractDelegateImpl.new() + tess.delegate = delegate + tess.image = image let results: boolean = tess.recognize() if (results == true) { @@ -223,7 +229,9 @@ function image2text(image: UIImage): string { } ``` + ## Limitations + - You should not extend an already extended class - You can't override static methods or properties - You can't expose static methods or properties