Help us understand the problem. What is going on with this article?

[SwiftUI]sheetで遷移するView間で値を引き渡す

はじめに

この記事はジーズアカデミーAdvent Calendar 2019 3日目の記事です。
こんにちは!Dev(週末コース)8期の@Ryu0823といいます。
ジーズではWebコースで、最後のDemo dayも参加できなかったのですが、卒業してからもいろいろな言語をかじり、最近はSwiftUIでアプリを制作しています。
SwiftUIはSwiftより簡単かつわかりやすいと感じていますが、まだ新しい言語のためできないこともあったり、情報も少ないのが現状です。
そこで今回は、sheetで遷移する場合にView間で値を渡す方法を探すのに苦労したので、備忘録的に書いてみます。
完成形は↓
Nov-24-2019 21-50-32.gif

やることと初期状態

FirstViewからsheetとしてSecondViewを表示し、SecondViewで入力された値をFirstViewに表示します。

struct FirstView: View {
    var body: some View {
        VStack {
            Text("Hello") // <-SecondViewで入力された値を表示する
            Button(action: {
                print("Button tapped.") // <-SecondViewへ遷移する
            }) {
                Text("Button")
            }
        }
    }
}

struct SecondView: View {
    @State var userName = ""

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName) // <-ここで入力された値をFirstViewへ戻す
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            // FirstViewへ戻る処理が必要
        }
    }
}

FirstViewからsheetとしてSecondViewを表示

struct FirstView: View {
    // SecondViewの表示/非表示のState
    @State var showSecondView = false

    var body: some View {
        VStack {
            Text("Hello") // <-SecondViewで入力された値を表示する

            Button(action: {
                // SecondViewの表示/非表示の切り替え
                self.showSecondView.toggle()
            }) {
                Text("Tell your name")
                } 
            }
            .sheet(isPresented: self.$showSecondView) {
                // SecondViewを表示
                SecondView(isPresent: self.$showSecondView)
            }
        }
    }
}

struct SecondView: View {
    @State var userName = ""
    // SecondViewの表示/非表示
    @Binding var isPresent: Bool

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName) // <-ここで入力された値をFirstViewへ戻す
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            // FirstViewへ戻る
            .navigationBarItems(trailing:
                Button(action: {
                    self.isPresent = false
                }) {
                    Text("Done")
                }
            )
        }
    }
}

View間で値を連携する

ObservableObjectを定義

View間で連携したい変数を、ObservableObject classを継承したclassのpropertyとして定義

final class ViewModel: ObservableObject {
    @Published var name = ""
}

FirstViewにObservedObjectを追加

struct FirstView: View {
    @State var showSecondView = false
    // ViewModelをObservedObjectとして初期化
    @ObservedObject var userName = ViewModel()

    var body: some View {
        VStack {
            // ObservedObjectのname propertyを表示(SecondViewの値を表示したい)
            Text("Hello \(self.userName.name)")

            Button(action: {
                self.showSecondView.toggle()
            }) {
                Text("Tell your name")
                } 
            }
            .sheet(isPresented: self.$showSecondView) {
                // SecondViewを表示
                SecondView(isPresent: self.$showSecondView)
            }
        }
    }
}

FirstViewで初期化したObservedObjectをSecondViewと共有する

SecondViewではFirstViewと同じObservedObjectを見る必要があります。
そのため、SecondViewではViewModel classを指定するだけで、初期化はしません。
初期化すると別のObservedObjectが作成され、View間で値の共有ができません。
(私はここでつまった💦)

struct SecondView: View {
    @Binding var isPresent: Bool
    // FirstViewから呼び出す際にFirstViewで初期化したViewModel変数を指定
    @ObservedObject var userName: ViewModel

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("What is your name?")) {
                    TextField("Name", text: $userName.name)
                }
            }
            .navigationBarTitle("SecondView", displayMode: .inline)
            .navigationBarItems(trailing:
                Button(action: {
                    self.isPresent = false
                }) {
                    Text("Done")
                }
            )
        }
    }
}

最後にFirstView内のSecondView初期化を修正

SecondViewで定義したuserName propertyにFirstViewのuserNameが入ります。
これでSecondViewのTextFieldに入力した値がFirstViewに連携されます。

SecondView(isPresent: self.$showSecondView, userName: self.userName)

終わりに & SwiftUI参考リンク

まだまだ勉強中ですが、この記事が少しでも役に立てば幸いです!
SwiftUIやSwiftそのほかモバイル向け言語やってる方いたらお気軽にSNSで繋がりましょう!

最後にSwiftUI学習の参考サイトを載せておきます。
100 days of SwiftUI - Hacking with Swift
#100daysOfSwiftUIでTwitterに毎日勉強したことを投稿してます。
The SwiftUI Lab
SwiftUIはまだ出来たばかりなので、ドキュメントが未整備な部分もあるのですが、このサイトではかなり詳しく解説してくれています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした