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:
button init
button appear
And on the iPhone 8 simulator, I see exactly that.
However, on an iPhone 12 simulator, I see:
button init
button appear
button disappear
button appear
Things get even weirder when I enable the listStyle
view modifier:
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,
// 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") }
}
}
In SwiftUI you don't control when items in a List
appear or disappear. The view graph is managed internally by SwiftUI and views may appear/disappear at any time.
You can, however, attach the onAppear
/ onDisappear
modifiers to the outermost view:
List {
Section(footer: FooterButton()) {
ForEach(items, id: \.self) {
Text($0)
}
}
}
.listStyle(GroupedListStyle())
.onAppear { print("list appear") }
.onDisappear { print("list disappear") }
Try this UIKit approach. Similar behavior continues to exist under iOS 14.
protocol RemoteActionRepresentable: AnyObject {
func remoteAction()
}
struct UIKitAppear: UIViewControllerRepresentable {
let action: () -> Void
func makeUIViewController(context: Context) -> UIAppearViewController {
let vc = UIAppearViewController()
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ controller: UIAppearViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(action: self.action)
}
class Coordinator: RemoteActionRepresentable {
var action: () -> Void
init(action: @escaping () -> Void) {
self.action = action
}
func remoteAction() {
action()
}
}
}
class UIAppearViewController: UIViewController {
weak var delegate: RemoteActionRepresentable?
override func viewDidLoad() {
view.addSubview(UILabel())
}
override func viewDidAppear(_ animated: Bool) {
delegate?.remoteAction()
}
}
extension View {
func onUIKitAppear(_ perform: @escaping () -> Void) -> some View {
self.background(UIKitAppear(action: perform))
}
}
Example:
var body: some View {
MyView().onUIKitAppear {
print("UIViewController did appear")
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With