UINavigationBar not updating on 'back' in landscape - objective-c

I'm working on a UINavigationController driven iPad app (testing in the simulator). There are only two UIViewControllers on the nav controllers stack. For demonstration, lets call them SetupController and ContentController. SetupController pushes a ContentController on the stack with
[self.navigationController pushViewController:contentController animated:YES];
While looking at the content, you can press the back button to go back to the setup controller. If the app is in portrait mode, things work correctly.
However, when the app is in landscape and I hit the back button, things go haywire. The view controller stack is updated properly (e.g. I see the SetupController's view), but the UINavigationBar is not updated properly. The UINavigation bar items associated with the ContentController are still displayed. To see the SetupCotroller's expected UINavigationBar items, I have to press the back button a second time, at which point the UINavigationBar animates to the correct state. Again, this only happens in landscape mode, portrait mode works perfectly.
As a test. In the [SetupController viewDidAppear:] method I have added the following debug output
if(self.navigationController.navigationBar.topItem != self.navigationItem) {
NSLog(#"wrong nav item!");
} else {
NSLog(#"correct nav item!");
}
I get the "wrong" message whenever the simulator is in landscape mode, and never when it is in portrait mode. I've tried removing all viewDidAppear: messages from both ViewControllers and any instances where I'm modifying their navigation items or the navigation bar itself.
Any thoughts? I'm assuming I'm doing something wrong here, but this sure feels like a bug to me.

I ran into the same problem. It's weird, but you need to make sure all of the view controllers in the stack have the following implemented (even if everything is displaying correctly rotated):
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}

Related

IAD lifecycle - dismiss of ad sends app back to splash screen

I have a simple spritekit app, SKView on intialization shows a splash screen(scene), the user clicks on a button they are taken to the main game scene.
Everything works fine, but wanted to add iads to it, so I instantiated an adview in the main scene, and assign its delegate to the main scene.
The add appears fine, and I click on it, it properly shows the ad, but then I dismiss the ad, and when I do that the app goes back and looks like it reinitializes the entire SKVIEW which displays the Splash Scene, not the main scene which was paused in the delegate calls.
So, when the IAD is dismissed, what exactly are the callbacks back into the parent app, because its clear its not just the delegate methods, its going back and reinitializing the view etc. Basically where in my view do I need to trap its returning from a canceled ad and handle this case properly.
Sounds like you're running the scene in viewWillLayoutSubviews without checking whether a scene is already running. Make sure the launch code looks like this, or it will relaunch the starting scene whenever the viewWillLayoutSubviews method runs (it can and will be called multiple times, for example when rotating the device and apparently also when dismissing an ad):
-(void)viewWillLayoutSubviews
{
SKView* skView = (SKView*)self.view;
// only create/launch first scene when SKView has no running scene
if (skView.scene == nil)
{
// create and present scene here ...
}
[super viewWillLayoutSubviews];
}

iOS7 - Setting selectedIndex of UITabBarController breaks touch events along right-hand edge of screen?

I've hit a weird problem with UITabBarController on iOS7 and can't seem to find a workaround, so any help would be welcome!
Scenario:
Navigation-based app using landscape orientation on iPad.
App consists of a main view, and a second view which is a UITabBarController.
TabBarController has two tabs.
First view has two buttons - each button performs a segue to the tab bar controller and sets a different tab as selected. (i.e. button1 selects the first tab, and button2 selects the second tab).
Setting the tab is done in prepareForSegue by calling setSelectedIndex on the tab bar controller.
Outcome:
On iOS 7 I am finding that the view shown in the tab bar controller fails to register any touch events along the right-hand edge of the view! So in the storyboard shown above, the UISwitch on the right side of the screen cannot be tapped.
I've even attached a tap gesture recognizer to the views and used it to log the area of the screen that can be touched - it seems to register touch events up to about x=770 points across. The remaining 1/4 of the screen is 'untouchable'!
After the segue, if you manually switch to the other tab and switch back again, the touch events are 'fixed' and the full view responds to touches again.
This doesn't seem to be a problem on iOS 5 / 6.
Any help much appreciated as to:
What is causing this to happen in the first place (iOS7 bug / change?)
How else can I work around this? I've tried calling setSelectedViewController as well as using setSelectedIndex and this seems to be the same.
Thanks in advance.
I ended up raising this with Developer Tech Support, and it looks like a bug. This is the response I got back from Apple:
The container view that the tab bar controller sets up to contain your view controller is not being resized to account for the interface being in landscape orientation. It's dimensions at the time your view controller is displayed are 768 (width) x 1024 (height).
The view hierarchy looks like this when the selected tab's view is displayed:
UIWindow
/* Navigation Controller */
UILayoutContainerView
UINavigationTransitionView
UIViewControllerWrapperView
/* Tab bar controller */
UILayoutContainerView
UITransitionView
UIViewControllerWrapperView <-- Incorrectly sized.
/* MyViewController */
MyViewController.view
The incorrect size of UIViewControllerWrapperView does not cause a display problem because subviews are still displayed even if they are outside their superview's bounds. However, event routing is much more strict. Events on the right quarter of the screen are never routed to your view controller's view because the hit test fails at the wrongly-sized UIViewControllerWrapperView where the event falls outside UIViewControllerWrapperView's bounds.
As a workaround, I subclassed UITabBarController, and added the following in viewWillAppear:
#implementation FixedIOS7TabBarController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Fix the frame of the UIViewControllerWrapperView
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
Hope that helps someone else....
As explained in this answer,
The container view that the tab bar controller sets up to contain your
view controller is not being resized to account for the interface
being in landscape orientation. Its dimensions at the time your view
controller is displayed are 768 (width) x 1024 (height).
I was encountering this problem when the TabBarController was originally displayed in portrait mode. When the device was rotated into landscape mode, the view was unresponsive on the right hand side.
The solution proposed in that answer did not work for me, because viewWillAppear: is invoked only once. However, viewDidLayoutSubvews is invoked whenever the view changes, including rotations, so my solution was to subclass UITabBarController and perform the workaround in viewDidLayoutSubvews:
#implementation FixedIOS7TabBarController
- (void)viewDidLayoutSubviews
{
// fix for iOS7 bug in UITabBarController
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
End up finding a workaround here:
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Right answer don't worked for me, cause user can change orientation; And it still not touchable in some area when change orientation.
So I create my own solution, I don't sure that is normal solution.
#implementation FixedIOS7TabBarController
- (UIView*)findInSubview:(UIView*)view className:(NSString*)className
{
for(UIView* v in view.subviews){
if([NSStringFromClass(v.class) isEqualToString:className])
return v;
UIView* finded = [self findInSubview:v className:className];
if(finded)
return finded;
}
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIView* wraperView = [self findInSubview:self.view className:#"UIViewControllerWrapperView"];
wraperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
#end
Works perfectly for me!
In the list of view controllers on the left hand side navigate to the views/view controllers affected, drag the view to underneath the first responder so that it is disassociated to the view controller's view.
Then go to the layout tab on the right hand side, select all 4 anchors and both sets of resizing arrows (horizontal + vertical).
Then drag the view back to where it was originally (just below the view controller).

Why UINavigationController keep popping out the top controller

I have UITabBarController, one of the tab points to a UINavigationController. The UINavigationController rootViewController is of class BGProfileView which shows users' profile
At viewDidAppear, I arranged that if users didn't logged in it will push a BGLogin view controller.
[BGLogin alreadyLoggedin:self.navigationController hideBackButton:YES anddoBlock:^{
[self whatToDoAfterLogin];
}];
Now everything is fine but with one minor issue. If I press the tab again, BGLogin will be poped out of UINavigationController.
I have no idea what makes that BGLogin poped out.
If I select a different tab and then click back to the BGProfile tab, this doesn't happen. It just happens when I click the same active tab. So I am in BGProfile tab, and I click that tab again. Basically it happens when I select the active tab that should simply do nothing. In fact, it does do nothing on others.
I put a breakpoint in viewWillDisappear and this is what I see:
As you see, viewDidAppear is called by the main loop. But why the hell the mainloop call viewDidAppear? Usually there is a code saying things like nav popViewController
Chances are there . that your tab bar controller is pushing new navigation controller with root view controller. and you interpreting that it's popping out. When same tab is selected you need to tell your tabBarController to not to do anything explicitly.
Example
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
UIViewController *tbSelectedController = self.tabBarController.selectedViewController;
if ([tbSelectedController isEqual:viewController]) {
return NO;
}
return YES;
}

PresentModalViewController from within UISplitViewController: weird behavior in landscape mode

I've been using PresentModalViewController a lot and never had any issues. But when showing a modal controller from within any controller hosted by a UISplitViewController I get strange orientation bugs.
In my table view (which is root controller of the UISplitView), when a cell is touched, I call:
MyController oModal = new MyController();
oModal.ModalPresentationStyle = UIModalPresentationStyle.FormSheet;
oModal.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
this.PresentModalViewControll(oModal, true);
If the iPad is in Portrait, all is okay. If it is in landscape however, the modal controller fades in but its orientation is incorrect. Then, after fading in has finished, it suddenly flips 90 degrees and adjusts to correct orientation.
I have overriden ShouldAutoRotateToInterfaceOrientation(), so that cannot be it.
Ideas?
René
I bumped into this and have a workaround, not a real solution. The problem is that the UIViewController you are presenting your modal controller from (in you code "this") is responding with the incorrect orientation to it's property interfaceOrientation. It happened to me and I'm not really sure why. The workaround is to add a custom getter for the property in your UIViewController ("this") like the following:
-(UIDeviceOrientation)interfaceOrientation
{
return [[UIDevice currentDevice] orientation];
}
I got the idea from this post. I believe the problem is that the View Controller is not properly embedded into the view controller hierarchy but haven't figured out how. Hope this helps.

MKMapView not allowing user interaction on Ipod Touch OS 3.1.3, works correctly on simulator 3.2 / 4.0

I have been coding and testing an app which uses a navigation controller, tab bar and table views together as shown in this tutorial video:
I have also coded a MapView page which shows custom annotations. This seems to work fine in every version of the simulator I have tried it on. This morning I have finally got the app running on my Ipod Touch which runs OS 3.1.3 - everything works as expected except the map does not seem to allow user interaction at all. I cannot tap on annotations, the current location or move and zoom at all.
I have been through all the settings in the Interface Builder for the mapview, and made sure that all the 'User Interaction', 'Allow Multitouch' boxes have been ticked. This doesn't seem to change anything.
Any help greatly appreciated.
The Mapview is put into the view as follows:
// Grab the maps view controller ready for loading
MapView *childController = [[MapView alloc] initWithNibName:#"MapView" bundle:nil];
childController.title = #"View on Map";
// Push the new view controller onto the stack
[self.navigationController pushViewController:childController animated:YES];
[childController release];
childController = nil;
I've also tried running the view in a modal view controller just to see what would happen. The view was shown and any interaction didnt seem to work - with the exception of a small section at the bottom where I made the view itself slightly shorter so it would fit in above the tab bar. This section seems to have another map underneath my view which DOES respond to user interaction. So there is a 1cm or so block which does move - my view seems to stay static on top of it, though.
The view underneath does not appear to have any annotations or the current user location.
Ok I've solved this one:
In the mapview.m file where I set up the view and load the annotations, within the viewDidLoad function I had the following code:
- (void)viewDidLoad {
// More code before this..
[mapView addAnnotations: eventPoints];
// This is causing the problems on the ipod touch.
// The view is added ON TOP of the first map..
//[self.view addSubview:mapView];
self.view = mapView;
// More code after this..
}
Where mapView is
IBOutlet MKMapView *mapView;
Adding a subview on top of the current view didn't want to work. Actually setting the view to be the new updated view with annotations seems to work fine. It's still strange that the simulator would work and not the device in the first place though.
Hope this helps someone.