Navigation between SwiftUI Views - swift

I don't know how to navigate between views with buttons.
The only thing I've found online is detail view, but I don't want a back button in the top left corner. I want two independent views connected via two buttons one on the first and one on the second.
In addition, if I were to delete the button on the second view, I should be stuck there, with the only option to going back to the first view being crashing the app.
In storyboard I would just create a button with the action TouchUpInSide() and point to the preferred view controller.
Also do you think getting into SwiftUI is worth it when you are used to storyboard?

One of the solutions is to have a #Statevariable in the main view. This view will display one of the child views depending on the value of the #Statevariable:
struct ContentView: View {
#State var showView1 = false
var body: some View {
VStack {
if showView1 {
SomeView(showView: $showView1)
.background(Color.red)
} else {
SomeView(showView: $showView1)
.background(Color.green)
}
}
}
}
And you pass this variable to its child views where you can modify it:
struct SomeView: View {
#Binding var showView: Bool
var body: some View {
Button(action: {
self.showView.toggle()
}) {
Text("Switch View")
}
}
}
If you want to have more than two views you can make #State var showView1 to be an enum instead of a Bool.

Related

Run action when view is 'removed'

I am developing an app which uses UIKit. I have integrated a UIKit UIViewController inside SwiftUI and everything works as expected. I am still wondering if there is a way to 'know' when a SwiftUI View is completely gone.
My understanding is that a #StateObject knows this information. I now have some code in the deinit block of the corresponding class of the StateObject. There is some code running which unsubscribes the user of that screen.
The problem is that it is a fragile solution. In some scenario's the deinit block isn't called.
Is there any recommended way to know if the user pressed the back button in a SwiftUI View (or swiped the view away)? I don't want to get notified with the .onDisppear modifier because that is also called when the user taps somewhere on the screen which adds another view to the navigation stack. I want to run some code once when the screen is completely gone.
Is there any recommended way to know if the user pressed the back button in a SwiftUI View (or swiped the view away)?
This implies you're using a NavigationView and presenting your view with a NavigationLink.
You can be notified when the user goes “back” from your view by using one of the NavigationLink initializers that takes a Binding. Create a custom binding and in its set function, check whether the old value is true (meaning the child view was presented) and the new value is false (meaning the child view is now being popped from the stack). Example:
struct ContentView: View {
#State var childIsPresented = false
#State var childPopCount = 0
var body: some View {
NavigationView {
VStack {
Text("Child has been popped \(childPopCount) times")
NavigationLink(
"Push Child",
isActive: Binding(
get: { childIsPresented },
set: {
if childIsPresented && !$0 {
childPopCount += 1
}
childIsPresented = $0
}
)
) {
ChildView()
}
}
}
}
}
struct ChildView: View {
var body: some View {
VStack {
Text("Sweet child o' mine")
NavigationLink("Push Grandchild") {
GrandchildView()
}
}
}
}
struct GrandchildView: View {
var body: some View {
VStack {
Text("👶")
.font(.system(size: 100))
}
}
}
Note that these initializers, and NavigationView, are deprecated if your deployment target is iOS 16. In that case, you'll want to use a NavigationStack and give it a custom Binding that performs the pop-detection.

How to pass data from a modal view list to parent view in SwiftUI?

I have (probably) an easy question related to SwiftUI state management.
A have a modal view with a simple list of buttons:
struct ExerciseList: View {
var body: some View {
List {
ForEach(1..<30) { _ in
Button("yoga") {
}
}
}
}
}
The parent view is this one:
struct SelectExerciseView: View {
#State private var showingSheet = false
#State private var exercise = "select exercise"
var body: some View {
Button(exercise) {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet){
ExerciseList()
}
}
}
How can I do to pass the selected button text from the list to the parent view ?
I'm thinking that I need a Binding variable inside the modal and use that, but not really sure how in this example.
At its most basic, you need the selected exercise in your parent view (SelectExerciseView) as a state variable. You then pass that in to the child view (the modal) via a binding. Assuming exercise as a string holds the variable you want to change:
.sheet(isPresented: $showingSheet) {
ExerciseList(exercise: $exercise)
}
Your modal then needs to have a #Binding reference.
struct ExerciseList: View {
#Binding var exercise: Exercise
var body: some View {
List {
ForEach(1..<30) { _ in
Button("yoga") {
exercise = "yoga"
}
}
}
}
}
Im not sure what you're asking...
Are you trying to show a "Detail View" from the modal.
Meaning theres the parent view -> Modal View -> Detail View
In your case it would be the SelectExerciseView -> ExerciseListView -> DetailView which shows the text of the button that was pressed on the previous view (can be any view you want)
If thats what you're trying to do I would use a NavigationLink instead of a button on the modal. The destination of the NavigationLink would be the detail view

Refresh view when navigating to it

In SwiftUI 2, when I navigate from a parent view to a child view and then back to the parent view, the parent view does not refresh its contents. How can I force it to refresh its contents?
In order to test if the contents get refreshed, in my parent view I displayed a random number using the following code:
Text("Random number is \(Int.random(in: 1..<100))")
When I navigate to a child view, and then I tap the Back button in the navigation bar to return to this parent view, the random number displayed remains the same. This indicates that the view is not refreshing.
How can I force the view to refresh itself whenever the user navigates back to it?
You could force SwiftUI to update the list by adding an .id(viewID) view modifier to the source view with an #State variable, in this case called "viewID". Then update this viewID in .onDisappear() of the destination view:
struct ContentView: View {
#State private var viewID: Int = 0
var body: some View {
NavigationView {
VStack {
Text("Hello, random world number \(Int.random(in: 1...100))")
.padding()
.id(viewID)
NavigationLink(
destination: destinationView,
label: { labelView })
}
}
}
private var labelView: some View {
Text("Go to Destination View")
}
private var destinationView: some View {
return Text("I am the Destination.")
.onDisappear{
viewID += 1
}
}
}
SwiftUI is declarative - as in you define the states the view could be in and Swift UI will take care of when it should update. In your example, there is no change in state, and thus, the view doesn't update.
If you want the view to refresh, you need to update state on appear events. You can do so like this:
struct ContentView: View {
#State var intValue: Int = 0
var body: some View {
Text("Random number is \(self.intValue)")
.onAppear {
intValue = Int.random(in: 1..<100)
}
}
}
You'll find that if you push a different view using a NavigationView/NavigationLink, and then navigate back, the label should update with a new random value.

Hide Navigation Bar after NavigationLink is Tapped but keeping back button

I have two different views, one with a NavigationView and another one that i want without NavigationView but since i put it in a NavigationLink I can still see it with a button to go back to the first view.
Here's the code:
struct MainView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Go to SecondView")
.navigationBarTitle("MainView")
}
}
}
struct SecondView: View {
var body: some View {
Text("This is the SecondView")
}
I want to hide the Navigation Bar in the Second View but leaving a button to go back. I don't know if i can keep the button that was already displayed in the Navigation Bar or should i make a new one...in this case how can I add one to go back to the previous view?

Complex Custom Navigation SwiftUI

Here is my current demo UI written in SwiftUI:
I want to make custom navigation and display decisions based on if the cell (View 1, View 2, ...) was tapped or if one of the buttons (arrows) in the cell was tapped.
For example, each cell represents a different view I want to navigate to and each arrow button represents some custom modal I want to present.
How do I keep track of context and propagate the different taps up to the view that will ultimately make the navigation decisions. Here is the view that makes the list and would make the navigation decisions:
import SwiftUI
struct ContentView: View {
#State var options: [String] = ["View 1", "View 2", "View 3", "View 4"]
var body: some View {
ZStack {
Color.white.edgesIgnoringSafeArea(.all)
ScrollView {
ForEach(self.options, id: \.self) { element in
OptionCell(optionDescription: element)
}
}
}
}
}
And here is my OptionCell class. I try and illustrate my problem in some comments in the button action here.
struct OptionCell {
//MARK: Input Properties
#State var optionDescription: String
//MARK: Computed Properties
//MARK: Init
//MARK: Functions
func cellTapped() {
///What can I call here to propagate the context of this cell along with the fact that it was tapped to something that needs to make UI transition decisions?
print("\(self.optionDescription) Tapped")
//What can I do to make micro navigation decisions if one of the "ArrowButtons" is tapped?
}
}
extension OptionCell: View {
var body: some View {
Button(action: { self.cellTapped() }) {
VStack {
HStack{
Text(self.optionDescription).foregroundColor(Color.black).padding()
Spacer()
HStack {
//These Views have a Button view in them with a call to a function in that struct. How do I get the call for the tap on each of these buttons propagated up to the decisions making view?
ArrowButton(direction: .LEFT)
ArrowButton(direction: ArrowDirection.RIGHT)
ArrowButton(direction: ArrowDirection.DOWN)
ArrowButton(direction: ArrowDirection.UP)
}.padding()
}
///Another custom view that I have
SeparatorView(separatorViewStyle: SeparatorViewStyle.init(weight: .THIN, gapPosition: .RIGHT, color: Colors.grey.medium))
}
}
}
}
In UIKit I would add button actions on the custom UITableViewCell and then create a delegate for the custom cell. Tapping the button would call the appropriate delegate function (Delegate.leftArrow(), Delegate.upArrow(), ...) and the UIViewController would become the delegate for the custom cell and make navigation decisions there.
I presume the question is:
How do I get the call for the tap on each of these buttons propagated up to the decisions making view?
In ContentView you setup something like this:
#ObservedObject var myChoice = MyChoiceClass()
where:
class MyChoiceClass: ObservableObject {
#Published var choice: String = ""
}
pass it around to your OptionCell(...) and then to the ArrowButton(...)
So when the ArrowButton(...) change the "choice" it is reflected everywhere.