I'm using Parchment to add menu items at the top. The hierarchy of the main view is the following:
NavigationView -> TabView --> Parchment PagingView ---> NavigationLink(ChildView)
All works well going to the child view and then back again repeatedly. The issue happens when I go to ChildView, then go to the background/Home Screen then re-open. If I click back and then go to the child again the back button and the whole navigation bar disappears.
Here's code to replicate:
import SwiftUI
import Parchment
@main
struct ParchmentBugApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
PagingView(items: [
PagingIndexItem(index: 0, title: "View 0"),
]) { item in
VStack {
NavigationLink(destination: ChildView()) {
Text("Go to child view")
}
}
.navigationBarHidden(true)
}
}
}
}
}
struct ChildView: View {
var body: some View {
VStack {
Text("Child View")
}
.navigationBarHidden(false)
.navigationBarTitle("Child View")
}
}
To replicate:
- Launch and go to the child view
- Click the home button to send the app to the background
- Open the app again
- Click on back
- Navigate to the child view. The nav bar/back button are not there anymore.
What I noticed:
- Removing the
TabView
makes the problem go away. - Removing
PagingView
also makes the problem go.
I tried to use a custom PagingController and played with various settings without success. Here's the custom PagingView
if someone would like to tinker with the settings as well:
struct CustomPagingView<Item: PagingItem, Page: View>: View {
private let items: [Item]
private let options: PagingOptions
private let content: (Item) -> Page
/// Initialize a new `PageView`.
///
/// - Parameters:
/// - options: The configuration parameters we want to customize.
/// - items: The array of `PagingItem`s to display in the menu.
/// - content: A callback that returns the `View` for each item.
public init(options: PagingOptions = PagingOptions(),
items: [Item],
content: @escaping (Item) -> Page) {
self.options = options
self.items = items
self.content = content
}
public var body: some View {
PagingController(items: items, options: options,
content: content)
}
struct PagingController: UIViewControllerRepresentable {
let items: [Item]
let options: PagingOptions
let content: (Item) -> Page
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PagingController>) -> PagingViewController {
let pagingViewController = PagingViewController(options: options)
return pagingViewController
}
func updateUIViewController(_ pagingViewController: PagingViewController, context: UIViewControllerRepresentableContext<PagingController>) {
context.coordinator.parent = self
if pagingViewController.dataSource == nil {
pagingViewController.dataSource = context.coordinator
} else {
pagingViewController.reloadData()
}
}
}
class Coordinator: PagingViewControllerDataSource {
var parent: PagingController
init(_ pagingController: PagingController) {
self.parent = pagingController
}
func numberOfViewControllers(in pagingViewController: PagingViewController) -> Int {
return parent.items.count
}
func pagingViewController(_: PagingViewController, viewControllerAt index: Int) -> UIViewController {
let view = parent.content(parent.items[index])
return UIHostingController(rootView: view)
}
func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem {
return parent.items[index]
}
}
}
Tested on iOS Simulator 14.4 & 14.5, and device 14.5 beta 2.
Any tips or ideas are very much appreciated.
Okay I found the issue while debugging something else that was related to Parchment as well.
The issue is
updateUIViewController()
gets called each time the encompassing SwiftUI state changes (and when coming back to the foreground), and thePageController
wrapper provided by the library will callreloadData()
since the data source data has already been set. So to resolve this just remove/comment out thereloadData()
call since the PageController will be re-built if the relevant state changes. The same issue was the cause for the bug I was debugging.