【SwiftUI】戻るボタンを使わずにコードで前の画面へ戻る方法(MVVMバージョン)

NavigationView によって遷移した画面から Back ボタンではなく、プログラムによって戻る方法については既に下記の記事で紹介しました。

遷移元のViewに @State の Bool変数を用意し、それを遷移先のViewで @Binding の Bool変数に紐付けるという方法を取っていました。

これを、「MVVM(ViewとViewModel)の形式でやってみよう」というのが今回の記事の内容です。

SwiftUI での MVVMの組み方、@ObservableObject@ObservedObjectについては以下の記事で解説していますのでご参考ください。

戻るボタンを使わずにコードで前の画面へ戻る方法(MVVMバージョン)

MVVMで考える上で、View側には表示データや状態などの変数を持たせたくありません。そのため、肝となる@State の変数も ViewModel に @Published として持たせます。

@Published var isShowSecond = false

紐付け先のViewでは、@PublishedBinding<Bool> 型の変数を宣言します。

@Published var isPresented: Binding<Bool>

イニシャライザーは以下のように宣言し受け取ります。

init(isPresented: Binding<Bool>) {
    self.isPresented = isPresented
}

遷移元のViewから紐付ける時は以下のように呼び出します。

.init(isPresented: $viewModel.isShowSecond)

これで遷移先の isPresentedfalse になると、遷移元の isShowSecond が同期して false になり遷移元のViewに戻ることができます。isPresentedBinding<Bool> 型なので、false をそのまま代入することはできないので以下のように wrappedValue プロパティに対して代入します。

self.isPresented.wrappedValue = false

以上が今回のポイントです。

最後に全体のソースコードを紹介します。

struct FirstView: View {
    
    @ObservedObject var viewModel: FirstViewModel
    
    var body: some View {
        NavigationView {
            VStack {
                
                Button(action: {
                    self.viewModel.showSecond()
                }, label: {
                    Text("Go to Second View.")
                })
                
                NavigationLink(
                    destination: SecondView(viewModel: .init(isPresented: $viewModel.isShowSecond)),
                    isActive: $viewModel.isShowSecond
                    ) {
                    EmptyView()
                }
            }
        }
    }
}
class FirstViewModel: ObservableObject {
    
    @Published var isShowSecond = false
    
    func showSecond() {
        self.isShowSecond = true
    }
}
struct SecondView: View {
    
    @ObservedObject var viewModel: SecondViewModel
    
    var body: some View {
        Button(action: {
            self.viewModel.dismiss()
        }, label: {
            Text("Back to First View.")
        })
    }
}
class SecondViewModel: ObservableObject {
    
    @Published var isPresented: Binding<Bool>
    
    init(isPresented: Binding<Bool>) {
        self.isPresented = isPresented
    }
    
    func dismiss() {
        self.isPresented.wrappedValue = false
    }
}

以上、MVVM形式で戻るボタンを使わずに前の画面へ戻るプログラムを紹介しました。

関連する記事として以下もご参考下さい(こちらも後々MVVM版の記事を書いてみようと思います)。