SwiftUI GeometryReader gagal memperbarui status induknya

Oct 12 2020

Kemarin saya menemukan posting ini: Apa itu GeometryReader di SwiftUI? , dan saya penasaran dengan perilakunya GeometryReader, jadi saya melanjutkan beberapa eksperimen, dan berikut ini yang saya lakukan setelah selesai membaca postingan:

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())

Hasil:

Dari atas, saya menggunakan dua pendekatan dan mencoba mengupdate ContentViewmelalui update @Statevariabel - variabelnya, walaupun pendekatan kedua berhasil, tetapi yang pertama gagal, adakah yang tahu mengapa gagal? Terima kasih.

Jawaban

1 Asperi Oct 12 2020 at 11:12
   //    but SwiftUI ignores this assignment, why?

Karena itu akan menghasilkan siklus rendering (mengubah status memulai penyegaran, yang mengubah status, dan sebagainya), dan mesin rendering SwiftUI cukup pintar untuk menghentikan pembaruan tersebut.

Anda perlu menunda pembaruan tersebut untuk loop acara berikutnya, seperti

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
}

Lihat pendekatan ini penggunaan yang benar untuk mis. dihttps://stackoverflow.com/a/60214735/12299030