How to present iOS Keyboard extension using presentViewController - ios

My goal is to present the iOS Keyboard with the method presentViewController:animated:completion whenever the keyboard has successfully instantiated, taking advantage of the smooth upward animation.
Some very brief background: the keyboard extension project is written in Objective-C, with KeyboardViewController.h/.m handling the logic and some basic view layout from a inputView.xib.
Based on this SO question, several of the the answers suggested using the approach of calling presentViewController from [UIApplication sharedApplication].keyWindow.rootViewController. The problem is, as this is an iOS extension, when I try to replicate this method, I get the error that sharedApplication is not available. I was wondering if a workaround exists where I could present the Keyboard with the method presentViewController, either somehow via itself or via a super? In my current attempts, calling [self presentViewController: self...] causes an exception.
Much appreciated!

It can't be done.
One can actually acquire a reference to [UIApplication sharedApplication] easily, the most straightforward (though liable to get you rejected in App Store review) would be:
Class appClass = NSClassFromString(#"UIApplication");
id app = [appClass performSelector:#selector(sharedApplication)];
Running on the iPhone 5 simulator, [app keyWindow] returns nil when called from the principal input view controller's viewDidAppear: method.
[UIApplication windows], however, does not, so we could try
[[[app windows].firstObject rootViewController] presentViewController:self
animated:YES
completion:nil];
...but it just throws an exception:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present
modally an active controller .'
Walking the view hierarchy starting from UIApplication windows] reveals an interesting fact: the window of our UIInputViewController isn't in this window hierarchy. self.view.window returns a separate _UIHostedWindow with no superView.
Furthermore, There is no variation across apps of the addresses in memory of windows returned from [UIApplication sharedApplication].
Conclusion: the UIApplication we get from sharedApplication is the one for our process, not for the host process. Presenting on views and windows it owns therefore is pointless, because its views aren't visible to the user.

Related

"Application windows are expected to have a root view controller" message when adding view immediately after launch, iOS 9 only

My app sends a request at startup and displays a brief message to users when it succeeds, by means of MTStatusBarOverlay. Unfortunately, my current implementation seems to run afoul of iOS 9's view life cycle paradigms. I get the message
Application windows are expected to have a root view controller at the end of application launch
and the app crashes. The app works fine on iOS 7 & 8.
From searching around online, it seems like this might occur when trying to add the message view to the view hierarchy before the root view controller is established for the app's UIWindow, but that doesn't seem to be the case here, see below.
Here is an excerpt of the UIApplicationDelegate implementation:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[...]
self.window.rootViewController = [[MyViewController alloc] init];
[...]
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[MyDataManager sendRequestWithCompletion:^{
// Displays a message with MTStatusBarOverlay
[self showSuccessOverlay];
}];
}
application:didFinishLaunchingWithOptions: is called before applicationDidBecomeActive: so it seems like there should never be an issue with rootViewController being established.
Why is this happening? What's different about iOS 9 that's causing the app to break?
MTStatusBarOverlay is a subclass of UIWindow, so instantiating one during app launch adds that UIWindow to the list that iOS checks for a populated rootViewController when launch completes.
I was able to work around the issue by instantiating and assigning a dummy controller before using the overlay, like so:
[MTStatusBarOverlay sharedInstance].rootViewController = [UIViewController new];
[[MTStatusBarOverlay sharedInstance] postMessage:#"Message"];

iOS app crashing after setting UIWindow rootViewController in iOS 9

My app was working great until running it on iOS 9.
In the AppDelegate, I check whether the user is logged in or not. If they're not logged in, I send them to the login screen with the following line:
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"Login"];
After the user logs in, I attempt to send them to the main app with the following line:
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"Tabs"];
This worked in iOS 8, but now it crashes the app. And it's a bad crash. The whole device has to reboot (although it's a very quick one, so I think it's more like a SpringBoard reboot or something).
I'm seriously at a loss as to why the first line works and the second doesn't. Any help is much appreciated!
EDIT
I ended up recreating the entire UIViewController flow that was crashing and it works fine now. As far as I can tell it was some strange bug in the new Xcode. Not considering this an answer, so if anyone has a true answer then feel free to share it.
Not sure why, but this works for me in iOS9. It might be that you're trying to do this already in the AppDelegate before the window has been properly loaded?
I've got a first ViewController in the storyboard and does this redirecting there instead.
UIViewController *viewController = [[UIStoryboard storyboardWithName:#"Storyboard"
bundle: nil] instantiateViewControllerWithIdentifier:#"viewController"];
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:viewController];
The code to set the root view controller works in iOS9 without issues.
[[[[UIApplication sharedApplication] delegate] window] setRootViewController:viewController];
If you get a crash in that line, it surely means that the viewController initialization logic or any of its subviews are throwing at some point.
Problem is that working in the simulator the app will crash with no errors in the output. Best thing to do here is setting a breakpoint in the viewController logic (ie: viewDidLoad) and check what line is causing the error.

UIViewController released without viewWillDisappear: being called?

I have a basic implementation of UITableViewController that presents a UIDocument. A reference to the document is kept as instance variable of the view controller.
In viewWillDisappear: I close the document if the view controller is removed (and not a sub-viewcontroller pushed to the stack):
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (!self.keepDocumentOpenAfterNavigatingAway) {
[self.deckDocument closeWithCompletionHandler:^(BOOL success){
// As side effect, this retains 'self' until
// the document is closed:
self.document = nil;
}];
}
}
Now, once in a while I receive crash reports caused by this (app is for iOS 7):
objc_msgSend() selector name: _setInConflict:
I suspect that maybe the view controller was deallocated without viewWillDisappear: being called. Then the document would have been deallocated along with it without being closed. When the conflict comes in from iCloud, the app crashes.
Maybe this happens while the app is in the background and runs low on memory? Is there such a scenario where a view controller would get deallocated without viewWillDisappear: being called?
EDIT: To clarify, this only happens in production, I was not able to reproduce this myself. That's why I'm assuming it might be related to a low memory/background scenario. I issued low memory warnings in the simulator, without any luck.

iOS, use AppDelegate in InterfaceBuilder, two instances created

In an iOS application I implemented some application logic in the AppDelegate.
Several view controllers etc. need to call the AppDelegate, so i placed the AppDelegate in Interface Builder and gave the ViewControllers an IBOutlet AppDelegate* and drew a link to it.
In the app I see now that there seem to be two instances of the AppDelegate created, one that is called and that gets all the notifications and one that is linked to the ViewControllers.
So i changed my app to use in the ViewControllers:
app_del = (AppDelegate*)[[UIApplication sharedApplication] delegate].
Then I'm calling this appDelegate. But this also is a different instance than the one that gets the notifications. Can anybody explain why?
Can anybody tell me what is going wrong? My suspicion from googling is that the iOS creates the AppDelegate instance and Interface Builder does not know about that one and creates another one? Is that correct?
Can anybody give me some hint on how to best create/plan instances in Interface Builder and in what order they are created in application at startup and in what notification I can rely that they are all created?
Thanks for any hints,
Torsten.
Your suspicion is correct. iOS creates an instance of your application delegate when launching your app, this instance is the one that is registered to receive all of the delegate events and so forth. See here.
Anything you add in to a xib or storyboard will be a new instance, and will not work.
To obtain a pointer to the application delegate, use this method:
[[UIApplication sharedApplication] delegate];
There is a school of thought that says you shouldn't overuse the application delegate as a conveniently available global data or method store, but that is outside the scope of this answer.

iOS: Hiding sensitive information on the screen when app is backgrounded

When a foreground app gets backgrounded (e.g. Home button gets pressed), how can I change elements on the topmost view controller prior to when iOS takes a snapshot of it and starts the animation to show the next screen?
I ask because I'm writing an app requiring HIPAA compliance, and I am concerned that the snapshot that the OS takes in order to do this animation sometimes contains sensitive data which should not be visible even for a split second when the app gets foregrounded later.
I'm aware that view controllers have lifecycle methods such as viewWillDisappear which might be usable, but I have a lot of controllers and I'd rather just have something in my App Delegate to handle this (e.g. by adding an opaque full-screen UIImageView overlay) rather than having to write custom code for this in every last controller.
I tried putting overlay-generating code in applicationWillResignActive, and I've been digging with Apple's docs and Google, but it's not working. I suspect the screenshot gets taken before the app has a chance to update the screen.
Thanks!
Not sure about HIPAA's requirements about backgrounding and possibly leaving the user logged in for someone else to resume, but the safest sounds like it would be to add a key UIApplicationExitsOnSuspend with a boolean value of YES to info.plist.
That will prevent the app from backgrounding entirely, and restarts it (possibly triggering a login procedure) every time you go back to it.
Most (if not all) mobile banking applications I've tested do this for safety reasons.
I believe the answer is to not concern oneself with changing what's on the screen before the backgrounding animation begins, but to simply modify what's displayed on the screen once the app enters the background (i.e. inside of applicationDidEnterBackground: in your App Delegate.) This solved my problem.
My UIImageView overlay idea worked here, although I decided just to pop to the root view controller instead. Simpler that way. My root view doesn't have any sensitive info.
Here's what it looks like:
-(void)applicationDidEnterBackground:(UIApplication *)application {
UINavigationController *navigationController =
(UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:NO];
...
}

Resources