7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUIAdvent Calendar 2024

Day 10

[SwiftUI] View Identityの変化を視覚的にわかるようにするmodifier

Last updated at Posted at 2024-12-17

背景

SwiftUIのViewはView Identityと呼ばれる識別子によって管理されています。このView Identityが変化すると完全に別のViewであると判断されるため、再描画コストがかかり、内部に持つ状態(@State)が初期化され、アニメーションなども期待通り動作しなくなります。

以上から、意図しないView Identityの変化はバグを生むため、避けなければいけません。

参考

本題

基本的にはコードからView Identityが変化する可能性に気付いて修正するのが理想ですが、既存のViewを調査する場合にコードを全て読み解いてバグの有無を判断するのはめんどくさい場合もあります。そんな場合に役立つmodifier。

private struct HighlightViewIdentityChanged: ViewModifier {
    @State private var loaded = false

    func body(content: Content) -> some View {
        content
            .overlay {
                if !loaded {
                    Color.yellow.opacity(0.5)
                }
            }
            .onAppear {
                withAnimation {
                    loaded = true
                }
            }
    }
}

public extension View {
    func _highlightViewIdentityChanged() -> some View {
        modifier(HighlightViewIdentityChanged())
    }
}

View Identityが変わるとStateが初期化される挙動を利用して、View Identityが変わった(生成された)瞬間Viewを黄色にハイライトする、というアプローチ。

使い方例

ViewIdentityが意図せず変わっている疑惑があるView(if, switchの内側のView)や、変わると困るView(描画コストが高いListとか、状態を内部に持ったView)に対して._highlightViewIdentityChanged()を付けてアプリをデバッグしてみる。

基本的にはmodifierの中で最も内側につけるのを推奨。

struct ContentView: View {
    @State var condition = false

    var body: some View {
        List {
            Text("Hello")

            Button("toggle") {
                condition.toggle()
            }
        }
        ._highlightViewIdentityChanged()
        .if(condition) {
            $0.foregroundStyle(.blue)
        }
    }
}

extension View {
    @ViewBuilder
    func `if`<Content: View>(
        _ condition: Bool,
        @ViewBuilder transform: (Self) -> Content
    ) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

ListのView Identityが変わっていることが視覚的にわかる。

Simulator Screen Recording - iPhone 16 Pro - 2024-12-18 at 02.31.13.gif

なお、このmodifierは付けた一点のView Identityの変化のみ検知する。つまり付けたViewの子ViewのView Identityだけが変化していても検知しない。

例えばText("Hello")のView Identityのみ変わるケースも検知したい場合はまた別の._highlightViewIdentityChanged()をつける必要がある。

struct ContentView: View {
    @State var condition = false

    var body: some View {
        VStack {
            Text("Hello")
                ._highlightViewIdentityChanged()
                .if(condition) {
                    $0.foregroundStyle(.blue)
                }

            Button("toggle") {
                condition.toggle()
            }
        }
        // これはText("Hello")のView Identity変化を検知しない
        ._highlightViewIdentityChanged()
    }
}
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?