【问题标题】:SwiftUI GeometryReader failed to update its parent's stateSwiftUI GeometryReader 未能更新其父级的状态
【发布时间】:2020-10-12 01:57:29
【问题描述】:

昨天偶然看到这个帖子:What is GeometryReader in SwiftUI?,对GeometryReader的行为很好奇,于是做了一些实验,以下是我看完帖子后所做的:

import SwiftUI
import PlaygroundSupport

/* ------- Approach 1 ------- */

struct ViewGettingSize: View {
    
    @Binding var size: CGSize
    
    func makeView(with geometry: GeometryProxy) -> some View {
        
        // ⭐️ Try to update @Binding var `size`,
        //    but SwiftUI ignores this assignment, why?
        //    @Binding var `size` is NOT updated.
        self.size = geometry.size
        
        print(geometry.size)       // (158.5, 45.5)
        print(self.size)           // (50, 50)
        
        return Color.pink
    }
    
    var body: some View {
        GeometryReader { geo in
            self.makeView(with: geo)  // Color.pink
        }
    }
}

/* ------- Approach 2 ------- */

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

struct ViewSettingSizePreference: View {
    func makeView(with geometry: GeometryProxy) -> some View {
        print(geometry.size)       // (158.5, 45.5)
        return Color.orange
            .preference(key: SizePreferenceKey.self, value: geometry.size)
    }
    var body: some View {
        GeometryReader { geo in
            self.makeView(with: geo)   // Color.orange
        }
    }
}

/* ------- Test These Approaches ------- */

let text = Text("some text").font(.largeTitle)

// live view
struct ContentView: View {
    
    @State private var size = CGSize(50, 50)
    @State private var size2 = CGSize(50, 50)
    
    var body: some View {
        VStack {
            
            Group {
                
                /* ------- Approach 1 ------- */
                text
                    // ⭐️ this one doesn't work.
                    .background(ViewGettingSize(size: $size))
                Color.blue
                    // ⭐️ `size` is still (50,50)
                    .frame(width: self.size.width, height: self.size.height)
                
                /* ------- Approach 2 ------- */
                text
                    // ⭐️ this one works.
                    .background(ViewSettingSizePreference())
                    .onPreferenceChange(SizePreferenceKey.self) { (size) in
                        print(size)        // (158.5, 45.5)
                        self.size2 = size  // ⭐️ `size2` updated successfully.
                        print(self.size2)  // (158.5, 45.5)
                }
                Color.purple
                    .frame(width: self.size2.width, height: self.size2.height)
                
            }// Group
                .border(Color.black)
            
        }// VStack (container)
            .padding()
            .background(Color.gray)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

结果:

从上面,我使用了两种方法,并尝试通过更新@State变量来更新ContentView,虽然第二种方法成功了,但是第一种方法失败了,有人知道为什么失败吗?谢谢。

【问题讨论】:

    标签: swiftui


    【解决方案1】:
       //    but SwiftUI ignores this assignment, why?
    

    因为它会产生渲染循环(改变状态会引发刷新,改变状态等等),而 SwiftUI 渲染引擎足够聪明,可以放弃这样的更新。

    您需要为下一个事件循环延迟此类更新,例如

    func makeView(with geometry: GeometryProxy) -> some View {
        DispatchQueue.main.async {        
           self.size = geometry.size
        }
        
        print(geometry.size)       // (158.5, 45.5)
        print(self.size)           // (50, 50)
        
        return Color.pink
    }
    

    请参阅此方法的正确用法,例如。在https://stackoverflow.com/a/60214735/12299030

    【讨论】:

    • 但是@Peter-Warbo 有reported 这种方法有时会崩溃,不是吗?和 IMO,这种方法对于 SwiftUI 来说看起来很“不自然”,是 hack 吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-23
    • 1970-01-01
    • 2017-12-03
    相关资源
    最近更新 更多