1

I want to put three links in a NavigationView.

  • First link: Displays a RoomView with a specific room ID, "room.A"
  • Second link: Displays a RoomView with a specific room ID, "room.B"
  • Third link: Displays a RoomView with a randomly generated room ID when the user clicks.

The following code does not work, because SwiftUI calls UUID().uuidString when it builds the NavigationView, and reuse it.

I want to generate a new random room ID when the user clicks the link, but I am not able to figure out how to do it. I am looking for a simple and natural way to achieve this.

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: RoomView(roomId: "room.A")) {
                    Text("Enter Room A")
                }
                NavigationLink(destination: RoomView(roomId: "room.B")) {
                    Text("Enter Room B")
                }
                NavigationLink(destination: RoomView(roomId: UUID().uuidString)) {
                    Text("Create a new Room and enter")
                }
            }
        }
    }
}

struct RoomView: View {
    let roomId: String
    var body: some View {
        Text("Room View \(roomId)")
    }
}

Satoshi Nakajima
  • 1,863
  • 18
  • 29

2 Answers2

2

That's because the navigation link is performed eagerly. I made a view to run the creation on tap.

import SwiftUI

struct LazyView<Content>: View where Content: View {

    private let viewBuild: () -> Content

    fileprivate init(_ viewBuild: @escaping () -> Content) {
        self.viewBuild = viewBuild
    }

    init(_ viewBuild: @escaping @autoclosure () -> Content) {
        self.init(viewBuild)
    }

    var body: some View {
        viewBuild()
    }

}

extension NavigationLink {

    init<V>(lazyDestination: @escaping @autoclosure () -> V, @ViewBuilder label: () -> Label) where Destination == LazyView<V> {
        self.init(destination: LazyView(lazyDestination), label: label)
    }

    init<V>(lazyDestination: @escaping @autoclosure () -> V, isActive: Binding<Bool>, @ViewBuilder label: () -> Label)
    where Destination == LazyView<V> {
        self.init(destination: LazyView(lazyDestination), isActive: isActive, label: label)
    }

    init<V, T>(lazyDestination: @escaping @autoclosure () -> V, tag: T, selection: Binding<T?>, @ViewBuilder label: () -> Label)
    where T : Hashable, Destination == LazyView<V> {
        self.init(destination: LazyView(lazyDestination), tag: tag, selection: selection, label: label)
    }

}

extension NavigationLink where Label == Text {

    init<V>(_ titleKey: LocalizedStringKey, lazyDestination: @escaping @autoclosure () -> V) where Destination == LazyView<V> {
        self.init(titleKey, destination: LazyView(lazyDestination))
    }

    init<V, S>(_ title: S, lazyDestination: @escaping @autoclosure () -> V) where S : StringProtocol, Destination == LazyView<V> {
        self.init(title, destination: LazyView(lazyDestination))
    }

    init<V>(_ titleKey: LocalizedStringKey, lazyDestination: @escaping @autoclosure () -> V, isActive: Binding<Bool>)
    where Destination == LazyView<V> {
        self.init(titleKey, destination: LazyView(lazyDestination), isActive: isActive)
    }

    init<V, S>(_ title: S, lazyDestination: @escaping @autoclosure () -> V, isActive: Binding<Bool>)
    where S : StringProtocol, Destination == LazyView<V>  {
        self.init(title, destination: LazyView(lazyDestination), isActive: isActive)
    }

    init<V, T>(_ titleKey: LocalizedStringKey, lazyDestination: @escaping @autoclosure () -> V, tag: T, selection: Binding<T?>)
    where T : Hashable, Destination == LazyView<V> {
        self.init(titleKey, destination: LazyView(lazyDestination), tag: tag, selection: selection)
    }

    init<V, S, T>(_ title: S, lazyDestination: @escaping @autoclosure () -> V, tag: T, selection: Binding<T?>)
    where S : StringProtocol, T : Hashable, Destination == LazyView<V> {
        self.init(title, destination: LazyView(lazyDestination), tag: tag, selection: selection)
    }
}

Then in your code, replace with

NavigationLink(lazyDestination: RoomView(roomId: UUID().uuidString)) {
                    Text("Create a new Room and enter")
                }
Rico Crescenzio
  • 3,952
  • 1
  • 14
  • 28
2

I found a simple solution, but this exposes the weirdness of SwiftUI. This code and the code above are logically identical, but behave differently because of the optimization of SwiftUI.

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: RoomView(roomId: "room.A")) {
                    Text("Enter Room A")
                }
                NavigationLink(destination: RoomView(roomId: "room.B")) {
                    Text("Enter Room B")
                }
                NavigationLink(destination: NewRoomView()) {
                    Text("Create a new Room and enter")
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct NewRoomView: View {
    var body: some View {
        RoomView(roomId: UUID().uuidString)
    }
}
Satoshi Nakajima
  • 1,863
  • 18
  • 29