Passing binding back to parent's parent view - ios

I have 4 views.
Grandparent
Parent
Child
EditView
Grandparent has a navigation link to Parent, and Parent a navigation link to Child. Child has a button which initializes a #State variable, location (a class), from Grandparent, via a binding in Parent and a binding in Child. That same button also updates a #State variable, showEditView, from Grandparent (via bindings again), which shows the EditView.
There are 11 lines which are currently commented out. If they are commented out, the app throws a "Fatal error: Unexpectedly found nil..." error when I tap the button in the Child view. If either section is uncommented, the button does not throw an error.
It also works if I pass the bound location to the EditView, not property itself like I'm currently doing, where it is then wrapped as an #ObservedObject.
I don't understand what is going on here. The only thing I can of is that, when it's working, SwiftUI is updating the location property because it's used in the body. If that is the case, that seems to indicate that I have to include a hidden Text view of this property every time I want to do have properties passed around this way.
Grandparent
import SwiftUI
struct Grandparent: View {
#State var location: Location!
#State var showEditView: Bool = false
var body: some View {
NavigationView {
VStack{
NavigationLink(
destination: Parent(location: $location, showEditView: $showEditView)) {
Text("Navigate")
}
// // section 1
// if location != nil {
// Text(location.name)
// } else {
// Text("No location yet")
// }
}
// // section 2
// .navigationBarItems(trailing:
// Button("Edit"){
// showEditView = true
// }
// .disabled(location == nil)
// )
}
.padding()
.sheet(isPresented: $showEditView) {
EditView(placemark: location, dismiss: { showEditView = false })
}
}
}
Parent
import SwiftUI
struct Parent: View {
#Binding var location: Location!
#Binding var showEditView: Bool
var body: some View {
NavigationLink(
destination: Child(location: $location, showEditView: $showEditView),
label: {
Text("Child")
})
}
}
Child
import SwiftUI
struct Child: View {
#Binding var location: Location!
#Binding var showEditView: Bool
var body: some View {
Button("Make location") {
location = Location(name: "Lebanon")
showEditView = true
}
}
}
EditView
import SwiftUI
struct EditView: View {
#ObservedObject var placemark: Location
var dismiss: () -> Void
var body: some View {
NavigationView {
Text(placemark.name)
.navigationTitle("Edit place")
.navigationBarItems(trailing: Button("Done") { dismiss() })
}
}
}
Location
import Foundation
class Location: ObservableObject {
init(name: String) {
self.name = name
}
#Published var name: String
}

You probably shouldn't declare your Location as ! and then do nil checking on it. Using ! is sort of asking for a crash to happen. I think what you're encountering is a the sheet getting rendered before location is set. There aren't any guarantees about when in the run loop a #State variable gets set, so it's better to account for scenarios where it is nil (and definitely not using ! to force unwrap it).
Secondly, at least given the scenario you have here, you probably shouldn't be using a class for Location -- it should just be a struct.
Eventually, you are going to run into a little bit of complexity, because judging by your View's name, you want to edit the Location at some point. This becomes a little more tricky with an Optional, since things like TextField want non-optional values, but this can be solved in various was (see where I used nonNilBinding).
Something like this is definitely a more safe approach than what you're currently doing. It may not be exactly what you want, but hopefully it can get you on the right path.
struct Location {
var name : String
}
struct Grandparent: View {
#State var location: Location?
#State var showEditView: Bool = false
var body: some View {
NavigationView {
VStack{
NavigationLink(
destination: Parent(location: $location, showEditView: $showEditView)) {
Text("Navigate")
}
if let location = location {
Text(location.name)
} else {
Text("No location yet")
}
}
.padding()
.sheet(isPresented: $showEditView) {
EditView(placemark: $location, dismiss: { showEditView = false })
}
}
}
}
struct Parent: View {
#Binding var location: Location?
#Binding var showEditView: Bool
var body: some View {
NavigationLink(
destination: Child(location: $location, showEditView: $showEditView),
label: {
Text("Child")
})
}
}
struct Child: View {
#Binding var location: Location?
#Binding var showEditView: Bool
var body: some View {
Button("Make location") {
location = Location(name: "Lebanon")
showEditView = true
}
}
}
struct EditView: View {
#Binding var placemark: Location?
var dismiss: () -> Void
var nonNilBinding : Binding<Location> {
.init { () -> Location in
placemark ?? Location(name:"Default")
} set: { (newValue) in
placemark = newValue
}
}
var body: some View {
NavigationView {
TextField("Name", text: nonNilBinding.name)
.navigationTitle("Edit place")
.navigationBarItems(trailing: Button("Done") { dismiss() })
}
}
}

Related

Why does my SwiftUI app jump back to the previous view?

I created an iOS app with SwiftUI and Swift. The purpose is as follows:
User enters an address into an input field (AddressInputView)
On submit, the app switches to ResultView and passes the address
Problem: The ResultView is visible for just a split second and then suddenly jumps back to AddressInputView.
Question: Why is the app jumping back to the previous view (Instead of staying in ResultView) and how can the code be adjusted to fix the issue?
My Code:
StartView.swift
struct StartView: View {
var body: some View {
NavigationStack {
AddressInputView()
}
}
}
AddressInputView.swift
enum Destination {
case result
}
struct AddressInputView: View {
#State private var address: String = ""
#State private var experiences: [Experience] = []
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
TextField("", text: $address, prompt: Text("Search address"))
.onSubmit {
path.append(Destination.result)
}
Button("Submit") {
path.append(Destination.result)
}
.navigationDestination(for: Destination.self, destination: { destination in
switch destination {
case .result:
ResultView(address: $address, experiences: $experiences)
}
})
}
}
}
}
ExperienceModels.swift
struct ExperienceServiceResponse: Codable {
let address: String
let experiences: [Experience]
}
ResultView.swift
struct ResultView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#Binding private var address: String
#Binding private var experiences: [Experience]
init(address: Binding<String>, experiences: Binding<[Experience]>) {
_address = Binding(projectedValue: address)
_experiences = Binding(projectedValue: experiences)
}
var body: some View {
NavigationStack {
ScrollView {
VStack(alignment: .leading) {
Text("Results")
ForEach(experiences, id: \.name) { experience in
ResultTile(experience: experience)
}
}
}
}
}
}
You have too many NavigationStacks. This is a container view that should be containing your view hierarchy, not something that is declared at every level. So, amend your start view:
struct StartView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
AddressInputView(path: $path)
}
}
}
AddressInputView should take the path as a binding:
#Binding var path: NavigationPath
And you should remove NavigationStack from the body of that view. Likewise with ResultView. I made these changes in a project using the code you posted and the issue was fixed.
I'm not sure exactly why your view is popping back but you're essentially pushing a navigation view onto a navigation view onto a navigation view, so it's not entirely surprising the system gets confused.

How to send extra data using NavigationStack with SwiftUI?

I have three views A,B and C. User can navigate from A to B and from A to C. User can navigate from B to C. Now I want to differentiate if the user have come from A to C or from B to C so I was looking in how to pass extra data in NavigationStack which can help me differentiate
Below is my code
import SwiftUI
#main
struct SampleApp: App {
#State private var path: NavigationPath = .init()
var body: some Scene {
WindowGroup {
NavigationStack(path: $path){
A(path: $path)
.navigationDestination(for: ViewOptions.self) { option in
option.view($path)
}
}
}
}
enum ViewOptions {
case caseB
case caseC
#ViewBuilder func view(_ path: Binding<NavigationPath>) -> some View{
switch self{
case .caseB:
B(path: path)
case .caseC:
C(path: path)
}
}
}
}
struct A: View {
#Binding var path: NavigationPath
var body: some View {
VStack {
Text("A")
Button {
path.append(SampleApp.ViewOptions.caseB)
} label: {
Text("Go to B")
}
Button {
path.append(SampleApp.ViewOptions.caseC)
} label: {
Text("Go to C")
}
}
}
}
struct B: View {
#Binding var path: NavigationPath
var body: some View {
VStack {
Text("B")
Button {
path.append(SampleApp.ViewOptions.caseC)
} label: {
Text("Go to C")
}
}
}
}
struct C: View {
#Binding var path: NavigationPath
var body: some View {
VStack {
Text("C")
}
}
}
Instead of "pass extra data in NavigationStack" you can pass data in a NavigationRouter. It gives you much more control
#available(iOS 16.0, *)
//Simplify the repetitive code
typealias NavSource = SampleApp.ViewOptions
#available(iOS 16.0, *)
struct NavigationRouter{
var path: [NavSource] = .init()
///Adds the provided View to the stack
mutating func goTo(view: NavSource){
path.append(view)
}
///Searches the stack for the `View`, if the view is `nil`, the stack returns to root, if the `View` is not found the `View` is presented from the root
mutating func bactrack(view: NavSource?){
guard let view = view else{
path.removeAll()
return
}
//Look for the desired view
while !path.isEmpty && path.last != view{
path.removeLast()
}
//If the view wasn't found add it to the stack
if path.isEmpty{
goTo(view: view)
}
}
///Identifies the previous view in the stack, returns nil if the previous view is the root
func identifyPreviousView() -> NavSource?{
//1 == current view, 2 == previous view
let idx = path.count - 2
//Make sure idx is valid index
guard idx >= 0 else{
return nil
}
//return the view
return path[idx]
}
}
Once you have access to the router in the Views you can adjust accordingly.
#available(iOS 16.0, *)
struct SampleApp: View {
#State private var router: NavigationRouter = .init()
var body: some View {
NavigationStack(path: $router.path){
A(router: $router)
//Have the root handle the type
.navigationDestination(for: NavSource.self) { option in
option.view($router)
}
}
}
//Create an `enum` so you can define your options
//Conform to all the required protocols
enum ViewOptions: Codable, Equatable, Hashable{
case caseB
case caseC
//If you need other arguments add like this
case unknown(String)
//Assign each case with a `View`
#ViewBuilder func view(_ path: Binding<NavigationRouter>) -> some View{
switch self{
case .caseB:
B(router: path)
case .caseC:
C(router: path)
case .unknown(let string):
Text("View for \(string.description) has not been defined")
}
}
}
}
#available(iOS 16.0, *)
struct A: View {
#Binding var router: NavigationRouter
var body: some View {
VStack{
Button {
router.goTo(view: .caseB)
} label: {
Text("To B")
}
Button {
router.goTo(view: .caseC)
} label: {
Text("To C")
}
}.navigationTitle("A")
}
}
#available(iOS 16.0, *)
struct B: View {
#Binding var router: NavigationRouter
var body: some View {
VStack{
Button {
router.goTo(view: .caseC)
} label: {
Text("Hello")
}
}.navigationTitle("B")
}
}
#available(iOS 16.0, *)
struct C: View {
#Binding var router: NavigationRouter
//Identify changes based on previous View
var fromA: Bool{
//nil is the root
router.identifyPreviousView() == nil
}
var body: some View {
VStack{
Text("Welcome\(fromA ? " Back" : "" )")
Button {
//Append to the path the enum value
router.bactrack(view: router.identifyPreviousView())
} label: {
Text("Back")
}
Button {
//Append to the path the enum value
router.goTo(view: .unknown("\"some other place\""))
} label: {
Text("Next")
}
}.navigationTitle("C")
.navigationBarBackButtonHidden(true)
}
}
You can read the second-to-last item in the path property to learn what the previous screen was.
To do this, it's easier to use an actual array of ViewOptions as the path, instead of a NavigationPath.
For example:
struct SampleApp: App {
// Use your own ViewOptions enum, instead of NavigationPath
#State private var path: [ViewOptions] = []
var body: some Scene {
WindowGroup {
NavigationStack(path: $path){
A(path: $path)
.navigationDestination(for: ViewOptions.self) { option in
option.view($path)
}
}
}
}
}
struct C: View {
#Binding var path: [ViewOptions]
var previousView: ViewOptions? {
path
.suffix(2) // Get the last 2 elements of the path
.first // Get the first of those last 2 elements
}
var body: some View {
VStack {
Text("C")
}
}
}
Remember, a NavigationPath is nothing more than a type-erased array. It can be used to build a NavigationStack quickly without having to worry that all destination values have to match the same type. Since as you're controlling the navigation flow with your own type ViewOptions, it makes no sense to use NavigationPath.

How to notify view that the variable state has been updated from a extracted subview in SwiftUI

I have a view that contain users UsersContentView in this view there is a button which is extracted as a subview: RequestSearchButton(), and under the button there is a Text view which display the result if the user did request to search or no, and it is also extracted as a subview ResultSearchQuery().
struct UsersContentView: View {
var body: some View {
ZStack {
VStack {
RequestSearchButton()
ResultSearchQuery(didUserRequestSearchOrNo: .constant("YES"))
}
}
}
}
struct RequestSearchButton: View {
var body: some View {
Button(action: {
}) {
Text("User requested search")
}
}
}
struct ResultSearchQuery: View {
#Binding var didUserRequestSearchOrNo: String
var body: some View {
Text("Did user request search: \(didUserRequestSearchOrNo)")
}
}
How can I update the #Binding var didUserRequestSearchOrNo: String inside the ResultSearchQuery() When the button RequestSearchButton() is clicked. Its so confusing!
You need to track the State of a variable (which is indicating if a search is active or not) in your parent view, or your ViewModel if you want to extract the Variables. Then you can refer to this variable in enclosed child views like the Search Button or Search Query Results.
In this case a would prefer a Boolean value for the tracking because it's easy to handle and clear in meaning.
struct UsersContentView: View {
#State var requestedSearch = false
var body: some View {
ZStack {
VStack {
RequestSearchButton(requestedSearch: $requestedSearch)
ResultSearchQuery(requestedSearch: $requestedSearch)
}
}
}
}
struct RequestSearchButton: View {
#Binding var requestedSearch: Bool
var body: some View {
Button(action: {
requestedSearch.toggle()
}) {
Text("User requested search")
}
}
}
struct ResultSearchQuery: View {
#Binding var requestedSearch: Bool
var body: some View {
Text("Did user request search: \(requestedSearch.description)")
}
}
Actually I couldn't understand why you used two struct which are connected to eachother, you can do it in one struct and Control with a state var
struct ContentView: View {
var body: some View {
VStack {
RequestSearchButton()
}
}
}
struct RequestSearchButton: View {
#State private var clicked : Bool = false
var body: some View {
Button(action: {
clicked = true
}) {
Text("User requested search")
}
Text("Did user request search: \(clicked == true ? "YES" : "NO")")
}
}
if this is not what you are looking for, could you make a detailed explain.

Value of Selected Option From a SwiftUI Picker does not Update the View

I have the following in a SwiftUI app. Basically I have some settings (Settings class) that I would like to use throughout the app. I have a Settings view that shows a picker to select the value of one of the settings. And other views of the app would only use the current set value of the settings. The following setup works in the sense that in ContentView I see the correct value of firstLevel setting. But the problem is that in SettingsView, I think since selectedFirstLevel is not a #State, its correct value is not shown on the picker I navigate to select either even or odd (oddly, the first time it's correct). This selection is carried correctly to ContentView, but it's not shown correctly on SettingsView. How can I fix this issue?
Settings.swift
import Foundation
class Settings: ObservableObject {
static let shared: Settings = Settings()
#Published var firstLevel: FirstLevel = .even
}
enum FirstLevel: String, CaseIterable, Identifiable {
case even
case odd
var id: String { self.rawValue }
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var showSettings: Bool = false
#ObservedObject var settings = Settings.shared
var body: some View {
VStack {
SettingsButton(showSettings: $showSettings, settings: settings)
Text(settings.firstLevel.id)
.padding()
}
}
}
struct SettingsButton: View {
#Binding var showSettings: Bool
var settings: Settings
var firstLevel: Binding<FirstLevel> {
return Binding<FirstLevel>(
get: {
return self.settings.firstLevel
}) { newFirstLevel in
self.settings.firstLevel = newFirstLevel
}
}
var body: some View {
Button(action: { self.showSettings = true }) {
Image(systemName: "gear").imageScale(.large)
}
.sheet(isPresented: $showSettings) {
SettingsView(selectedFirstLevel: self.firstLevel)
}
}
}
SettingsView.swift
import SwiftUI
struct SettingsView: View {
#Binding var selectedFirstLevel: FirstLevel
var body: some View {
NavigationView {
Form {
Picker("First Level", selection: $selectedFirstLevel) {
ForEach(FirstLevel.allCases) { level in
Text(level.rawValue).tag(level)
}
}
}
.navigationBarTitle("Settings", displayMode: .inline)
}
}
}
It looks overcomplicated, moreover Binding is unreliable as communication between different view hierarchies (which is sheet in your case).
Here is simplified and worked variant. Tested with Xcode 12 / iOS 14.
struct ContentView: View {
#ObservedObject var settings = FLevelSettings.shared
var body: some View {
VStack {
SettingsButton(settings: settings)
Text(settings.firstLevel.id)
.padding()
}
}
}
struct SettingsButton: View {
#State private var showSettings: Bool = false
var settings: FLevelSettings
var body: some View {
Button(action: { self.showSettings = true }) {
Image(systemName: "gear").imageScale(.large)
}
.sheet(isPresented: $showSettings) {
FLevelSettingsView(settings: self.settings)
}
}
}
struct FLevelSettingsView: View {
#ObservedObject var settings: FLevelSettings
var body: some View {
NavigationView {
Form {
Picker("First Level", selection: $settings.firstLevel) {
ForEach(FirstLevel.allCases) { level in
Text(level.rawValue).tag(level)
}
}
}
.navigationBarTitle("Settings", displayMode: .inline)
}
}
}
Note: it can be even more simplified, if you want, due to presence of FLevelSettings.shared, so you can use it inside FLevelSettingsView directly. Just in case.

SwiftUI NavigationLink loads destination view immediately, without clicking

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)")
}
}
}
I had the same issue where I might have had a list of 50 items, that then loaded 50 views for the detail view that called an API (which resulted in 50 additional images being downloaded).
The answer for me was to use .onAppear to trigger all logic that needs to be executed when the view appears on screen (like setting off your timers).
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
}
}
}
For iOS 14 SwiftUI.
Non-elegant solution for lazy navigation destination loading, using view modifier, based on this 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
)
)
}
}
Call it like this:
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)
}
}
)
}
}
I was recently struggling with this issue (for a navigation row component for forms), and this did the trick for me:
#State private var shouldShowDestination = false
NavigationLink(destination: DestinationView(), isActive: $shouldShowDestination) {
Button("More info") {
self.shouldShowDestination = true
}
}
Simply wrap a Button with the NavigationLink, which activation is to be controlled with the button.
Now, if you're to have multiple button+links within the same view, and not an activation State property for each, you should rely on this initializer
/// 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
Along the lines of this example:
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")
}
}
}
More info (and the slightly modified example above) taken from https://www.hackingwithswift.com/articles/216/complete-guide-to-navigationview-in-swiftui (under "Programmatic navigation").
Alternatively, create a custom view component (with embedded NavigationLink), such as this one
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
}
and use it repeatedly as part of a Form (or List):
Form {
FormNavigationRow(title: "One", destination: Text("1"))
FormNavigationRow(title: "Two", destination: Text("2"))
FormNavigationRow(title: "Three", destination: Text("3"))
}
In the destination view you should listen to the event onAppear and put there all code that needs to be executed only when the new screen appears. Like this:
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
}
}
}

Resources