установить @State var внутри geometryReader

Как можно установить @State var внутри geometryReader

это мой код

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

Есть идеи? Спасибо!

6 ответов

Вы можете использовать onAppear(perform:) для обновления @Stateпеременные с начальным размером представления и onChange(of:perform:) для обновления переменных при изменении размера представления:

      struct MyView: View {
  @State private var size: CGSize = .zero

  var body: some View {
    GeometryReader { geometry in
      ZStack {
        Text("Hello World")
      }.onAppear {
        size = geometry.size
      }.onChange(of: geometry.size) { newSize in
        size = newSize
      }
    }
  }
}

Хотя включение кода в функцию - приятный штрих, может возникнуть другая проблема, связанная с изменением переменной @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")        
    }
}

У меня вчера тоже была похожая проблема. Но я пытался передать значение из 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
}

Попробуй это

      @State private var viewSize: CGSize = .zero

var body: some View {
    VStack {
        // ...
    }
    .background(GeometryReader { proxy in
        Color.clear.preference(
            key: ViewSizePreferenceKey.self,
            value: proxy.size
        )
    })
    .onPreferenceChange(ViewSizePreferenceKey.self) { size in
        viewSize = size
    }
}

private struct ViewSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = value.width + value.height > nextValue().width + nextValue().height ? value : nextValue()
    }
}

Вы можете обновить @Stateпеременные в onAppearметод, если вам нужны начальные значения геометрии

      @State var windowSize = CGSize()

var body: some View {

    return GeometryReader { geometry in 
        VStack {
            Text("Hello SwiftUI")  
        }
        .onAppear {
            windowSize = geometry.size
        }      
    }
}

Так что вполне возможно обновить внутри. Решение простое. Однако есть нюанс:

вы можете получить бесконечный цикл (ничего особенного, я представлю решение здесь)

Вам просто понадобится DispatchQueue.main.asyncи явно объявить тип представления внутри. Если вы выполните представление ниже (не забудьте остановить его), вы увидите, что он никогда не прекращает обновлять значение 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 будет «рисовать», который обновит a View. Таким образом, новый @Stateделает представление недействительным, вызывая его перерисовку. Следовательно, вернемся к первому шагу (отрисовка и обновление состояния).

Чтобы решить эту проблему, вам нужно наложить некоторые ограничения на отрисовку файла. Вместо того, чтобы возвращать ваш View внутри, нарисуйте его, затем добавьте 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())
    }
}

Примечание: вам не нужно устанавливать те же ограничения, например, в моем случае я хотел переместить представление с помощью жеста перетаскивания. Итак, я установил ограничения: запускать, когда пользователь касается земли, и останавливаться, когда пользователь заканчивает жест перетаскивания.

Другие вопросы по тегам