UIKeyboardWillShowNotification - iOS8 vs iOS7 - notifications

With iOS8, I noticed that a view controller was no longer receiving a UIKeyboardWillSHowNotification, when it previously was with iOS7.
Here's the scenario:
1.) View Controller A is displaying a keyboard, and pushes View Controller B without resigning first responder
2.) View Controller B has a control that becomes first responder during its viewDidLoad call, while it's being created by VCA, before it's pushed onto the nav controller
3.) If VC A is NOT displaying a keyboard when pushing B, the notifications work fine. However, if A is still editing when pushing B, then B does not get a keyboard will show notification.
Without the keyboard notification, VC B is not resizing / repositioning and does not look right.
The workaround I'm using until I find a solution is to do the following from any view controllers that might be editing when pushing another view controller that might be editing:
i.e., before pushing another view controller, be sure to call:
[self.view endEditing:YES];
While it works, it doesn't seem good that the view controller (B) can be 'broken' by the state of the app prior to displaying it.
Question: Am I doing something wrong here?
As far as I can tell, one of 3 things are possible:
A.) I should be getting the notifications, but I'm not b/c I'm doing something wrong
B.) I should be getting the notifications, but I'm not b/c of a bug
C.) I can't rely on always getting the notifications...But if I don't get the notifications in VC B when it appears, I need to be able to get the keyboard dimensions of the displayed keyboard without relying on the keyboard notification info. All the apple docs say to use the notifications though (as far as I can find).... which points back to options A.) or B.).
I can create and upload sample code later tonight / early tomorrow to try and isolate / for you all to test/reproduce to see what I'm doing.

I can see the same issue with iOS8 / xCode6 (works with iOS7 and xCode5). In my case, I'm observing a systemStatus property on the model in my AppDelegate so as to log the user out and bring the user back to the login screen when the user logs out from anywhere in the app. I'm doing that by setting the window.rootViewController to the loginViewController in my App Delegate observeValueForKeyPath: method.
This works fine on iOS7 / xCode5 but on iOS8 / xCode6, I loose the keyboard in the way. Looks like my loginViewController might be registering for keyboard notifications (in its ViewWillAppear method) before the window's rootViewController switch is complete (in iOS8) thus registering to the old window's notification center...
I moved the registration for keyboard notifications to the ViewDidAppear: method instead and that seems to fix it but somehow this seems to be called twice for some reason.

Related

Modal UINavigationController hides although not dismisses

Okay, so I'm building an universal iOS app with an initial login view (view controller named LoginVC), just a plain simple UIViewController. If the login is successful the app segues to an navigation controller (MainNavigationVC). I created this segue through the storyboard gui of XCode, so no programmatic creation of the nav controller is done. The nav controller is presented modally in fullscreen, so the rest of the app is run atop the login view, with this nav controller as the centerpiece of everything.
The navigation controller contains a view (with a view controller named UserStartPageVC), and in its navigation bar is a logout button. This button sends an target action to UserStartPageVC, with the goal of dismissing the nav controller thus bringing the user back to the login view.
So far everything works fine. I can login and use the app as intended. But! When I log out and then re-login XCode tells me this:
Warning! Attempt to present <MainNavigationVC: 0x753110> on
<LoginVC: 0x756fcf0> while a presentation is in progress!
I suppose this means that the login view is trying to modally display a MainNavigationVC navigation controller, but another one is already displayed, right? But how? Can a view be presented without showing?
And how can I get rid of the old nav controller when logging out? I've tried several ways of dismissing the modal view, for instance:
from within UserStartpageVC running
[x dismissViewControllerAnimated:YES completion:NULL]
[x dismissModalViewControllerAnimated:YES]
where x is either self, self.parentViewController or self.presentingViewController.
setting the LoginVC as a property in UserStartpageVC and running
[self.loginVC dismissViewControllerAnimated:YES completion:NULL]
and so on.
All of the tested calls actually brings me back to the login screen, so it's kind of working.
Any ideas? Relevant code samples can be provided if necessary, I just couldn't figure out which pieces that were of interest. The seguing to the navigation controller has no code (except for a performSegueWithIdentifier:sender:), and the code for dismissing it is the part I cannot seem to get straight.
As a sidenote. So far this isn't a REAL problem; the app runs, and it IS possible to logout and re-login without any other side-effects than an error message in XCode. But I suppose this will be a memory leak if users logout and login multiple times, and I'm not in the mood of an unnecessary rejection from Apple.
I discovered another way to get the exact same error message. Lucky me!
If you created a segue at one point and had it tied to a button (click button -> new view) and then later give that segue a name and invoke it directly using
[self performSegueWithIdentifier:#"identifierName" sender:self];
then you can get this error because you can effectively trigger the segue twice. I thought making the button invoke an IBAction would turn off the segue I had set up in the first place, but apparently not. Hitting the button triggered the segue twice, but after I deleted the segue and re-created it as a manual segue on the view with the same identifier then I was able to invoke it via the above code and not get the warning message.
Hoopla! My bad.
Seemed I had set up the notification observing from the login API call in a stupid way. For every time the user triggered a login (or re-login), it added itself as an observer for the notification; the result was that it performed one more segue for every time a login was done.
And doing multiple segues at the same time, is... well, obviously bad.

UIScrollView not functioning correctly after Modal view controller displayed in iOS 5.1

I have a strange problem when using a UIScrollView controller combined with iPhone 4 and iOS 5.1.
I have a UIScrollView which has a content size of 640x480 (double screen effectively) and in addition to the swipe to switch between the two "screens" I also permit the user to tap the screen in response to which I call something like...
[scrollView scrollRectToVisible:(CGRectMake 320,0,320,480) animated:YES];
the first 320 would be 0 if the tap occurred whilst the right hand side of the scroll view was displayed (note the scroll view has paging enabled so it can only come to rest either fully left or fully right).
I also have a situation where I sometimes display an additional view controller modally using presentModalViewController over this view controller containing the scroll view.
Everything works perfectly until the modal view controller is presented and subsequently dismissed after which the scrollRectToVisible method will no longer work if animated is set to YES (if I change animated to NO then it works as expected). Note, the tap is still being registered and the scrollRectToVisible being called, it just doesn't do anything when animated is set to YES).
Here's the kicker, this bug only occurs on an iPhone 4 runnings iOS 5.x.
It works perfectly (even after the modal view controller has been displayed) on my:
iPhone 3G running 4.x,
iPhone 3GS running 3.x,
iPod touch (2nd Gen) running 4.x
and most surprisingly the simulator running 5.x.
I wondered if this was a bug in the animation system so disabled the animation on the modal view controller presentation and dismiss and this had no effect, problem still occurred on the iPhone 4 with iOS 5.1.
Anyone got any ideas as to what might be causing this and how I might work around it?
Finally tracked this down. what a pig...
I'm embedding a view from a view controller as a subview of another view controllers view. So my scroll view contains a view which also has an associated view controller.
Prior to iOS 5.x the methods viewWillAppear, viewWillDisappear, viewDidAppear and viewWillDisappear are never called on the sub views view controllers, only the main view controller. Already knowing this I set up my main view controller to manually call the sub views view controllers methods when these events happen.
However it appears that in iOS 5.x this issue has been "fixed" so where I was manually passing the call to viewWillAppear to my sub view controller I no longer need do this under 5.x as the method automatically gets called under 5.x - as a result it's now being called twice under 5.x but still only once when running on a 4.x or earlier device.
As a result, under 5.x my NSTimer used to call my updateUI method is being created twice, but because in viewDidDisappear I only destroy the timer if it is non nil it only gets destroyed once - therefore I'm leaking NSTimers under 5.x through double allocation where I'm not under 4.x.
As a result of multiple NSTimers hanging around all repeatedly calling my updateUI method is that the constant updating of the UI is killing the animation system and so the animation for the scrollView fails when running on an actual device. I guess it continued working OK on the simulator running 5.x as the CPU in the Mac is more than capable of handling the extra workload and still performing the animations correctly.
A simple check in my viewWillAppear method to ensure the NSTimer hasn't already been created has fixed the problem and has kept compatibility with 4.x and earlier.
I frustratingly run up against these kinds of issues every time Apple update their iOS by major versions... The morale of this story is don't assume that long standing classes still exhibit the same behaviour under different revisions of the OS.
I had the same problem. I realized that after modalViewController is dismissed my UIScrollerView shifts downs by 20px, which is the same height as status bar. So, it means when my UIViewController is loaded and UIScrollView is created, UIScrollView thinks there is no status bar, when actually it is there.
So I tried to put in viewDidLoad:
[[UIApplication sharedApplication] setStatusBarHidden:NO];
Now my UIScrollView always stays under status bar, with Y position 20px. It never shifts down.
I finally managed to get this working on an iPhone4 running 5.1. Ensuring the bounces horizontally property for the scroll view is set fixed the problem, though why having this unchecked should cause the problem in the first place is beyond me - I'm pretty certain this is a bug in iOS.

MonoTouch: StoryBoarding - manual segues?

iPhone/iPad dev newb here...
I am using MonoTouch to create a universal iPad/iPhone storyboard app. In the primary view controller (RootViewController) the default auto-generated behavior is a table with a single cell "Detail" in it, which hardwires you to the next destination (DetailViewController).
I'd like to change this RootViewController to instead show a login control I've made extending UIViewController (LoginView). I have had some success putting the LoginView inside the RootViewController, but can't figure out how to make it 'segue' to DetailViewController. And upon watching how the iPad app works, where there is no segue (that I can see) between the two, am I going about this wrong?
To summarize: How do I enhance this storyboard app with a preceding login screen, reusing its contents/xib between the iPad and iPhone variant?
Apologies if this is a bit unclear and muddled.......
I have not tried a storyboard app in Monotouch yet, but I did spend a lot of time working out the best approach for a login screen for my application. What I ended up doing, which could work in your situation as well, is presenting the login screen as a Modal view. Once the user has successfully logged in, we then push the next appropriate view.
So if you are automatically pushed into the detail view by the storyboard viewcontroller, you could display the login dialog as a modal dialog from the detail view's DidLoad or DidAppear methods.

Present modal view controller on top of Quick Look PreviewController results in checkerboard-screen?

Situation: my app needs to present a full screen modal view whenever it becomes active (from background) to ask the user for a PIN. All fine.
Unless: if the user previews a file using QLPreviewController, leaves the app and comes back, the PIN input controller will be presented modally from the QLPreviewController which I'm keeping a reference to. The PIN input is shown but when it dismisses, I see a checkerboard-styled background which is even scrollable. Seems to be some leftover of the PreviewController but the actual preview data is no longer shown. Any idea what could cause that?
Do I have to reload the contents of the preview somehow?
I had similar issue and I've managed to pin down the problem to either viewWillDisappear or viewDidDisappear methods. My solution was to subclass QLPreviewController and overwrite those methods with empty implementation i.e. skipping the call to super. I don't know if it's very safe, though I haven't encountered issues and it solved my problem.

UITabBarController, MoreNavigationController and the Holy Grail of Device Rotation

UPDATE: See my answer to this question first. This appears to be a bug. A minimal test case has been created and a report has been filed with Apple. (Fixed as of iPhone OS 3.1.)
Here's a puzzler from the "I'm so close!" department.
I have a Tab Bar-based iPhone app. Each tab features a UINavigationController with the usual suspects (nav bar, table view ... which in turn can lead to another VC, etc.).
Now, one of those lower-level VCs is to be used in portait and landscape modes. But there's a problem. Our landscape-friendly VC's shouldAutorotateToInterfaceOrientation: won't get called out-of-the-box! What to do?
Here's what we do. In my Tab Bar Controller, which I have implemented in its own file, I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
This ends up propagating the request to my landscape-friendly VC, which also responds to this message. All my other VCs don't implement this method, so they simply go with the default portrait orientation.
Problem solved!!! Yay!
Well, not quite. :(
Seems like things don't go so well when my landscape-friendly VC is invoked from within the depths of the tab bar controller's MoreNavigationController.
I decided to compare/contrast between a VC called from within one of the first four tab bar UINavigationControllers ... and that same VC called from within the MoreNavigationController. This is going to be a bit ultra-detailed, so bear with me. Hopefully the play by play proves useful for sleuthing things out.
When the app loads, there are several initial calls to the tab bar controller's shouldAutorotate... method. In these early cases, selectedViewController is nil. However, we eventually finish loading, the initial tab item is selected, and all is well.
Right. First, let's pick one of the first four tab bar items and drill down to our VC.
We'll pick third nav bar item, so that's the third nav controller. We drill down to our VC that supports rotation. A quick inspection confirms that the parent is indeed the third nav controller from our tab bar's view controller list. Good!
Let's rotate the device. The tab bar controller is asked to autorotate (see the above code). We observe that selectedViewController is also that third nav controller, plus the nav controller's top and visible view controllers are both set to our trusty VC that supports rotation.
Thus, the tab bar controller will forward the shouldAutorotate message over to the third nav controller ... but our rotation-friendly VC ultimately gets the message. (I'm not doing anything special here. Maybe the desired VC gets the message because it's the top and/or visible VC?) In any event, we rotate to landscape, things are resized, and all is well. "Great Success!"
Now let's hit that back button and pop the VC stack, leaving landscape mode in the process. The tab bar controller is queried again.
Time for a little aside here. The topViewController for our nav controller is still that rotation-friendly VC, but visibleViewController is now set to UISnapshotModalViewController! Heh. Never saw this one before ... but Erica Sadun has. Looks like it's for "disappearing view controllers" (which in this case is certainly true - it's disappearing alright).
As I keep stepping through, the visible VC stays as Snapshot, but the top VC eventually changes to the next VC on the stack, since my special VC is eventually gone. Fair enough.
So that's the scenario where everything works well.
Now let's try the same test, only this time we're going to go to the MoreNavigationController (the More tab bar item) and drill down to the same VC class as before. In my case it happens to be the 7th one in the tab bar controller's VC list.
We enter the rotation-aware VC and ... this time it gets asked to rotate directly! The Tab Bar Controller is not asked for permission to rotate at all. Hmm.
A quick check of the parent VC shows it is a MoreNavigationController. OK, that makes sense.
Now let's try to rotate the device. NOTHING GETS CALLED. None of our breakpoints get hit. Not in our VC. Not in our tab bar controller. (Huh?!?!)
O-kaaaay. Let's pop the stack, go back into the same VC and try to rotate again. Weird. NOW we get a call in the Tab Bar Controller asking for autorotation permission. Here, the selected Controller is our trusty Nav controller (#7), but this time its visibleViewController and topViewController are SET TO NIL!
Once we continue from here, a mysterious message appears in the debugger console:
Using two-stage rotation animation. To
use the smoother single-stage
animation, this application must
remove two-stage method
implementations.
Mysterious because I'm not using two-stage rotation animation! No SecondHalf method variants are in play anywhere in my source code.
Alas, my rotation-aware VC is never told that rotation is occurring (even though rotation does occur on-screen), so of course my view is all fouled up as a result. Mayhem and sadness ensue. :(
We won't even bother popping the stack at this point.
I think the View Controller doc hints at the possible problem:
If you want to perform custom
animations during an orientation
change, you can do so in one of two
ways. Orientation changes used to
occur in two steps, with notifications
occurring at the beginning, middle,
and end points of the rotation.
However, in iPhone OS 3.0, support was
added for performing orientation
changes in one step. Using a one-step
orientation change tends to be faster
than the older two-step process and is
generally recommended for any new
code.
I wonder if MoreNavigationController is still responding to the two-step process, and is thus tripping up any attempts to use the one-step process? Note that, if you respond to the two-step messages, the one-step variant will not work (again, per the docs). I'm not responding to them, but I have a sneaking suspicion something behind-the-scenes IS.
In fact, if I comment out the single-step method, and try to respond to willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:, I do get the memo! But it still doesn't pop off the stack very cleanly (in terms of visuals). Even stranger: willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: is NOT called, even when I tried to sneak a call to self (using the FirstHalf message) in shouldAutorotateToInterfaceOrientation:. It returns immediately during a trace, as if I never even defined it. Sigh.
So that's the play-by-play.
In summary, has anyone successfully handled one-step device rotation for a VC invoked from within a Tab Bar Controller's MoreNavigationController? Inquiring minds want to know!
Apple advises against subclassing UITabBarController, so I found an easy way to handle autorotation using categories instead. It doesn't fix your bug with the More... view controllers, but I think it's a more Apple-friendly way of getting the job done (and means less subclassing for you).
To make every tab in my application autorotate properly, I've defined -shouldAutorotateToInterfaceOrientation: in my custom view controllers, but they are all inside UINavigationControllers within a UITabBarController, so the message won't get sent down the chain to my VC until those two also respond. So I added the following lines to my app delegate files:
Added to the bottom of MyAppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
Added to the bottom of MyAppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
return YES;
}
#end
It appears we have a bug. I have created a reproducible, minimal test case, and reported it via Apple Bug Reporter (RADAR Problem 7139857).
Update: This has been fixed as of iPhone OS 3.1.
The essential problem is:
View controllers already on a
Navigation Controller stack do not
receive
willAnimateRotationToInterfaceOrientation:duration:
messages when a Tab Bar Controller's
'More Navigation Controller' is in
use.
This problem does not occur when the tab bar item view controllers are basic view controllers. Only when they are navigation controllers and only when the "More" navigation hierarchy is in use.
The console message (regarding two-stage rotation animation) suggests that something within the framework (the More Navigation Controller?) is still using a two-stage animation, even though single stage is now recommended as of iPhone OS 3.0.
That could explain why willAnimateRotationToInterfaceOrientation: is not called in that particular case. Per Apple's view controler documentation, this message will NOT be invoked when two-stage, first/second-half orientation messages are being responded to instead.
A slightly modified version of Victorb's answer which allows every single view controller to decide if it allows rotation.
Here as a gist for easier copying & forking
AppDelegate.h
#interface UITabBarController (MyApp)
#end
#interface UINavigationController (MyApp)
#end
AppDelegate.m
#implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *selectedVC = [self selectedViewController];
if ([selectedVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end
#implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *visibleVC = [self visibleViewController];
if ([visibleVC respondsToSelector:#selector(shouldAutorotateToInterfaceOrientation:)]) {
return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
}
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
}
#end