🍏/iOS

[iOS] status bar hidden / navigation bar shrink or overlap

eungding 2022. 10. 28. 11:26
728x90
반응형

 

☑️ 이 글의 제목

- status bar를 hidden한 뷰컨트롤러를 푸쉬했다가 back 했을때 navigation bar가 shrink 되는 현상

- status bar를 hidden한 뷰컨트롤러를 푸쉬했다가 back 했을때 navigation bar 가 status bar 와 overlap 되는 현상

 

(두가지를 계속 고민하다가 결국 둘다 너무 길어서 찐제목에는 키워드만 적음,,,) 

 

 

☑️ 주된 테스트 환경

Xcode 13.4.1 / iOS 15


 

[1] 준비

아래와 같은 상황으로 준비해줍니다  (feat. 노치없는 시뮬레이터!) 

 

- A 는 네비게이션의 root 이다. 

- A 에서 B 를 푸쉬한다

import UIKit

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.backgroundColor = .red
    }
}

class AViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .lightGray
    }
    
    @IBAction func push(_ sender: Any) {
        let viewController = BViewController()
        self.navigationController?.pushViewController(viewController, animated: true)
    }
}

class BViewController: UIViewController {
        
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .darkGray
    }
}

 

 

[2] B의 statusBar 를 감추기

 

B의 statusBar를 감추고 싶어서 prefersStatusBarHidden 을 오버라이딩해줍니다.

 

class BViewController: UIViewController {
    
    override var prefersStatusBarHidden: Bool { return true }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .darkGray
    }
}

 

그럼 statusBar는 잘 감춰지나.. swipe back 해보면 네비게이션이 shrink 하는 것을 볼 수 있습니다,, 

 

 

 

preferredStatusBarUpdateAnimation도 바꿔보고 

class BViewController: UIViewController {
    
    override var prefersStatusBarHidden: Bool { return true }
    ✅ override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { return .none }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .darkGray
    }
}

 

setNeedsStatusBarAppearanceUpdate()  도 불러보고 했는데도 (참고:  prefersStatusBarHidden)

동일증상이 발생합니다. 

 

 

[3] 왜 애플은...?

이게 애플 기본 동작이라고...??!?!?  하는 생각이 들면서 혼란스러웠지만

스택오버플로우 (NavBar overlaps Status Bar in iOS 13 Swift) 에서 살짝 힌트를 찾은 것 같습니다. 

now Apple seems to be assuming that if you have a navigation bar you won't hide the status bar at all
"I want in the first view controller a full screen user experience" Ah, 
but if you wanted that you would hide the navigation bar too. That's the point I'm making.

 

- 이제 애플은 네비게이션 바가 있으면 상태 바를 전혀 숨기지 않을 것이라고 가정하고 있는 것으로 보인다

- 너가 user experience 를 위해 full screen을 제공하고 싶은거면 statusbar 뿐만 아니라 navigation bar도 감춰야지 말이 된다

 

 

이건 애플의 공식발표(?)기 아니라 단지 추측일 뿐이지만,,, 

제가 생각하기에도,,,,  present 도 아니고

같은 네비게이션에서 push를 하는데 특정화면만 status bar 를 숨기는게 좋은 사용성인지 잘모르겠습니다,, 

 

 

[4]  원인 찾기 & 문제 해결 

아무튼 해결은 하고 싶으니 원인을 찾아봅시다

원인은 무엇일까요?! 

A로 back 했을 때  '네비게이션 컨트롤러의 safeAreaTopInset' 과  '네비게이션 바의 y' 가 다르기 때문입니다. 

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.backgroundColor = .red
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        print("1) \(self.view.safeAreaInsets.top)")
        print("2) \(self.navigationBar.frame.minY)")
    }
}



// A 일때
1) 20.0
2) 20.0


// B 로 push 했을때
1) 0.0
2) 0.0

// A 로 돌아왔을때 
1) 20.0
✅ 2) 0.0

 

 

원인을 알았으니 아래와 같이 해결해주면 되겠습니다. 

import UIKit

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.backgroundColor = .red
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        ✅ self.navigationBar.frame.origin.y = self.view.safeAreaInsets.top
    }
}

 

또는 viewDidLayoutSubviews 말고 viewSafeAreaInsetsDidChange 를 오버라이딩 하셔도 됩니다. 

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationBar.backgroundColor = .red
    }

     override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        ✅ self.navigationBar.frame.origin.y = self.view.safeAreaInsets.top
    }
}

 

 

 

[5] UINavigationBarAppearance 를 쓴 케이스

UINavigationBarAppearance 를 써준 경우도 대응이 잘 될까요?! 
(status bar background가 navigation bar background와 동일한 경우)

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appearance = UINavigationBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.backgroundColor = .red
        
        self.navigationBar.standardAppearance = appearance
        self.navigationBar.scrollEdgeAppearance = appearance
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.navigationBar.frame.origin.y = self.view.safeAreaInsets.top
    }
}

 

좀 아쉽게 되는군요,,,  (swipe back 하는 도중에는 안변하니까,,)

 

 

(참고로 viewWillAppear 에서 configureWithOpaqueBackground 를 다시 불러주거나 appearance 를 다시 설정해주거나,, 해도 동일합니다)

 

 

그렇다면! 아래처럼 대응해줄 수 있습니다. 

import UIKit

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appearance = UINavigationBarAppearance()
        appearance.configureWithDefaultBackground()
        appearance.backgroundColor = .red
        
        self.navigationBar.standardAppearance = appearance
        self.navigationBar.scrollEdgeAppearance = appearance
    }
}

class BaseViewController: UIViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        ✅ self.navigationController?.isNavigationBarHidden = false
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        ✅ self.navigationController?.isNavigationBarHidden = true
    }
}

class AViewController: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .lightGray
    }
    
    @IBAction func push(_ sender: Any) {
        let viewController = BViewController()
        self.navigationController?.pushViewController(viewController, animated: true)
    }
}

class BViewController: BaseViewController {

    override var prefersStatusBarHidden: Bool { return true }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .darkGray
    }
}

 

 

 

 

만약 높이차이가 좀 나니까,, swipe back 할때 좀 더 자연스럽고 싶다!! 하면

swipe back 의 percent 변화를 활용해서 alpha 값 또는 y 값을 설정해주셔도 좋습니다. 

 

class MyNavigationController: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ... 
        self.interactivePopGestureRecognizer?.addTarget(self, action: #selector(self.didSwipeBack(_:)))
    }
    
    @objc
    private func didSwipeBack(_ sender: UIGestureRecognizer) {
        let percent = self.transitionCoordinator?.percentComplete ?? 1
        self.navigationBar.alpha = percent
        self.navigationBar.frame.origin.y = ....
    }
}

 

 

 

 

반응형