ipadswiftuiswiftui-navigationlinkswiftui-navigationsplitview

NavigationSplitView not updating


Can someone please help me why the NavigationLink is not working as intended? As shown down below (in the code) I use the MarkdownWebView(url: <url>) with 3 different URL’s.

But when I want to switch between them, the view doesn’t update. If I open another view in between it’s working. And on the iPhone (NavigationStack) it also works.

The Problem

GIF of issue

My Code:

Section("Legal") {
    NavigationLink {
        MarkdownWebView(url: "https://<url>/privacy.md", scrollbar: false)
            .navigationTitle("Privacy Policy")
    } label: {
        Text("")
            .font(.custom(CustomFonts.FADuotone, size: 20))
            .frame(width: 30)
            .foregroundColor(.gray)
        Text(String(localized: "Privacy Policy", comment: "/"))
    }
    NavigationLink {
        MarkdownWebView(url: "https://<url>/tos.md", scrollbar: false)
            .navigationTitle("Terms of use")
    } label: {
        Text("")
            .font(.custom(CustomFonts.FADuotone, size: 20))
            .frame(width: 30)
            .foregroundColor(.gray)
        Text(String(localized: "Terms of Service", comment: "/"))
    }
    NavigationLink {
        MarkdownWebView(url: "https://<url>/licenses.md", scrollbar: false)
            .navigationTitle("Licenses")
    } label: {
        Text("")
            .font(.custom(CustomFonts.FADuotone, size: 20))
            .frame(width: 30)
            .foregroundColor(.gray)
        Text(String(localized: "Licenses", comment: "/"))
    }
}

NavigagationSplitView

This is what the NavigationSplitView looks like:

var body: some View {
      NavigationSplitView(columnVisibility: $navigationVM.selectedColumnVisibility) {
          column1Form
              .navigationTitle(String(localized: "Dashboard", comment: "/"))
              .navigationBarTitleDisplayMode(.large)
      } content: {
          secondForm
      }detail: {
          detailForm
      }
      .navigationSplitViewStyle(.balanced)
}
@ViewBuilder
  var secondForm: some View {
      switch navigationVM.selectedCategory {
      case .findWineries: findWineries()
      case .profile: ProfileView()
      case .stats: StatisticsView()
      case .favWines: FavWineView()
      case .favWineries: FavWineriesView()
      case .cellar: CellarView()
      case .orders: OrderListView()
  ->  case .settings: SettingsView()
      case .none: Text("")
      }
  }
  
  @ViewBuilder
  var detailForm: some View {
      switch navigationVM.selectedDetail {
      case .map: MapView()
      case .order: Text("orderTest")
      case .orderDetail: OrderDetailView(Status: .delivered)
      case .none: Text("")
      }
}

On the second column of the SplitView I navigate to the SettingsView() (marked in the code with an arrow).

From there (SettingsView) I want to push the third row with the NavigationLink.

This works fine if I push separate Views. But it doesn’t work with the same View and different parameters (as shown in the post above).

MarkdownWebView()

import SwiftUI
import MarkdownUI

struct MarkdownWebView: View {
    @State var url: String
    @State var scrollbar: Bool
    @State var error: Bool = false
    
    @State private var fileContent: String? = nil
    var body: some View {
        VStack {
            if let content = fileContent {
                ScrollView(showsIndicators: scrollbar) {
                    Markdown(content)
                }
            } else {
                if (error) {
                    VStack(spacing: 20) {
                        Text("")
                            .font(.custom(CustomFonts.FADuotone, size: 100, relativeTo: .body))
                            .foregroundColor(.red)
                        Text("Document not found")
                            .font(.title)
                    }
                } else {
                    VStack(spacing: 20) {
                        ProgressView()
                        Text("loading")
                    }
                }
            }
            
        }
        .onAppear {
            loadMarkdownFile(url: url)
        }
        .padding()
    }
    
    private func loadMarkdownFile(url: String) {
        DispatchQueue.global().async {
            guard let fileUrl = URL(string: url) else {
                print("File not found")
                self.error = true
                return
            }
            do {
                let content = try String(contentsOf: fileUrl)
                DispatchQueue.main.async {
                    self.fileContent = content
                }
            } catch {
                self.error = true
                print("Error reading file: \(error)")
            }
        }
    }
}

Solution

  • In the way you use NavigationLink the .onAppear in MarkdownWebView is only called once for the first view. So the content doesn't refresh on other selections, because the view is already visible and .onAppear isn't called again.

    I can suggest two options:

    1. quick and dirty
    Give each call of MarkdownWebView a different .id which forces a redraw:

    struct SettingsView: View {
        
        var body: some View {
            List {
                Section("Legal") {
                    NavigationLink {
                        MarkdownWebView(url: "https://www.lipsum.com", scrollbar: false)
                            .navigationTitle("Privacy Policy")
                            .id(1) // here
                    } label: {
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Privacy Policy", comment: "/"))
                    }
                    NavigationLink {
                        MarkdownWebView(url: "https://www.apple.com", scrollbar: false)
                            .navigationTitle("Terms of use")
                            .id(2) // here
                    } label: {
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Terms of Service", comment: "/"))
                    }
                    NavigationLink {
                        MarkdownWebView(url: "https://www.google.com", scrollbar: false)
                            .navigationTitle("Licenses")
                            .id(3) // here
                   } label: {
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Licenses", comment: "/"))
                    }
                }
            }
        }
    }
    

    2. new SwiftUI navigation logic
    Use the new init of NavigationLink with value and provide a .navigationDestination. I used Int values here (1,2,3) but you can (and should) also use enum values.

    struct SettingsView2: View {
        
        var body: some View {
            List {
                Section("Legal") {
                    NavigationLink(value: 1) { // value 1
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Privacy Policy", comment: "/"))
                    }
                    NavigationLink(value: 2) { // value 2
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Terms of Service", comment: "/"))
                    }
                    NavigationLink(value: 3) { // value 3
                        Text("")
                            .font(.system(size: 20))
                            .frame(width: 30)
                            .foregroundColor(.gray)
                        Text(String(localized: "Licenses", comment: "/"))
                    }
                }
            }
            .navigationDestination(for: Int.self) { value in // define destinations based on value
                switch value {
                case 1:
                    MarkdownWebView(url: "https://www.lipsum.com", scrollbar: false)
                        .navigationTitle("Privacy Policy")
                case 2:
                    MarkdownWebView(url: "https://www.apple.com", scrollbar: false)
                        .navigationTitle("Terms of use")
                case 3:
                    MarkdownWebView(url: "https://www.google.com", scrollbar: false)
                        .navigationTitle("Licenses")
                default: Text("nothing")
                }
            }
        }
    }