You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免SwiftUI状态更新时重绘所有子视图?

解决SwiftUI中状态更新时子视图批量重绘的问题

问题场景

你定义了可识别的Object类型、存储状态的ObjectStates结构体,在ContentView中通过List展示多个MyToggle子视图,绑定到objectStates的下标值。但切换任意一个Toggle时,所有MyToggle都会触发重绘;而添加额外的@State属性后,更新该属性时子视图却不会重绘。

原代码示例:

struct Object: Identifiable {
    let id = UUID()
    let name: String
}

struct ObjectStates {
    var states: [Object.ID: Bool] = [:]

    subscript(objectId: Object.ID) -> Bool {
        get { states[objectId, default: false] }
        set { states[objectId] = newValue }
    }
}

struct ContentView: View {
    let objects = [Object(name: "Toggle 1"), Object(name: "Toggle 2"), Object(name: "Toggle 3"), Object(name: "Toggle 4"), Object(name: "Toggle 5")]
    @State var objectStates = ObjectStates()
    
    var body: some View {
        List(objects) { object in
            MyToggle(name: object.name, isOn: $objectStates[object.id])
        }
    }
}

struct MyToggle: View {
    let name: String
    @Binding var isOn: Bool
    
    var body: some View {
        let _ = print(name)
        let _ = Self._printChanges()
        
        Toggle(name, isOn: $isOn)
    }
}

问题原因

ObjectStates是值类型结构体,当通过下标修改其中某个状态值时,整个objectStates实例会被标记为“已更改”。SwiftUI会认为父视图的状态发生了整体变化,进而重新计算父视图的body,并对所有子视图进行重绘检查——即使大部分子视图的绑定值并未实际改变。

而添加额外@State属性后,更新该属性时objectStates并未发生变化,SwiftUI能精准判断只有关联的主Toggle需要更新,因此子视图不会被触发重绘。

解决方案

有两种常用方式可以避免子视图批量重绘:

方案1:让子视图遵循Equatable,精准控制重绘时机

让MyToggle遵循Equatable协议,仅当isOn或name实际变化时才触发重绘:

struct MyToggle: View, Equatable {
    let name: String
    @Binding var isOn: Bool
    
    static func == (lhs: MyToggle, rhs: MyToggle) -> Bool {
        lhs.name == rhs.name && lhs.isOn == rhs.isOn
    }
    
    var body: some View {
        let _ = print(name)
        let _ = Self._printChanges()
        
        Toggle(name, isOn: $isOn)
    }
}

然后在父视图的子视图声明后添加.equatable()修饰符:

List(objects) { object in
    MyToggle(name: object.name, isOn: $objectStates[object.id])
        .equatable()
}

方案2:将状态容器改为引用类型ObservableObject

把ObjectStates从结构体改为ObservableObject,利用SwiftUI的精准依赖追踪,仅当对应状态变化时通知子视图更新:

class ObjectStates: ObservableObject {
    @Published var states: [Object.ID: Bool] = [:]

    subscript(objectId: Object.ID) -> Bool {
        get { states[objectId, default: false] }
        set { states[objectId] = newValue }
    }
}

在ContentView中使用@StateObject持有状态容器,并通过自定义Binding绑定到子视图:

struct ContentView: View {
    let objects = [Object(name: "Toggle 1"), Object(name: "Toggle 2"), Object(name: "Toggle 3"), Object(name: "Toggle 4"), Object(name: "Toggle 5")]
    @StateObject var objectStates = ObjectStates()
    
    var body: some View {
        List(objects) { object in
            MyToggle(name: object.name, isOn: Binding(
                get: { self.objectStates[object.id] },
                set: { self.objectStates[object.id] = $0 }
            ))
        }
    }
}

这样,修改某个状态时,只有绑定该状态的子视图会收到更新通知并触发重绘,其他子视图不受影响。

内容的提问来源于stack exchange,提问作者Ashley Mills

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 21:46:06