在 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() -&gt; some View - 一个返回 GeometryReader 或空视图的函数 if timesCalled &lt; 1 - 最后,停止获取帧的条件

以上是关于在 geometryReader 中设置 @State var的主要内容,如果未能解决你的问题,请参考以下文章

全局变量在 Python Canvas 中设置然后重置

如何使用 Cloud Firestore 在颤动中设置参考选择数组的位置条件 [关闭]

如何在程序中设置 Android 的 IP,DNS 和网关

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

在不修改视图的情况下使用 GeometryReader

为啥 VStack 不能在带有滚动视图的 GeometryReader 中工作?