졸업 프로젝트 중에 SheetPresentation 위에 탭바를 표시하는 것에 애먹었기 때문에 글을 남긴다. 스택오버플로우나 유튜브 모든 곳을 뒤져봐도 SheetPresentation 위에 탭바를 표시하는 방법은 안나와있어서 거진 10시간은 삽질했다..
만약 찾으시는 분은 그러지 마시길 바라며 적는다.
물론 나도 공부하는 입장이고 완벽하지 않은 방법이겠지만, 구동은 되니까 남긴다..^_^
아래에서 위로 스와이프해서 목록을 볼 수 있게 해주는 BottomSheet를 쉽게 생성해주는 기능이다.
swift자체에서 제공하는 프레임워크로, modal 등 뷰 표시 방식 중 하나이다.
사실 처음에 이런 게 존재하는 줄 모르고 scrollable bottomsheet로 서치해서 나오는 frame, gesture등을 사용하여 별도로 만든 코드를 사용했다. 근데 당연히 이해하기 어려워서 코드 재사용성도 안좋아져서 다시 찾아보다가 UISheetPresentation을 찾아 채택하게 되었다.
//여기서 tab은 sheet로 띄우고 싶은 viewController
if let sheet = tab.sheetPresentationController {
let fraction = UISheetPresentationController.Detent.custom { context in
140
}
sheet.detents = [.medium(), .large(), fraction]
sheet.largestUndimmedDetentIdentifier = .medium // nil 기본값
sheet.prefersScrollingExpandsWhenScrolledToEdge = false // true 기본값
sheet.prefersEdgeAttachedInCompactHeight = true // false 기본값
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true // false 기본값
sheet.prefersGrabberVisible = true
}
tab.isModalInPresentation = true
present(tab, animated: true, completion: nil)
fraction
은 별도로 표시하고자 하는 sheet의 detent를 설정한 것이다. 기본값으로는 .small()
같은건 없고, .medium()
,.large()
만 지원한다.
기타 속성들은 공식문서를 참고하자.
이제 여기서 중요한 점은 탭바를 어떻게 위에 표시하냐는 것이다.
나같은 경우에는
1. 새로 띄운 뷰의 frame을 기존의 탭바보다 위로 올려 표시하기
2. 탭바보다 새로 띄운 뷰가 화면 안쪽으로 가도록 z값 바꿔주기
3. etc
등을 시도해 보았으나 당연히 안된다.
하지만 당연히 방법이 있으니 다른 앱들에서 해당 기능을 지원하는 것이겠거니 생각해 포기할 수 없었다.
그러던 중 한 stackOverflow에서 아예 탭바를 새로 만드는게 나을 수도 있다는 덧글을 보고 시도해보게 된 방법이 정답이었다.
애초에 sheetPresentation방법으로 새로운 뷰를 present한것이기 때문에 screen에서 현재 뷰(시트로 띄워진 뷰)는 기존에 탭바에 임베드된 navigationController에 종속되어있지 않다. 따라서 애초에 탭바를 띄울 수 없는 것이다.
즉, 우리가 해야할 처리는 새로운 sheetPresentation된 뷰를 위한 탭바를 새로 만들어서 등록해주기 인 것이다.
let bottomSheetVCSB = UIStoryboard(name: "스토리보드이름", bundle: nil)
let bottomSheetVC = bottomSheetVCSB.instantiateViewController(withIdentifier: "MyPlaceScrollableViewController")//스토리보드ID지정해주자
let nav = UINavigationController(rootViewController: bottomSheetVC)
바로 탭바 컨트롤러에 임베드되는것이 아닌, 네비게이션 컨트롤러에 임베드된다. 물론 이 부분을 생략하고 바로 탭바에 임베드해도 되긴 하지만, 띄워진 sheetPresentation 뷰컨트롤러에서 또 다른 뷰로 이동할 때 네비게이션 컨트롤러가 필요하다면 임베드해주도록 하자.
let tab = UITabBarController()
tab.viewControllers = self.navigationController?.tabBarController?.viewControllers
기존의 탭바컨트롤러의 vc들을 등록해야 새로운 탭바에서도 기존 화면의 탭바와 동일하게 표시되기 때문이며, 기존 탭바에서 표시된 vc들로 이동 가능하기 때문이다.
tab.viewControllers?[2] = nav
nav.tabBarItem.title = "플레이스"
nav.tabBarItem.image = UIImage(systemName: "star")
tab.selectedIndex = 2
현재 표시되는 뷰컨트롤러(sheetPresentation된 뷰컨트롤러)가 탭바에 포함되어야하기 때문에 바꿔주어야 한다.
tab.delegate = self
탭바 아이템을 선택했을 때 다시 풀스크린으로 표시되도록 해줘야 하기 때문에 델리게이트를 위임해준다. 주의해야 할 점은 지금 위임해주는 탭바컨트롤러는 시트뷰를 띄워주는 기존 뷰의 탭바가 아닌, 새로 띄울 탭바컨트롤러의 델리게이트를 설정해준다는점이다. 헷갈리지 않도록 하자.
private func setSheetView(){
let bottomSheetVCSB = UIStoryboard(name: "스토리보드이름", bundle: nil)
let bottomSheetVC = bottomSheetVCSB.instantiateViewController(withIdentifier: "MyPlaceScrollableViewController")
let nav = UINavigationController(rootViewController: bottomSheetVC)
let tab = UITabBarController()
tab.delegate = self
tab.viewControllers = self.navigationController?.tabBarController?.viewControllers
tab.viewControllers?[2] = nav
nav.tabBarItem.title = "플레이스"
nav.tabBarItem.image = UIImage(systemName: "star")
tab.selectedIndex = 2
if let sheet = tab.sheetPresentationController {
let fraction = UISheetPresentationController.Detent.custom { context in
140
}
sheet.detents = [.medium(), .large(), fraction]
sheet.largestUndimmedDetentIdentifier = .medium // nil 기본값
sheet.prefersScrollingExpandsWhenScrolledToEdge = false // true 기본값
sheet.prefersEdgeAttachedInCompactHeight = true // false 기본값
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true // false 기본값
sheet.prefersGrabberVisible = true
}
tab.isModalInPresentation = true
present(tab, animated: true, completion: nil)
}
extension MyPlaceViewController: UITabBarControllerDelegate{
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if tabBarController.selectedIndex != 2 {
dismiss(animated: true)
if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate {
let storyboard = UIStoryboard(name: "Jinhee", bundle: nil)
if let tab = storyboard.instantiateViewController(identifier: "InitialTabBar") as? UITabBarController{
sceneDelegate.window?.rootViewController = tab
tab.selectedIndex = tabBarController.selectedIndex
}
}
}
}
}
현재 선택된 인덱스가 탭바에서 sheetPresentation된 뷰가 아닌 경우, sheetPresentation뷰를 dismiss 해주어야 한다. 그렇지 않으면 sheetPresentation된 뷰가 rootViewController가 되어 다른 뷰를 present하더라도 계속 해당 시트뷰 안에 표시되기 때문이다.
중요한 부분은 다음과 같다.
이렇게 되면 기존의 rootViewController(tabBarController)을 다시 생성
->해당 탭바에서 선택했던 인덱스의 vc를 생성하여 화면에 띄움
이렇게 되어 sheetPresentation의 뷰에서 벗어날 수 있게 되는 것이다.
당연히 빙빙 돌아서 땜빵한 격이라 좋은 코드가 아닌 것은 알겠지만, 탭바를 시트위에 올리는 것에 성공한것에 의의를 두려고 한다.
당장 시트뷰 위에 탭바를 표시하는 해답이 적힌 자료가 없으니..^^... 참고라도 되시라고 올려본다.,