2

如何在geometryReader 中设置@State var

这是我的代码

@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()

任何想法?谢谢!

4

3 回答 3

3

我昨天也有类似的问题。但我试图从 GeometryReader 内部传递一个值。

我尝试了几种方法,但没有奏效。当我使用@State var声明变量时,编译器再次以紫色线抱怨说在更新期间修改视图将使其变为未定义。

当我尝试使用varonly 声明一个变量时,编译器只是告诉我它是不可变的。

然后,我尝试将其存储到我的@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
}
于 2020-10-29T06:42:56.370 回答
1

虽然将代码放入函数中是一个不错的选择,但可能会出现另一个问题,那就是在更新阶段更改 @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")        
    }
}
于 2021-03-04T08:34:02.063 回答
0

所以完全有可能在 a 中更新@Statea 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,这将更新@StateView 的 a 。因此,新@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())
    }
}

注意:你不需要设置相同的约束,例如,在我的例子中,我想用拖动手势移动视图。因此,我将约束设置为在用户触摸时开始,并在用户结束拖动手势时停止。

于 2021-06-06T03:40:04.300 回答