簡體   English   中英

SwiftUI NavigationLink 立即加載目標視圖,無需單擊

[英]SwiftUI NavigationLink loads destination view immediately, without clicking

使用以下代碼:

struct HomeView: View {
    var body: some View {
        NavigationView {
            List(dataTypes) { dataType in
                NavigationLink(destination: AnotherView()) {
                    HomeViewRow(dataType: dataType)
                }
            }
        }
    }
}

奇怪的是,當HomeView出現時, NavigationLink立即加載AnotherView 結果,所有AnotherView依賴項也被加載,即使它在屏幕上還不可見。 用戶必須單擊該行才能使其出現。 我的AnotherView包含一個DataSource ,其中發生了各種事情。 問題是此時加載了整個DataSource ,包括一些計時器等。

難道我做錯了什么..? 如何以這種方式處理它,一旦用戶按下HomeViewRow就會加載AnotherView

我發現解決這個問題的最好方法是使用惰性視圖。

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

然后 NavigationLink 將如下所示。 您可以將要顯示的視圖放在()

NavigationLink(destination: NavigationLazyView(DetailView(data: DataModel))) { Text("Item") }

編輯:請參閱@MwcsMac 的答案以獲得更簡潔的解決方案,該解決方案將視圖創建包裝在閉包中,並且僅在視圖呈現后才對其進行初始化。

由於函數構建器確實必須評估表達式,因此需要自定義ForEach來執行您的要求

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

為了能夠顯示HomeViewRow(dataType:)每個可見行,在這種情況下AnotherView()必須初始化AnotherView()

所以為了避免這種情況,自定義ForEach是必要的。

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)")
        }
    }
}

我遇到了同樣的問題,我可能有一個包含 50 個項目的列表,然后為調用 API 的詳細視圖加載了 50 個視圖(這導致下載了 50 個額外的圖像)。

我的答案是使用 .onAppear 來觸發所有需要在視圖出現在屏幕上時執行的邏輯(比如關閉你的計時器)。

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
        }
    }
}

在目標視圖中,您應該監聽onAppear事件並將所有需要在新屏幕出現時執行的代碼放在那里。 像這樣:

struct DestinationView: View {
    var body: some View {
        Text("Hello world!")
        .onAppear {
            // Do something important here, like fetching data from REST API
            // This code will only be executed when the view appears
        }
    }
}

我最近在這個問題上苦苦掙扎(對於表單的導航行組件),這對我有用:

@State private var shouldShowDestination = false

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

只需用NavigationLink包裝一個Button ,該激活將通過該按鈕進行控制。

現在,如果您要在同一個視圖中有多個按鈕+鏈接,而不是每個按鈕的激活State屬性,您應該依賴這個初始化程序

    /// 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

沿着這個例子的思路:

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")
        }
    }
}

https://www.hackingwithswift.com/articles/216/complete-guide-to-navigationview-in-swiftui (在“程序化導航”下)獲取的更多信息(以及上面稍微修改的示例)。

或者,創建一個自定義視圖組件(帶有嵌入式NavigationLink ),例如這個

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
}

並將其作為Form (或List )的一部分重復使用:

Form {
    FormNavigationRow(title: "One", destination: Text("1"))
    FormNavigationRow(title: "Two", destination: Text("2"))
    FormNavigationRow(title: "Three", destination: Text("3"))
}

對於 iOS 14 SwiftUI。

延遲導航目標加載的非優雅解決方案,使用視圖修飾符,基於這篇文章

extension View {
    func navigate<Value, Destination: View>(
        item: Binding<Value?>,
        @ViewBuilder content: @escaping (Value) -> Destination
    ) -> some View {
        return self.modifier(Navigator(item: item, content: content))
    }
}

private struct Navigator<Value, Destination: View>: ViewModifier {
    let item: Binding<Value?>
    let content: (Value) -> Destination
    
    public func body(content: Content) -> some View {
        content
            .background(
                NavigationLink(
                    destination: { () -> AnyView in
                        if let value = self.item.wrappedValue {
                            return AnyView(self.content(value))
                        } else {
                            return AnyView(EmptyView())
                        }
                    }(),
                    isActive: Binding<Bool>(
                        get: { self.item.wrappedValue != nil },
                        set: { newValue in
                            if newValue == false {
                                self.item.wrappedValue = nil
                            }
                        }
                    ),
                    label: EmptyView.init
                )
            )
    }
}

像這樣調用它:

struct ExampleView: View {
    @State
    private var date: Date? = nil
    
    var body: some View {
        VStack {
            Text("Source view")
            Button("Send", action: {
                self.date = Date()
            })
        }
        .navigate(
            item: self.$date,
            content: {
                VStack {
                    Text("Destination view")
                    Text($0.debugDescription)
                }
            }
        )
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM