iOS 14 .onAppear() is called on DISappear instead of appear

I've got a View with
Code Block
.onAppear(perform: {
print("appear")
})
.onDisappear(perform: {
print("disappear")
})
set on it.

In iOS 13.5, when I navigate away from the view via a NavigationLink, it prints "disapear"; when I close the new view and get back to the first view "appear" gets printed. All as it should be.

Now in iOS 14, this happens instead:
  • when the view is first shown, it prints "appear"

  • when navigating away from the view, it prints "disappear" and "appear"

  • when navigating back to the view, nothing gets printed

  • when navigating away from the view, then "appear" gets printed, along with the expected "disappear"

...is this some new super-lazy way of doing things or just plain a bug?

It runs havoc with my login, as I set @Binding vars in the second view, which are evaluated back in the first view when .onAppear() runs. Worked great until iOS 14...
Post not yet marked as solved Up vote post of Emnasut Down vote post of Emnasut
20k views

Replies

Create issue, thanks UIKit))

Code Block
struct UIKitAppear: UIViewControllerRepresentable {
    let action: () -> Void
    func makeUIViewController(context: Context) -> UIAppearViewController {
       let vc = UIAppearViewController()
        vc.action = action
        return vc
    }
    func updateUIViewController(_ controller: UIAppearViewController, context: Context) {
    }
}
class UIAppearViewController: UIViewController {
    var action: () -> Void = {}
    override func viewDidLoad() {
        view.addSubview(UILabel())
    }
    override func viewDidAppear(_ animated: Bool) {
        action()
    }
}
public extension View {
    func uiKitOnAppear(_ perform: @escaping () -> Void) -> some View {
        self.background(UIKitAppear(action: perform))
    }
}


Example:

Code Block
var body: some View {
SomeView().uiKitOnAppear {
print("I am uiKitAppear")
}
}

  • This UIKit function works well, thank you for providing this nice idea. Until Apple fixes this serious onAppear bug, I use this function.

Add a Comment
Any Update here?
I can't seem to find an acceptable workaround.

I am using xCode 12.2 and am still experiencing this issue
This issue happens in Xcode Version 12.2 (12B45b) and iOS 14.2. Is there any workaround for this issue?
Echoing everyone else: This issue is still painfully present, and there's no known workaround. Something needs to happen. This is not okay.
Found a weird quirk. When you pass in the method directly (line 9/10) it's a lot more stable.

Code Block swift
.onAppear {
viewModel.onAppear()
}
.onDisappear {
viewModel.onDisappear()
}
.onAppear(perform: viewModel.onAppear)
.onDisappear(perform: viewModel.onDisappear)


Just found out that it works correctly on iOS 14 if you have parentheses after onAppear/onDisappear:

Code Block Swift
.onAppear() {
print("...code here")
}
.onDisappear() {
print("...code here")
}


Is there any update? I cannot believe that this is not working. Is there a new way of using these lifecycle methods?
Hi Guys, I don't have a nativ fix for this problem but a workaround I have found after 3 days of research which I have posted here: https://stackoverflow.com/a/65857640/6131259 I hope it helps some of you.
On Xcode 12.3 / iOS 14.3 and onAppear is still very flaky. I'm actively avoiding it at all costs everywhere and calling functions manually while constructing the view instead.
Only relamquad's answer with UIKit gave me the most confidence and it works. With the flakiness from version to version for SwiftUI's onAppear, just a simple syntax change and everything stops working, relying solely on SwiftUI is too risky. I can't believe this is not high on the bug list. onAppear is such an essential part
Was using relamquad's answer quite successfully until I noticed it can lead to memory leaks due to strong reference cycles.

Using it like described works fine:
Code Block Swift
var body: some View {
SomeView().uiKitOnAppear {
print("I am uiKitAppear")
}
}


However if you pass a ViewModel's function for example, the ViewModel will not be deallocated once the View should be deinitialized:
Code Block Swift
@ObservedObject var viewModel: ViewModel
var body: some View {
SomeView().uiKitOnAppear {
viewModel.startUpdating()
}
}


If been trying to find a solution using weak references, but then again, somehow startUpdating is not called on .uiKitOnAppear().
Does anybody know a solution to this?
Code Block
import SwiftUI
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: ActionRepresentable {
        var action: () -> Void
        init(action: @escaping () -> Void) {
            self.action = action
        }
        func remoteAction() {
            action()
        }
    }
}
protocol ActionRepresentable: AnyObject {
    func remoteAction()
}
class UIAppearViewController: UIViewController {
    weak var delegate: ActionRepresentable?
    override func viewDidLoad() {
        view.addSubview(UILabel())
    }
    override func viewDidAppear(_ animated: Bool) {
        delegate?.remoteAction()
    }
}
public extension View {
    func onUIKitAppear(_ perform: @escaping () -> Void) -> some View {
        self.background(UIKitAppear(action: perform))
    }
}

Thanks gfdgdfg for the quick response and solution proposition.

In my specific case, it did not solve the memory leak issue. What helped was resetting the action in updateUIViewController(_ controller: UIAppearViewController, context: Context).

Extending relamquad's answer like this:
Code Block swift
struct UIKitAppear: UIViewControllerRepresentable {
    let action: () -> Void
    func makeUIViewController(context: Context) -> UIAppearViewController {
       let vc = UIAppearViewController()
        vc.action = action
        return vc
    }
    func updateUIViewController(_ controller: UIAppearViewController, context: Context) {
controller.action = action
}
}


I found that the issue with .onAppear exist only when I using ObservedObject in my View if I change it to the StateObject the problem is gone but I need to support an iOS 13 so as the temporary solution I calling my API call inside init of my view
  • At first I thought this did the trick, but this just reuses previous object and does not actually help in calling onAppear/task.

    I solved it by defining a funcion didInitialLoad() in viewmodel, and if it figures that there was no initial load, it will load data, same as it would on onAppear/task. I call this didInitialLoad inside SwiftUI where I show either list of data, or loading indicator view, something like:

    if viewModel.didInitialLoad() { List { ... } } else { LoadingView() }

Add a Comment
Can someone please fix this? It's just so dumb! .onAppear() is very very wonky. Did you guys not even review the PRs before pushing out the commits? And the worst thing is .onDisappear() doesn't work at all.