SwiftUI NavigationLink loads destination view immediately, without clicking

前端 未结 4 1757
不知归路
不知归路 2020-12-05 13:01

With following code:

struct HomeView: View {
    var body: some View {
        NavigationView {
            List(dataTypes) { dataType in
                Na         


        
相关标签:
4条回答
  • 2020-12-05 13:34

    The best way I have found to combat this issue is by using a Lazy View.

    struct NavigationLazyView<Content: View>: View {
        let build: () -> Content
        init(_ build: @autoclosure @escaping () -> Content) {
            self.build = build
        }
        var body: Content {
            build()
        }
    }
    

    Then the NavigationLink would look like this. You would place the View you want to be displayed inside ()

    NavigationLink(destination: NavigationLazyView(DetailView(data: DataModel))) { Text("Item") }
    
    0 讨论(0)
  • 2020-12-05 13:35

    I had the same issue where I might have had a list of 50 items, that then loaded 50 views for the detail view that called an API (which resulted in 50 additional images being downloaded).

    The answer for me was to use .onAppear to trigger all logic that needs to be executed when the view appears on screen (like setting off your timers).

    struct AnotherView: View {
        var body: some View {
            VStack{
                Text("Hello World!")
            }.onAppear {
                print("I only printed when the view appeared")
                // trigger whatever you need to here instead of on init
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 13:41

    EDIT: See @MwcsMac's answer for a cleaner solution which wraps View creation inside a closure and only initializes it once the view is rendered.

    It takes a custom ForEach to do what you are asking for since the function builder does have to evaluate the expression

    NavigationLink(destination: AnotherView()) {
        HomeViewRow(dataType: dataType)
    }
    

    for each visible row to be able to show HomeViewRow(dataType:), in which case AnotherView() must be initialized too.

    So to avoid this a custom ForEach is necessary.

    import SwiftUI
    
    struct LoadLaterView: View {
        var body: some View {
            HomeView()
        }
    }
    
    struct DataType: Identifiable {
        let id = UUID()
        var i: Int
    }
    
    struct ForEachLazyNavigationLink<Data: RandomAccessCollection, Content: View, Destination: View>: View where Data.Element: Identifiable {
        var data: Data
        var destination: (Data.Element) -> (Destination)
        var content: (Data.Element) -> (Content)
        
        @State var selected: Data.Element? = nil
        @State var active: Bool = false
        
        var body: some View {
            VStack{
                NavigationLink(destination: {
                    VStack{
                        if self.selected != nil {
                            self.destination(self.selected!)
                        } else {
                            EmptyView()
                        }
                    }
                }(), isActive: $active){
                    Text("Hidden navigation link")
                        .background(Color.orange)
                        .hidden()
                }
                List{
                    ForEach(data) { (element: Data.Element) in
                        Button(action: {
                            self.selected = element
                            self.active = true
                        }) { self.content(element) }
                    }
                }
            }
        }
    }
    
    struct HomeView: View {
        @State var dataTypes: [DataType] = {
            return (0...99).map{
                return DataType(i: $0)
            }
        }()
        
        var body: some View {
            NavigationView{
                ForEachLazyNavigationLink(data: dataTypes, destination: {
                    return AnotherView(i: $0.i)
                }, content: {
                    return HomeViewRow(dataType: $0)
                })
            }
        }
    }
    
    struct HomeViewRow: View {
        var dataType: DataType
        
        var body: some View {
            Text("Home View \(dataType.i)")
        }
    }
    
    struct AnotherView: View {
        init(i: Int) {
            print("Init AnotherView \(i.description)")
            self.i = i
        }
        
        var i: Int
        var body: some View {
            print("Loading AnotherView \(i.description)")
            return Text("hello \(i.description)").onAppear {
                print("onAppear AnotherView \(self.i.description)")
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 13:55

    I was recently struggling with this issue (for a navigation row component for forms), and this did the trick for me:

    @State private var shouldShowDestination = false
    
    NavigationLink(destination: DestinationView(), isActive: $shouldShowDestination) {
        Button("More info") {
            self.shouldShowDestination = true
        }
    }
    

    Simply wrap a Button with the NavigationLink, which activation is to be controlled with the button.

    Now, if you're to have multiple button+links within the same view, and not an activation State property for each, you should rely on this initializer

        /// Creates an instance that presents `destination` when `selection` is set
        /// to `tag`.
        public init<V>(destination: Destination, tag: V, selection: Binding<V?>, @ViewBuilder label: () -> Label) where V : Hashable
    

    https://developer.apple.com/documentation/swiftui/navigationlink/3364637-init

    Along the lines of this example:

    struct ContentView: View {
        @State private var selection: String? = nil
    
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: Text("Second View"), tag: "Second", selection: $selection) {
                        Button("Tap to show second") {
                            self.selection = "Second"
                        }
                    }
                    NavigationLink(destination: Text("Third View"), tag: "Third", selection: $selection) {
                        Button("Tap to show third") {
                            self.selection = "Third"
                        }
                    }
                }
                .navigationBarTitle("Navigation")
            }
        }
    }
    

    More info (and the slightly modified example above) taken from https://www.hackingwithswift.com/articles/216/complete-guide-to-navigationview-in-swiftui (under "Programmatic navigation").

    Alternatively, create a custom view component (with embedded NavigationLink), such as this one

    struct FormNavigationRow<Destination: View>: View {
    
        let title: String
        let destination: Destination
    
        var body: some View {
            NavigationLink(destination: destination, isActive: $shouldShowDestination) {
                Button(title) {
                    self.shouldShowDestination = true
                }
            }
        }
    
        // MARK: Private
    
        @State private var shouldShowDestination = false
    }
    

    and use it repeatedly as part of a Form (or List):

    Form {
        FormNavigationRow(title: "One", destination: Text("1"))
        FormNavigationRow(title: "Two", destination: Text("2"))
        FormNavigationRow(title: "Three", destination: Text("3"))
    }
    
    0 讨论(0)
提交回复
热议问题