How to hide the master popover view on first-time app load? - ios

I have nice helper functions that allow me to show/hide the master popover view controller. However, I can not for the life of me figure out how to hide it on initialization, so that it is hidden when the app first starts.
I've tried a couple of things (such as trying to dismiss is from the viewLoaded or viewDidAppear) but these throw strange errors (e.g. too made slider counts...etc).
Now I'm starting to believe there must be a simpler, and the right way, to do this.

Are you trying to hide the splitviewController Master Popover? Which orientation would make this question more specific. I will assume you are in fact trying to hide the SplitView MasterPopover in landscape (since it should already be hidden in portrait).
Do this:
Your detailViewController should have UISplitViewControllerDelegate. And simply just drop this code in:
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation{
return YES;
}
This will make popoverView hidden when the app starts up. Let me know, if it works for you.

Related

UISplitViewController how to show the master view?

I need a ridiculously simple thing - in one of the detail views of my UISplitViewController I have a button. Clicking it should show/open master view. That's it. Is it even possible?
P.S. it should work for all the layouts (iphone & ipad) and orientations. Even if the detail view part is a navigation and I am deep inside several pages, just want to open master view. You can assume iOS8+.
EDIT: Just to clarify what I meant by "deep inside several pages". Here is my storyboard screenshot:
Suppose I have a button in Detail Page 2 which should show the master. Setting the preferredDisplayMode works only for non-compact sizes, like iPad. On iPhone 6, for example, nothing changes after setting it. The back button points on Detail Page 1 so even swiping doesn't open master, it goes to previous page in detail navigation. I noticed that in this mode there is no split view at all, it is simulated by a navigation controller. So the questions is: is what I need possible at all or am I wrong trying to conceptually treat it as a "left drawer" which can be opened in any case and device?
At iOS8+ you can change visibility of the master view using
an animatable property preferredDisplayMode
#property (nonatomic) UISplitViewControllerDisplayMode preferredDisplayMode
Universal way to change visibility for all iOS versions is overriding delegate method
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return _needsHideMasterView;
}
Here _needsHideMasterView is BOOL ivar which can be changed in your code to hide master view. For example,
- (void)hideMasterView:(BOOL)needsHide
{
_needsHideMasterView = needsHide;
[splitViewController.view setNeedsLayout];
[splitViewController.view layoutIfNeeded];
}
try setting preferredDisplayMode to UISplitViewControllerDisplayModeAllVisible like
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;

Return values to presenting view controller when navigation back button pressed

I'm having trouble piecing this all together. I have a view controller that opens up another (pushes it on to the navigation stack). On that presented view controller, the user enters a value in a text view. When the user pushes the back button in the navigation, I want to be able to pass the value that they entered in the text view back to the presenting controller.
I've looked for a way to use unwind segue with the back button but haven't found anything. When I create my back button (programmatically) I use initWithTitle:style:target:action but I'm not sure how in implementing the action method that I'll be able to access the value set in the presented controller. Might have to use a delegate to link the two, but not sure of the exact integration point for this scenario.
I feel like I'm so close here and a little help would get me there. Thanks!
The two most common models to use for this interaction are for the child view controller to have either a delegate or a completion block. Either would be set in the prepareForSegue method. My personal preference is the completion block method just because it keeps code contained, but ymmv.
There are also multiple models for detecting when your child view controller is dismissed and you need to invoke the delegate and/or completion:
Use a custom back button. Not a fan of this as it can be an issue to create a back button that really looks and acts like the Apple original, especially if supporting iOS 6 and iOS 7.
Hook viewDidDisappear and see if you're still in the navigation controller's viewControllers array. This is better as the back button works right, but it still feels kind of hokey.
Use the UINavigationBarDelegate method navigationBar:shouldPopItem: This is attractive, especially if you have other validation that needs to happen like checking for saved/unsaved values. To implement this you'll have to subclass UINavigationController and forward the method to your child view controller.
EDIT: Details on Option 2:
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(![self.navigationController.viewControllers containsObject:self])
{
// We're not still in the navigation stack so we must've been
// popped. If we were pushed, viewDidDisappear would be called
// but viewControllers containsObject:self would be true
}
}
EDIT: Clarified Option 3: in your navigation controller subclass
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController* top = self.topViewController;
if([top respondsToSelector:#selector(navigationBar:shouldPopItem:)])
return [(id)top navigationBar:navigationBar shouldPopItem:item];
return [super navigationBar:navigationBar shouldPopItem:item];
}
Then you can implement navigationBar:shouldPopItem: in the classes that need the functionality.
the back button does not actually comes up with any event associated with itself so that you can pass the values between the previous and to be Popped ViewController.
You would have to implement Delegate pattern to pass values. In this case as you cant catch when backButton is pressed, you need to use custom leftBarButtonItem or use a image with < in itself.

How do I make my keyboard popup more fluent with my view controller appearing modally?

I want the keyboard to slide up as the view controller slides up. But for one of my view controllers that I present modally, the keyboard appears instantly when the view controller is presented, so the keyboard appears then the view controller slides up from under it, causing an ugly effect.
Oddly enough, this instantaneous behaviour happens when it's in viewDidLoad, but having it there works fine for another view controller. (But in the instantaneous one it appears for a UITextField, while the proper one is for a UITextView.)
Here's what the code looks like:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.URLTextField becomeFirstResponder];
}
How do I make it present alongside the view controller? I don't have to do an ugly dispatch_after do I?
If it's loading too quickly with some methods (ViewDidLoad/ViewWillAppear) and too slowly with others you could try doing something in the middle.
I wouldn't suggest it as I'm sure theres a way to have it do what you like but I imagine that in viewDidLoad you can set the view up to respond to to keyboardWillShow and then become first responder and in the notification delay for a few milliseconds

My iOS app complains about two-staged rotation but I'm not using it

I have to support down to iOS 4.3.
My app outputs in the console :
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
As far as I know I'm not using two-staged rotation. I just have this method in my view controllers :
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
What else should I check in order to fix that?
Edit:
More precisions: my app uses a UITabbarController subclass. When the app starts, it checks if a user is logged in and then initiates the controllers of the tabbar controller if it's the case. If there's no user logged in, a modal view is presented (from the tabbar controller) the prompt the user to login and the controllers of the tabbar controller aren't initialized (yet). The "two-staged rotation" error is shown only at that moment and the rotation doesn't work.
So to summarize, the problem happens in that situation:
The rootViewController of the main window is the tabbar controller
The tabbar controller is empty (there are no view controller in the tabs and there's no tab)
A view controller is modally presented from the tabbar controller
OK I found a solution.
It seems like the presented modal view won't rotate until the viewControllers property of the UITabBarController is initialized. Since the concerned modal view is actually for login, I don't want to display anything behind it until the user is logged in because the views intended to be hold by the tabbar controller depend on the fact that a user is logged in.
So now just before presenting the modal view, I initialize the tabbar controller with a single, empty UIViewController and I remove it when the modal view is dismissed (i.e. a user logged in).
Perhaps it seems like a hack but it works well. And even if I don't understand why, it doesn't seem completely illogical that the tabbar controller doesn't behave like we want until it is fully initialized.
If someone has a better solution or explanation, feel free to comment :)
There aren't many posts regarding this error, so I'll admit my own shortcomings for the benefit of the next person so focused on the trees they might miss the forest. I found a missing
[super viewWillAppear:animated];
call inside my sub.

UISplitViewController delegate methods not called

I am using a UISplitViewController inside a UITabBarController with a plain UIViewController in the master pane of the split view and a UINavigationController in the detail pane, which itself contains a vanilla UIViewController.
I am aware that Apple advise to use split views at the root level only, however I have seen other applications (eg, Amazon- 'Wish List' tab) that use split views in tabs so I'm sure it's possible.
My problem is that the delegate methods of the split view, ie. those in UISplitViewControllerDelegate do not get called, which prevents me from creating my pop-over menu when switching into Portrait mode.
The methods in question are the following -
// Called when a button should be added to a toolbar for a hidden view controller
- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc;
// Called when the view is shown again in the split view, invalidating the button and popover controller
- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem;
// Called when the view controller is shown in a popover so the delegate can take action like hiding other popovers.
- (void)splitViewController: (UISplitViewController*)svc popoverController: (UIPopoverController*)pc willPresentViewController:(UIViewController *)aViewController;
The UISplitViewController does receive the rotation notifications.
I can get the willShowViewController method to be called if I force the status bar orientation to landscape right (or left) at the beginning of the app launch, using
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
However, the willHideViewController doesn't get called. And I don't want to force the app to start in landscape. If I do the same thing but force it to portrait, I don't receive the callbacks.
I don't understand why the split view controller is not calling it's delegate methods when it is otherwise behaving correctly. These methods should be called from its method-
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
internally, and when I breakpoint inside this, I can check that the delegate is set and that it is still alive.
Been stuck on this all day! Everything else is working great and I'm very pleased that the splitview / tabbar / navbar combination is working well. I just need these notifications.
Should I perhaps just call them manually when I rotate? Seems very wrong when the `UISplitViewController' should be doing this.
Solved, it has to be at either root level or a direct subview of a tabBar which also must be at root level. Annoying!
First, try to see if you are setting the correct delegates.
e.g., lets say you created three controllers,
UISplitViewController* splitView;
UIViewController* masterView;
UIViewController* detailView;
You implemented the delegate protocol at the detail view, so that when orientation changes, detail view should be able to put a button in the toolbar.
Now in order for splitView to call this function from delegate, you need to set the delegate itself.
So somewhere, if you are missing the following call,
splitView.delegate = detailView;
detailView's will never get notified of the orientation changes etc. At least this is where I was stuck.
I like the following method of sending a message from the master UIViewController to the detail UIViewController. Somewhere inside the master's implementation:
id detailViewController = [[self.splitViewController viewControllers] lastObject];
[detailViewController setSomeProperty:…];
This is from Paul Hegarty's Fall 2011 Stanford iTunesU iPad and iPhone Application Development course.

Resources