Entering new view after button press (Swift) - swift

struct EnterOptions: View {
var body: some View {
VStack{
Button(action: {
//Action here
} ){
Text("SIGN UP").frame(maxWidth: 300)
.frame(maxHeight: .greatestFiniteMagnitude)
.font(.largeTitle)
.fontWeight(.heavy)
}
I create a button where I want the user to decide to sign up or login. Just the sign up is shown.
struct makeM: View {
#StateObject var viewModel: makeM.ViewModel = .init()
#StateObject var home: makeM.LoggedIn = .init()
var body: some View {
VStack{
TextField("Username", text: $viewModel.username)
TextField("Password", text: $viewModel.password)
TextField("Email", text: $viewModel.email)
Button("Sign Up", action: {
Task{await viewModel.signUp() }} )
Button("Sign In", action: {
Task{ await viewModel.signIn() }
})
//if(viewModel.si == true){
Button("Sign Out", action: {
Task{ await viewModel.signOutLocally() }
})
//}
}
.padding(.horizontal, 20)
}
I want the user to see this screen after clicking the button. I've tried using "makeM()" or putting MakeM() in a WindowGroup() but not sure how I can switch screens for the user using the right syntax. Having trouble in the "//Action Here" part in the first code snippet. Am I going about this the wrong way? Not trying to use storyboard

You should use NavigationLink if you want to show another screen, but make sure that whole view is wrapped by NavigationStack or NavigationView.
For example:
struct EnterOptions: View {
var body: some View {
NavigationStack {
VStack{
NavigationLink {
// Destination
} label: {
Text("SIGN UP").frame(maxWidth: 300)
.frame(maxHeight: .greatestFiniteMagnitude)
.font(.largeTitle)
.fontWeight(.heavy)
}
}

Related

How to hide tab bar swift ui ios

I implemented my own tab bar:
struct MainView: View
{
#State var selectedIndex = 0
let icons = ["menucard", "house"]
let iconsNames = ["meniu", "oferte"]
var body: some View{
VStack(spacing: 0){
ZStack{
switch selectedIndex{
case 0:
MeniuListView()
case 1:
ProfileView()
}
Divider()
HStack{
ForEach(0..<2, id: \.self){number in
Spacer()
Button(action: {
self.selectedIndex=number
}, label: {
VStack(spacing: 3){
Image(systemName: icons[number])
.font(.system(size: 25,
weight: .regular,
design: .default))
}
}
}
}
Now the question is how I can hide it if I want to go to a specific view?
What is the best approach to do so?
For example I want to navigate to a login page, but the tab bar does not hide..
This is my ProfileView() that call the login page but the tab bar does not disappear.. How I can hide it?
ProfileView code:
struct ProfileShopView: View {
#State var goToNextScreen : Int? = nil
var body: some View {
NavigationView{
VStack{
Form{
}
NavigationLink(destination: LoginView().navigationBarHidden(true), tag: 1, selection: $goToNextScreen)
{
EmptyView()
}
Button(action: {
goToNextScreen=1
UserDefaults.standard.set(false, forKey: "isLogin")
} //need to hide the tab bar when navigating to login view
}
}
Approach
Use a full screen cover for login view
After sign in login view is dismissed
Use a tab bar
Tap on logout show login view again
Code
Login
struct LoginView: View {
#Environment(\.dismiss) private var dismiss
var body: some View {
ZStack {
Color.yellow
Button("Sign in") {
dismiss()
}
.buttonStyle(.bordered)
}
.ignoresSafeArea()
}
}
Tab
enum TabContent: Int {
case menu
case profile
}
Content
struct ContentView: View {
#State private var selection = TabContent.menu
#State private var isLoginShown = true
var body: some View {
NavigationStack {
TabView(selection: $selection) {
Text("Menu list")
.tabItem {
Label("Menu", systemImage: "list.bullet")
}
.tag(TabContent.menu)
VStack {
Text("Profile view")
Button("Logout") {
isLoginShown = true
}
}
.tabItem {
Label("Profile", systemImage: "person.crop.circle")
}
.tag(TabContent.profile)
}
}
.fullScreenCover(isPresented: $isLoginShown) {
LoginView()
}
}
}

SwiftUI: FocusState animations stop working when using NavigationViews

I am trying to implement an animation relating to a TextField. A Cancel button slide in when the textfield is clicked. However, it only works correctly when it's in a standalone view. When I try to nest the view within a NavigationLink, the animation stops working. Here's the code:
struct TestView: View {
#FocusState private var isEditing: Bool
var body: some View {
VStack {
Button("click me", action: { isEditing.toggle() })
HStack {
TextField("Search", text: .constant("test"))
.focused($isEditing)
.padding(8)
.padding(.leading, 25)
.padding(.trailing, 22)
.background(Color.gray)
.cornerRadius(10)
.padding(.horizontal)
if isEditing {
Button {} label: {
ZStack {
Text("Cancel")
.foregroundColor(.primary)
.padding(.trailing)
}
}
.transition(.move(edge: .trailing))
}
}
.animation(.spring(), value: isEditing)
.navigationBarHidden(true)
}
}
}
Correct animation:
https://imgur.com/iqGr7fx
However, when I have a second view with a NavigationLink containing the previous view:
struct TestView2: View {
#State var test: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(isActive: $test, destination: { TestView() }, label: {})
Button("click me", action: { test.toggle() })
}
.navigationBarHidden(true)
}
}
}
The animation looks like this:
https://imgur.com/a/LK9pxf2
Is this a bug related to SwiftUI? Or am I not supposed to use FocusState for animations? If so, how can I change the code to have the animation work in both versions?
Fixed the animations by changing the FocusState variable to a State variable and then removing the .focused modifier and changing it to .onTapGesture, setting the isEditing variable to true

NavigationLink keeps aligning my text elements to center instead of leading SwiftUI

I have a CustomSearchBar view that looks like this
However, when I wrap it with NavigationLink, the placeholder text will be centered. And user inputs will be centered too.
How do I maintain the leading alignment while using NavigationLink?
My code structure looks like this:
enum Tab {
case social
}
struct MainAppView: View {
#State var selection: Tab = .social
var body: some View {
TabView(selection: $selection) {
ZStack{
CustomButton()
NavigationView { SocialView() }
}.tabItem{Image(systemName: "person.2")}.tag(Tab.social)
// other tabs....
}
struct SocialView: View {
// ...
var body: some View {
GeometryReader{ geometry in
VStack{
NavigationLink(destination: Text("test")) {
CustomSearchBar()
//...
}.navigationBarHidden(true)
.navigationBarTitle(Text(""))
}
}
}
}
struct CustomSearchBar: View {
var body: some View {
VStack{
HStack {
SearchBarSymbols(// some binding arguments)
CustomTextField(// some binding arguments)
CancelButton(// some binding arguments)
}
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
}
.padding(.horizontal)
}
}
struct CustomTextField: View {
var body: some View {
TextField("friend name", text: $searchText)
.frame(alignment: .leading)
.onTapGesture {
// some actions
}
.foregroundColor(Color("SearchBarSymbolColor"))
.accentColor(Color("SearchBarSymbolColor"))
.disableAutocorrection(true)
}
}
The issues with your code are:
Your navigation view contains the search field. This means that any new view that gets pushed will cover the search field.
Your search field is inside of the navigation link. There are conflicting interactions here as it effectively turns the field into a button, ie tapping the search field vs tapping the navigation link.
Solution:
Move the navigation view below the text field, so that the new view will appear without covering it. Then change the navigation link so that it is activated via a binding that gets triggered when the search field is editing:
struct SocialView: View {
#State private var text: String = ""
#State private var isActive: Bool = false
var body: some View {
GeometryReader{ geometry in
VStack {
CustomTextField(searchText: $text, isActive: $isActive)
.padding(.vertical, 8.0)
.padding(.horizontal, 10.0)
.background(Color("SearchBarBackgroundColor"))
.clipShape(Capsule())
NavigationView {
NavigationLink(isActive: $isActive, destination: { Text("test") }, label: { EmptyView() })
}
}
}
}
}
struct CustomTextField: View {
#Binding var searchText: String
#Binding var isActive: Bool
var body: some View {
TextField("friend name", text: $searchText) { editing in
self.isActive = editing
} onCommit: {
}
.frame(alignment: .leading)
.disableAutocorrection(true)
}
}

Multiple Bottom sheets - the content doesn't load SwiftUI

I have made a view with two possible bottom sheets. The action works, and Bottom Sheets do open. Crazy thing is they open without the view inside. I have to close the one I opened and open the other one. When I do and than come back to the first one I will see the content. The code builds without warnings:
LogInView - where the logic is:
import SwiftUI
struct LogInView: View {
#EnvironmentObject var userInfo: UserInfo
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action:Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet){
if self.action == .resetPW{
ModalResetPWView()
}else if self.action == .signUp{
ModalSignUpView()
}
}
}
}
The view from which actions come:
import SwiftUI
struct LoginEmailView: View {
#EnvironmentObject var userInfo: UserInfo
#StateObject var user:LogInViewModel = LogInViewModel()
// ----- > THERE IS BINDING
#Binding var showSheet: Bool
#Binding var action:LogInView.Action?
// ----- >
var body: some View {
VStack{
Spacer()
Image("logo")
HStack{
Text("Adres email:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
TextField("Enter e-mail adress", text: self.$user.email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal, 10)
.keyboardType(.emailAddress)
HStack{
Text("Password:")
.padding(.horizontal, 10)
.font(.title)
.foregroundColor(.black)
Spacer()
}
SecureField("Enter password", text: self.$user.password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.title)
.padding(.horizontal,10)
HStack{
Spacer()
// ----- > First Bottom sheet
Button(action: {
self.action = .resetPW
self.showSheet = true
}) {
Text("Forgot Password")
}
.padding(.top, 5)
.padding(.trailing, 10)
// ----- >
}
Button(action: {
self.userInfo.isAuthenticated = .signedIn
}) {
Text("Log in")
}
.font(.title)
.padding(5)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.top, 10)
.opacity(user.isLogInComplete ? 1 : 0.7)
.disabled(!user.isLogInComplete)
// ----- > Second bottom sheet
Button(action: {
self.action = .signUp
self.showSheet = true
}) {
Text("Sign Up")
}
// ----- >
.padding(.top, 35)
Spacer()
}
}
}
The .sheet modifier will create the sheet view as soon as LogInView() is initialized. In your 'if.. else if..' statement, there is no logic to catch 'else' situations (situations where action == nil). Therefore, since action == nil on init(), the first .sheet that will present will fail your 'if..else if' and an EmptyView will present.
But don't worry! This is a common issue and can be easily solved. Here are 2 easy ways to implement methods to fix this (I prefer the 2nd method bc it's cleaner):
METHOD 1: Present a single view & change that view's content instead of switching between which view to present.
Instead of doing the 'if.. else if..' statement within the .sheet modifier, present a static view (I've called it SecondaryView ) that has a #Binding variable connected to your action. This way, when LogInView() appears, we can ensure that it will definitely render this view and then we can simply modify this view's content by changing the #Binding action.
import SwiftUI
struct LogInView: View {
enum Action{
case resetPW, signUp
}
#State private var showSheet = false
#State private var action: Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet) {
SecondaryView(action: $action)
}
}
}
struct LoginEmailView: View {
#Binding var showSheet: Bool
#Binding var action: LogInView.Action?
var body: some View {
VStack(spacing: 40 ){
Text("Forgot Password")
.onTapGesture {
action = .resetPW
showSheet.toggle()
}
Text("Sign Up")
.onTapGesture {
action = .signUp
showSheet.toggle()
}
}
}
}
struct SecondaryView: View {
#Binding var action: LogInView.Action?
var body: some View {
if action == .signUp {
Text("SIGN UP VIEW HERE")
} else {
Text("FORGOT PASSWORD VIEW HERE")
}
}
}
METHOD 2: Make each Button it's own View, so that it can have it's own .sheet modifier.
In SwiftUI, we are limited to 1 .sheet() modifier per View. However, we can always add Views within Views and each subview is then allowed it's own .sheet() modifier as well. So the easy solution is to make each of your buttons their own view. I prefer this method because we no longer need to pass around the #State/#Binding variables between views.
struct LogInView: View {
var body: some View {
LoginEmailView()
}
}
struct LoginEmailView: View {
var body: some View {
VStack(spacing: 40 ){
ForgotPasswordButton()
SignUpButton()
}
}
}
struct ForgotPasswordButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Forgot Password")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("FORGOT PASSWORD VIEW HERE")
})
}
}
struct SignUpButton: View {
#State var showSheet: Bool = false
var body: some View {
Text("Sign Up")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("SIGN UP VIEW HERE")
})
}
}

SwiftUI Navigation Link containing a Button

I have a SwiftUI list that looks like this:
List {
ForEach(items) { item in
NavigationLink(destination: EditItemView(item: item)) {
ItemView(item: item, buttonAction: {
// Do something
})
}
}
where ItemView is:
struct ItemView: View {
var item: Item
let buttonAction: () -> Void
var body: some View {
HStack(alignment: .center) {
Text(item.tile)
.font(.headline)
Spacer()
Button(action: buttonAction,
label: {
Image(systemName: "plus.circle")
})
.padding(.horizontal)
}
}
}
However, whenever I tap on the button, instead of performing the button action it goes to the EditItemView which is the navigation link action.
So how can I have a button performing an action inside a navigation link?
Or if that is not possible, how can I make that tapping the item view does one action and tapping the button does another action?
Here is possible approach - changed only ItemView by replacing default button with tap gesture (tested with Xcode 12 / iOS 14)
struct ItemView: View {
var item: Int
let buttonAction: () -> Void
var body: some View {
HStack(alignment: .center) {
Text("Item \(item)")
.font(.headline)
Spacer()
Image(systemName: "plus.circle")
.padding(.horizontal).contentShape(Rectangle())
.highPriorityGesture(TapGesture().onEnded(buttonAction))
}
}
}