티스토리 뷰
☑️ 이 글의 제목
- 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 = ....
}
}
'🍏 > iOS' 카테고리의 다른 글
[iOS] Library, Framework, Swift Package (0) | 2022.11.06 |
---|---|
[iOS] object_setClass 의 위험성 (0) | 2022.11.04 |
[iOS] Swift Package 에서 RELEASE Flag 사용하기 (0) | 2022.07.04 |
[iOS] DiffableDataSource 헷갈리는 것 정리 (1) | 2022.06.29 |
[iOS] UITableViewDiffableDataSource의 4가지 apply 메서드 (0) | 2022.06.28 |
- Total
- Today
- Yesterday
- github actions
- Dart Factory
- cocoapod
- ribs
- 장고 URL querystring
- drf custom error
- flutter 앱 출시
- 장고 Custom Management Command
- Flutter Text Gradient
- Flutter 로딩
- Python Type Hint
- Django Heroku Scheduler
- 구글 Geocoding API
- PencilKit
- Flutter getter setter
- 플러터 싱글톤
- SerializerMethodField
- Django FCM
- Watch App for iOS App vs Watch App
- Flutter Spacer
- Flutter Clipboard
- flutter build mode
- ipad multitasking
- Sketch 누끼
- flutter dynamic link
- Django Firebase Cloud Messaging
- 플러터 얼럿
- flutter deep link
- METAL
- DRF APIException
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |