SwiftUI何时为值类型的视图提供持久标识

首先来一段英文,这是apple wwdc中的一句,没兴趣的话可以略过不看

value types do not have a canonical reference that SwiftUI can use as a persistent identity for its views

developer.apple.com/videos/play...

"值类型没有规范引用,SwiftUI无法将其用作视图的持久标识"-果然每个字都认识,不知道在说啥

规范引用(Canonical Reference):指的是一个唯一的、持久的引用,比如对象在内存中的地址。对于引用类型(类),每个实例在内存中都有唯一的地址,因此可以将其作为标识。这里说的引用是指索引,标记,使用来找到对应值的东西,并非我们经常提到的"引用类型"中的引用。

SwiftUI 面临的挑战

less 复制代码
// 初始状态
VStack {    
    Text("Hello") // 视图1    
    Button("Click") // 视图2
}
// 更新后状态
VStack {    
    Button("Clicked") // 这是原来的按钮吗?    
    Text("Hello")     // 这是原来的文本吗?
}

当视图更新时:

  • 如果 SwiftUI 无法识别视图是"同一个"

    1、会导致状态丢失(如输入框内容清空)

    2、动画会中断而不是平滑过渡

    3、性能下降(因为要重建整个视图、

我们看下if- else的情况

less 复制代码
import SwiftUI
struct ContentView: View {
    @State var showFirst = true
    @State var counter = 0
    
    var body: some View {
        VStack {
            Button("切换视图") {
                showFirst.toggle()
            }
            
            // 示例1: 不设置 id - SwiftUI 会认为是两个不同的 view
            Text("示例1: 不设置 id")
            if showFirst {
                CounterView(title: "第一个", counter: $counter)
                    .background(Color.red.opacity(0.3))
            } else {
                CounterView(title: "第二个", counter: $counter)
                    .background(Color.blue.opacity(0.3))
            }
            
            Divider()
            
            // 示例2: 设置相同的 id - SwiftUI 会认为是同一个 view
            Text("示例2: 设置相同的 id")
            if showFirst {
                CounterView(title: "第一个", counter: $counter)
                    .background(Color.red.opacity(0.3))
                    .id("same_view")
            } else {
                CounterView(title: "第二个", counter: $counter)
                    .background(Color.blue.opacity(0.3))
                    .id("same_view")
            }
        }
        .padding()
    }
}
struct CounterView: View {
    let title: String
    @Binding var counter: Int
    @State private var internalCounter = 0
    
    var body: some View {
        VStack {
            Text(title)
            Text("内部计数器: \(internalCounter)")
            Button("内部 +1") {
                internalCounter += 1
            }
            Text("外部计数器: \(counter)")
            Button("外部 +1") {
                counter += 1
            }
        }
        .padding()
        .border(Color.gray)
        .onAppear {
            print("创建子视图")
        }
    }
}

可以发现只要是if-else的swiftUI都会使用结构化标识,不论是否设置id,if和else中的view设置id相同与否,都不影响结果:每一次if和else的转换都会导致生产新的CounterView实例,因为其内部计数都是从0开始。

结论:我们无法在if和else中通过设置view的id来达到优化为只创建一个view的效果对于使用结构化标识的view,swiftUI还会自动优化,将上述例子中的一些代码做一下修改:

css 复制代码
var body: some View {
        VStack {
            Button("切换视图") {
                showFirst.toggle()
            }

            //第一种
    //            showFirst ? CounterView(title: "第一个", counter: $counter)
    //                .background(Color.red.opacity(0.3)) : CounterView(title: "第二个", counter: $counter)
    //                .background(Color.blue.opacity(0.3))

            //第二种
            showFirst ? CounterView(title: "第一个")
                .background(Color.red.opacity(0.3)) : CounterView(title: "第二个")
                .background(Color.blue.opacity(0.3))
        }
        
    
    }

可以发现无论是第一种方式-使用了绑定,还是第二种-无任何绑定,counterView都只生成了一次,无论我们切换试图多少次,已经生成的counterView的内部计数都是连续的如果我们给每个CounterView的实例设置了不同的id,就可以生成两个view,并且每次切换showFirst,都会重新生成要展示的view可以看出给view设置不同的id,会分割生命周期,这也会导致不显示的view的回收

SwiftUI 的两种身份标识

**结构化标识(位置标识)

通过视图在视图树中的位置识别:

scss 复制代码
- ContentView  
    - VStack    
        - Text("标题")      // 位置0    
        - CounterButton()  // 位置1 ← 靠这个位置识别

显式标识(唯一ID)

手动指定唯一标识符:

scss 复制代码
CounterButton()    
.id("mainCounter") // 显式标识
相关推荐
fendoudexiaoniao_ios3 天前
iOS 列表拖拽cell排序
ios·swift
CYpdpjRnUE3 天前
光伏电池PV建模及其基于Boost Buck电路的最大功率追踪MPPT算法研究及仿真效果探究
swiftui
大熊猫侯佩4 天前
Swift 6 驱魔实录:揭开 Combine 与 @Sendable 的“血色契约”
swift·block·combine·preconcurrency·sendable·mainactor·isolation
初级代码游戏4 天前
iOS开发 SwiftUI 15:手势 拖动 缩放 旋转
ios·swiftui·swift
ujainu4 天前
Flutter + OpenHarmony 游戏开发进阶:虚拟摄像机系统——平滑跟随与坐标偏移
开发语言·flutter·游戏·swift·openharmony
zhyongrui6 天前
SnipTrip 菜单 Liquid Glass 实现方案:结构、材质、交互与深浅色策略
ios·性能优化·swiftui·交互·开源软件·材质
zhyongrui6 天前
SnipTrip 不发烫的实现路径:局部刷新 + 合成缓存 + 峰值削减
ios·swiftui
初级代码游戏7 天前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏7 天前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui7 天前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift