Problem
It seems to be a SwiftUI bug. All Reader type views (such as GeometryReader or ScrollViewReader) don't update their content even if state changes.
This is the code that reproduces the behavior.
class MainViewController: ComposableController {
final class Environment: ObservableObject {
@Published
var show: Bool = false
}
// MARK: - Property
// MARK: - Initializer
init() {
let env = Environment()
super.init(env)
run {
VStack {
GeometryReader { _ in
// Not updated
Text(env.show ? "A" : "B")
}
// Updated
Text(env.show ? "AA" : "BB")
Button("toggle") {
env.show.toggle()
}
}
}
}
...
}
Pure SwiftUI version
class ViewModel: ObservableObject {
@Published
var show: Bool = false
}
struct Parent: View {
let viewModel = ViewModel()
var body: some View {
Child(viewModel) {
VStack {
GeometryReader { _ in
// Not updated
Text(viewModel.show ? "A" : "B")
}
// Updated
Text(viewModel.show ? "AA" : "BB")
Button("toggle") {
viewModel.show.toggle()
}
}
}
}
}
struct Child<Content: View>: View {
var body: some View {
content()
}
@StateObject
var viewModel: ViewModel
let content: () -> Content
init(_ viewModel: ViewModel, content: @escaping () -> Content) {
self._viewModel = .init(wrappedValue: viewModel)
self.content = content
}
}
There seems to be a problem with the state capture system for rendering. When the content closure captures a StateObject, the content closure of the reader view isn't capturing the state correctly.
Workaround
Make as sub view that include reader view.
AS-IS
VStack {
GeometryReader { _ in
Text(env.show ? "A" : "B")
}
Text(env.show ? "AA" : "BB")
Button("toggle") {
env.show.toggle()
}
}
TO-BE
VStack {
SubView(show: .init(
get: { env.show },
set: { env.show = $0 }
))
Text(env.show ? "AA" : "BB")
Button("toggle") {
env.show.toggle()
}
}
/// Or make as `struct`.
@ViewBuilder
func SubView(show: Binding<Bool>) -> some View {
GeometryReader { _ in
Text(show.wrappedValue ? "A" : "B")
}
}
Problem
It seems to be a
SwiftUIbug. AllReadertype views (such asGeometryReaderorScrollViewReader) don't update their content even if state changes.This is the code that reproduces the behavior.
Pure
SwiftUIversionThere seems to be a problem with the state capture system for rendering. When the content closure captures a
StateObject, the content closure of the reader view isn't capturing the state correctly.Workaround
Make as sub view that include reader view.
AS-IS
TO-BE