77440


### 発生している問題

以下のコードを実行した際に、showを押してもText("aaa")の方が表示されてします。  
Buttonのactionでself.studentには値を設定しているので、挙動に違和感を覚えました。  
ちなみに一回Modalを閉じてもう一回開くとSubViewが表示されます。

### お聞きしたいこと

解決策としては `.sheet(item:content:)` を使えばいいことは分かっているのですが、なぜこのような挙動になるかお聞きしたいです。

よろしくお願いいたします。

**ソースコード:**

[code]

    import SwiftUI
    
    class Student: ObservableObject {
      var name: String
      
      init(name: String) {
        self.name = name
      }
    }
    
    struct ContentView: View {
      @State private var student: Student?
      @State private var show = false
      
      var body: some View {
        VStack(spacing: 10) {
          Button(action: {
            let newStudent = Student(name: "bbb")
            self.student = newStudent
            self.show = true
          }, label: {
            Text("Show")
          })
        }
        .sheet(isPresented: self.$show, content: {
          if let student = self.student {
            SubView(student: student)
          } else {
            Text("aaa")
          }
        })
      }
    }
    
    struct SubView: View {
      @ObservedObject var student: Student
      
      var body: some View {
        TextField("生徒名", text: self.$student.name)
      }
    }
    
[/code]


CC BY-SA 4.0
2021-06-09T13:27:19.510
77436
2021-06-09T16:34:25.427
2021-06-09T16:34:25.427
3060
24870
question
swift ios swiftui
.sheet(isPresented:content:)を使いModalを表示する際に渡した値がnilになる


下記のようなグローバル変数 `i` を参照できる関数を用意します。

[code]

    var i = 0
    func addIAndShowI() {
        i = i + 1
        print(i)
    }
    
[/code]

全体のコードは下記です。

[code]

    import SwiftUI
    
    var i = 0
    func addIAndShowI() {
        i = i + 1
        print(i)
    }
    
    class Student: ObservableObject {
      var name: String
      
      init(name: String) {
        self.name = name
      }
    }
    
    struct ContentView: View {
      @State private var student: Student?
      @State private var show = false
      
      var body: some View {
        VStack(spacing: 10) {
          let _ = addIAndShowI() // <-- ボタンを押すたびに処理が走ることがわかる
          Button(action: {
            let newStudent = Student(name: "bbb")
            self.student = newStudent
            self.show = true
          }, label: {
            Text("Show")
          })
        }
        .sheet(isPresented: self.$show, content: {
          if let student = self.student {
            SubView(student: student)
          } else {
            Text("aaa")
          }
        })
      }
    }
    
    struct SubView: View {
      @ObservedObject var student: Student
      
      var body: some View {
        TextField("生徒名", text: self.$student.name)
      }
    }
    
[/code]

ボタンを押すたびに、

[code]

    1
    2
    3
    
[/code]

と表示されることがわかります。

ここからの解説は、説明がみつからないので、私の推測が入りますが、

そもそも`ContentView`は構造体であり、Swiftの構造体は変更を加えることができないのですが、書き換わったように見せるために`@State`等があります。`content:`の引数にはクロージャが渡っており、クロージャが生成された時点では、`self.student`は`nil`です。  
そのクロージャを使うときも`nil`のままです。`@State`な`self.student`に変更が加わると内部的なロジックによって関連するViewが生まれかわるので、VStackが再度生成されます(`addIAndShowI`が何回も処理されるので、再生成されていることがわかる)。このとき2回目の`content:`へ新しいクロージャが渡ります。この2回目のときは`self.student`は、`name`が`bbb`のものです。このようにクロージャが生成されるタイミングと使われるタイミングを考慮することで1回目が`nil`であることの説明がつくと思います。


CC BY-SA 4.0
2021-06-09T15:07:56.420
77440
2021-06-09T15:29:29.513
2021-06-09T15:29:29.513
9008
9008
77436
answer