在geometryReader中设置@State var

时间:2020-04-04 11:21:51

标签: swiftui geometryreader

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

有什么主意吗?谢谢!

3 个答案:

答案 0 :(得分: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
}

答案 1 :(得分:0)

虽然将代码放入函数是一个很好的接触,但可能会出现另一个问题,那就是在更新阶段改变 @State 变量: [SwiftUI] Modifying state during view update, this will cause undefined behavior

使用 NotificationCenter 移动 @State 变量更新 视图更新阶段之后会有所帮助,但可以使用更简单的解决方案,例如使用 DispatchQueue 在渲染阶段之后立即执行变量更新。< /p>

@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")        
    }
}

答案 2 :(得分:0)

因此完全有可能在 @State 中更新 GeometryReader。解决方法很简单。但是,有一个警告:

你可能会陷入无限循环 (没什么太麻烦的,我会在这里提出解决方案)

您只需要一个 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)")
        }
    }
}

发生这种情况是因为 View 将“绘制”GeometryReader,这将更新 View 的 @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,我想使用拖动手势移动视图。因此,我将约束设置为在用户触摸时开始并在用户结束拖动手势时停止。