Se você usar o onAppear
método para rolar para uma visualização SwiftUI com a ajuda de um ScrollViewProxy
, isso pode levar à perda da transição de estado na visualização ao usar valores publicados de um ObservableObject
.
O problema ocorre em todos os SDKs iOS atuais (17.5 / 18.0), nos simuladores iOS e em dispositivos reais.
Aqui está um código reduzido ao mínimo para reproduzir o problema.
Visualização SwiftUI:
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
@Namespace private var state2ID
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView(.vertical) {
VStack(spacing: 15) {
if viewModel.state2 {
VStack {
Text("State2 is set")
}
.id(state2ID)
.onAppear {
withAnimation {
scrollProxy.scrollTo(state2ID)
}
}
}
VStack(spacing: 0) {
Text("State1: \(viewModel.state1)")
Text("State1 changes from 'false -> true -> false' when the button is pressed.")
.font(.footnote)
}
Button("Toggle States") {
viewModel.toggleStates()
}
.buttonStyle(.bordered)
Color.teal
.frame(height: 900)
}
.padding()
}
}
}
}
Ver modelo:
@MainActor
final class ContentViewModel: ObservableObject {
@Published private(set) var state1 = false
@Published private(set) var state2 = false
private var stateToggle = false
func toggleStates() {
Task { @MainActor in
state1 = true
defer {
// This change never becomes visible in the view!
// state1 will be improperly shown as 'true' when this method returns while it actually is 'false'.
print("Resetting state1")
state1 = false
}
stateToggle.toggle()
if stateToggle {
withAnimation {
state2 = true
}
} else {
state2 = false
}
}
}
}
Quando você pressiona o botão "Toggle States", o texto para state1
mostra “State1: true” quando a tarefa é concluída, mas deveria mostrar “State1: false” porque state1
está definido como false
na tarefa de toggleStates
. Neste ponto, os valores reais do modelo de visualização e os valores exibidos na visualização não estão mais sincronizados.
Se você remover a scrollProxy.scrollTo(state2ID)
chamada no onAppear
método, o problema não ocorre. Portanto, parece ser acionado pelo processo de rolagem.
Isso parece muito inesperado e um bug, ou estou esquecendo de algo fundamental? Alguma ideia do porquê isso acontece e como usar o scrollTo
método de um ScrollViewProxy
objeto para que esse erro não ocorra?
Esse problema pode ser uma questão de tempo relacionada às alterações realizadas pelo modelo de exibição
withAnimation
.Eu sugeriria que seria melhor se um modelo de visualização não se preocupasse com detalhes de apresentação, como animação.
Em qualquer caso, você pode fazê-lo funcionar retirando os controles de animação do modelo de visualização e colocando-os na visualização:
1. Em
ContentViewModel
, comente owithAnimation
Claro, você pode simplificar o código enquanto estiver fazendo isso:
...ou você pode descartar
stateToggle
e apenas alternarstate2
. Isso pode ajudar a tornar o modelo de visualização um pouco menos complicado.2. Em
ContentView
, adicione um.animation
modificador ao nível superiorVStack
dentro doScrollView
Isso dá uma animação de slide ao revelar e também ao esconder. Se você quiser suprimir a animação para esconder (como você tinha antes), então você pode usar um operador ternário no
.animation
modificador: