viewWillAppear is not called in the master view of UISplitViewController - ios

I have a splitvew controller (set in storyboard) with master and detail, and I set the preferredDisplayMode as UISplitViewControllerDisplayModeAllVisible in the master viewController.
I have these method on the master view controller:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//This is called
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//This is not called
[self.searchController.searchBar sizeToFit];
[self configNavBar]; //Adds few buttons to the nav bar
}
I'm wondering why the viewwillAppear isn't called here but viewDidLoad, viewDidAppear are called. The breakpoint in the viewWillAppear didn't hit .
If I set the preferred display mode as UISplitViewControllerDisplayModePrimaryOverlay, then viewWillAppear is getting called.
But here the detail view controller doesn't occupy the half screen.

I know its an old one but I have the same problem here. On iPad, no problem, the viewWillAppear is called but on iPhone it is not called at all the first time i display the controller.
I finally found that it was due to the call of this lines of code:
if let target = displayModeButtonItem.target, let action = displayModeButtonItem.action {
UIApplication.shared.sendAction(action, to: target, from: view, for: nil)
}
which was called after the initialization of my UISplitViewController.
When I removed this, it worked. But I was also able to bypass it by using the mode: .primaryHidden on iPhone (I dont use the iPhone on landscape thats why my condition on this example will be iPhone / iPad). So my custom UISplitViewController look like this:
override func viewDidLoad() {
super.viewDidLoad()
self.preferredDisplayMode = UIDevice.current.userInterfaceIdiom == .pad ? .allVisible : .primaryHidden
}
Hope this could help someone.

Related

viewWillAppear called but viewDidAppear not called after pushViewController

In my app, sometimes pushViewController fails for no reason and what happens is very weird. The navigationBar and navigationItem change but the ViewController is not pushed. Then I can tap nothing on the screen. I find that viewWillAppear is called but viewDidAppear isn't called. I push the home button of iPhone to enter background. After entering foreground again, the ViewController is pushed and viewDidAppear is called. I don't know why and when it happens.
normal viewDidAppear callstack
viewDidAppear after enterBackground callstack
If you can repro by:
Try using the left edge pop gesture when there are no view
controllers below it (i.e on root view controllers, your VC-Home
controller)
Try clicking on any UI elements after this.
Then Disable interactivePopGestureRecognizer when current viewController is the firstVC in navigation controller.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
reference:
iOS App Freezes on PushViewController
Is the viewcontroller which you pushed into the view hierarchy overwrite the viewWillAppear/viewDidAppear accidently without calling [super viewWillAppear/viewDidAppear:animated]?
You're probably accidentally calling [super viewDidLoad] inside your viewWillAppear method
For anyone having the same issue as me: Check all your custom views to see if you're not having an infinite loop of layoutSubviews. This is on of the things that happens in between a viewWillAppear and a viewDidAppear.
In my implementation I had a custom Tab bar controller and fore some reason viewDid Appear was empty, putting super calling solved it.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) //was missing this line
}
You know,if you overwrite navigationController.interactivePopGestureRecognizer.delegate and not restore it appropriately, this bug will appear,too

How to Pop to a View Controller without Animation

I would like to display an image in response to a local notification event that occurs while the iPhone is locked. When the user swipes on the notification, I would like my app to go to the root view controller, via popToViewController:animated, and then push a view controller that displays the image. This works when I set animated = YES. When animated = NO, the view controller that displays the image doesn't respond when the user taps the back button. Any thoughts on why the image view controller's navigation controls don't work when I popToViewController without animation? Thanks in advance.
Here's the relevant code...
- (void) localNotificationHandler
{
#ifdef kAnimatePop
animated = YES; // This works
#else
animated = NO; // This doesn't work
#endif
_showImage = YES;
// Check if this view controller is not visible
if (self.navigationController.visibleViewController != self) {
// Check if there are view controllers on the stack
if ([self.navigationController.viewControllers count] > 1) {
// Make this view controller visible
[self.navigationController popToViewController:self animated:animated];
}
}
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (_showImage) {
_showImage = NO;
// Show image in a view controller
[self performSegueWithIdentifier:#"MainToImageSegue" sender:self];
}
}
You don't set _showImage until after popToViewController has been invoked. If it's animated viewDidAppear won't be called until later, so it unexpectedly works. If it's not animated then viewDidAppear is called immediately, before _showImage has been set. Move _showImage = true; to before the nav controller stack manipulations.
I might do the check this way:
- (void) localNotificationHandler{
...
...
if (self.navigationController.topViewController != self) {
[self.navigationController popToRootViewControllerAnimated:NO];
}
}
I would also recommend pushing your image view controller programmatically with something like
[self.navigationController pushViewController:<imageVC> animated:YES];
If your image view controller's navigation buttons are not working, it is likely because it is sending those navigation messages to nil. aka, the imageViewControllers self.navigationController is equal to nil.
I'm not sure how to fix this with storyboards, but if you present it programatically that problem should go away.

Black bar appears under navigation bar

There are several similar questions which got no answers but were describe vaguely. I have reduced the problem into a very thin application, and added detailed screenshots. I would highly appreciate a solution for this!
The only involved code is one line added to viewDidLoad of the root VC. The purpose of this line is to make the navigation controller opaque:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
}
A critical information for this question is that 'Title1' has a prompt in its navigation item, while 'Title2' has not prompt.
I have a storyboard with one navigation controller, one root VC called "Title1", with a segue button which takes to a second VC called "Title2"
When pressing the button here:
I'm getting this strange screen:
When pressing back (Title1), it gets worse (i.e.: the original label of Title1 was pushed up and now not being seen anymore!!!):
Anyone please??
Late answer but I stumbled across this problem today and found your question and it doesn't have an accepted answer yet.
I got this error while going from a prompted viewController to a non prompted viewController in storyboard.
I got that black bar just like you.
And to fix:
// In prompted vc
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
UIView.setAnimationsEnabled(false)
self.navigationItem.prompt = nil
UIView.setAnimationsEnabled(true)
}
This will remove the prompt instantly before switching viewcontroller.
UPDATE
func prompt() -> String? {
return nil
}
override func viewWillAppear(animated: Bool) {
let action = { self.navigationItem.prompt = self.prompt() }
if self.navigationController?.viewControllers.count <= 1 {
UIView.performWithoutAnimation(action)
}
else {
action()
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
UIView.performWithoutAnimation {
self.navigationItem.prompt = (segue.destinationViewController as? ViewController)?.prompt()
}
}
It appeared as translucent property of UINavigationBar appeared to be messed up with frame other view controllers.
I would recommend following approach.
Create a base view controller from which other view controllers will inherit as follows,
#import "BaseViewController.h"
#interface BaseViewController ()
#end
#implementation BaseViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.translucent = NO;
}
other view controllers will inherit above BaseViewController
// interface
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
#interface ViewController : BaseViewController
#end
// implementation
#import "ViewController.h"
#implementation ViewController
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Here translucent property is enabled when the view is about to be disappeared.
// However note that, translucent property needs to be enabled only on those view controllers which has prompt set on their navigation items.
self.navigationController.navigationBar.translucent = YES;
}
Other view controllers without prompt implementation will work as usual however they also needs to inherit from BaseViewController.
The best way to solve this issue is setting the background of the window during push i.e,
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window?.backgroundColor = UIColor.white
Seems like Xcode has some issues when changing the navigationBar height since main controller view is not resized accordingly.
I found a solution to do this, not sure it is the best... but it's working.
Just inherit your viewWillAppear and viewWillDisappear methods in your first view controller (the one with a prompt):
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationItem.prompt = #"Prompt1";
[UIView animateWithDuration:UINavigationControllerHideShowBarDuration
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
[self.view setFrame:CGRectMake(0, 94, 320, 386)];
}
completion:^(BOOL finished){
}];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Sets prompt to nil
self.navigationItem.prompt = nil;
[UIView animateWithDuration:UINavigationControllerHideShowBarDuration
delay:0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
[self.view setFrame:CGRectMake(0, 64, 320, 416)];
}
completion:^(BOOL finished){
}];
}
I didn't focus on frame size (it's for 3,5" iPhone frame sizes). You must calculate this size or you might have some issues with larger screens.
The top answer on this thread just solved this problem for me, thank you.
Here's the thing, the app I'm working on uses coordinators and autolayout, so looking at this showed the animation need to be shut off. I had to change the navigation controller's push view controller function's animate parameter to false.
Ex:
navigationController?.pushViewController(view, animated: false)

UIViewController viewWillAppear not called when adding as subView

I have a UIViewController that I am loading from inside another view controller and then adding its view to a UIScrollView.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
I've put breakpoints in the statistics view controller and viewDidLoad is being called but viewWillAppear isn't.
Is it because I'm not pushing it onto the hierarchy or something?
You should add statisticsController as a child view controller of the controller whose view you're adding it to.
self.statisticsController = [self.storyboard instantiateViewControllerWithIdentifier:#"StatisticsViewController"];
self.statisticsController.match = self.match;
[self.scrollView addSubview:self.statisticsController.view];
[self addChildViewController:self.statisticsController];
[self.statisticsController didMoveToParentViewController:self];
I'm not sure this will make viewDidAppear get called, but you can override didMoveToParentViewController: in the child controller, and that will be called, so you can put any code that you would have put in viewDidAppear in there.
I encounter -viewWillAppear: not called problem again. After googling, I came here. I did some tests, and find out that the calling order of -addSubview and -addChildViewController: is important.
Case 1. will trigger -viewWillAppear: of controller, but Case 2, it WON'T call -viewWillAppear:.
Case 1:
controller?.willMoveToParentViewController(self)
// Call addSubview first
self.scrollView.addSubview(controller!.view)
self.addChildViewController(controller!)
controller!.didMoveToParentViewController(self)
Case 2:
controller?.willMoveToParentViewController(self)
// Call adChildViewController first
self.addChildViewController(controller!)
self.scrollView.addSubview(controller!.view)
controller!.didMoveToParentViewController(self)
By default, appearance callbacks are automatically forwarded to children.
It's determined with shouldAutomaticallyForwardAppearanceMethods property. Check value of this propery, if it's NO and if your child viewController should appear right on container's appearance, you should notify child with following methods in container's controller life-cycle implementation:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:YES animated:animated];
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.child endAppearanceTransition];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
for (UIViewController *child in self.childViewControllers) {
[child beginAppearanceTransition:NO animated:animated];
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.child endAppearanceTransition];
}
Customizing Appearance and Rotation Callback Behavior
Fixed my problem! Hope it would be helpful.
As mentioned in another answer, the parent view controller might not call viewWillAppear etc. when shouldAutomaticallyForwardAppearanceMethods is set to false. UINavigationController and UITabBarController are known to do that. In this case, you need to call beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) on the child view controller with isAppearing set to true when the view appears and vice versa.
You have to place these calls at appropriate places in your code, normally when you add and remove your child view controller.
Don't forget to call endAppearanceTransition on your child view controller when your custom transition has ended, otherwise viewDidAppear and viewDidDisappear are not called.
Per Apple (https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html), the correct order of API calls to add a child view controller is:
[self addChildViewController:childVC];
[self.view addSubview:childVC.view];
[childVC didMoveToParentViewController:self];
But I still had the problem where viewWillAppear in the child VC was not sporadically getting called. My issue was that there was a race condition that could cause the code above to get executed before viewDidAppear in the container view controller was called. Ensuring that viewDidAppear had already been called (or deferring the addition of the child VC until it was) solved it for me.
The previous answers are correct, but in case it helps someone - if you override loadView in the child view controller, then none of the other UIViewController methods get called.
Took me some time to realize why my code wasn't running properly, until I realized that I had accidentally overridden loadView instead of viewDidLoad.
Check if your parent VC is a UINavigationViewController (or any other container). In this case the shouldAutomaticallyForwardAppearanceMethods is False and the appearance methods are not called.
I can't understand your questions and your description.
My problem was similar to this only.
CustomTabBarController -> CustomUINavigationController -> RootViewcontroller
viewWillAppear of CustomUINavigationController and RootViewController are not getting called unless you switched to another tab and come back.
The solution is call super.viewWillAppear(animated: true)
override func viewWillAppear(_ animated: Bool) {
**super.viewWillAppear(true)**
}
I struggled for more than a day for this small mistake.
View appearance methods also will not get forwarded if your view controller hasn't loaded its view. This could happen if you override loadView in your child view controller, and the view is already added to the view hierarchy.
In that case, you could do
addChild(childVC)
childVC.loadViewIfNeeded()
childVC.didMove(toParent: self)

Pushing self.view to navigation controller by allocing, but when coming back same data is shown

I am pushing self view to self.navigationcontroller by allocating. I have a tableView on that view so I am changing the content of tableview. But when I am pressing back button (that is automatically created), I am not able to show previous content. Its showing updated content.
Please suggest.
You set code in viewWillAppear method
- (void)viewWillAppear:(BOOL)animated
{
//code set here
}
If you fill the tableView's data in viewWillAppear: or viewDidAppear:, it will reload even if you only press the back button of your top viewController. If you do not want to have your content changed, you are supposed to use initWithNibName: or viewDidLoad: methoads. They are called only at creation time of the view.
Based on the comments on #Kirti's post, You can check if your viewcontroller is being popped by following method, and take some necessary actions for you controller holding table.
-(void) viewWillDisappear:(BOOL)animated
{
if(![self.navigationController.viewControllers containsObject:self])
{
YourControllerWithTable *instance = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 1];
instance.loadOldContent = YES;
}
}
In viewWillAppear: of YourControllerWithTable, you can check:
-(void) viewWillAppear:(BOOL)animated
{
if(loadOldContent)
{
//Do your work here
}
}
You don't push UIView instances onto a UINavigationController instance, only instances of UIViewController.

Resources