11
votes

In iOS 7, I was able to handle layout for a custom presented view controller to also account for the in-call status bar by adding constraints where the presented view controller has the same center and the same width and height as the presenting view controller. This was preferred to using auto-resizing for the width and height because, otherwise, when the in-call status bar goes away, the presented view controller would remain pushed down by 20 points from the top for some reason.

In iOS 8, however, this trick doesn't work anymore. For one thing, I found through experimentation that the presenting view controller does not seem to necessarily be in the container view, so I can't add those constraints to the container view. (The sample code for the "A Look Inside Presentation Controllers" 2014 WWDC video does not put the presenting view controller in there.) It seems that having the presented view controller filling the container view using either auto-layout or auto-resizing would work, but I found that the container view itself may remain pushed down by 20 points when the in-call status bar disappears (which was not the case in iOS 7).

I've been playing around with the "A Look Inside Presentation Controllers" sample code, but even this code does not handle the in-call status bar correctly. (I'm still trying to get a handle on the new UIPresentationController API, by the way.)

6

6 Answers

2
votes

What worked for me was adding an observer for UIApplicationWillChangeStatusBarFrameNotification, then finding the transitionContext.containerView() and updating the frame. I noticed when view debugging that the transitionContext.containerView() frame wasn't updated when going back to the normal status bar height.

func willChangeStatusBarFrame(notification: NSNotification)
{
    if let userInfo = notification.userInfo
    {
        if let value = userInfo[UIApplicationStatusBarFrameUserInfoKey] as? NSValue
        {
            let statusBarFrame = value.CGRectValue()
            let transitionView = UIApplication.sharedApplication().delegate!.window!!.subviews.last! as UIView
            var frame = transitionView.frame
            frame.origin.y = statusBarFrame.height-20
            frame.size.height = UIScreen.mainScreen().bounds.height-frame.origin.y
            UIView.animateWithDuration(0.35) {
                transitionView.frame = frame
            }
        }
    }
}
0
votes

Inside the view controller that is being pushed on top of navigation stack, I added:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.frame = [[UIScreen mainScreen] bounds];
    // rest of the code
}

Now, I don't have a problem with the top offset anymore. Not sure if this is the right solution for you. If this view controller is sometimes displayed not in full screen, then obviously manual frame setting inside viewDidLoad is a bad idea.

0
votes

In appdelegate add following two methods. On simulator press Command+Y to toggle call status bar and test. Solution is tested with autolayouts only.

-(void) application:(UIApplication *)application didChangeStatusBarFrame: (CGRect)oldStatusBarFrame
{
 [self.window layoutIfNeeded];
 [self.window updateConstraintsIfNeeded];
}

-(void) application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame
{
 [self.window layoutIfNeeded];
 [self.window updateConstraintsIfNeeded];
}
0
votes

The didChange notification is not helping if the in-call bar is already visible when the app starts. Also I don't see the necessary unrollment of getting the statusBarSize in the notification events, you can simply get it via UIApplication.shared.statusBarSize.

This is a solution in Swift 3 that works for me:

class MainTabBarViewController: UITabBarController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setNeedsStatusBarAppearanceUpdate()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(
            self,
            selector: #selector(statusBarFrameDidChange),
            name: .UIApplicationDidChangeStatusBarFrame,
            object: nil)
        fixViewSizeDependingOnStatusbar()
        self.view.layoutIfNeeded()
    }

    private func fixViewSizeDependingOnStatusbar() {
        self.view.frame = CGRect(
            x:0,
            y:0,
            width: UIScreen.main.bounds.width,
            height: UIScreen.main.bounds.height - 
                    UIApplication.shared.statusBarFrame.height + 20)
            // I have no clue why I have to add 20, 
            // looks like UIScreen.main.bounds already 
            // shrinked by 20 for defaultStatusBar size?
    }

    override func viewWillLayoutSubviews() {
        fixViewSizeDependingOnStatusbar()
        super.viewWillLayoutSubviews()
    }

    func statusBarFrameDidChange() {
        fixViewSizeDependingOnStatusbar()
        self.view.layoutIfNeeded()
    }
}
0
votes

I solved this by simple one line of code

self.tabBarController?.tabBar.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleTopMargin.rawValue)))

for reference click here

0
votes

Swift 4

The problem is that modal presentations take place inside a container view (transitionContext.containerView) and that container, by default, isn't configured for auto layout and neither is the view controller that is presented inside it.

Therefore, in the animateTransition(using transitionContext: UIViewControllerContextTransitioning) method, configure the container view to take advantage of auto layout:

guard let toViewController = transitionContext.viewController(forKey: .to),
    let root = UIApplication.shared.keyWindow!.rootViewController else {

        return transitionContext.completeTransition(false)

}

containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.leadingAnchor.constraint(equalTo: root.view.leadingAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: root.view.widthAnchor).isActive = true
containerView.heightAnchor.constraint(equalTo: root.view.heightAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: root.view.trailingAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: root.view.bottomAnchor).isActive = true

Because view controllers are typically presented from the root (to avoid the warning that view controllers should not be presented on detached view controllers), anchor the container to the root's view (which most-reliably responds to double-height status bar changes).

And then configure the presented view controller to also take advantage of auto layout:

toViewController.view.leadingAnchor.constraint(equalTo: root.view.leadingAnchor).isActive = true
toViewController.view.widthAnchor.constraint(equalTo: root.view.widthAnchor).isActive = true
toViewController.view.heightAnchor.constraint(equalTo: root.view.heightAnchor).isActive = true
toViewController.view.trailingAnchor.constraint(equalTo: root.view.trailingAnchor).isActive = true
toViewController.view.bottomAnchor.constraint(equalTo: root.view.bottomAnchor).isActive = true

As long as you don't explicitly set the frame of the presented view controller somewhere else (as is typically the case with programmatically-created view controllers), this modal view controller will respond to the double-height status bar like every other view controller.