dismiss
- SwiftUI에서 화면 전환을 위해 사용할 수 있는 Environment가 등장했다. 익숙한 이름대로
sheet(isPresented:onDismiss:content:)
,fullScreenCover(isPresented:onDismiss:content:)
등의 올라온 화면을 닫는다.
private struct SheetContents: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
Button("Done") {
dismiss()
}
}
}
- 사실 실제로 함수는 아니고
DismissAction
타입인데callAsFunction()
을 가지고 있기 때문에 함수로 호출할 수 있다.
다른 동작
- 문제는 이 메소드가 하는 동작이 더 있다는 것이다. 그건 바로 NavigationStack에서 pop을 해버린다는것.
- NavigationView가 Deprecated되고, 비슷한 동작이었던 NavigationLink(destination:isActive:label:) 을 NavigationStack에서 navigationDestination(isPresented:destination:)이 이어받았는데, 굳이 이 modifier로 이동하지 않고 navigationDestination(for:destination:)로 구현했더라도 Environment를 가져와 호출하면 마치 isPresented가 토글된것처럼 pop 동작이 이루어진다.
문제점
- 그러면
NavigationStack을
가진 뷰를 modal로 띄우면 어떨까? 예측이 되지 않는다는 시점에서 이미 실패한 설계라고 볼 수 있을법하다.
import SwiftUI
struct ContentView: View {
@State var isPresented: Bool = false
var body: some View {
VStack {
Button {
self.isPresented = true
} label: {
Text("Present")
}
}
.sheet(isPresented: self.$isPresented) {
NavigationSessionView()
}
}
}
struct NavigationSessionView: View {
@State var path = NavigationPath()
var body: some View {
NavigationStack(path: self.$path) {
Button {
self.path.append("Push")
} label: {
Text("Push")
}
.navigationDestination(for: String.self) { text in
NavigationSessionNextView()
}
}
}
}
struct NavigationSessionNextView: View {
@Environment(\.dismiss) var dismiss: DismissAction
var body: some View {
Button {
self.dismiss()
} label: {
Text("Dismiss")
}
}
}
navigation pop
- 결과는 보다시피 이름이
dismiss()
임에도 불구하고 모달창 닫기 없이NavigationPath
만 한스택 빠진다. - 실제로 복잡한 앱을 개발하다 보면, present를 하고 navigation을 하는 것은 물건 결제나 회원가입 등 비즈니스적인 흐름이 시작된다는 것이고, 특별한 키인
dismiss()
를 한다는 것은 모달창을 닫는것이 더 맥락상 맞는 것 같다고 생각하는데 아니었다.
해결
- 결국 이 주어진
dismiss()
는 안쓰는걸로 마음먹었다. 그리고 Environment로closeSheet()
를 하나 추가해서 넣어주었다. 처음 봤을부터 이름도 마음에 안들었다. - 내비게이션스택을 래핑하여 모디파이어를 달고 내비게이션스택을 내리도록
isPresent
를 토글하면 된다. - 덕분에 모든 내비게이션이 isPresent를 가지게 되었는데, 루트쪽은
.constant(true)
넣으면 되니까…뭐가 됐든 iOS 화면전환은 2가지인데 기획에서 썩 잘 챙겨나오는 편이 아니라서 개발자가 신경써줘야 한다.그런데 프레임워크부터 이런식이면 어떡하나
private struct CloseSheetEnvironmentKey: EnvironmentKey {
static let defaultValue: () -> () = {}
}
public extension EnvironmentValues {
var closeSheet: () -> () {
get { self[CloseSheetEnvironmentKey.self] }
set { self[CloseSheetEnvironmentKey.self] = newValue }
}
}