How to track whenever a component view is updated? - swiftui

I want to track when a given view is reloaded. How do I do that?
I was thinking of printing something, but I cannot put print() in the body of a SwiftUI view

Here it is
struct Demo: View {
var body: some View {
print("<< updated")
return Group {
// ... your views here
}
}
}
if you have at top some container view you can return it directly, for example
var body: some View {
print("<< updated")
return VStack {
Text("Some 1")
Text("Some 2")
Text("Some 3")
}
}
the common rule - it must be returned single view

Related

NavigationDestination in subViews

I have a main view with a NavigationStack, NavigationLink and navigationDestination.
From that view I need to go to a second view, where the user will input the name, and then navigate to a third view:
struct MainView: View {
var body: some View {
NavigationStack {
NavigationLink(value: "second") {
Text("Second View")
}
.navigationDestination(for: String.self, destination: { _ in
SecondaryView()
})
}
}
}
struct SecondaryView: View {
#State var name = ""
var body: some View {
VStack {
TextField("", text: $name)
NavigationLink(value: "third") {
Text("Third View")
}
.navigationDestination(for: String.self), destination: { _ in
ThirdView(name: name)
}
}
}
}
I know I can create an enum an switch over it on the MainView, but the problem is: I can't say something like that:
ThirdView()
on the MainView, because the user hasnt submited the name yet.
I need to navigate from the second view. I tried changing the secondView value to Int, and then it worked. Something like this:
struct SecondaryView: View {
#State var name = ""
var body: some View {
VStack {
TextField("", text: $name)
NavigationLink(value: 3) {
Text("Third View")
}
.navigationDestination(for: Int.self), destination: { _ in
ThirdView(name: name)
}
}
}
}
Is there any other way to do that without needing to use different data types? Because that way I'll need to create one data type for each screen, and that makes me feel like I'm doing something wrong.
Probably it is easier to use destination/label NavigationLink instead of value based ones.
Especially since you aren't using the path anyway.
NavigationLink {
%destination%
} label: {
%label%
}
It would be possible to bind a path to the NavigationStack, and then inspect the length of the path, at which position your element is.
But that's messy, since having a repeating value in the path makes it difficult to make the right choices.

Navigate inside master instead of detail in SplitView in SwiftUI

I have a split view in my iPad ready app:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink("Show the slave view HERE", destination: SlaveView())
.navigationBarTitle("Master view")
}
Text("Detail view")
.navigationBarTitle("DO NOT show the slave view here")
}
}
}
So I like the SlaveView view to open in the list itself, not in the detail view.
I have tried setting another NavigationView in the Slave, also a text below that, also setting all navigationViewStyles on both and each Master and Slave with no luck.
This is the simplest Slave view you can have to make it build:
struct SlaveView: View {
var body: some View {
List {
NavigationLink("Sub Detail view", destination: Text("Sub Detail view"))
}
.navigationBarTitle("Slave view")
}
}
So how can I change the Master (left) view of the split view instead of the detail (right) view?
Note that this is a simplified reproducible code. The real project uses more complex lists for master and slaves and so on. Also, we don't want to loos navigation stuff like transitions, title transforming, back button and etc.
For more clarifying, I need this state in the flow:
Just modify link that it is not a detail
NavigationLink("Show the slave view HERE", destination: SlaveView())
.isDetailLink(false)
You may try using a Button instead of a NavigationLink and replace your master view:
struct ContentView: View {
#State var showSlaveView = false
var body: some View {
NavigationView {
masterView
.navigationBarTitle("Master view")
Text("Detail view")
.navigationBarTitle("DO NOT show the slave view here")
}
}
}
extension ContentView {
#ViewBuilder
var masterView: some View {
if showSlaveView {
SlaveView()
.onTapGesture { self.showSlaveView = false }
} else {
Button("Show the slave view HERE") {
self.showSlaveView = true
}
}
}
}
You can try using StackNavigationViewStyle()
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Show the slave view HERE", destination: SlaveView())
.navigationBarTitle("Master view")
Text("Detail view")
.navigationBarTitle("DO NOT show the slave view here")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}

SwiftUI View Controls

I'm trying to use .alert and .onAppear for one of my views, and I want .onAppear to be called first, because .alert is dependent on a variable in .onAppear. Any way to specifically order ui controls?
SwiftUI provides a declarative user interface rather than the more imperative paradigm used in UIKit. So instead of using a sequence of commands to modify state and views separately, a SwiftUI view is defined as a function of its state.
So in this case the order of .onAppear and .alert does not change the presentation of the alert.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.onAppear() {
self.isAlertPresented = true
}
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
}
}
In the example above, when the Text view appears, .isAlertPresented changes to true. Separately, an Alert is added that will display whenever .isAlertPresented is true.
The example below has the same result, even changing the order of the two ViewModifiers.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
.onAppear() {
self.isAlertPresented = true
}
}
}

How to avoid spacer in NavigationView with Forms?

I have an NavigationView with an NavigationLink, when I get to the second View I have a strange spacer between the head and the beginning of my form:
This is my code:
struct SampleView: View {
var body: some View {
NavigationView {
VStack {
Form {
Section(header: Text("Hallo")){
Text("abc")
}
Section(header: Text("abc")){
Text("lorem")
Text("ispsum")
Button(action: {
//UIApplication.shared.open(eintrag.eintrag.url as URL)
}) {
Text("starten")
}
}
}
}.navigationBarTitle("Hello")
}
}
}
Is this a bug? How to avoid this? Its only when navigation in. If I set the view as my start view it looks like it should:
What do I need to do to get in on the top?

Is it possible for a NavigationLink to perform an action in addition to navigating to another view?

I'm trying to create a button that not only navigates to another view, but also run a function at the same time. I tried embedding both a NavigationLink and a Button into a Stack, but I'm only able to click on the Button.
ZStack {
NavigationLink(destination: TradeView(trade: trade)) {
TradeButton()
}
Button(action: {
print("Hello world!") //this is the only thing that runs
}) {
TradeButton()
}
}
You can use .simultaneousGesture to do that. The NavigationLink will navigate and at the same time perform an action exactly like you want:
NavigationLink(destination: TradeView(trade: trade)) {
Text("Trade View Link")
}.simultaneousGesture(TapGesture().onEnded{
print("Hello world!")
})
You can use NavigationLink(destination:isActive:label:). Use the setter on the binding to know when the link is tapped. I've noticed that the NavigationLink could be tapped outside of the content area, and this approach captures those taps as well.
struct Sidebar: View {
#State var isTapped = false
var body: some View {
NavigationLink(destination: ViewToPresent(),
isActive: Binding<Bool>(get: { isTapped },
set: { isTapped = $0; print("Tapped") }),
label: { Text("Link") })
}
}
struct ViewToPresent: View {
var body: some View {
print("View Presented")
return Text("View Presented")
}
}
The only thing I notice is that setter fires three times, one of which is after it's presented. Here's the output:
Tapped
Tapped
View Presented
Tapped
NavigationLink + isActive + onChange(of:)
// part 1
#State private var isPushed = false
// part 2
NavigationLink(destination: EmptyView(), isActive: $isPushed, label: {
Text("")
})
// part 3
.onChange(of: isPushed) { (newValue) in
if newValue {
// do what you want
}
}
This works for me atm:
#State private var isActive = false
NavigationLink(destination: MyView(), isActive: $isActive) {
Button {
// run your code
// then set
isActive = true
} label: {
Text("My Link")
}
}
Use NavigationLink(_:destination:tag:selection:) initializer and pass your model's property as a selection parameter. Because it is a two-way binding, you can define didset observer for this property, and call your function there.
struct ContentView: View {
#EnvironmentObject var navigationModel: NavigationModel
var body: some View {
NavigationView {
List(0 ..< 10, id: \.self) { row in
NavigationLink(destination: DetailView(id: row),
tag: row,
selection: self.$navigationModel.linkSelection) {
Text("Link \(row)")
}
}
}
}
}
struct DetailView: View {
var id: Int;
var body: some View {
Text("DetailView\(id)")
}
}
class NavigationModel: ObservableObject {
#Published var linkSelection: Int? = nil {
didSet {
if let linkSelection = linkSelection {
// action
print("selected: \(String(describing: linkSelection))")
}
}
}
}
It this example you need to pass in your model to ContentView as an environment object:
ContentView().environmentObject(NavigationModel())
in the SceneDelegate and SwiftUI Previews.
The model conforms to ObservableObject protocol and the property must have a #Published attribute.
(it works within a List)
I also just used:
NavigationLink(destination: View()....) {
Text("Demo")
}.task { do your stuff here }
iOS 15.3 deployment target.