With following code:
struct HomeView: View { var body: some View { NavigationView { List(dataTypes) { dataType in NavigationLink(destination: AnotherView()) { HomeViewRow(dataType: dataType) } } } } }
What's weird, when HomeView
appears, NavigationLink
immediately loads the AnotherView
. As a result, all AnotherView
dependencies are loaded as well, even though it's not visible on the screen yet. The user has to click on the row to make it appear. My AnotherView
contains a DataSource
, where various things happen. The issue is that whole DataSource
is loaded at this point, including some timers etc.
Am I doing something wrong..? How to handle it in such way, that AnotherView
gets loaded once the user presses on that HomeViewRow
?
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") }
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)") } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With