🦋

SwiftUI: Hashableに適当に対応すると意図しない挙動になりうる

2022/12/29に公開

記事としては後でまとめる。とりあえずコードを貼る。

以下のコードでユーザーがボタンを押したりトグルスイッチを切り替えても動かないUIが生成される。

import SwiftUI

struct Hoge: Hashable {
    let id = UUID()
    var flag: Bool = false
    var number: Int = 0

    static func == (lhs: Hoge, rhs: Hoge) -> Bool {
        return lhs.id == rhs.id
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

struct InnerView: View {
    @Binding var flag: Bool
    @Binding var number: Int

    var body: some View {
        HStack {
            Toggle(isOn: $flag) {
                Image(systemName: "flag.circle.fill")
            }
            Button {
                number += 1
            } label: {
                Image(systemName: "plus.app.fill")
            }
        }
    }
}

struct ContentView: View {
    @State var hoge = Hoge()

    var body: some View {
        VStack {
            Text(hoge.flag.description)
            Text(hoge.number.description)
            InnerView(flag: Binding(get: { hoge.flag }, set: { v, _ in hoge.flag = v }),
                      number: Binding(get: { hoge.number }, set: { v, _ in hoge.number = v }))
        }
        .padding(20)
    }
}

実験

struct==の内容をもっと厳密にしたら意図通りには動いた。

struct Hoge: Hashable {
    let id = UUID()
    var flag: Bool = false
    var number: Int = 0

    static func == (lhs: Hoge, rhs: Hoge) -> Bool {
        return lhs.id == rhs.id && lhs.flag == rhs.flag && lhs.number == rhs.number
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

Discussion