How to bind an action to the navigationview back button? - button

Hi I would like to bind an action to the Back button in the navigationview toolbar, is it possible?
picture about the situation
var body: some View {
List {
ForEach(mainViewModel.items) { item in
NavigationLink(destination: EditTaskView(item: item)) {
HStack {
ListRowView(item: item)

You can't bind directly to the back button, but you can have the navigation link itself be activated based on state, and then listen to the change of the state value like so. Do note that this requires that you manage the setting of state to true (no auto tap like with the default initializer)
struct ContentView: View {
#State private var showingNavView = false
var body: some View {
NavigationView {
List {
NavigationLink("Sub View", isActive: $showingNavView) {
SubView()
}.onTapGesture {
showingNavView = true
}.onChange(of: showingNavView) { newValue in
print(newValue) // Will change to false when back is pressed
}
}
}
}
}
struct SubView: View {
var body: some View {
ZStack {
Color.green
Text("Cool Beans")
}
}
}

Related

SwiftUI using Environment Object on multiple views giving issues with Navigation

Don't know if I'm abusing the idea of environment object, but experiencing an issue when using an environment object that publishes a delayed async value. One view navigates to the next, but then the 'root' gets updated subsequently and as a result causes an 'echo', or even if that is handled a navigation problem. The issue becomes even more evident when using transitions between navigation.
Is there a correct use pattern to avoid this? Or some other solution maybe?
Any guidance will be appreciated.
Attached a condensed sample to illustrate the problem.
Xcode 12.4 ios 14.1
final class SetColor: ObservableObject {
#Published var asyncVal: Bool = false
func flipIt() {
DispatchQueue.main.asyncAfter(deadline: .now()+0.5, execute: {self.asyncVal.toggle()})
}
}
struct HomeView: View {
#StateObject var setCol: SetColor = SetColor()
#State private var navActive: Bool = false
var body: some View {
NavigationView {
ZStack {
Color(setCol.asyncVal ? .blue : .purple)
Button(action: {
setCol.flipIt()
navActive.toggle()
}, label: {
Text("Change and Move")
})
.navigationTitle("Home")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: NavChild1().environmentObject(setCol),isActive: $navActive, label: { Text("GoTo 1 >") })
}
}
}
}
}
}
struct NavChild1: View {
#EnvironmentObject var setCol: SetColor
#State private var navActive: Bool = false
var body: some View {
ZStack {
Color(setCol.asyncVal ? .yellow : .orange)
Button(action: {
setCol.flipIt()
navActive.toggle()
}, label: {
Text("Change and Move")
})
.navigationTitle("Nav 1")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: NavChild2().environmentObject(setCol),isActive: $navActive, label: { Text("GoTo 2 >") })
}
}
}
}
}
struct NavChild2: View {
#EnvironmentObject var setCol: SetColor
#State private var navActive: Bool = false
var body: some View {
ZStack {
Color(setCol.asyncVal ? .yellow : .orange)
Button(action: {
setCol.flipIt()
navActive.toggle()
}, label: {
Text("Change and Move")
})
.navigationTitle("Nav 2")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: NavChild3().environmentObject(setCol),isActive: $navActive, label: { Text("GoTo 3 >") })
}
}
}
}
}
struct NavChild3: View {
#EnvironmentObject var setCol: SetColor
#State private var navActive: Bool = false
var body: some View {
ZStack {
Color(setCol.asyncVal ? .yellow : .orange)
Button(action: {
setCol.flipIt()
navActive.toggle()
}, label: {
Text("Change and Move")
})
.navigationTitle("Nav 3")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: NavChild3().environmentObject(setCol), isActive: .constant(false), label: { Text("Go Home") })
}
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
You do not need the deadline you put in GCD action. It causes navigation actions even if user does not press on navigation (I've tested the code in a project).
This is because you accumulate jobs in the GCD queue and when they are executed, you're in another View (due to the 0.5 stall). By the way, they cause navigation since the flip is Observed and therefore whoever listens , will execute the navigation.
Anyway, what you wanna do is change the dispatch command to this:
DispatchQueue.main.async { self.asyncVal.toggle() }
And navigation will be smoother with no extra navigation commands executed afterwards.

SwiftUI: Button in Form

I am creating a Form in SwiftUi with a section that is including a flexible number of instruction.
Next to the last instruction TextField, I am showing a "+"-Button that is extending the instructions array with a new member:
var body: some View {
NavigationView {
Form {
...
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel.recipe.instructions)
}
...
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
if(index == instructions.count-1) {
addInstructionButton
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}
Now the problem is, that the button click-area is not limited to the picture but to the whole last row. Precisely the part just around the textField, meaning if I click in it, I can edit the text, but if I click on the border somewhere, a new entry is added.
I assume that this is specific to Form {} (or also List{}), since it does not happen if I use a Button next to a text field in a "normal" set-up.
Is there something wrong with my code? Is this an expected behaviour?
I am not sure why border is getting tappable, but as a workaround I used plainButtonStyle and that seems to fix this issue, and keeps functionality intact .
struct TestView: View {
#State private var endAmount: CGFloat = 0
#State private var recipeViewModel = ["abc","Deef"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Instructions")) {
InstructionsSectionView(instructions: $recipeViewModel)
}
}
}
}
}
struct InstructionsSectionView: View {
#Binding var instructions: [String]
var body: some View {
ForEach(instructions.indices, id: \.self) { index in
HStack {
TextField("Instruction", text: $instructions[index])
Spacer()
if(index == instructions.count-1) {
addInstructionButton
.buttonStyle(PlainButtonStyle())
.foregroundColor(.blue)
}
}
}
}
var addInstructionButton: some View {
Button(action: {
instructions.append("")
}) {
Image(systemName: "plus.circle.fill")
}
}
}

Calling a navigation view from another view Swift 5

I am trying to call a navigation view from another view using a navigation bar. However, when I try to call it it just comes up blank. I think something goes wrong if I call a view that has a navigation view on it. The view I'm trying to call is TeamList(). I tried calling other views and it works, but only TeamList() doesn't work since it has navigation view on it. Any ideas?
Here is the View I am trying to call
import SwiftUI
struct TeamList: View {
init() {
UITableView.appearance().backgroundColor = UIColor(.pink)
UITableViewCell.appearance().backgroundColor = UIColor(.pink)
UITableView.appearance().tableFooterView = UIView()
}
var body: some View {
NavigationView {
List(teamData) { team in
NavigationLink(destination: TeamDetail(team: team)) {
TeamRow(team: team)
}
}
.navigationBarTitle(Text("Club List"))
}
}
}
struct TeamList_Previews: PreviewProvider {
static var previews: some View {
TeamList()
}
}
And here is the view I am calling it from
import SwiftUI
struct ContentView: View {
var body: some View {
CustomNaviagtionBar()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Home : View {
var body: some View {
VStack {
HStack {
Image("epl")
VStack(alignment: .leading) {
Text("Premier League")
Text("See the latest matches")
}
.padding(.top, 40)
Spacer()
}
Spacer()
}.background(Color.pink).edgesIgnoringSafeArea(.all).foregroundColor(.white)
}
}
var tabs = ["Home","Ranking","Clubs"]
struct BarButton : View {
var image : String
#Binding var Tab : String
var body: some View {
Button(action: {Tab = image}) {
Image(image)
.renderingMode(.template)
.foregroundColor(Tab == image ? Color(.blue) : Color.black.opacity(0.4))
.padding()
}
}
}
struct CustomNaviagtionBar : View {
#State var Tab = "Home"
#State var edge = UIApplication.shared.windows.first?.safeAreaInsets
var body: some View {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)){
TabView(selection: $Tab) {
Home()
.tag("Home")
Ranking()
.tag("Ranking")
Clubs()
.tag("Clubs")
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.ignoresSafeArea(.all, edges: .bottom)
HStack(spacing: 0){
ForEach(tabs, id: \.self){image in
BarButton(image: image, Tab: $Tab)
if image != tabs.last{
Spacer(minLength: 0)
}
}
}
}
}
}
struct Ranking : View {
var body: some View{
VStack{
Text("Ranking")
}
}
}
struct Clubs : View {
var body: some View{
TeamList() // This is the view that I am trying to call but come up blank
}
}

SwiftUI: navigationBarItems missing on first render

I'm seeing a strange issue that I was able to reproduce with a small sample. If you have a detail view that has navigationBarItems set, and that detail is the second view pushed on a navigation stack, the items do not show up when you get to the detail page. Here is the sample:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: MiddleTestView()) {
Text("Push View")
}
}
}
}
}
struct MiddleTestView: View {
var body: some View {
VStack {
NavigationLink(destination: TestView()) {
Text("Push Another View")
}
}
}
}
struct TestView: View {
var body: some View {
VStack {
Text("Testing 1, 2, 3")
}
.navigationBarItems(leading: Button("Test") { print("pressed") })
}
}
If anything causes the TestView to re-render, then the button will show, for instance if the TestView does this:
struct TestView: View {
#State var hasChanges = false
var body: some View {
VStack {
Text("Testing 1, 2, 3")
Button("Toggle") { hasChanges = !hasChanges }
}
.navigationBarItems(leading: Button(hasChanges ? "Test1" : "Test2") { print("pressed") })
}
}
Then pressing the "Toggle" button once will cause the navigationBarItems to appear, and they will stay there until the view is dismissed. Additionally, if the TestView is shown first, instead of the MiddleTestView, then there is no problem with the navigationBarItems. I cannot see any reason for this behavior, it seems like a pretty glaring bug that makes working with navigation stacks in SwiftUI fundamentally broken, unless I'm missing something. Does anyone have any insight into what is going on here or how to get around it?

swiftui open view from menu button

I am trying to do something very basic, but I have been through the official tutorials, and through dozens of stack overflow posts. I am trying to open an activity from a menu button. I do want the standard navigation back arrow when I do this.
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {
Activities()
}) {
Label("Activities", systemImage: "doc")
}
"Activities()" is my view. When I do this nothing happens. I have read in other posts that you cannot have a navigation link in a menu either, which would be fine if that worked. How do I programmatically open up a view so the back arrow works?
Thank you
In this case where you want to open a view but can't use a NavigationLink directly you can use it in another place and activate it programmatically from the button via a State property:
#State private var isShowingDetailView = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
NavigationLink(destination: Activities(), isActive: $isShowingDetailView) {
EmptyView()
}
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Button(action: {
isShowingDetailView = true
}) {
Label("Activities", systemImage: "doc")
}
}
}
}
}
}

Resources