Sunday, December 6, 2015

Adjust UI components programmatically with double-height status bar

Update January 4, 2016: "Pixels" should be "Points".
Update April 21, 2016: Swift 2.2 with Xcode 7.3.

Here is an issue that new iOS developers may often ignore:

The default height of the iPhone status bar at the top of the screen is 20 pixels points.
When the personal hotspot WiFi function is turned on, the height of the status bar is increased to 40 pixels points. This double-height status bar may make the UI layout to be incorrect. This post shows this issue and how to fix it.

To test the double-height condition, the simplest method is to use the iOS simulator. Select Hardware -> Toggle In-Call Status Bar to change the status bar height.


Let's try this simple code:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor.orangeColor()
        
        let labelHeight : CGFloat = 30
        let labelWidth : CGFloat = 150
        let statusHeight : CGFloat = UIApplication.sharedApplication().statusBarFrame.size.height
        
        let labelTop = UILabel(frame: CGRectMake(0,statusHeight,labelWidth,labelHeight))
        labelTop.backgroundColor = UIColor.blueColor()
        labelTop.textColor = UIColor.whiteColor()
        labelTop.text = "Top"
        labelTop.textAlignment = NSTextAlignment.Center
        view.addSubview(labelTop)
        
        let labelBottom = UILabel(frame: CGRectMake(0,view.bounds.height-labelHeight,labelWidth,labelHeight))
        labelBottom.backgroundColor = UIColor.blueColor()
        labelBottom.textColor = UIColor.whiteColor()
        labelBottom.text = "Bottom"
        labelBottom.textAlignment = NSTextAlignment.Center
        view.addSubview(labelBottom)

    }

Run the iOS simulator, and the result is:

When the status bar is increased in height, the screen becomes:



Both the top and bottom labels with blue background color are moved downwards by 20 pixels points, and hence the bottom label is moved outside of the screen corner.

To fix this issue, modify the code as:

    let statusHeightDefault : CGFloat = 20
    var statusHeight : CGFloat!
    var labelBottom : UILabel!
    let screenHeight : CGFloat = UIScreen.mainScreen().bounds.height
    let labelHeight : CGFloat = 30
    let labelWidth : CGFloat = 150
    var labelBottomY : CGFloat!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(statusBarHeightChanged), name: UIApplicationWillChangeStatusBarFrameNotification, object: nil)
        
        view.backgroundColor = UIColor.orangeColor()
        
        statusHeight = UIApplication.sharedApplication().statusBarFrame.size.height
        
        let labelTop = UILabel(frame: CGRectMake(0,statusHeightDefault,labelWidth,labelHeight))
        labelTop.backgroundColor = UIColor.blueColor()
        labelTop.textColor = UIColor.whiteColor()
        labelTop.text = "Top"
        labelTop.textAlignment = NSTextAlignment.Center
        view.addSubview(labelTop)
        
        labelBottomY = screenHeight - statusHeight + statusHeightDefault - labelHeight
        
        labelBottom = UILabel(frame: CGRectMake(0,labelBottomY,labelWidth,labelHeight))
        labelBottom.backgroundColor = UIColor.blueColor()
        labelBottom.textColor = UIColor.whiteColor()
        labelBottom.text = "Bottom"
        labelBottom.textAlignment = NSTextAlignment.Center
        view.addSubview(labelBottom)
        
    }
    
    func statusBarHeightChanged() {
        statusHeight = UIApplication.sharedApplication().statusBarFrame.size.height
        
        labelBottomY = screenHeight - statusHeight + statusHeightDefault - labelHeight
        
        UIView.animateWithDuration(0.3, animations: {
            self.labelBottom.frame = CGRectMake(0,self.labelBottomY,self.labelWidth,self.labelHeight)
        })

    }

The video for this solution shows the bottom label position is now dynamically adjusted according to different the status bar heights:




Refererence:
personal hotspot 造成 status bar 高度改變
Screen size data of iPhones 4s, 5/5s/5c, 6/6s, and 6 Plus/6s Plus

2 comments:

  1. Instead of notification, u could use viewWillLayoutSubviews, viewDidLayoutSubviews

    ReplyDelete
    Replies
    1. For me, these two methods are not called when the status bar is changed, but the following two methods in AppDelegate work fine:

      application: willChangeStatusBarFrame
      application: didChangeStatusBarFrame

      Delete