我们如何在 SwiftUI 中制作自定义 GeometryReader?

Posted

技术标签:

【中文标题】我们如何在 SwiftUI 中制作自定义 GeometryReader?【英文标题】:How can we make a Custom GeometryReader in SwiftUI? 【发布时间】:2021-11-10 19:09:47 【问题描述】:

我想知道 GeometryReader 是如何工作的,我有兴趣构建一个自定义 GeometryReader 用于学习目的!

坦率地说,我认为我们在 body 中使用的每个视图都是 GeometryReaderView 的一种,不同之处在于它们不使用闭包来为我们发送代理,而且每个视图都回调它的代理会很烦人!因此苹果决定给 GeometryReader 提供几何阅读功能!所以这只是我的想法!

所以我正在寻找一种可能且更有可能的 SwiftUI-isch 方法来读取视图代理,或者换句话说,查看我的代码:

struct ContentView: View 
    
    var body: some View 
        
        CustomGeometryReaderView  proxy in
            
            Color.red
                .onAppear() 
                    print(proxy)
                
            
        
        
    
    


struct CustomGeometryReaderView<Content: View>: View 
    
    @ViewBuilder let content: (CGSize) -> Content
    
    var body: some View 
        
        // Here I most find a way to reading the available size for CustomGeometryReaderView and reporting it back!
        return  Color.clear.overlay(content(CGSize(width: 100.0, height: 100.0)), alignment: .topLeading)
        
    
    

我还知道,视图的读取和报告代理不仅仅是视图的大小,它还与 CoordinateSpace、框架有关……但现在为了让事情更容易解决,我只是在研究大小!所以尺寸很重要!

正如我所说,我对使用 UIKit 或 UIViewRepresentable 来读取大小不感兴趣!可能苹果在掩护下使用类似的东西,也可能不使用! 我的目标是尝试用纯 SwiftUI 解决问题,或者你们中的一些人可能有一些关于 GeometryReader 源代码的很好的链接,以便阅读和学习它。

【问题讨论】:

GeometryReader 只会使用一些隐藏在 SwiftUI 中的东西。毕竟,SwiftUI 中的大多数组件只是UIViews。 SwiftUI 将能够读取其中的frames、布局约束和其他任何相关内容。这样做是不合理的。 谢谢,这也是我的问题,我不确定 GeometryReader 是否真的使用UIView!我的意思是我们都知道 SwiftUI 的大部分内容来自UIKit,但我不知道是不是像你说的那样 100%。如果我们可以确定除了使用UIKit 之外别无他法,那么我称之为离目标更近一步。 SwiftUI 中的一些东西不是视图,但可能是CALayers。但同样,你不会知道它是真的CALayer 还是只是CALayer 之类的东西,所以你很可能无法可靠地做你想做的事。 【参考方案1】:

好的,SwiftUI 中有几个工具提供了对视图大小的访问(当然,GeometryReader 除外)。

问题当然是将那个大小值转移到视图构建阶段,因为只有GeometryReader 允许在同一个构建周期中这样做。

这里是使用Shape 的可能方法的演示 - 设计的形状没有自己的大小并消耗所有可用的东西,因此覆盖所有区域,并已提供该区域作为输入。

使用 Xcode 13 / ios 15 测试

struct CustomGeometryReaderView<Content: View>: View 

    @ViewBuilder let content: (CGSize) -> Content

    private struct AreaReader: Shape 
        @Binding var size: CGSize

        func path(in rect: CGRect) -> Path 
            DispatchQueue.main.async 
                size = rect.size
            
            return Rectangle().path(in: rect)
        
    

    @State private var size = CGSize.zero

    var body: some View 
        // by default shape is black so we need to clear it explicitly
        AreaReader(size: $size).foregroundColor(.clear)
            .overlay(Group 
                if size != .zero 
                    content(size)
                
            )
    

替代:相同,但使用基于回调的模式

struct CustomGeometryReaderView<Content: View>: View 

    @ViewBuilder let content: (CGSize) -> Content

    private struct AreaReader: Shape 
        var callback: (CGSize) -> Void

        func path(in rect: CGRect) -> Path 
            callback(rect.size)
            return Rectangle().path(in: rect)
        
    

    @State private var size = CGSize.zero

    var body: some View 
        AreaReader  size in
            if size != self.size 
                DispatchQueue.main.async 
                    self.size = size
                
            
        
        .foregroundColor(.clear)
        .overlay(Group 
            if size != .zero 
                content(size)
            
        )
    

【讨论】:

感谢您的回答,坦率地说,我知道尝试使用 Shape 来读取尺寸!但我并没有尝试这样做,因为在那种情况下,我的问题仍然没有答案!更多信息:我不知道geometryReader 如何在掩护下工作,与Shape 相同的问题,我不知道Shape 如何在掩护下工作!我希望我能说出我的意思!问题是geometryReader 或Shape 如何测量可用空间!我现在只是对geometryReader 提出疑问,但是一旦我们知道它是如何工作的,它就可以用来了解Shape 的工作原理。【参考方案2】:

每当我需要知道视图的框架时,GeometryReader 都会变得不必要地复杂,并且“绑定首选项尝试每帧更新多次”警告非常可怕。所以我决定创建CustomGeometryReader

【讨论】:

以上是关于我们如何在 SwiftUI 中制作自定义 GeometryReader?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 视图中使用自定义 UI 控件,例如 Button?

导航时如何在 SwiftUI 中使用自定义视图转换?

SwiftUI之如何创建常量绑定与自定义绑定

你如何制作这种风格的文字? SwiftUI

SwiftUI-自定义容器

SwiftUI:自定义模态动画