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") // 显式标识
相关推荐
不会写代码的丝丽1 天前
iOS 队列与线程
swift
Swift社区2 天前
从字符串中“薅出”最长子串:LeetCode 340 Swift 解法全解析
算法·leetcode·swift
东吴贾诩2 天前
[WWDC 2025] 用新设计构建一个SwiftUI应用程序
swiftui·wwdc
大熊猫侯佩3 天前
无需自己写半行代码:让 AI 编程智能体(Agent)化身神笔马良为我们自动仿制 App 界面
swiftui·agent·cursor
杂雾无尘3 天前
SwiftUI 动画新技能,让你的应用「活」起来!
ios·swiftui·swift
东坡肘子3 天前
Blender 正在开发 iPad 版本 | 肘子的 Swift 周报 #095
swiftui·swift·apple
songgeb6 天前
Concurrency in Swift学习笔记-初识
ios·swift
杂雾无尘7 天前
2025 年了,是否该全面拥抱 Swift 6?
ios·swift·客户端
杂雾无尘8 天前
用高斯公式优化 Swift 代码,让运行速度飞跃数十万倍!
ios·swift·apple
杂雾无尘9 天前
解决 Xcode 烦人错误:"Build input file cannot be found" 一招搞定!
ios·swift·客户端