如何在 SwiftUI 中创建一个获取 View 并返回自定义结果的函数?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中创建一个获取 View 并返回自定义结果的函数?【英文标题】:How can make a function which take View and returns custom result in SwiftUI? 【发布时间】:2021-02-27 15:16:27 【问题描述】:

我想建立一个独立于 ContentView 的函数,我可以使用这个函数来初始化一些值,例如在这个向下的代码中,我想用函数获取 View 的大小,但是由于我未知的原因它返回零,我认为背景修改在此版本中无法正常工作。有什么帮助吗?

  func viewSizeReaderFunction<Content: View>(content: Content) -> CGSize 
    
    var sizeOfView: CGSize = CGSize()
    
    content
        .background(
            GeometryReader  geometry in
                Color
                    .clear
                    .onAppear()  sizeOfView =  geometry.size 
                
            )

    return sizeOfView

 

let sizeOfText: CGSize = viewSizeReaderFunction(content: Text("Hello, world!"))

struct ContentView: View 

    var body: some View 

        Color.red
            .onAppear() 
                print(sizeOfText)
            

    
  

【问题讨论】:

【参考方案1】:

一般的想法是让视图使用preference 报告其大小,并创建一个视图修饰符来捕获它。但是,就像@RobNapier 所说,结构必须在视图层次结构中,因此在渲染上下文中,才能谈论大小。

struct SizeReporter: ViewModifier 
   @Binding var size: CGSize
   func body(content: Content) -> some View 
      content
         .background(GeometryReader  geo in
             Color.clear
                  .preference(key: SizePreferenceKey.self, value: geo.size)
         )
         .onPreferenceChange(SizePreferenceKey.self, perform:  value in
             size = value
         )
   

我们需要定义SizePreferenceKey

extension SizeReporter 
   private struct SizePreferenceKey: PreferenceKey 
      static let defaultValue: CGSize = .zero
      static func reduce(value: inout CGSize, nextValue: () -> CGSize) 
         value = nextValue()
      
   

你可以在View上创建一个方便的方法:

extension View 
   func getSize(_ size: Binding<CGSize>) -> some View 
      self.modifier(SizeReporter(size: size))
   

并像这样使用它:

@State var size: CGSize = .zero

var body: some View 
   Text("hello").getSize($size)

【讨论】:

您的所有代码对我来说都是可以理解的,@RobNapier 构建了一种 ViewModifier 以及仅在 1 个自定义函数中一起使用该 ViewModifier 的函数,但是为什么您使用 PreferenceKey?我认为绑定将涵盖我们需要的内容,您能解释一下吗? .onAppear 不同,首选项允许您捕捉大小的变化,例如,如果您有Text(text).getSize($size)TextField("", text: $text),则每次键入时,size 都会改变跨度> 现在我可以看到你做了什么,坦率地说我从来没有使用过除 onAppear 或 onChange 之外的任何其他方式,如果我错了请纠正我,我认为你使用 PreferenceKey 的方式 非常安全,并保证获得我们正在寻找的价值。对吗? @swiftPunk,我认为这取决于您的需求。 onAppear 为您提供初始大小。这使您可以跟踪更改。如果需要,您可以修改它以使用回调而不是绑定。 你解决问题的方式对我来说很有趣,因为你明确地告诉 SwiftUI 你在寻找什么,并且在出现的方式中没有错误或被破坏的地方,因此我认为 PreferenceKey 肯定是比 onAppear 更好的选择,感谢您教导和展示最佳方法。【参考方案2】:

视图只是描述视图布局的数据。它们不是像 UIView 那样代表实际“视图”的对象。它们没有自己的逻辑或状态(这就是为什么它们需要@State 变量而不仅仅是var)。

您编写的代码将零大小分配给sizeOfView,然后创建一个立即丢弃的视图结构,然后返回sizeOfView。没有任何东西可以评估 View 结构。我希望编译器会就此向您发出警告,例如“未使用后台调用结果”。

您所描述的方式是使用首选项,.onPreferenceChange,通常是@State。围绕 Stack Overflow 有很多答案。

https://***.com/search?q=%5Bswiftui%5D+size+of+view

这是一个例子,特别注意.hidden()的使用:

extension View 
    func saveSize(handler: @escaping (CGSize) -> Void) -> some View 
        return self
            .background(
                GeometryReader  geometry in
                    Color.clear
                        .onAppear 
                            handler(geometry.size)
                        
                )
    


struct ContentView: View 
    @State var sizeOfText: CGSize = .zero

    var body: some View 
        ZStack 
        Color.red
            .onAppear() 
                print(sizeOfText)
            
            Text("Hello, world!")
                .hidden()
                .saveSize  sizeOfText = $0 
        
    

请注意,这段代码有点危险,因为它依赖于调用onAppear 的顺序,而这并不是承诺的。在实践中,您通常需要处理尚未设置大小的情况。这可以通过 Preferences 变得更加健壮,但这往往很麻烦。

【讨论】:

按照您所说的我们使用它的正常方式获取视图大小的问题为零,我想测试另一种方式,就像我在问题中询问的那样,我试图做到这一点的原因发生了,是这样的:我只需要那个值而不是那个视图,我可以在内容视图中以正常方式实现它,但这意味着它始终附加到应用程序或我的内容视图,即使我们将其设为 Color.Clear 并且我们没有看到它,但它只是为了读取一个值!这就是为什么我想让它独立!运行功能!得到答案,再见不需要查看! 这是不可能的。视图在渲染上下文之外没有大小,您无法创建渲染上下文,也无法在运行时访问。 Text("Hello, world!") 没有具有固有大小。它取决于当前环境(当前字体是什么?)并且取决于提供给它的空间(您的代码不提供任何空间)。那些不存在于视图中;它们存在于渲染上下文中。 为了保证 sizeOfText 正确答案的安全性,我们也可以使用 onChange 来确保我们得到了大小。对?因为它是 SwiftUI,所以它可以先渲染 Color.red,然后再渲染 onAppear,然后再渲染 Text!这只是我想要涵盖的一个示例,但实际上是从底部到顶部的 Swift 渲染!因为文本更接近屏幕,在这种情况下,我们应该总是在调用 onAppear 时回答,因为文本已经被渲染了!对吗? 没有任何承诺。 onAppear 方法的调用顺序未定义。

以上是关于如何在 SwiftUI 中创建一个获取 View 并返回自定义结果的函数?的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中创建具有特定日期的 DatePicker

如何使用 swiftUI 获取 ComboBox

如何创建一个带有输入的闭包结构参数,在 SwiftUI 中返回“some View”而不是“AnyView”?

如何在 swiftUI 中创建具有多列的列表?

如何在 SwiftUI 中创建指数

如何在 SwiftUI 文本元素中创建可变字符串?