установить @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())
}
}
Примечание: вам не нужно устанавливать те же ограничения, например, в моем случае я хотел переместить представление с помощью жеста перетаскивания. Итак, я установил ограничения: запускать, когда пользователь касается земли, и останавливаться, когда пользователь заканчивает жест перетаскивания.