SwiftUIでは可能な限り値を丸めずにviewに渡すことを心がけている

主語大きい。何が言いたいかと言うと、よく利用するIdentifiableを準拠させる構造体のプロパティは可能な限り丸めずに渡した方がIDの一意性を担保しやすい。

開発環境

$ xcodebuild -version
Xcode 14.1
Build version 14B47b

SwiftUIにおけるIdentifiable

WWDCの動画でSwiftUIにおけるIdentifiableの利用例が出てくる。

developer.apple.com

本記事では、以下のようなワークアウトのviewを作る際のIdentifiableについて考える。

コード

このコードには問題があります。どこでしょう?

struct WorkoutItem: Identifiable {
    enum WorkoutType: String {
        case cycling = "Cycling"
        case running = "Running"
        case walking = "Walking"
    }

    let type: WorkoutType
    let distanceString: String  // 1.70
    let dateString: String      // 2023.01.19

    var id: String { distanceString + dateString }
}

struct WorkoutItemView: View {
    let item: WorkoutItem

    // 省略
}

コードの問題点

struct WorkoutItem: Identifiable {
    let distanceString: String  // 1.70
    let dateString: String      // 2023.01.19

    var id: String { distanceString + dateString }
}

全てのプロパティが重複する可能性がある。つまり一意性を担保できない。同じ日付に同じ距離のワークアウトを行ったら重複する。

実際に自分用のアプリで試したら歯抜けになった。歯抜けの箇所は2023.01.19の1.70km。同じ日に同じ距離歩いた。

対応として、日付をStringから丸める前のDateに変える。そうすると全く同じ時刻に同じワークアウトを始めなければ重複することはない。分身が出来なければ不可能なのでこれで良いだろう。

struct WorkoutItem: Identifiable {
    let distanceString: String
    let date: Date  // 変更

    var id: String { distanceString + date.hashValue.description }  // 変更
}

距離も元々DoubleなのでDoubleとして持たせておくとより一意性が担保できる。

ここから得られる学び

viewに値を渡す際はなるべく値を丸めずに渡してあげる。そうるすると、Identifiableに準拠する際に一意性を担保するための手札が増える。

UIKit+Storyboardで開発していた時はviewに渡す際に値を丸めていた。UIKitのviewはクラスで参照型でポインタで一意性を担保しているためコードでは表示に必要な値さえ渡せられれば十分だったからだ*1。SwiftUIでは丸めずに渡そう。

参考

*1:ログ送信を行う際はその限りではない