SwiftUI's onAppear() and onDisappear() called multiple times and inconsistently on iOS 14.1

I've come across some strange behavior with SwiftUI's onAppear() and onDisappear() events. I need to be able to reliably track when a view is visible to the user, disappears, and any other subsequent appear/disappear events (the use case is tracking impressions for mobile analytics).

I was hoping to leverage the onAppear() and onDisappear() events associated with swiftUI views, but I'm not seeing consistent behavior when using those events. The behavior can change depending on view modifiers as well as the simulator on which I run the app.

In the example code listed below, I would expect that when ItemListView2 appears, I would see the following printed out in the console:


Code Block
button init
button appear


And on the iPhone 8 simulator, I see exactly that.

However, on an iPhone 12 simulator, I see:

Code Block
button init
button appear
button disappear
button appear


Things get even weirder when I enable the listStyle view modifier:

Code Block
button init
button appear
button disappear
button appear
button disappear
button appear
button appear


The iPhone 8, however remains consistent and produces the expected result.

I should also note that in no case, did the Button ever seem to disappear and re-appear to the eye.

These inconsistencies are also not simulator only issues, i noticed them on devices as well.

I need to reliably track these appear/disappear events. For example I'd need to know when a cell in a list appears (scrolled into view) or disappears (scrolled out of view) or when, say a user switches tabs.

Has anyone else noticed this behavior? To me this seems like a bug in SwiftUI, but I'm not certain as I've not used SwiftUI enough to trust myself to discern a programmer error from an SDK error. If any of you have noticed this, did you find a good work-around / fix?

Thanks,
  • Norm


Code Block
// Sample code referenced in explanation
// Using Xcode Version 12.1 (12A7403) and iOS 14.1 for all simulators
import SwiftUI
struct ItemListView2: View {
let items = ["Cell 1", "Cell 2", "Cell 3", "Cell 4"]
var body: some View {
ListingView(items: items)
}
}
private struct ListingView: View {
let items: [String]
var body: some View {
List {
Section(
footer:
FooterButton()
.onAppear { print("button appear") }
.onDisappear { print("button disappear") }
) {
ForEach(items) { Text($0) }
}
}
// .listStyle(GroupedListStyle())
}
}
private struct FooterButton: View {
init() {
print("button init")
}
var body: some View {
Button(action: {}) { Text("Button") }
}
}

Same problem here. I've read that a ticket has been filed, and the workaround is to set a state variable. This seems wonky and potentially working around some reasoning that apple has for calling it more than once, which they don't indicate why.

I ran into this today. I can't explain it, but it's a real issue. Where did you read about the filed bug, @cyclic?

I've verified that it's fixed in iOS 15b1. Apple really needs to back-port this fix. I worked around it by posting a notification in .onAppear() higher up the hierarchy, where .onAppear was being called appropriately.

Huge improvement in iOS 15 Beta. Thank you, @Apple.

I'm using Xcode 13.2.1 with Simulator iOS 15.2 and iPhone 12 Pro iOS 15.2. I'm still facing the same issue. It's getting even worse. I need to track when a view disappears. Sometime the onDisappear is called and sometimes not.

I'm see these issues but also I'm see extra updates too on Xcode 13.4.1

I have created an environment object that i pass to child screens.

I'm seeing the .onAppear() being called on the parent screen when a child screen updates a field in the environment object

This is really starting to hold up development

Anyone find a good work around for this? I am still having the same issue in 13.4.1

You will have to rely on a static variable. so change the @State to static. I previously noticed that @State was not enough, cause it seems the main render cycle of @State. Consider my example that has one issue with it.

    @State var canExecuteOnDisappear = false
 modalContent
                        .zIndex(1)
                        .onAppear {
                            canExecuteOnDisappear = true
                        }
                        .onDisappear {
                            // HACK: onDisappear fires multiple times for some reason
                            if canExecuteOnDisappear {
                                print("CDM onDisappear")
                                canExecuteOnDisappear = false
                            }
                        }


if I were to go through 4 different types of modal content for each content in an array. presenting another sheet after dismissing the previous one I get the behavior:

("CDM onDisappear") gets printed 3 times just fine, and then the last time it prints out 3 instead of 1 so in total 7.

CDM onDisappear
CDM onDisappear
CDM onDisappear
CDM onDisappear
CDM onDisappear
CDM onDisappear
CDM onDisappear

So change that to a static variable so it updates immediately and not on the main thread. - @State var canExecuteOnDisappear = false + static var canExecuteOnDisappear = false

this fixes this issue.

SwiftUI's onAppear() and onDisappear() called multiple times and inconsistently on iOS 14.1
 
 
Q