使用以下代码:
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
都已加载,包括一些计时器等。
我做错什么了吗?如何以这种方式处理,一旦用户按下AnotherView
就会加载HomeViewRow
?
答案 0 :(得分:45)
我发现解决此问题的最佳方法是使用惰性视图。
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") }
答案 1 :(得分:3)
由于函数构建器必须评估表达式,因此需要自定义ForEach
来完成您要的操作
NavigationLink(destination: AnotherView()) {
HomeViewRow(dataType: dataType)
}
要使每个可见行都能够显示HomeViewRow(dataType:)
,在这种情况下,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)")
}
}
}
答案 2 :(得分:3)
我遇到了同样的问题,我可能有一个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
}
}
}
答案 3 :(得分:0)
我最近在为这个问题而苦苦挣扎(用于表单的导航行组件),这为我成功了:
@State private var shouldShowDestination = false
NavigationLink(destination: DestinationView(), isActive: $shouldShowDestination) {
Button("More info") {
self.shouldShowDestination = true
}
}
只需用Button
包装一个NavigationLink
,即可通过按钮控制激活。
现在,如果您在同一个视图中有多个按钮+链接,而不是每个视图都有一个激活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"))
}
答案 4 :(得分:0)
在目标视图中,您应该侦听事件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
}
}
}
答案 5 :(得分:0)
适用于 iOS 14 SwiftUI。
延迟导航目标加载的非优雅解决方案,使用视图修饰符,基于此 post。
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)
}
}
)
}
}