![](/img/trans.png)
[英]SwiftUI NavigationLink: how to call a function before showing destination view
[英]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.