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") // 显式标识
相关推荐
杂雾无尘10 小时前
开发者必看:如何在 iOS 应用中完美实现动态自定义字体!
ios·swift·apple
旷世奇才李先生2 天前
Swift 安装使用教程
开发语言·ios·swift
Daniel_Coder3 天前
Xcode 中常用图片格式详解
ios·xcode·swift
帅次3 天前
Objective-C面向对象编程:类、对象、方法详解(保姆级教程)
flutter·macos·ios·objective-c·iphone·swift·safari
RyanGo3 天前
iOS断点下载
ios·swift
杂雾无尘3 天前
掌握生死时速:苹果应用加急审核全攻略!
ios·swift·apple
HarderCoder3 天前
Swift 6.2 中的 `@concurrent`
ios·swift
YungFan3 天前
iOS26适配指南之通知
ios·swift
大熊猫侯佩13 天前
消失的它:摆脱 SwiftUI 中“嵌入视图数量不能超过 10 个”限制的秘密
swiftui·swift·apple
大熊猫侯佩13 天前
Swift 抛砖引玉:从数组访问越界想到的“可抛出错误”属性
swift·apple