82494


## 実現したいもの

`TextField`に入力したものを`追加ボタン`を押すと下に並んでいくというものです。

## 問題点

下のコードでは追加ボタンを押しただけでは下に表示されず、TextFieldにさらに文字を入力すると突如追加されます。  
これは何故でしょうか？

[code]

    import SwiftUI
    
    struct TestView: View {
        @ObservedObject var testViewModel = TestViewModel()
        
        var body: some View {
            List {
                TextField("new Value", text: $testViewModel.testModel.inputValue)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                
                Button(action: {
                    self.testViewModel.addValue()
                    
                }, label: {
                    Text("追加")
                })
                
                ForEach(self.testViewModel.testModel.valueList) { valueModel in
                    Text(valueModel.value)
                }
            }
        }
    }
    
    class TestViewModel : ObservableObject {
        @Published var testModel : TestModel = TestModel()
        
        func addValue() {
            let newTestModel = ValueModel(value: self.testModel.inputValue)
            self.testModel.valueList.append(newTestModel)
        }
    }
    
    class TestModel : ObservableObject {
        @Published var inputValue: String = ""
        @Published var valueList: [ValueModel] = []
    }
    
    class ValueModel : ObservableObject, Identifiable {
        @Published var value: String
        
        init(value: String) {
            self.value = value
        }
    }
    
[/code]


CC BY-SA 4.0
2021-09-14T17:09:41.577
82490
2021-09-14T23:38:55.813
40856
question
swift swiftui
SwiftUIで＠Publishedの配列が即時に反映されない


> これは何故でしょうか？

`TestModel`が参照型であるためです。

SwiftUIでは、`@Published`なプロパティの値(またはその一部)が参照型である場合、参照されているオブジェクト内のプロパティが変更されても「値の変化」とはみなしません。

(これはSwift言語の「値の変化」に基づいて決定されている仕様です。)

内側の参照型を`ObservableObject`に適合させて`@Published`を付けても全く効果はありません。

特に理由がなければ、プロパティの値が変化した時にUIを更新させたい、と言う場合にはstructを使うべきです。

[code]

    import SwiftUI
    
    struct TestView: View {
        @StateObject var testViewModel = TestViewModel()
        
        var body: some View {
            List {
                TextField("new Value", text: $testViewModel.testModel.inputValue)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                
                Button(action: {
                    self.testViewModel.addValue()
                }, label: {
                    Text("追加")
                })
                
                ForEach(self.testViewModel.testModel.valueList) { valueModel in
                    Text(valueModel.value)
                }
            }
        }
    }
    
    class TestViewModel : ObservableObject {
        @Published var testModel: TestModel = TestModel()
        
        func addValue() {
            let newTestModel = ValueModel(value: self.testModel.inputValue)
            self.testModel.valueList.append(newTestModel)
        }
    }
    
    struct TestModel {
        var inputValue: String = ""
        var valueList: [ValueModel] = []
    }
    
    struct ValueModel: Identifiable {
        let id = UUID()
        var value: String
        
        init(value: String) {
            self.value = value
        }
    }
    
[/code]

* * *

なお、ご質問内容とは直接関係しませんが、`@ObservedObject`で宣言した変数に直接初期値を与えるような使い方をすると、それを含むviewのイニシャライザが呼ばれるたびにその変数が初期化されてしまいます。

SwiftUIにおいて、viewのイニシャライザは、実際のviewのライフサイクルとは無関係に予測困難なタイミングで呼び出される可能性があります。直接初期値を与えるような使い方をする場合には、`@ObservedObject`ではなく`@StateObject`を使わないと「中身が不定のタイミングでクリアされてしまう?」と言った事態が起こります。


CC BY-SA 4.0
2021-09-14T23:38:55.813
82494
2021-09-14T23:38:55.813
13972
82490
answer