will update answer*
Post
Replies
Boosts
Views
Activity
I cannot provide exact repro steps as the bug is intermittent. But I can provide some general code as to how the StateObjects are used:
import SwiftUI
import Combine
struct RootView: View {
private let columns: [GridItem] = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(0..<20) { _ in
ContentView()
}
}
}
}
}
struct ContentView: View {
@StateObject
private var viewModel = ViewModel()
var body: some View {
content(viewModel.viewState)
.onAppear {
viewModel.subscribe()
}
}
@ViewBuilder
private func content(_ viewState: ViewState) -> some View {
switch viewState {
case .loading:
ProgressView()
case .content(let counter):
Text("\(counter)")
.padding()
}
}
}
extension ContentView {
enum ViewState: ObservableViewState {
case loading
case content(Int)
static var initialViewState: ViewState = .loading
}
enum Input {
case timerUpdated
}
enum Action {
case increment
case setState(ViewState)
}
class ViewModel: ObservableObject {
@Published
var viewState: ViewState = .loading
private let inputSubject = PassthroughSubject<Input, Never>()
private var subscribed = false
var internalInputPublisher: AnyPublisher<Input, Never> {
Timer.publish(every: 1, on: RunLoop.main, in: .common)
.autoconnect()
.eraseToAnyPublisher()
.map({ _ in
Input.timerUpdated
})
.eraseToAnyPublisher()
}
func subscribe() {
if subscribed {
return
}
subscribed = true
Publishers.Merge(internalInputPublisher, inputSubject)
.flatMap { input -> AnyPublisher<Action, Never> in
switch input {
case .timerUpdated:
return Just(.increment)
.eraseToAnyPublisher()
}
}
.scan(viewState, { currentState, action in
switch action {
case .setState(let state):
return state
case .increment:
if case .content(let counter) = currentState {
return .content(counter + 1)
} else {
return .content(1)
}
}
})
.removeDuplicates()
.assign(to: &$viewState)
}
func send(_ input: Input) {
inputSubject.send(input)
}
}
}
pretty much all view models follow this reactive publisher pattern for channeling view state updates to the listening view. The bug can happen to random views throughout the app