変数が変更されたときにSwiftUIのViewがリロードされるようにする（@State、@Binding、@Published・@ObservedObject）

SwiftUIのビューは自動的に再描画され、通常は特定の関数をコールして再読み込みさせることはできません。どの変数を監視させるかをSwiftUIのビューに知らせるには、次の変数型を使用できます：

- `@State`
- `@Binding`
- `@Published`

本記事ではこれらの変数型を紹介します。

# `@State`

`@State` 変数を変更すると、そのビューは自動的に更新されます。

```swift
struct ContentView: View {
    
    @State var textContent = "Loading..."
    
    var body: some View {
        
        Text(textContent)
            .padding()
            .onAppear(perform: {
                Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
                    self.textContent = "Hello world!"
                }
            })
        
    }
    
}
```

# `@Binding`

ビュー・オブジェクトが多くのサブビューに分割されている場合、サブビューには `@Binding` 属性を定義し、主要ビューには `@State` 変数を定義することが可能です。

```swift
struct ContentView_SubView: View {
    
    @Binding private var textContent: String
    
    init(textContent: Binding<String>) {
        _textContent = textContent
    }
    
    var body: some View {
        
        Text(textContent)
            .padding()
        
    }
    
}

struct ContentView: View {
    
    @State var textContent = "Loading..."
    
    var body: some View {
        
        ContentView_SubView(textContent: $textContent)
            .onAppear(perform: {
                Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
                    self.textContent = "Hello world!"
                }
            })
        
    }
    
}
```

# `@ObservedObject` and `@Published`

例えば、もしSwiftUIのビューにユーザーのロケーションを表示したい場合、ヘルパークラスである `LocationHelper` を書くことができます。

`LocationHelper` は `CLLocationManager` からロケーションを取得し、変数 `userCoordinate` に座標を保存してくれます。

`ObservableObject` プロトコルに準拠させるために `LocationHelper` を、そしてプロパティ・ラッパーの `@Published` を持たせるために `userCoordinate` を設定できます。

```swift
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    @Published var userLocation: CLLocation?
    
    let locationManager = CLLocationManager()
    
    init(accuracy: CLLocationAccuracy) {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = accuracy
        self.locationManager.requestAlwaysAuthorization()
        self.locationManager.startUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.userLocation = location
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
    
}
```

SwiftUIのビューでは `LocationManager` に `@ObservedObject` を追加することができます：

```swift
@ObservedObject var location: LocationManager
init() {
    self.location = LocationManager(accuracy: kCLLocationAccuracyNearestTenMeters)
}
```

そして変数の変化を監視するために関数 `.onChange` を使用できます。

```swift
struct ContentView: View {

    @ObservedObject var location: LocationManager
    
    init() {
        self.location = LocationManager(accuracy: kCLLocationAccuracyNearestTenMeters)
    }
    
    var body: some View {
        
        Form {
            // TODO
        }
        
        .onChange(of: self.location.userLocation, perform: { value in
            if let receivedUpdate = value {
                self.locationsRecorded.append(locationEntry(timeStamp: receivedUpdate.timestamp, coordinate: receivedUpdate.coordinate))
            }
        })
        
    }
    
}
```

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">