I'm trying to change the navigation bar title and leading & trailing items in a SwiftUI app.
Here is the situation:
A user logs in, then he gets transferred to a view inside a TabView.
Before login, in the welcome screen and login & register screens I have no navigation bar title, but after login, I want to add the navigation bar items and title.
What is important to know is that my after-login-view is inside a TabView.
If I wrap my after-login-view in a new NavigationView and then change the navigation bar items and title it works! but it shows me 2 navigation bars:
which is not what I want.
here is the code I use :
This is the after-Login-View
import SwiftUI
struct DiscoveryView: View {
var body: some View {
List{
Text("List Items")
}.offset(y: 10)
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(leading:
Text("Discovery").font(Font.custom("Quicksand- Bold", size: 24))
, trailing:
Button(action: {
}) {
HStack{
Text("120").font(Font.custom("Quicksand-Bold", size: 15)).foregroundColor(Color("BlueColor"))
Text("following").font(Font.custom("Quicksand-Bold", size: 15)).foregroundColor(Color.black)
}
}
).navigationBarBackButtonHidden(true)
}
}
This is the TabView:
struct TabController : View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection){
DiscoveryView()
.tabItem {
VStack {
Image(systemName: "list.dash")
Text("Discover")
}
}.tag(1)
ExploreView()
.tabItem {
VStack {
Image(systemName: "square.and.pencil")
Text("Explore")
}
}.tag(2)
SelfProfileView()
.tabItem{
VStack {
Image(systemName: "person.circle")
Text("Profile")
}
}.tag(3)
}.accentColor(Color("BlueColor"))
}
}
This is the code of the login page:
import SwiftUI
struct LoginView: View {
var body: some View {
ZStack{
VStack{
Image("mainLogo").resizable().frame(width: 250, height: 125)
Text("").frame(width: UIScreen.main.bounds.width, height: 100)
HStack{
TextField("Username / Email", text: $email)
.padding(.leading, 10)
.frame(width: 313, height: 49)
.background(RoundedRectangle(cornerRadius: 4).stroke( Color.black.opacity(0.3)).frame(width: 313, height: 39).background(Color("GrayColor")))
}
Text("").frame(width: UIScreen.main.bounds.width, height: 40)
HStack {
HStack{
if self.visable{
TextField("Password", text: $password)
.padding(.horizontal)
} else {
SecureField("Password", text: $password)
.padding(.horizontal)
}
Button(action: {
self.visable.toggle()
}){
Image(systemName: self.visable ? "eye.slash" : "eye")
.padding(.trailing, 20)
.foregroundColor(Color.black)
}
}.background(RoundedRectangle(cornerRadius: 4).stroke( Color.black.opacity(0.3)).frame(width: 313, height: 39).background(Color("GrayColor")) )
}.padding(.horizontal, 40).padding(.vertical)
Text("").frame(width: UIScreen.main.bounds.width, height: 100)
Button(action: {
//pass email password and conpassword to cognito
DSManager().signIn(username: self.email, password: self.password, error: {
error in
if let error = error{
self.error = error.errorString
self.alert.toggle()
print(error.errorString)
}
else{
DSManager().getSelfUserInformation(response: {
userInfo, userCollections,dsError in
if let dsError = dsError{
self.error = dsError.errorString
self.alert.toggle()
print(dsError.errorString)
}
if let userInfo = userInfo{
print("success")
//use userInfo to load stuff for profile page
if let userCollections = userCollections{
self.profileInfo.setProperties(userInfo: userInfo, collections: userCollections)
}
else{
self.profileInfo.setProperties(userInfo: userInfo, collections: [:])
}
self.isComplete.toggle()
}
})
}
})
}) {
Text("Login")
.fontWeight(.semibold)
.frame(width: 313, height: 48)
.background(fillAllFields() ? Color("YellowColor") : Color.gray)
.foregroundColor(Color.white)
.font(.system(size: 17))
.cornerRadius(15)
.shadow(color: Color("GrayColor"), radius: 0, x: 3, y: 3)
}.disabled(!fillAllFields())
NavigationLink(destination: ProfileView(), isActive: $isComplete) {
EmptyView()
}
Spacer()
}
.padding()
.navigationBarTitle("", displayMode: .inline)
if self.alert {
ErrorView(err: $error, alert: $alert)
}
}
}
func fillAllFields() -> Bool {
if self.email == "" || self.password == "" {
return false
}
return true
}
}
As mentioned, I'm trying to edit bar items and title of a view inside and tab view .
Thanks!
So as per your updated question, I added DiscoveryView Inside NavigationView and it worked as expected. I have added code and screenshot as well.
struct TabController : View {
#State private var selection = 0
var body: some View {
TabView(selection: $selection) {
NavigationView {
DiscoveryView()
}
.tabItem {
VStack {
Image(systemName: "list.dash")
Text("Discover")
}
}.tag(1)
Text("Explore View")
.tabItem {
VStack {
Image(systemName: "square.and.pencil")
Text("Explore")
}
}.tag(2)
Text("SelfProfileView")
.tabItem{
VStack {
Image(systemName: "person.circle")
Text("Profile")
}
}.tag(3)
}.accentColor(Color("BlueColor"))
}}
Related
I have an app with a TabView. One of these tabs(1) displays a CartView.
There is another tab(2) that Adds items to the cart and Also allows Navigation to the CartView. The contents of the cart are maintained in an EnvironmentObject CartModel Object.
The issue I am having is that When I add items to Cart and navigate to the CartView, I am able to see the items. Should I back out and add something else, I can still see all the items as long as I am in tab(2). However I cannot see anything when I try to access the cart via tab(1). Hoping to get some pointers on what is happening here.
Clearly the underlying model code is accessible to the CartView on one of the tabs.
Relevant code:
Tabbed View:
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
StoreByCategoryView()
.tag(0)
.tabItem { Image(systemName: "bag") }
CreditDetailsView()
.tag(1)
.tabItem { Image(systemName: "creditcard") }
CartView()
.tag(2)
.tabItem { Image(systemName: "cart") }
SettingsView()
.tag(3)
.tabItem { Image(systemName: "gearshape") }
SocialMainView()
.tag(4)
.tabItem { Image(systemName: "globe") }
}
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarHidden(true)
.padding(.horizontal, 5)
}
CartView
struct CartView: View {
#EnvironmentObject var cartModel: CartModel
var body: some View {
let _ = print("CartView")
if cartModel.totalQuantity == 0 {
Text("Nothing present in the cart")
} else {
let _ = print("CartView --- ELSE")
VStack(alignment:.leading , spacing: 5){
let _ = print("CartView --- VSATCK")
List {
ScrollView(/*#START_MENU_TOKEN#*/.vertical/*#END_MENU_TOKEN#*/, showsIndicators: false){
ForEach(Array(cartModel.productQuantityMap.keys), id: \.self) {(product) in
CartItemView(product: product)
}
}
}
Group {
Divider()
HStack {
Spacer()
Text("Total: $A\(cartModel.totalValue)" as String)
.fontWeight(.semibold)
.foregroundColor(.primary)
}
.padding(.bottom)
Divider()
}
HStack(alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/, spacing: /*#START_MENU_TOKEN#*/nil/*#END_MENU_TOKEN#*/){
Spacer()
Button(action: {
checkoutModel.checkout(productQty: cartModel.productQuantityMap, customertoken: session.customerAccessToken)
}){
Text("Checkout")
.font(.system(.title2, design: .rounded))
.fontWeight(.semibold)
.foregroundColor(.white)
}
.frame(width: 200, height: 35)
.padding(15)
.background(Color(UIColor(red: 0.39, green: 1.00, blue: 0.85, alpha: 1.00)))
.clipShape(Capsule())
Spacer()
}
}
...
}
}
}
How to hide the tabBar in specific screens? I'm navigating from login to directly to tabBar. Is there any way to hide? In UIKit we're hiding by pushing and I have no idea how to do it in SwiftUI, by presenting the view not going to work.
Here is my TabBar
struct ReceiverTabBar: View {
#State private var selection: Int = 0
var body: some View {
TabView(selection: $selection){
.tabItem {
selection == 0 ? Image("")
Text("")
}
.tag(0)
ReceiverProfileView()
.tabItem {
selection == 1 ? Image("")
Text("")
}
.tag(1)
ReceiverNotificationsView()
.tabItem {
selection == 2 ? Image("")
Text("")
}
.tag(2)
ReceiverMoreView()
.tabItem {
selection == 3 ? Image("")
Text("")
}
.tag(3)
}
.accentColor(.black)
}
}
and I want hide tabBar in this view
struct MakingDonationView: View {
#Environment(\.presentationMode) var presentationMode
#State var selected = 0
var body: some View {
ScrollView(showsIndicators: false) {
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal,30)
.padding(.top,40)
.frame(height: UIScreen.main.bounds.height/5)
Text("")
.font(.custom("Poppins-SemiBold", size: 16))
.foregroundColor(Color("#252422"))
.padding(.top,20)
Text("")
.font(.custom("Poppins-SemiBold", size: 12))
.foregroundColor(Color("#5E5E5E"))
Text("")
.font(.custom("Poppins-Medium", size: 12))
.foregroundColor(Color("#A0A0A0"))
}
Spacer()
Divider()
MakingDonation(selected: $selected)
}
.padding(.all)
}
.padding(.horizontal,20)
.edgesIgnoringSafeArea(.bottom)
}
Button(action: {
}, label: {
Spacer()
Text("Confirm Donation Amount")
.font(.custom("Poppins-SemiBold", size: 13))
.foregroundColor(.black)
Spacer()
})
.frame(height:44)
.background(Color("#FFA919"))
.padding(.horizontal,20)
}
.shadow(color: .gray, radius: 1, x: 0, y: 2)
.cornerRadius(4)
}
.shadow(color: .gray, radius: 2, x: 0, y: 2)
.edgesIgnoringSafeArea(.all)
.frame(height:80)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("Making Donation", displayMode: .inline)
}
}
func goBack(){
self.presentationMode.wrappedValue.dismiss()
}
}
Try this:
// Container Screen view for handling all screen
struct ContainerView: View {
#State var tabSelection: Screens = .screen1
var body: some View {
NavigationView{
TabView(selection: $tabSelection){
// Screen 1
// Hide tab bar view only for Screen 1
NavigationLink(destination: PushedView()){
VStack{
Text("Screen 1")
Text("Tap to PushedView")
}
}
.tabItem { Text("Screen 1") }
.tag(Screens.screen1)
// Screen 2
// same view using for screen 2 for directly shows on that
PushedView()
.tabItem { Text("Screen 2") }
.tag(Screens.screen2)
}
.navigationBarTitle( self.tabSelection.title)
}
}
}
// New view for pushing
struct PushedView: View {
var body: some View {
Text("Hi! This is the new View")
.navigationBarTitle("NewView")
}
}
// Tab screens
enum Screens{
case screen1, screen2
var title: String {
switch self {
case .screen1:
return "Screen1"
case .screen2:
return "Screen2"
}
}
}
In my app you go through the onboarding during sign-up. That works perfectly. However, you can go through it again via the Profile Page.
The profile page code in question looks like this (embedded in a top-level NavigationView of course)
NavigationLink(destination:
EndDateView().environmentObject(OnboardingVM(coordinator: viewModel.appCoordinator))
) {
HStack {
Image(systemName: "chart.pie.fill")
.font(.title)
Text("Edit Spending Plan")
.fontWeight(.bold)
.scaledFont("Avenir", 20)
}
}
.buttonStyle(ButtonWithIcon())
This leads you to a screen where you set a Date via a date picker. All is good at this point.
When you navigate to the third page of this flow (Profile -> Datepicker -> Set Income) and interact with a SwiftUI TextField, the #EnvironmentObject reinitializes itself.
Below you will see the code snippets that show how we navigate/pass the env object around
DatePicker Navigation Code
NavigationLink(destination: SetIncomeView().environmentObject(onboardingVM)) {
PurpleNavigationButton(buttonText: onboardingVM.continueButton)
}
SetIncome Code
struct SetIncomeView: View {
#EnvironmentObject var onboardingVM: OnboardingVM
#ObservedObject var setIncomeVM = SetIncomeVM()
var body: some View {
VStack {
HStack {
CustomHeader1(text: SetIncomeContent.title)
Button(action: {
setIncomeVM.info.toggle()
}) {
Image(systemName: "info.circle")
}
}
.alert(isPresented: $setIncomeVM.info) {
Alert(title: Text(SetIncomeContent.title),
message: Text(SetIncomeContent.header),
dismissButton: .default(Text(SetIncomeContent.dismiss)))
}.padding()
Spacer()
ScrollView {
HStack {
CustomHeader2(text: SetIncomeContent.income)
TextField("", text: $onboardingVM.expectedIncomeAmount)
.keyboardType(.numberPad)
.frame(width: 100, height: 35, alignment: .center)
.scaledFont("Avenir", 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
HStack {
CustomHeader2(text: SetIncomeContent.onBasis)
Picker(selection: $onboardingVM.selectedBasis, label: Text("")) {
ForEach(0 ..< self.onboardingVM.basis.count) {
Text(self.onboardingVM.basis[$0])
}
}
.frame(width: 100)
.clipped()
CustomHeader2(text: SetIncomeContent.basis)
}
Button(action: {
setIncomeVM.otherIncome.toggle()
}) {
if setIncomeVM.otherIncome {
Image(systemName: "minus.circle")
} else {
Image(systemName: "plus.circle")
}
Text(SetIncomeContent.anotherSource)
.foregroundColor(.black)
.underline()
.fixedSize(horizontal: false, vertical: true)
.padding()
}
//TODO: I am getting a bug where if i type or scroll it closes the view below
HStack {
CustomHeader2(text: SetIncomeContent.income)
TextField("", text: $onboardingVM.additionalIncome)
.keyboardType(.numberPad)
.frame(width: 100, height: 35, alignment: .center)
.scaledFont("Avenir", 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}.isHidden(!setIncomeVM.otherIncome)
HStack {
CustomHeader2(text: SetIncomeContent.onBasis)
Picker(selection: $onboardingVM.additionalBasis, label: Text("")) {
ForEach(0 ..< self.onboardingVM.basis.count) {
Text(self.onboardingVM.basis[$0])
}
}
.frame(width: 100)
.clipped()
CustomHeader2(text: SetIncomeContent.basis)
}.isHidden(!setIncomeVM.otherIncome)
}
Spacer()
NavigationLink(destination: SetSavingsView().environmentObject(onboardingVM),
isActive: $setIncomeVM.savingsLink1Active) {
Button(action: {
setIncomeVM.savingsLink1Active.toggle()
}) {
Text(SetIncomeContent.noIncome)
.foregroundColor(.black)
.underline()
.fixedSize(horizontal: false, vertical: true)
.padding()
}
}
NavigationLink(destination: SetSavingsView().environmentObject(onboardingVM), isActive: $setIncomeVM.savingsLink2Active) {
Button(action: {
self.onboardingVM.saveIncomeBasis()
if !setIncomeVM.savingsLink2Active {
setIncomeVM.savingsLink2Active.toggle()
}
}) {
PurpleNavigationButton(buttonText: onboardingVM.continueButton)
}
}
}
Spacer()
}
}
Any idea why this happens?
Trying to work with SwiftUI and experiencing a weird issue. I have a List of Views that should be clickable to push to a detail view, swipable to delete, and have a couple of interactive elements in the view. The list is more or less set up as follows:
NavigationView {
ZStack(alignment: .top) {
if viewModel.value != nil {
List {
ForEach(viewModel.value!, id: \.address) { model in
VStack {
NavigationLink(destination: AliasDetailView(alias: model) { msg in
self.toastText = msg
self.showCopiedToast = true
}) {
ModelRow(alias: alias) { msg in
self.toastText = msg
self.showCopiedToast = true
}
}
}
}
.onDelete(perform: self.deleteModel)
}
.listStyle(PlainListStyle())
.alert(isPresented: $showDefaultAlert) {
Alert(title: Text("Default Address"),
message: Text("Would you like to set this as your default address?"),
primaryButton: .default(Text("Yes")) {
NSUbiquitousKeyValueStore.default.set(self.targetAddress, forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
print("Stored key")
self.targetAddress = ""
}, secondaryButton: .cancel({
NSUbiquitousKeyValueStore.default.set("NONE", forKey: SettingsKeys.DefaultAddress)
NSUbiquitousKeyValueStore.default.synchronize()
self.targetAddress = ""
}))
}
} else {
Text("Loading...")
}
VStack {
Spacer()
.alert(isPresented: $showInvalidAddressAlert) {
Alert(title: Text("Invalid Email"), message: Text("Please enter a valid address."), dismissButton: .default(Text("Ok")))
}
// Begin Floating action button
HStack {
Spacer()
Button(action: {
addModelAction()
}, label: {
Text("+")
.font(.largeTitle)
.frame(width: 77, height: 70)
.foregroundColor(.white)
.padding(.bottom, 7)
})
.background(Color.blue)
.cornerRadius(38.5)
.padding()
.shadow(color: Color.black.opacity(0.3),
radius: 3, x: 3, y: 3)
.alert(isPresented: $showAccountLimitAlert) {
Alert(title: Text("Account Limit Reached"), message: Text("You are currently at your account limit for models. Upgrade your account to create more."), dismissButton: .default(Text("Ok")))
}
}
}
}
.navigationBarTitle("Models")
.navigationBarItems(trailing:
Button(action: {
self.showSettingsModal = true
}) {
Image(systemName: "gearshape")
.font(.title)
}
)
.addPartialSheet(style: PartialSheetStyle(
backgroundColor: .black,
handlerBarColor: .secondary,
enableCover: true,
coverColor: Color.black.opacity(0.6),
blurEffectStyle: .dark))
.popup(isPresented: $showCopiedToast, type: .toast, position: .bottom, autohideIn: 2) {
HStack {
Text(self.toastText)
}
.frame(width: 200, height: 60)
.background(Color(red: 0.85, green: 0.8, blue: 0.95))
.cornerRadius(30.0)
.padding(15)
}
.sheet(isPresented: $showSettingsModal) {
SettingsView()
.environmentObject(ProductsStore.shared)
}
I've included everything in the body because I have a suspicion its related to something else blocking the touches. I've tried removing some things but can't seem to identify it. Here is the view code for the row:
HStack {
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text(model.address)
.font(.headline)
.lineLimit(1)
.truncationMode(.tail)
Text("➜ \(model.target)")
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
VStack {
Image(systemName: "doc.on.doc")
.frame(width: 35, height: 38)
.font(.headline)
.foregroundColor(.accentColor)
.onTapGesture {
copyAlias()
}
}
}
HStack {
Text("Received: \(model.received)")
Spacer()
Toggle("", isOn: $isActive)
.labelsHidden()
.onReceive([self.isActive].publisher.first()) { value in
if value != prevActive {
toggleModel()
}
}
}
}
}
Also note: The list view does work if you hold the elements. i.e. Tapping a row will not trigger the navigation link but holding the cell will eventually trigger it.
Turns out it was the Toast Library I was using. Likely however their view modifier works its capturing touches.
I've a List of object, in NavigationView, If I blur it, the view is changing the offset, seems it force edgesIgnoringSafeArea at list view:
case 1: If I blur it, the view is changing the offset.
case 2: If I put something before List in VStack, the offset is right (with the space of the object)
case 3: the workaround I found, is to add offset y:1 at the blur effect
struct Test: View {
#State private var arr = [1,2,3,4]
var body: some View {
TabView {
NavigationView {
HStack {
VStack {
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
}
VStack {
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
.blur(radius: 3)
}
}
.navigationBarTitle(Text("TITLE"), displayMode: .inline)
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())//DoubleColumnNavigationViewStyle())
.tabItem {
Image(systemName: "message")
Text("LIST + LIST BLUR")
}
.tag(1)
NavigationView {
HStack {
VStack {
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
}
VStack {
Text("2").frame(width: 0, height: 0)
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
.blur(radius: 3)
}
}
.navigationBarTitle(Text("TITLE"), displayMode: .inline)
}
.tabItem {
VStack {
Image(systemName: "circle")
Text("LIST+VSTACKBLUR")
}
}
.tag(2)
NavigationView {
HStack {
VStack {
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
}
VStack {
List {
ForEach (arr, id:\.self) { _ in
Image(systemName: "person")
.resizable()
.frame(width: 150, height: 150)
}
}
.blur(radius: 3).offset(y:1)
}
}
.navigationBarTitle(Text("TITLE"), displayMode: .inline)
}
.tabItem {
VStack {
Image(systemName: "circle")
Text("List+BLUR+OFFSET")
}
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
UPDATE:
I can't blur inside list because I'm using an extension of View to show personal TextFieldAlert
extension View {
func textFieldAlert(isShowing: Binding<Bool>,
text: Binding<String>,
title: String, grayText: String, onReturnPressed: #escaping () -> Void) -> some View {
TextFieldAlert(isShowing: isShowing,
text: text,
presenting: { self },
title: title, grayText: grayText, onReturnPressed:
onReturnPressed)
}
}
struct TextFieldAlert<Presenting>: View where Presenting: View {
#Binding var isShowing: Bool
#Binding var text: String
let presenting: () -> Presenting
let title: String
var grayText:String
let onReturnPressed: () -> Void
func returnFunction() {//quando l'utenti schiaccia ok o invio}
self.isShowing = false
self.onReturnPressed()
}
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
ZStack {
if self.isShowing {
self.presenting()
.blur(radius: 3).offset(y:1) //I've added this
.disabled(self.isShowing)
}
else {
self.presenting()
.disabled(self.isShowing)
}
VStack {
Spacer()
.frame(height: 8)
Text(self.title)
Divider()
TextField(self.grayText, text: self.$text, onCommit: {
self.returnFunction()
})
Divider()
Spacer()
.frame(height: 10)
HStack {
Button(action: {
withAnimation {
self.returnFunction()
}
}) {
Text("OK")
.frame(width: 150, height: 30)
.background(Color.white)
//.foregroundColor(.white)
.cornerRadius(15)
}
}
}
.padding()
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 15))
.frame(
width: 250,//deviceSize.size.width*0.6,
height: 220//deviceSize.size.height*0.6
)
.offset(y: -100)
.shadow(radius: 20)
.opacity(self.isShowing ? 1 : 0)
ZStack {
Circle().frame(width: 40, height: 40)
.foregroundColor(Color.white)
Image(systemName: "pencil.circle.fill")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 50, height: 50)
.clipShape(Circle())
.foregroundColor(Color.blue)
}
.offset(x:0, y: -220/3 - 110)//-deviceSize.size.height*0.6/6 - 110)
.opacity(self.isShowing ? 1 : 0)
}
}
}
}