Hiding and unhiding views in SwiftUI
Trying to hide and unhide views in SwiftUI is not easy. At least not if you want to keep the continuation of state!
Consider this example where we have a CounterView we want to show and hide. SwiftUI makes this very difficult. Its counterstate should probably be a @Binding sent from outside, but nonetheless this should also be easy to do in SwiftUI in my opinion with @State variables.
Steps to reproduce
1. Open app
2. Tap hide
3. Tap showExpected result
Counter continues from last number
When the view is hidden it gets no accidental onAppear callbacksActual result
Counter restarts from 0
When hiding the view a new view is created and it gets an onAppear callback
Solution
In order to work around this issue we need to maintain the state somehow and be able to do something like view.hide(true) without creating a different view tree.
By wrapping the view in a UIKit view we can achieve that:
Original view structure
MyViewNew view structure
HideableView (SwiftUI)
-> HideableView.Container (UIView)
-> UIHostingController (UIView)
-> MyView (SwiftUI)
We then get full control over the lifecycle of the view as we can add and remove the UIHostingController from its superview (HideableView.Container).
Try running the example again, but this time using the HideableView we’ve created and with this render function
You’ll see that the counter gracefully continues and is not reset. The onAppear and onDisappear is also working great with the only caveat that we needed to Dispatch.main.async for the first presentation of the view in order to avoid an excessive onAppear, onDisappear callback. Notice this is only true if the content is within a NavigationView, which you likely use anyway.
If you try another solution make sure
- State passed in to your view is updated
- State within your view is in a continuation
- Callbacks for onAppear and onDisappear is sensical and sound
Hope you like it and find it useful!