在 geometryReader 中设置 @State var
Posted
技术标签:
【中文标题】在 geometryReader 中设置 @State var【英文标题】:set @State var inside geometryReader 【发布时间】:2020-04-04 11:21:51 【问题描述】:如何在geometryReader 中设置@State 变量
这是我的代码
@State var isTest:Int = 0
var body: some View
VStack
ForEach(self.test, id: \.id) Test in
VStack
GeometryReader geometry in
self.isTest = 1
我尝试了一个函数但不起作用
@State var isTest:Int = 0
func testValue()
self.isTest = 1
var body: some View
VStack
ForEach(self.test, id: \.id) Test in
VStack
GeometryReader geometry in
testValue()
有什么想法吗?谢谢!
【问题讨论】:
@State var 等于几何宽度,self.isTest = geometry.size.width。谢谢!! 【参考方案1】:我昨天也遇到了类似的问题。但我试图从 GeometryReader 内部传递一个值。
我尝试了几种方法,但都不起作用。
当我使用@State var
声明变量时,编译器再次以紫色线抱怨说在更新期间修改视图将使其变为未定义。
当我尝试仅使用 var
声明变量时,编译器只是告诉我它是不可变的。
然后,我尝试将其存储到我的@EnvironmentObject
。而我刚刚陷入死循环。
所以,我最后的希望是使用通知方式以及它是如何工作的。但不知道是不是标准的实现方式。
@State private var viewPositionY:CGFloat = 0
首先,通过通知发布值frame.origin.y
。
GeometryReader geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.global)
NotificationCenter.default.post(name: Notification.Name("channelBlahblahblah"), object:nil, userInfo:["dict":frame.origin.y])
return Text("My View Title")
然后声明一个发布者来接收通知。
private let myPublisher = NotificationCenter.default.publisher(for: Notification.Name("channelBlahblahblah"))
最后,使用 .onReceive 修饰符来接收通知。
.onReceive(myPublisher) (output) in
viewPositionY = output.userInfo!["dict"] as! CGFloat
//you can do you business here
【讨论】:
【参考方案2】:虽然将代码放入函数中是一个不错的选择,但可能会出现另一个问题,那就是在更新阶段更改 @State 变量:
[SwiftUI] Modifying state during view update, this will cause undefined behavior
使用 NotificationCenter 移动 @State 变量更新在视图更新阶段之后会有所帮助,但可以使用更简单的解决方案,例如使用 DispatchQueue
在渲染阶段之后立即执行变量更新。
@State var windowSize = CGSize()
func useProxy(_ geometry: GeometryProxy) -> some View
DispatchQueue.main.async
self.windowSize = geometry.size
return EmptyView()
var body: some View
return GeometryReader geometry in
self.useProxy(geometry)
Text("Hello SwiftUI")
【讨论】:
【参考方案3】:所以完全可以在GeometryReader
中更新@State
。解决方案很简单。但是,有一个警告:
你可能会陷入无限循环 (没什么太麻烦的,我这里给出一个解决方案)
您只需要一个DispatchQueue.main.async
并在GeometryReader
中明确声明视图的类型。如果您执行下面的视图(不要忘记停止它),您会看到它永远不会停止更新Text
的值。
不是最终解决方案:
struct GenericList: View
@State var timesCalled = 0
var body: some View
GeometryReader geometry -> Text in
DispatchQueue.main.async
timesCalled += 1 // infinite loop
return Text("\(timesCalled)")
发生这种情况是因为视图将“绘制”GeometryReader
,这将更新视图的@State
。因此,新的@State
使视图无效,导致视图被重绘。因此回到第一步(绘制GeometryReader
并更新状态)。
要解决这个问题,您需要在GeometryReader
的绘制中设置一些约束。与其在GeometryReader
中返回您的视图,不如绘制它,然后将GeometryReader
添加为透明覆盖或背景。这将产生相同的效果,但您可以在演示文稿中设置约束。
就个人而言,我宁愿使用叠加层,因为您可以添加任意数量的内容。请注意,覆盖不允许在其中包含if else
,但可以调用函数。这就是为什么下面有func geometryReader()
。另一件事是,为了返回不同类型的视图,您需要在其前面添加@ViewBuilder
。
在此代码中,GeometryReader 仅被调用一次,并且您会更新 @State var timesCalled。
最终解决方案:
struct GenericList: View
@State var timesCalled = 0
@ViewBuilder
func geometryReader() -> some View
if timesCalled < 1
GeometryReader geometry -> Color in
DispatchQueue.main.async
timesCalled += 1
return Color.clear
else
EmptyView()
var body: some View
Text("\(timesCalled)")
.overlay(geometryReader())
注意:你不需要设置相同的约束,例如in my case,我想用拖动手势移动视图。因此,我设置了约束在用户触摸时开始,并在用户结束拖动手势时停止。
【讨论】:
你能跳到最终的解决方案吗? ;) 它确实存在,并标有最终解决方案。其核心元素是:@State var timesCalled = 0
一个状态来控制你想要获得多少次框架(如果你只想要一个,也可以是一个布尔值)@ViewBuilder
- 所以你可以在函数 func geometryReader() -> some View
- 一个返回 GeometryReader
或空视图的函数 if timesCalled < 1
- 最后,停止获取帧的条件以上是关于在 geometryReader 中设置 @State var的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Cloud Firestore 在颤动中设置参考选择数组的位置条件 [关闭]