SwiftUI中onChange获取值始终为旧值的问题及原理咨询
SwiftUI中onChange的工作机制及旧版本延迟问题解析
我创建了如下的ChildView图片视图,但更新图片时出现异常:旧版本onChange闭包调用时,url始终是旧值,存在一轮延迟;但在ContentView中使用onChange时,闭包里的url却是最新的。想知道onChange的工作机制是怎样的?
复现代码
import SwiftUI struct ContentView: View { @State var url:URL? = Bundle.main.url(forResource: "image1", withExtension: "jpeg") let alt = Bundle.main.url(forResource: "image1", withExtension: "jpeg") let alt1 = Bundle.main.url(forResource: "image2", withExtension: "PNG") var body: some View { VStack { Button("Change Image") { url == alt1 ? (url = alt) : (url = alt1) } Text("url : \(url?.lastPathComponent ?? "nil")") ChildView(url: url) } .padding() } } struct ChildView: View { var url:URL? @State var image: UIImage? = nil var body: some View { VStack{ if let image = image { Image(uiImage: image) .resizable() .scaledToFit() } } /* .task(id:url) { loadImage() //works fine. } */ .onAppear{ loadImage() } .onChange(of: url) { value in print("old OnChange \(url?.lastPathComponent) \(value?.lastPathComponent)") // "url: old, value: new" <--------- very strange here. loadImage() } .onChange(of:url) { old, new in //<--------- works fine, but only available in iOS 17. } } func loadImage() { if let url = url { image = UIImage(contentsOfFile: url.path) } } }
问题原因及onChange工作机制
1. iOS 16及更早版本的单参数onChange
旧版本onChange(of:perform:)的执行时机是视图属性更新前:当监听的url发生变化时,SwiftUI会先触发闭包执行,此时ChildView的url实例属性还未同步更新为新值,所以直接访问self.url拿到的是旧值,而闭包的value参数才是变化后的新值。
而ContentView中url是@State属性,状态变更后ContentView的body会立刻重新计算,此时url已经更新为新值,所以在它的onChange里访问url能拿到最新值。
2. iOS 17+的双参数onChange
iOS17新增的onChange(of:initial:perform:)(带old和new参数的版本)调整了执行时机,闭包会在视图属性更新完成后调用,所以能直接通过参数拿到明确的新旧值,不会出现旧版本的延迟问题。
解决方案
如果需要兼容iOS16及以下,有两种可行方案:
- 修改
loadImage方法,接收新值作为参数,在onChange闭包里使用value(新值)调用:
.onChange(of: url) { value in print("old OnChange \(url?.lastPathComponent) \(value?.lastPathComponent)") loadImage(from: value) } func loadImage(from url: URL?) { if let url = url { image = UIImage(contentsOfFile: url.path) } }
- 使用你注释掉的
.task(id:url),它的触发时机是id变化且视图属性更新完成后,此时访问self.url能拿到新值,所以loadImage()可以正常工作。
内容的提问来源于stack exchange,提问作者Perry Wang
相关产品推荐
相关产品推荐

