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()
}
}
...
}
}
}
Related
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"
}
}
}
I want to nagivate to the details screen using button. the buttons are designed in a separate view and called Using ForEach function. Please guide me to solve this.
This is the code I designed for button
struct ButtonsDesignView: View {
var buttons: MyModel.buttons
var body: some View {
Button(action: { }) {
ZStack {
HStack{
VStack{
Text(btn.title)
.foregroundColor(Color(.blue))
.padding()
Text(btn.subtitle)
.foregroundColor(Color(.blue))
.padding()
}
Spacer()
VStack{
Image(systemName: (btn.image))
.resizable()
.scaledToFit()
.foregroundColor(Color(.blue))
.padding()
}
}
}
}
This is code code I've place in ContentView to call display the buttons in scrollview Using ForEach.
ScrollView(.vertical, showsIndicators: false)
{
ForEach(buttonsData) {
item in
NavigationLink(destination: DetailView(buttons: item))
{
ButtonsDesignView(buttons: item)
.padding(10)
}
}.padding(.horizontal, 30)
}
But with this code navigation link is not working.
You should add navigationLink to ButtonDesignView.
I think that this should work;
NavigationLink(destination: DetailView(buttons: buttons)) {
Button(action: { }) {
ZStack {
HStack{
VStack{
Text(btn.title)
.foregroundColor(Color(.blue))
.padding()
Text(btn.subtitle)
.foregroundColor(Color(.blue))
.padding()
}
Spacer()
VStack{
Image(systemName: (btn.image))
.resizable()
.scaledToFit()
.foregroundColor(Color(.blue))
.padding()
}
}
}
}
}
I am creating a custom list displaying time information (minutes and seconds, not used in the snippet below to simplify the code). I managed to implement a nice animation when the user adds an entry to the list, but deleting an entry has no animation (1st GIF).
With iOS 14, the animation is working, however, the animation only removes the last rectangle from the list and then updates the text in each row (2nd GIF). That's not what I want - My goal is if a row has been deleted, the other rows should fill up that space and move accordingly - with an animation.
Probably something is wrong with the IDs of the rows but I just wasn't able to fix that. Thanks for helping!
struct ContentView: View {
#State var minutes = [0]
#State var seconds = [0]
#State var selectedElement = 0
var body: some View {
ScrollView(){
VStack{
ForEach(minutes.indices, id: \.self){ elem in
ZStack{
EntryBackground()
Text("\(self.minutes[elem])")
.transition(AnyTransition.scale)
HStack{
Button(action: {
withAnimation(.spring()){
self.seconds.remove(at: elem)
self.minutes.remove(at: elem)
}
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
withAnimation{
self.minutes.append(self.minutes.count)
self.seconds.append(0)
}
})
{
ZStack{
EntryBackground()
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}
}
}
struct EntryBackground: View {
var body: some View {
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
}
}
You need to make each row uniquely identified, so animator know what is added and what is removed, so animate each change properly.
Here is possible approach. Tested with Xcode 12 / iOS 14
struct TimeItem: Identifiable, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
let id = UUID() // << identify item
let minutes: Int
let seconds: Int = 0
}
struct ContentView: View {
#State var items = [TimeItem]()
#State var selectedElement: TimeItem?
var body: some View {
ScrollView(){
VStack{
ForEach(items){ elem in // << work by item
ZStack{
EntryBackground()
Text("\(elem.minutes)")
.transition(AnyTransition.scale)
HStack{
Button(action: {
self.items.removeAll { $0.id == elem.id }
})
{
Image(systemName: "minus.circle.fill")
.foregroundColor(Color.red)
.font(.system(size: 22))
.padding(.leading, 10)
}
Spacer()
}
}
.padding(.horizontal)
.padding(.top)
.contentShape(Rectangle())
.onTapGesture {
withAnimation(.spring()){
self.selectedElement = elem
}
}
}
}
Spacer()
Button(action: {
self.items.append(TimeItem(minutes: self.items.count))
})
{
ZStack{
EntryBackground()
Text("Add")
HStack{
Image(systemName: "plus.circle.fill")
.foregroundColor(Color.green)
.font(.system(size: 22))
.padding(.leading, 10)
Spacer()
}
}.padding()
}
}.animation(.spring(), value: items) // << animate changes
}
}
struct EntryBackground: View {
var body: some View {
Rectangle()
.cornerRadius(12)
.frame(height: 40)
.foregroundColor(Color.gray.opacity(0.15))
}
}
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"))
}}
I have a ContentView.swift which contains a TabView.
View1 has a NavigationView in it with some childViews.
View2 is just a single View.
I would like to perform some action (always return to View1, even when being in a childView of View1), when the first tabItem is pressed. Even if it is already the active tabItem.
I tried onTapGesture as seen below, which didn't seem to do anything. Adding it right before ".tag(0)" didn't change anything, either:
TabView {
View1()
.onTapGesture {
print("Test")
}
.tabItem {
Image(systemName: "doc.plaintext")
.font(.system(size: 25))
Text("View1")
}.tag(0)
View2()
.tabItem {
Image(systemName: "person.crop.circle")
.font(.system(size: 25))
Text("View2")
}.tag(1)
}
Here is possible approach. Tested with Xcode 11.4 / iOS 13.4
struct TestTabSelectionAction: View {
#State private var selectedTab = 0
var body: some View {
let selection = Binding<Int>(
get: { self.selectedTab },
set: { self.selectedTab = $0
print("Pressed tab: \($0)")
if $0 == 0 {
// <<< your action here !!
}
})
return TabView(selection: selection) {
View1()
.tabItem {
Image(systemName: "doc.plaintext")
.font(.system(size: 25))
Text("View1")
}.tag(0)
View2()
.tabItem {
Image(systemName: "person.crop.circle")
.font(.system(size: 25))
Text("View2")
}.tag(1)
}
}
}