Skip to content

Instantly share code, notes, and snippets.

@Tiagoperes
Last active May 2, 2022 12:40
Show Gist options
  • Save Tiagoperes/98f3c2da435ffe44c75b10b306ad3e87 to your computer and use it in GitHub Desktop.
Save Tiagoperes/98f3c2da435ffe44c75b10b306ad3e87 to your computer and use it in GitHub Desktop.
Swift UI Navigator API (can only push and pop to Root)

Problem

This implementation is a proof that I can't pop to a specific view using the isActive property of the navigation link. I can easily push, pop and pop to the root. But I can't pop to a specific view.

Discussion

I could not find anything to make the NavigationView work with a proper popToView behavior. Here's a discussion on the Apple Forums: https://developer.apple.com/forums/thread/134078.

Solutions

  1. Abort popToView, support only popToRoot.
  2. Use multiple navigators to create multiple roots and instead of popToView, implement popToPinned, which pops to the last pinned route.
  3. It looks like there's a lib that does exactly what we need, it might be interesting to take a look: https://github.com/johnpatrickmorgan/FlowStacks
import SwiftUI
func safeAt<T>(array: [T], position: Int) -> T? {
return (position <= array.count - 1) ? array[position] : nil
}
protocol Navigator {
func push(url: String) -> Void
func pop() -> Void
}
class CoreNimbusView {
let navigator: Navigator
private var listener: ((String) -> Void)?
private var lateUrl: String?
init(navigator: Navigator) {
self.navigator = navigator
}
func onChange(listener: @escaping (String) -> Void) {
self.listener = listener
if (lateUrl != nil) {
listener(lateUrl!)
lateUrl = nil
}
}
func paint(url: String) {
if (listener == nil) {
lateUrl = url
} else {
listener!(url)
}
}
}
func pushActionHandler(url: String, view: CoreNimbusView) {
view.navigator.push(url: url)
}
func popActionHandler(view: CoreNimbusView) {
view.navigator.pop()
}
class HistoryItem: ObservableObject {
let url: String
var linkActive = false
let coreView: CoreNimbusView
init(url: String, coreView: CoreNimbusView) {
self.url = url
self.coreView = coreView
}
}
class History: ObservableObject {
@Published var pages: [HistoryItem] = []
@Published var current = -1
}
struct NimbusView: View, Identifiable {
let id: Int
@State var url = ""
@EnvironmentObject var history: History
var body: some View {
let page = safeAt(array: history.pages, position: id)
if (page == nil) {
EmptyView()
} else {
let page = page!
VStack {
// navigation logic
UseMemo(key: page.linkActive) { linkActive in
VStack {
NavigationLink(
destination: NimbusView(id: id + 1),
isActive: $history.pages[id].linkActive
) {
EmptyView()
}
}
}
// view content
UseMemo(key: url) { keys in
VStack {
if (url.isEmpty) {
Text("No URL yet")
} else {
Text(url)
Button("Next") { pushActionHandler(url: "url \(id)", view: page.coreView) }
Button("Previous") { popActionHandler(view: page.coreView) }
//Button("Previous") { history.forceUpdate.toggle() }
}
}
}
}.onAppear() {
page.coreView.onChange(listener: { content in url = content })
}
}
}
}
struct NimbusNavigator: View, Navigator {
let initialUrl: String
@StateObject var history = History()
func push(url: String) {
if (history.current >= 0) {
history.pages[history.current].linkActive = true
}
let newPage = HistoryItem(url: url, coreView: CoreNimbusView(navigator: self))
history.pages.removeLast(history.pages.count - 1 - history.current)
history.pages.append(newPage)
newPage.coreView.paint(url: url)
history.current += 1
}
func pop() {
if (history.current <= 0) {
return print("Can't pop single view")
}
history.pages[history.current - 1].linkActive = false
history.current -= 1
}
var body: some View {
NavigationView { NimbusView(id: 0) }
.environmentObject(history)
.onAppear() {
push(url: initialUrl)
}
}
}
struct ContentView: View {
var body: some View {
NimbusNavigator(initialUrl: "initial")
}
}
import SwiftUI
struct UseMemo<KeyType: Equatable, ViewType: View>: View, Equatable {
let key: KeyType
let content: (KeyType) -> ViewType
var body: some View {
content(key)
}
static func ==(lhs: UseMemo, rhs: UseMemo) -> Bool {
return lhs.key == rhs.key
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment