获取视图的CGRect

3

我正在使用“RectGetter”来获取一个“View”的CGRect

像这样:

Text("Hello")
    .background(RectGetter(rect: self.$rect))


struct RectGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { proxy in
            self.createView(proxy: proxy)
        }
    }

    func createView(proxy: GeometryProxy) -> some View {
        DispatchQueue.main.async {
            self.rect = proxy.frame(in: .global) // crash here
        }

        return Rectangle().fill(Color.clear)
    }
}

但有时在设置矩形时会出现崩溃。
Thread 0 name:
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x00000001a1ec5ec4 __pthread_kill + 8
1   libsystem_pthread.dylib         0x00000001a1de5724 pthread_kill$VARIANT$armv81 + 216 (pthread.c:1458)
2   libsystem_c.dylib               0x00000001a1d358c0 __abort + 112 (abort.c:147)
3   libsystem_c.dylib               0x00000001a1d35850 abort + 112 (abort.c:118)
4   AttributeGraph                  0x00000001cce56548 0x1cce25000 + 202056
5   AttributeGraph                  0x00000001cce2b980 0x1cce25000 + 27008
6   SwiftUI                         0x00000001d89ce9b4 $s7SwiftUI27_PositionAwareLayoutContextV10dimensionsSo6CGSizeVvg + 56 (<compiler-generated>:0)
7   SwiftUI                         0x00000001d8a43584 $sSo6CGSizeVIgd_ABIegr_TRTA + 24 (<compiler-generated>:0)
8   SwiftUI                         0x00000001d8a43a54 $s7SwiftUI13GeometryProxyV4sync33_4C4BAC551E328ACCA9CD3748EDC0CC3ALLyxSgxyXElFxAA9ViewGraphCXEfU_... + 92 (GeometryReader.swift:126)
9   SwiftUI                         0x00000001d8a43f20 $s7SwiftUI13GeometryProxyV4sync33_4C4BAC551E328ACCA9CD3748EDC0CC3ALLyxSgxyXElFxAA9ViewGraphCXEfU_... + 20 (<compiler-generated>:0)
10  SwiftUI                         0x00000001d8c4842c $s7SwiftUI16ViewRendererHostPAAE06updateC5Graph4bodyqd__qd__AA0cG0CXE_tlF + 80 (ViewRendererHost.swift:64)
11  SwiftUI                         0x00000001d8a438d4 $s7SwiftUI13GeometryProxyV5frame2inSo6CGRectVAA15CoordinateSpaceO_tF + 196 (GeometryReader.swift:125)
12  MyApp                           0x0000000102cf5c54 closure #1 in RectGetter.createView(proxy:) + 128 (RectGetter.swift:22)

有没有更可靠的方法(不会崩溃)来获取视图的CGRect


1
问题不在于获取CGRect,而是你对它的处理方式。你正在将其设置到其他地方。在SwiftUI中,这通常不是你想要进行布局的方式(典型的工具是Preference、Anchor或Alignment,而不是.async设置器)。这些视图不是类;它们是结构体。不能保证在调用main.async时,self以有意义的方式存在。在这种情况下,不清楚self.$rect是什么以及它会导致什么后果。你想实现什么样的布局? - Rob Napier
self.$rect是一个@State变量,rect = CGRect.zero...我需要CGRect来进行定位。我认为上述用法并不特别,我从一些SwiftUI博客中截取的。 - Peter Warbo
1
我在许多博客中看到过这个,但我没有从苹果那里看到过,并且我认为它不是基于任何承诺的工作。它只是碰巧起作用(Asperi的答案很好,因为它在存在时评估几何形状,但它仍然依赖于我从未见过苹果建议的行为。)SwiftUI-Lab有一个非常好的系列关于如何在SwiftUI中进行声明性布局。https://swiftui-lab.com/alignment-guides/ https://swiftui-lab.com/communicating-with-the-view-tree-part-1/ - Rob Napier
1
还有特别针对这一点:https://swiftui-lab.com/state-changes/ 我通常发现,如果目标是布局,那么对齐指南是你真正想要的工具,而不是“先布局,让一个状态变量解决问题,然后再布局”,我猜这就是你在这里做的事情(在我重写所有内容以使用AlignmentGuides之前,我自己也这样做过)。 - Rob Napier
1
(为了明确起见,我不确定这些异步设置器是否有效。显然,我们可以进行异步工作并设置状态变量,这是完全支持的。但是,布局导致设置器重新布局似乎违反了SwiftUI的声明性质,并且目前尚不清楚它是一种可能会出问题的黑客技巧,还是一种依赖于稳定基础的不良实践,或者是完全没有问题的。至今为止,SwiftUI-Lab是我发现的最可靠的探索者,能够解答这些问题。) - Rob Napier
1
感谢Rob详细的评论,确实苹果官方还没有对此(以及SwiftUI的许多文档)给出明确的说明。AlignmentGuides可能是正确的方法,但现在我有点懒得学习那个API,所以我只是尝试一下Asperis的建议。 - Peter Warbo
2个回答

13

更新:通过帮助扩展,改进和简化了一种可能的解决方案,以便在任何坐标空间中读取任何视图的矩形。自Xcode 11.1以来可用,并已重新测试通过Xcode 13.3。

主要部分:

func rectReader(_ binding: Binding<CGRect>, _ space: CoordinateSpace = .global) -> some View {
    GeometryReader { (geometry) -> Color in
        let rect = geometry.frame(in: space)
        DispatchQueue.main.async {
            binding.wrappedValue = rect
        }
        return .clear
    }
}

使用相同的方法

Text("Test").background(rectReader($rect))

或使用新扩展名

Text("Test").reading(rect: $rect)

详细发现和代码请点击此处


0
另一种方法是使用首选项键,如此处所述
总之,您可以设置一个修饰符:
struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero

    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
}

struct SizeModifier: ViewModifier {
    private var sizeView: some View {
        GeometryReader { geometry in
            Color.clear.preference(key: SizePreferenceKey.self, value: geometry.frame(in: .global))
        }
    }

    func body(content: Content) -> some View {
        content.background(sizeView)
    }
}

然后使用那个:

SomeView()
   .modifier(SizeModifier())
   .onPreferenceChange(SizePreferenceKey.self) { print("My rect is: \($0)") }

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