Nativescript: Allow iOS root view controller properties to be overridden

Created on 8 Dec 2016  Â·  14Comments  Â·  Source: NativeScript/NativeScript

Hi Folks,

Did you verify this is a real problem by searching [Stack Overflow]

Yes.

Tell us about the problem

There are frequently properties in the base ViewController that need to be overridden in iOS.
A great example of this is prefersStatusBarHidden, which returns a boolean. At this time, there is no way to override this property (and others like it).
The getter should be provided at the time of the "extend" call (whether JS or TypeScript) to be registered with the native side. If you change it later it will only work if you call it from JavaScript, but when Objective-C calls the property it won't check for new implementation because that could be potentially slowing things down.

Which platform(s) does your issue occur on?

iOS

Please provide the following version numbers that your issue occurs with:

  • CLI:
    2.4.1

  • Cross-platform modules:
    2.4.1

  • Runtime(s):
    ios: 2.4.0

  • Plugin(s):
    "@angular/common": "2.1.2",
    "@angular/compiler": "2.1.2",
    "@angular/core": "2.1.2",
    "@angular/forms": "2.1.2",
    "@angular/http": "2.1.2",
    "@angular/platform-browser": "2.1.2",
    "@angular/platform-browser-dynamic": "2.1.2",
    "@angular/router": "3.1.2",
    "nativescript-angular": "1.1.2",
    "nativescript-theme-core": "^0.1.3",
    "reflect-metadata": "~0.1.8",
    "rxjs": "5.0.0-beta.12",
    "tns-core-modules": "2.4.1"

Please tell us how to recreate the issue in as much detail as possible.

This is a request, not a bug.

Is there code involved? If so, please share the minimal amount of code needed to recreate the problem.

This is what I have tried in an Angular component:

private hideStatusBar: boolean = true;
var self = this;
Object.defineProperty(_page.ios /*this is the view controller that owns the property I'm trying to 
overwrite*/, "prefersStatusBarHidden", {
                    get: function () {
                        return self.hideStatusBar;
                    },
                    enumerable: true,
                    configurable: true
                });

Then later

this._page.ios.setNeedsStatusBarAppearanceUpdate();

I've also tried this with the top level view controller:

var ctrl = frameModule.topmost().ios.controller;


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

feature ios

Most helpful comment

@Socha17 I placed the above code in app.ts, using NativeScript Core (not Angular).

Anyways, I do it like this now, and it works with Nativescript 6:

import * as application from 'application'
import * as builder from 'ui/builder'
import * as view from 'ui/core/view'
import {PropertyChangeData} from 'data/observable'
import * as app from './app-root'

class AppRootView {
    static create () {
        const rootView = builder.load('~/app-root.xml', app)
        if (application.ios) {
            const UILayoutViewController = (<any>view.ios.UILayoutViewController) as typeof UIViewController
            const RootViewController = <typeof UIViewController>UIViewController['extend']({
                ...UILayoutViewController.prototype,
                owner: new WeakRef(rootView),
                preferredScreenEdgesDeferringSystemGestures() {
                    return UIRectEdge.Bottom
                }
             }, {
                exposedMethods: {
                    preferredScreenEdgesDeferringSystemGestures: {returns: interop.types.uint32}
                }
            })
            const rootViewController = rootView.viewController = RootViewController.new()
            rootView.on('loaded', () => {
                rootViewController.view.addSubview(rootView.ios)
            })
        }
        return rootView
    }
}

application.run(AppRootView);

Not sure how it would work for Nativescript Angular.

All 14 comments

@NathanaelA I don't think exposing delegates will help in this case as I can't see how the prefersStatusBarHidden can be set trough a delegate.

@vakrilov - The patch wasn't ONLY delegates; it also included the UIViewController. :-) It exported things that needed to be to allow easy extending of them, as right now people are stuck because of the design. I exported it since I happened to run into the same issues. I might have even mentioned it in the long discussion...

https://github.com/NativeScript/NativeScript/pull/2827/commits/9e69d14466fac20dd8c715e32bd499428898470f#diff-4f8787317e942a3868cc7ff32e6e334fR53

@NathanaelA yep, delegates at least have had workarounds. The issue that I'm trying to solve here is view controller properties that are read only. Your approach to export UIViewController JavaScript implementations from page and/or frame might do the trick. I think it's not very pretty, but it is a quick way to get things done. In the long term it would be good to provide an official hook into these types of overrides.

@alexziskind1 - Actually without exporting delegates you get into the same boat... I have ran into multiple places where the only way for me to "add" support for things is to manually change the core modules or to create my own component (meaning that when they change the underlying widgets; my component breaks). That was the whole point to #2827, was to allow me to patch them at runtime so that I didn't have to manually modify tns-core-modules...

However, I am working on a project called TNSCorePatcher which should fix this issue once and for all. Basically it would allow you to have a plugin that said it needs to patch Frame.ios.js and add the following code: xyz: function() { blah blah bah } so that you are able to patch in the missing delegates or properties to the raw source code before it is built but making it much more maintainable. :-D I haven't had time to finish it; but I'm hoping to have it out before 2.5 is released. :grinning:

@NathanaelA That's great! This is such a common problem that I personally deal with (as well as others), that it should really be build in to NativeScript as your #2827 describes. Therefore, these issues are still relevant. And, while a plugin is nice, it's not as stable as official support.

@alexziskind1 Hey Alex, would this code work in this case:

page.ios.prefersStatusBarHidden = true;
page.ios.setNeedsStatusBarAppearanceUpdate();

Hi @atanasovg , Yes, that would be a great addition.

@alexziskind1, @atanasovg, @NathanaelA, @vakrilov please take a look at my proposed solution at https://github.com/NativeScript/ios-runtime/issues/850

Thanks for thinking about this problem. Hopefully your idea can be integrated.

On Jan 4, 2018, at 12:02 PM, Gheric Speiginer notifications@github.com wrote:

@alexziskind1, @atanasovg, @NathanaelA, @vakrilov please take a look at my proposed solution at NativeScript/ios-runtime#850

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

Here is a terribly hacky (but functional) work-around for now (working with [email protected]), at least until the solution I proposed at NativeScript/ios-runtime#850 is implemented:

import * as page from 'ui/page';
import * as frame from 'ui/frame';
import * as color from 'color/color';

if (frame.isIOS) {
    const UIViewControllerImpl = new page.Page().ios.constructor as typeof UIViewController;

    const MyCustumUIViewController = UIViewController['extend'](Object.assign(
      {},
      // merge in the original methods
      ...UIViewControllerImpl.prototype,
      // add additional instance method / property overrides here, such as ...
      {
        preferredScreenEdgesDeferringSystemGestures() {
            console.log("This will be called from native!");
            return UIRectEdge.All;
        }
      }
    ));

    const performNavigation = frame.Frame.prototype['performNavigation'];
    frame.Frame.prototype['performNavigation'] = function(navigationContext:{entry:frame.BackstackEntry}) {
        const page = navigationContext.entry.resolvedPage;
        const controller = (<typeof UIViewController>MyCustumUIViewController).new();
        controller['_owner'] = new WeakRef(page);
        controller.automaticallyAdjustsScrollViewInsets = false;
        controller.view.backgroundColor = new color.Color("white").ios;
        page['_ios'] = controller;
        page.setNativeView(controller.view);
        performNavigation.call(this, navigationContext);
    }
}

Note: the above should happen before the first page is navigated to. YMMV.

Thanks for posting this. I'll give it a try next time I need the workaround.

On Fri, Jan 5, 2018 at 12:32 AM, Gheric Speiginer notifications@github.com
wrote:

Here is a terribly hacky (but functional) work-around for now, at least
until functionality such as the solution I proposed it available:

import * as page from 'ui/page';
import * as frame from 'ui/frame';
import * as color from 'color/color';

if (frame.isIOS) {
const UIViewControllerImpl = (new page.Page()).ios.constructor as typeof UIViewController;

const MyCustumUIViewController = UIViewController['extend'](Object.assign({
    // add instance method / property overrides here ...
    preferredScreenEdgesDeferringSystemGestures() {
        console.log("This will be called from native!");
        return UIRectEdge.All;
    }
}, UIViewControllerImpl.prototype));

const performNavigation = frame.Frame.prototype['performNavigation'];
frame.Frame.prototype['performNavigation'] = function(navigationContext:{entry:frame.BackstackEntry}) {
    const page = navigationContext.entry.resolvedPage;
    const controller = (<typeof UIViewController>MyCustumUIViewController).new();
    controller['_owner'] = new WeakRef(page);
    controller.automaticallyAdjustsScrollViewInsets = false;
    controller.view.backgroundColor = new color.Color("white").ios;
    page['_ios'] = controller;
    page.setNativeView(controller.view);
    performNavigation.call(this, navigationContext);
}

}

Note: the above should happen before the first page is navigated to.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/NativeScript/NativeScript/issues/3264#issuecomment-355476483,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABkAs8gRMHiqfDzGMvJtKLmi6sY2r9wOks5tHbPZgaJpZM4LHR3X
.

--

Alexander Ziskind
(Technical Director)
+1 (855) 656-NUVI

@speigg Great work around! I have a couple quick questions about it. Do you know if it works with "tns-core-modules": "~4.0.0"?

Also were you using Nativescript Angular, if so what file did you place the work around to get it working? I tried main.ts and app.component.ts no luck so far.

It gets inside the if (frame.isIOS) {} code block, but I never see the This will be called from native! console log, any ideas? Thanks again.

@Socha17 I placed the above code in app.ts, using NativeScript Core (not Angular).

Anyways, I do it like this now, and it works with Nativescript 6:

import * as application from 'application'
import * as builder from 'ui/builder'
import * as view from 'ui/core/view'
import {PropertyChangeData} from 'data/observable'
import * as app from './app-root'

class AppRootView {
    static create () {
        const rootView = builder.load('~/app-root.xml', app)
        if (application.ios) {
            const UILayoutViewController = (<any>view.ios.UILayoutViewController) as typeof UIViewController
            const RootViewController = <typeof UIViewController>UIViewController['extend']({
                ...UILayoutViewController.prototype,
                owner: new WeakRef(rootView),
                preferredScreenEdgesDeferringSystemGestures() {
                    return UIRectEdge.Bottom
                }
             }, {
                exposedMethods: {
                    preferredScreenEdgesDeferringSystemGestures: {returns: interop.types.uint32}
                }
            })
            const rootViewController = rootView.viewController = RootViewController.new()
            rootView.on('loaded', () => {
                rootViewController.view.addSubview(rootView.ios)
            })
        }
        return rootView
    }
}

application.run(AppRootView);

Not sure how it would work for Nativescript Angular.

Was this page helpful?
0 / 5 - 0 ratings