SwiftUI Tabbed View prevent swipe to next tab [duplicate] - ios

This question already has answers here:
SwiftUI TabView PageTabViewStyle prevent changing tab?
(2 answers)
Closed 7 months ago.
This post was edited and submitted for review 7 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I'm trying to prevent swiping to the second tab until the user has clicked a button on the first tabbed view indicating the data is complete. Then the user can swipe. I thought it was #State in the first tab view since it's the source of truth and a #Binding in the main content view, but that didn't work. I've simplified the code and removed the Binding info so that at least it will build. Any thoughts or better approach appreciated. (Quite new at this...)
//ContentView.swift file
struct ContentView: View {
#State private var selection = 0
#State var allowSwipeTo2 = false
var body: some View {
VStack {
Text("Main Content View")
Text(String(allowSwipeTo2)) //added this line, and can see it change from false to true when button is clicked, but still not swipable
.font(.system(size: 18))
}
TabView(selection: $selection){
tab1View(allowSwipeTo2: $allowSwipeTo2) //modified based on recommendation
.tag(0)
if allowSwipeTo2 == true {
tab2View()
.tag(1)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
//tab1View.swift file with the button and the booleans for disabling further changes and ok to swipe:
struct tab1View: View {
#State private var p8_10 = 0
#State private var stepperDisabled = false
#Binding var allowSwipeTo2: Bool //modified based on recommendation
#State private var showingAlert = false
var body: some View {
VStack{
HStack{
Text("Things")
Stepper("Total: \(p8_10)", value: $p8_10, in: 0...15)
}.font(.system(size: 15))
.disabled(stepperDisabled)
HStack{
Spacer()
Button("Entry Complete") {
showingAlert = true
}.alert(isPresented: $showingAlert){
Alert(
title: Text("Entry Complete?"),
message: Text("You will no longer be able to change your entry"),
primaryButton: .cancel(),
secondaryButton: .destructive(
Text("OK"),
action:{
stepperDisabled = true
allowSwipeTo2 = true
})
)
}
Spacer()
}
}
}
}
//tab2View.swift file
struct tab2View: View {
var body: some View {
Text("Tab 2 Content!")
.font(.system(size: 15))
}
}```

Small wrong approach. In your ContentView, you are supposed to bind the allowSwipeTo2 variable with the tab1View.
To fix your problem, change the type of variable allowSwipeTo2 in tab1View to #Binding var allowSwipeTo2 : Bool, and finally in your ContentView, call the first tab view like this tab1View(allowSwipeTo2: $allowSwipeTo2)
I have modified the code as below. You can find the modified parts with this comment //modified.
struct ContentView: View {
#State private var selection = 0
#State var allowSwipeTo2 = false
var body: some View {
VStack {
Text("Main Content View")
.font(.system(size: 18))
}
TabView(selection: $selection){
tab1View(allowSwipeTo2: $allowSwipeTo2) //modified
.tag(0)
if allowSwipeTo2 == true {
tab2View()
.tag(1)
}
}.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
struct tab1View: View {
#State private var p8_10 = 0
#State private var stepperDisabled = false
#Binding var allowSwipeTo2 : Bool //modified
#State private var showingAlert = false
var body: some View {
VStack{
HStack{
Text("Things")
Stepper("Total: \(p8_10)", value: $p8_10, in: 0...15)
}.font(.system(size: 15))
.disabled(stepperDisabled)
HStack{
Spacer()
Button("Entry Complete") {
showingAlert = true
}.alert(isPresented: $showingAlert){
Alert(
title: Text("Entry Complete?"),
message: Text("You will no longer be able to change your entry"),
primaryButton: .cancel(),
secondaryButton: .destructive(
Text("OK"),
action:{
stepperDisabled = true
allowSwipeTo2 = true
})
)
}
Spacer()
}
}
}
}
struct tab2View: View {
var body: some View {
Text("Tab 2 Content!")
.font(.system(size: 15))
}
}

Related

SwiftUI. Present a detail view pushed from the root view as the initial application view

I have two SwiftUI views, the first view has a navigation link to the second view and I want to show the second view that is "pushed" out of the first view, as the initial application view.
This is the behavior of the iOS Notes app, where users see a list of notes as the initial view controller and can return to the folder list with the back navigation button.
Can I implement this with SwiftUI and how?
Here is a simple demo. Prepared & tested with Xcode 11.7 / iOS 13.7
struct ContentView: View {
#State private var isActive = false
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second View"), isActive: $isActive) {
Text("First View")
}
}
.onAppear { self.isActive = true }
}
}
you can add another state variable to hide the first view until the second view appears on the screen.
struct ContentView1: View {
#State private var isActive = false
#State private var showView = false
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second View")
.onAppear {
self.showView = true
},
isActive: $isActive) {
if self.showView {
Text("First View")
} else {
EmptyView()
}
}
}
.onAppear {
self.isActive = true
}
}
}
As mentioned in my comments to another answer, by setting an initial state for a variable that controls the presentation of the second view to true, your ContentView presents this second view as the initial view.
I've tested this using the simulator and on device. This appears to solve your problem and does not present the transition from the first view to the second view to the user - app opens to the second view.
struct ContentView: View {
#State private var isActive = true
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second View"), isActive: $isActive) {
Text("First View")
}
}
}
}
I made my own implementation based on #Asperi and #Mohammad Rahchamani answers.
This implementation allows you to navigate even from a list with multiple navigation links. Tested on Xcode 12 with SwiftUI 2.0.
struct IOSFolderListView: View {
#State var isActive = false
#State var wasViewShown = false
var body: some View {
let list = List {
NavigationLink(destination: Text("SecondView").onAppear {
self.wasViewShown = true
}, isActive: $isActive) {
Text("SecondView")
}
NavigationLink(destination: Text("ThirdView")) {
Text("ThirdView")
}
.onAppear {
self.isActive = false
}
}
if wasViewShown {
list.listStyle(GroupedListStyle())
.navigationBarTitle("FirstView")
.navigationBarItems(leading: Image(systemName: "folder.badge.plus"), trailing: Image(systemName: "square.and.pencil"))
} else {
list.opacity(0)
.onAppear {
self.isActive = true
}
}
}
}

SwiftUI NavigationItem doesn't respond

When I click on the left arrow it should dismiss the view, but only the UI responds as the button being clicked and the view pushed by a NavigationLink is not dismissed...
The same view pushed by another NavigationLink in another view works perfectly, but when pushed by this NavigationLink, I click on the left arrow, only 1 in 20 times it dismisses the view. Is it bug in SwiftUI again? Is there a workaround?
import SwiftUI
import Firebase
import struct Kingfisher.KFImage
struct SearchView: View {
#Environment(\.presentationMode) var presentation
#State var typedSearchValue = ""
#State var createNewPost = false
#State var showMessaging = false
#EnvironmentObject var userInfo : UserData
#Binding var switchTab: Int
#State var text : String = ""
#State var foundUsers: [FetchedUser] = []
#State var showAccount = false
#State var fetchedUser = FetchedUser()
var body: some View {
NavigationView {
ZStack{
// navigate to that user's profile << the problem navlink
NavigationLink(destination:
UserAccountView(fetchedUser: self.$fetchedUser, showAccount: self.$showAccount)
.environmentObject(self.userInfo)
.environmentObject(FetchFFFObservable())
,isActive: self.$showAccount
){
EmptyView()
}.isDetailLink(false)
//...
NavigationLink(destination:
MessagingMainView(showMessaging: self.$showMessaging)
.environmentObject(UserData())
.environmentObject(MainObservable()),
isActive: self.$showMessaging
){
Text("")
}
VStack(spacing: 0) {
SearchBarMsg(text: $text, foundUsers: $foundUsers)
.environmentObject(userInfo)
.padding(.horizontal)
VStack{
if text != ""{
List(foundUsers, id: \.username){ user in
FetchedUserCellView(user: user)
.environmentObject(self.userInfo)
.onTapGesture {
self.fetchedUser = user
self.showAccount = true
}
}
}else{
//....
}
}
}
.navigationBarColor(.titleBarColor)
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
Navigates to this view, and the button in navigationItems doesn't work, although the UI responds:
struct UserAccountView: View {
#Environment(\.presentationMode) var presentation
//...
#Binding var showAccount : Bool
var body: some View {
VStack {
//.....
}
.navigationBarColor(.titleBarColor)
.navigationBarTitle(Text(fetchedUser.username), displayMode: .inline)
.navigationBarItems(leading: Button(action: {
//this button doesn't work!
self.showAccount = false
}, label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 15)
})).accentColor(.white)
)
}
}

Long List with Textfields in SwiftUI

I try to do a List with SwiftUI where you can insert two Strings in Textfields and one Boolean Button in each Row. However when the list exceeds the screen and you scroll to the last rows they sometimes erase the content.
I created a minimal example:
struct ContentView: View {
#State var bindOne = "one"
#State var bindTwo = "two"
#State var bindThree = "three"
#State var bindFour = "four"
#State var bindFive = "five"
#State var bindSix = "six"
#State var buttonValue = false
var body: some View {
VStack{
Text("test")
.font(.largeTitle)
List{
Row(someBind: bindOne, buttonValue: false)
Row(someBind: bindTwo, buttonValue: false)
Row(someBind: bindThree, buttonValue: false)
Row(someBind: bindFour, buttonValue: false)
Row(someBind: bindFive, buttonValue: false)
Row(someBind: bindSix, buttonValue: false)
}
}
}
}
with the supporting View:
struct Row: View {
#State var someBind: String
#State var buttonValue: Bool
var body: some View {
HStack{
TextField(someBind, text: $someBind)
.font(.largeTitle)
TextField(someBind, text: $someBind)
.font(.largeTitle)
Button(action: {self.buttonValue.toggle()}){
if buttonValue{
Text("Yes")
.font(.largeTitle)
}else{
Text("No")
.font(.largeTitle)
}
}
}
.padding(.vertical, 70)
}
}
The result is
When the lines fit on the screen there is no problem, but sometimes you just have a long list.
Is this a bug or am I missing something?
The problem is not present anymore. Checked with Xcode Version 11.4.1 (11E503a).

NavigationLink with SwiftUI: Type of expression is ambiguous without more context

I'm trying to use a button I made to navigate to another view. I done it for the other view, but when applying it to the next one it shows the error "Type of expression is ambiguous without more context." I've been reading from other people that errors in SwiftUI don't appear on the right line sometimes and can be happening somewhere else.
Here's the code I wrote that worked with NavigationLink
struct ContentView: View {
#State private var isActive: Bool = false
#State private var username: String = ""
#State private var email: String = ""
#State private var password: String = ""
var body: some View {
NavigationView{
VStack {
NavigationLink(destination: signUp(), isActive: self.$isActive) {
Text("")
}
Image("versLogo")
.resizable()
.frame(width: 400, height: 400)
TextField("Username", text: $username)
TextField("Email", text: $email)
TextField("Password", text: $password)
Button(action: {}) {
Text("Login")
}
Spacer()
//button for moving to next view
Button(action: {
self.isActive = true
}) {
Text("Don't have an account?")
}
}//VStack
}//nav
}
}
Now here's the code I wrote applying it the same way, but is giving me that error. I don't have anything for the text in the NavigationLink because I'm using the button so I left it empty.
struct signUp: View {
#State var isShowingImagePicker = false
//for the next view
#State private var isActive: Bool = false
#State private var username: String = ""
#State private var email: String = ""
#State private var password: String = ""
#State private var confirm: String = ""
var body: some View {
NavigationLink{
VStack {
NavigationLink(destination: DOB_finalSignUp(), isActive: self.$isActive) {
Text("") //ERROR is on this line
}
Image(uiImage: UIImage())
.frame(width: 200, height: 200)
.border(Color.black, width: 1)
Button(action: {
self.isShowingImagePicker.toggle()
}, label: {
Text("Select Image")
})
TextField("Username", text: $username)
TextField("Email", text: $email)
TextField("Password", text: $password)
TextField("Confirm Password", text: $confirm)
//button for moving to next view
Button(action: {
self.isActive = true
}) {
Text("Continue")
}
}//VStack
}//nav
}
}
You used NavigationLink instead of NavigationView in the very first line of var body inside struct signUp.
So this:
var body: some View {
NavigationLink{
,,,
}//nav
}
should be:
var body: some View {
NavigationView{
,,,
}//nav
}
NavigationLink is like a trigger to navigate, while NavigationView is responsible to handle visuals, animations and other stuff about navigating.
And note that since SwiftUI is very young, Xcode is not able to detect what is the issue exactly and sometimes shows wrong or unclear messages. But apple is constantly working on it and improving it release by release.

NavigationLink, isActive doesn´t work in SwiftUI

I´m trying to programmatically change the current view to other, but isActive attribute from NavigationLink is not working, I guess that I´m forgeting something.
struct MainView: View {
#State public var pushActive = true
var body: some View {
NavigationView{
Text("hello")
NavigationLink(destination: ContentView(), isActive: $pushActive) {
Text("")
}.hidden()
}.onAppear{
self.pushActive = true
}
}
}
This view always show "hello" instead of redirect to ContentView
I solved the problem, The error was put the Text("hello").
The answer is simple:
NavigationView{
if(pushActive){
NavigationLink(destination: ContentView(), isActive: $pushActive) {
Text("")
}.hidden()
}else{
Text("hello")
}
NavigationView accepts only the first child, that's why it wasn't working. Try this:
struct MainView: View {
#State public var pushActive = true
var body: some View {
NavigationView {
VStack {
Text("hello")
NavigationLink(destination: ContentView(), isActive: $pushActive) {
Text("")
}.hidden()
}
}.onAppear {
self.pushActive = true
}
}
}

Resources