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
编辑:请参阅@MwcsMac 的答案以获得更清晰的解决方案,该解决方案将视图创建包装在闭包中,并且仅在呈现视图后才对其进行初始化。
需要自定义 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: [=11=])
}
}()
var body: some View {
NavigationView{
ForEachLazyNavigationLink(data: dataTypes, destination: {
return AnotherView(i: [=11=].i)
}, content: {
return HomeViewRow(dataType: [=11=])
})
}
}
}
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
}
}
}
我发现解决此问题的最佳方法是使用 Lazy View。
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") }
我最近一直在努力解决这个问题(用于表单的导航行组件),这对我有用:
@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(在 "Programmatic navigation" 下)。
或者,创建一个自定义视图组件(嵌入 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"))
}
在目标视图中,您应该监听事件 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
}
}
}
对于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([=11=].debugDescription)
}
}
)
}
}
使用以下代码:
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
编辑:请参阅@MwcsMac 的答案以获得更清晰的解决方案,该解决方案将视图创建包装在闭包中,并且仅在呈现视图后才对其进行初始化。
需要自定义 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: [=11=])
}
}()
var body: some View {
NavigationView{
ForEachLazyNavigationLink(data: dataTypes, destination: {
return AnotherView(i: [=11=].i)
}, content: {
return HomeViewRow(dataType: [=11=])
})
}
}
}
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
}
}
}
我发现解决此问题的最佳方法是使用 Lazy View。
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") }
我最近一直在努力解决这个问题(用于表单的导航行组件),这对我有用:
@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(在 "Programmatic navigation" 下)。
或者,创建一个自定义视图组件(嵌入 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"))
}
在目标视图中,您应该监听事件 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
}
}
}
对于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([=11=].debugDescription)
}
}
)
}
}