观察SwiftUI中的框架变化。

3

我有一个视图可以被拖动并放置在其他视图 (比如分类) 上面。为了检测我当前在哪个分类视图上方,我将它们的位置信息存储在一个 frames 数组中,这是通过他们不可见的覆盖层的 onAppear 函数实现的 (基于 Paul Hudson 在教程中的实现)。

这种方法十分有效,但是当这些视图的位置改变时,比如设备方向改变或 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)
                        }
                }
            )
        }
    }
}

有任何更新这些实例的帧或以不同方式观察它们的想法都将受到欢迎。


1
不完全是这样,因为他们使用的是UIKit,而不是对我来说更理想的纯SwiftUI。他们说可以使用GeometryReader实现,但没有解释如何实现。 - hoshy
2个回答

8

我曾经遇到过类似的问题,看了这篇文章后找到了解决方法。也许对其他人也有用。 只需将onChange修改器与onAppear相同,并在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)
                        }
                }
            )
        }
    }
}

5

通过使用视图首选项,可以在刷新时动态读取视图框架,因此您不需要关心方向,因为每次重新绘制视图时都有实际的框架。

这是方法草案的草稿。

引入视图首选项键的模型:

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
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接