SwiftUI GeometryReader无法更新其父级的状态

时间:2020-10-12 01:57:29

标签: swiftui

昨天我碰到了这篇帖子: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())

结果:

content view

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

1 个答案:

答案 0 :(得分: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