Combine onChange and onAppear events in SwiftUI view?
It looks like onReceive
may be what you need. Instead of:
.onChange(of: model.counter, perform: someLogic)
.onAppear { someLogic(counter: model.counter) }
you could do:
.onReceive(model.$counter, perform: someLogic)
The difference between onChange
and onReceive
is that the latter also fires when the view is initialised.
onChange
If you take a closer look at onChange
, you'll see that it performs an action only when a value changes (and this doesn't happen when a view is initialised).
/// Adds a modifier for this view that fires an action when a specific
/// value changes.
/// ...
@inlinable public func onChange<V>(of value: V, perform action: @escaping (V) -> Void) -> some View where V : Equatable
onReceive
However, the counter's publisher will emit the value also when a view is initialised. This will make onReceive
perform an action passed as a parameter.
/// Adds an action to perform when this view detects data emitted by the
/// given publisher.
/// ...
@inlinable public func onReceive<P>(_ publisher: P, perform action: @escaping (P.Output) -> Void) -> some View where P : Publisher, P.Failure == Never
Just note that onReceive
is not an equivalent of onChange
+onAppear
.
onAppear
is called when a view appears but in some cases a view may be initialised again without firing onAppear
.
Infinite loop when using onAppear in SwiftUI
This issue is a bug in iOS 14 with Group, and will still happen even when building with Xcode 13.
What's happening is the SwiftUI runtime is messing up its view diff, so it believes that it "appeared" when really it only reloaded, and thus did not disappear (which is a requirement for onAppear
to be called, hence a bug).
To compensate for this issue, you'll need to use initialisers rather than relying on onAppear
.
I've amended your code, this amended version does not infinitely loop.
struct DetailView: View {
@StateObject var viewModel = DetailViewModel()
var body: some View {
Group {
switch viewModel.state {
case .loading:
Text("Loading...")
case .loaded:
HStack {
Text("Loaded")
Button("Retry") {
viewModel.fetchData()
}
}
}
}
}
}
class DetailViewModel: ObservableObject {
enum State {
case loading
case loaded
}
@Published var state: State = .loading
init() {
self.fetchData()
}
func fetchData() {
state = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.state = .loaded
}
}
}
Hope the project goes well!
How to make a reusable modifier for view lifecycle events (onAppear, onDisappear)?
What you try to do is already present and named opacity transition, which is written in one modifier.
Here is a demo:
struct ActiveView: View {
@State var showCode: Bool = false
var body: some View {
ZStack {
if self.showCode {
Color.black.opacity(0.7)
.transition(AnyTransition.opacity.animation(.default))
}
Button("Demo") { self.showCode.toggle() }
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Using SwiftUI and Combine to conditionally display a view based on authorization status?
Try adding @ObservedObject var healthKitAuthManager = HealthKitAuthManager()
instead of let healthKitAuthManager = HealthKitAuthManager()
. This way the @Published
variable will trigger a new view rendering.
SwiftUI: ViewModifier doesn't listen to onReceive events
ok, this works for me:
func body(content: Content) -> some View {
content
.onAppear() // <--- this makes it work
.onReceive(viewModel.$myProperty) { theValue in
print("-----> The Value is \(theValue)") // <--- this will be executed
}
}
Related Topics
Change Lock Screen Background Audio Controls Text
iPhone - Convert Ctfont to Uifont
How Do We Create a Bigger Center Uitabbar Item
How to Open Maps App Programmatically with Coordinates in Swift
Uicollectionview Adding Uicollectioncell
How to Change the Default Text of Cancel Button Which Appears in the Uisearchbar +Iphone
How to Retrieve Keystrokes from a Custom Keyboard on an iOS App
Facebook Sdk 3.1 for iOS - Runs on iOS6, But Crashes on iOS 5.X
How to Pass Data from One Container to Another, Both Embedded in the Same Uiviewcontroller in Swift
How to Use Sfsafariviewcontroller with Swiftui
How to Cache or Preload Sklabelnode Font
How to Display the Default iOS 6 Share Action Sheet with Available Share Options
How to Remove the Xcode Warning Apple MACh-O Linker Warning 'Pointer Not Aligned at Address
How to Use Static Cells in Uitableview Without Using Storyboards