이번 포스팅에서 구현할 기능은 저장 버튼을 누르면 맨 처음 TextEditor로 오도록 하는 기능입니다. 하위 View에 클로저를 전달해서 간단하게 구현해보록 하겠습니다.
제가 사용한 특정 TextEditor로 커서를 옮기는 방법은 @FocusState 변수를 활용하는 것입니다. 자세한 방법은 이 포스팅과 이 포스팅을 참조하시면 좋을 것 같습니다. 저는 이 변수의 포인터를 하위 View에 전달해서 하위 View에서 해당 변수에 .meaning을 할당할 생각이었습니다.
하지만 @Binding으로 쉽게 하위 View에 전달할 수 있었던 @State 변수와는 다르게 @FocusState 변수는 쉽지가 않았습니다. 그래서 생각해 낸 다른 방법이 오늘 구현하고자 하는 방법입니다.
바로 버튼이 클릭이 되면 실행할 클로저를 전달하는 방법입니다. 아래 코드를 한번 보도록 하겠습니다. 커스텀 버튼 내부에 클로저를 저장할 변수를 하나 선언해두고 initializer에서 클로저를 인자로 받아서 init합니다. 해당 클로저는 init의 코드 블럭 바깥에서 실행될 예정이므로 @escaping 키워드를 붙입니다.
이렇게 하면 하위 View에서 상위 View의 Property의 참조를 가지고 있지 않아도 클로저를 실행을 통해서 @FocusState 변수를 바꿀 수 있습니다. View는 최대한 멍청(?)하면 좋다는 원칙에도 부합하기도 하고요.
struct SaveButton: View {
@EnvironmentObject private var viewModel: ViewModel
private let saveButtonTapped: () -> Void
init(saveButtonTapped: @escaping () -> Void) {
self.saveButtonTapped = saveButtonTapped
}
var body: some View {
if viewModel.isUploading {
ProgressView()
} else {
Button {
saveButtonTapped()
} label: {
Text("저장")
}
.disabled(viewModel.isSaveButtonUnable)
.keyboardShortcut(.return, modifiers: [.control])
}
}
}
상위 View에서는 이렇게 클로저 안에 클릭하면 실행하고자 하는 코드를 선언해두면 됩니다. 아래 처럼 trailing closure를 통해서 코드를 간략하게 할 수도 있습니다.
SaveButton {
viewModel.saveWord()
editFocus = .meaning
}