modal의 onDismiss 활용하기

SteadySlower·2022년 9월 3일
0
post-custom-banner

개발하고자 하는 기능

현재 단어장을 마감하는 기능을 구현하고 있습니다. 간단하게 설명하면 공부를 다한 단어장을 마감처리하고 단어장을 공부 목록에서 제거하는 기능입니다.

단어장을 마감하기 위해서는 아직 공부를 마무리하지 못한 단어들을 새로운 단어장으로 보내야 합니다. 그 보낼 단어장을 고르기 위해 “닫기" 버튼을 누르면 modal이 뜨도록 했습니다.

자 그리고 마지막으로 보낼 단어장까지 모두 골랐다면 어떻게 해야할까요? 이제 현재 단어장은 닫힌 단어장입니다. 따라서 메인 화면으로 다시 돌아가야 맞습니다. 즉 modal이 dismiss될 때 현재 View도 Dismiss되어야 합니다. 바로 아래 캡쳐처럼요.

반면에 단어장 마감을 취소하면 어떻게 되어야 할까요? 취소했다면 아직 현재 단어장은 닫히지 않았습니다. 아래 캡쳐처럼 계속 해당 단어장을 보여줘야겠지요.

onDismiss

두 동작은 모두 modal이 dismiss될 때 동작해야 하는 코드입니다.

우리가 Modal을 띄우기 위해서 View에 정의된 sheet라는 메소드를 사용했습니다. sheet라는 메소드를 자세히 보면 우리가 사용해왔던 isPresented (modal을 띄울지 말지 결정하는 Bool 변수)와 content (modal의 View)외에 onDismiss라는 함수를 하나 더 전달할 수 있는 것을 볼 수 있습니다. (기본값은 nil)

바로 이 함수가 이 기능 구현의 핵심이 되는 함수입니다. 이 함수는 modal이 dismiss될 때 실행됩니다. 아래처럼 현재 View를 dimiss하는 코드를 전달하면 modal이 dismiss될 때 현재의 View도 dismiss됩니다.

// 상위 View
@Environment(\.dismiss) private var dismiss

// 상위 View body
.sheet(
		isPresented: $showCloseModal, 
		onDismiss: { dismiss() }) {
    WordBookCloseView(wordBook: viewModel.wordBook, toMoveWords: viewModel.toMoveWords)
}

조건부 dismiss

하지만 우리의 경우 무조건 View를 dismiss하는 것이 아니라 modal에서 “닫기" 버튼을 누르면 dismiss하고 “취소" 버튼을 누르면 dismiss 되지 않도록 해야 합니다. 따라서 하위 View인 modal에서 상위 View에 dismiss할지 말지에 대한 정보를 전달해야 합니다.

상위 View

여기서는 간단하게 Binding을 이용해보도록 하겠습니다. 먼저 상위 View에 @State로 Bool 변수를 하나 선언합니다. 해당 변수는 현재 View가 dismiss 되어야 하는지 아닌지 결정하는 변수입니다.

그리고 이 변수의 참조를 (Binding)을 하위 View에 전달합니다.

// 상위 View
@Environment(\.dismiss) private var dismiss
@State private var shouldDismiss: Bool = false

// 상위 View body
.sheet(
		isPresented: $showCloseModal, 
		onDismiss: { if shouldDismiss { dismiss() } }) {
    WordBookCloseView(wordBook: viewModel.wordBook, toMoveWords: viewModel.toMoveWords, didClosed: $shouldDismiss)
}

하위 View에서 Binding으로 전달받은 참조를 @Binding 변수에 할당하여 가지고 있습니다. 그리고 각각의 버튼에 “취소"인 경우에 해당 변수에 false를 할당하고 (상위 View dismiss 안함), “닫기" 혹은 “이동"인 경우 true를 할당하면 됩니다. (상위 View dismiss 함)

// 하위 View
@Binding private var didClosed: Bool

// 하위 View initializer
init(wordBook: WordBook, toMoveWords: [Word], didClosed: Binding<Bool>) {
  self.viewModel = ViewModel(toClose: wordBook, toMoveWords: toMoveWords)
  self._didClosed = didClosed
}

// 하위 View body
HStack {
    Button("취소") {
        didClosed = false
        dismiss()
    }
    Button(viewModel.selectedID != nil ? "이동" : "닫기") {
        showProgressView = true
        viewModel.closeBook {
            didClosed = true
            dismiss()
        }
    }
}

onDismiss

마지막으로 주목할 부분은 onDismiss입니다. 하위 View에서 버튼을 누르면 상위 View의 @State에 할당이 되고 modal이 dismiss되면서 onDismiss에 있는 코드가 실행됩니다. 이 때 shouldDismiss가 true인 경우 현재 View를 dismiss하도록 하면 됩니다.

// 상위 View
@Environment(\.dismiss) private var dismiss
@State private var shouldDismiss: Bool = false

// 상위 View body
.sheet(
		isPresented: $showCloseModal, 
		onDismiss: { if shouldDismiss { dismiss() } }) {
    WordBookCloseView(wordBook: viewModel.wordBook, toMoveWords: viewModel.toMoveWords, didClosed: $shouldDismiss)
}
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.
post-custom-banner

0개의 댓글