观察 SwiftUI 中的帧变化

Posted

技术标签:

【中文标题】观察 SwiftUI 中的帧变化【英文标题】:Observe frame changes in SwiftUI 【发布时间】:2020-08-02 20:25:07 【问题描述】:

我有可以拖放到其他视图之上的视图(比如说类别)。为了检测我在哪个类别视图之上,我将它们的帧存储在一个帧数组中,这发生在它们的不可见叠加层的 onAppear 中。 (这是基于 this 教程中的 Paul Hudsons 实现)。

这一切都很好,除非这些视图的位置发生变化,例如在 iPad 上调整设备方向或窗口大小。这当然不会触发 onAppear,因此帧不再匹配。

HStack() 
ForEach(categories)  category in
    ZStack 
        Circle()
        Rectangle()
            .foregroundColor(.clear)
            .overlay(
                GeometryReader  geo in
                    Color.clear
                        .onAppear 
                            categoryFrames[index(for: category)] = geo.frame(in: .global)
                        
                
            )
        
    

因此,欢迎任何想法如何更新这些实例中的帧或如何以不同的方式观察它们。

【问题讨论】:

不完全是,因为他们用 UIKit 实现了这一点,这对我来说不如纯 SwiftUI 理想。他们说 GeometryReader 应该可以,但没有解释如何。 【参考方案1】:

可以在刷新期间使用视图首选项动态读取视图帧,因此您不必关心方向,因为每次重绘视图时都有实际帧。

这是一个方法草案。

为视图偏好键引入模型:

struct ItemRec: Equatable 
    let i: Int        // item index
    let p: CGRect     // item position frame


struct ItemPositionsKey: PreferenceKey 
    typealias Value = [ItemRec]
    static var defaultValue = Value()
    static func reduce(value: inout Value, nextValue: () -> Value) 
        value.append(contentsOf: nextValue())
    

现在你的代码(假设@State private var categoryFrames = [Int, CGRect]()

HStack() 
ForEach(categories)  category in
    ZStack 
        Circle()
        Rectangle()
            .foregroundColor(.clear)
            .background(        // << prefer background to avoid any side effect
                GeometryReader  geo in
                    Color.clear.preference(key: ItemPositionsKey.self,
                        value: [ItemRec(i: index(for: category), p: geo.frame(in: .global))])
                
            )
        
    
    .onPreferenceChange(ItemPositionsKey.self) 
        // actually you can use this listener at any this view hierarchy level
        // and possibly use directly w/o categoryFrames state
        for item in $0 
           categoryFrames[item.i] = item.p
        
    


【讨论】:

【参考方案2】:

我遇到了类似的问题,这篇文章启发了我寻找解决方案。所以也许这对其他人有用。 只需像对onAppear 所做的那样分配给onChange 修饰符,并在geo.size 更改时将其设置为触发。

HStack() 
ForEach(categories)  category in
    ZStack 
        Circle()
        Rectangle()
            .foregroundColor(.clear)
            .overlay(
                GeometryReader  geo in
                    Color.clear
                        .onAppear 
                            categoryFrames[index(for: category)] = geo.frame(in: .global)
                        
                        .onChange(of: geo.size)  _ in
                            categoryFrames[index(for: category)] = geo.frame(in: .global)
                        
                
            )
        
    

【讨论】:

以上是关于观察 SwiftUI 中的帧变化的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:观察@Environment 属性变化

在 SwiftUI 小部件中添加观察者的位置

SwiftUI 双向绑定到枚举案例中 ObservableObject 中的值

在 SwiftUI 中观察 PassthroughSubject 中的错误

声明后不可访问的可观察对象中的变量(SwiftUI)

如何将可观察对象发布的整数变量绑定到 SwiftUI 中的 textField?