如何在 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