I am trying to great a side menu navigation to so the user is able to select each category within the menu and it will display the screen that I build to the user. For Example in the use selects "Fire Number" it will show the CalcFireNum file I created to the user. I have built a Enum with switch statements and cant figure out how to get the screens to show instead of the Strings.
import Foundation
import SwiftUI
enum SideMenuViewModel: Hashable {
case profile
case Mainscreen
case FireNumber
case Mindset
case Debt
case help
case logout
var title: String {
switch self {
case .profile: return "Profile"
case .Mainscreen: return "Main Screen"
case .FireNumber: return "Calculate Fire Number"
case .Mindset: return "Wants Vs Needs"
case .Debt: return "Debt Reduction"
case .help: return "Help"
case .logout: return "Logout"
}
}
var imageName: String {
switch self {
case .profile: return "Profile"
case .Mainscreen: return "Lists"
case .FireNumber: return "Bookmarks"
case .Mindset: return "Wants Vs Needs"
case .Debt: return "Debt Reduction"
case .help: return "help"
case .logout: return "Logout"
}
}
}
I am also running the a ForEach loop for the SideMenuViewModel
import SwiftUI
struct SideMenu: View {
#Binding var ishowing: Bool
var body: some View {
ZStack{
LinearGradient(gradient: Gradient(colors: [Color.white, Color.brown]),
startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack {
SideMenuHeaderView(isShowing: $ishowing)
.frame(height: 240)
ForEach(SideMenuViewModel.allCases, id: \.self) { option in
NavigationLink(destination: Text(option.title), label: {
SideMenuOptionView(viewModel: option)
})
}
Spacer()
}
}.navigationBarHidden(true)
}
And also have a SideMenuOptionView File that is for the users profile photo thats being called from the enum in the sideMenuViewModel
import SwiftUI
struct SideMenuOptionView: View {
let viewModel: SideMenuViewModel
var body: some View {
HStack(spacing: 16) {
Image(systemName: viewModel.imageName)
.frame(width: 24, height: 24)
Text(viewModel.title)
.font(.system(size: 15, weight: .semibold))
Spacer()
}
.foregroundColor(.white)
.padding()
}
}
struct SideMenuOptionView_Previews: PreviewProvider {
static var previews: some View {
SideMenuOptionView(viewModel: .help)
}
}
Related
I want to make my first app - it will be my resume(cv). Here is what I created in my mind:
I want to have multiple buttons connected with many views. all displayed in grid with 2 columns
inside of each view Ii will have different things, like education info, gallery, and later - my portfolio apps.
I want to join somehow views with titles - how can I do it better without json?
I did it like this: views and titles are done separately. Is there any better way to do this without json?
One important thing: I want to have more than 10 buttons, so I want to do it with arrays instead of placing just buttons separately.
import SwiftUI
struct ContentView: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
let buttonsViews: [AnyView] = [
AnyView(AboutView()),
AnyView(EducationView()),
AnyView(GalleryView())
]
let titles: [String] = [
"About",
"Education",
"Gallery"
]
var body: some View {
NavigationStack{
LazyVGrid(columns: columns) {
ForEach(buttonsViews.indices, id: \.self) { ind in
NavigationLink("\(titles[ind])") {
buttonsViews[ind]
}
.frame(height: 50)
.frame(minWidth: 100)
.foregroundColor(.red)
.padding()
.background(Color.black)
.cornerRadius(5)
}
}
}
}
}
If you're using AnyView, you're probably doing it wrong. Try an array of enums representing each view (about, education, gallery, etc), then ForEach on the array, switch on their enum, and return the relevant View.
enum ButtonType: Identifiable, CaseIterable {
var id: Self { self }
case about, education, gallery
var title: String {
switch self {
case .about: return "About"
case .education: return "Education"
case .gallery: return "Gallery"
}
}
}
struct ContentView: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationStack{
LazyVGrid(columns: columns) {
ForEach(ButtonType.allCases) { type in
NavigationLink(type.title) {
switch type {
case .about:
Text("AboutView")
case .education:
Text("EducationView")
case .gallery:
Text("GalleryViewView")
}
}
.frame(height: 50)
.frame(minWidth: 100)
.foregroundColor(.red)
.padding()
.background(Color.black)
.cornerRadius(5)
}
}
}
}
}
I'm trying to do NavigationLink with ForEach Loop to different views, but i can't do it.
Only what i know, that i should to change something in this code, but what and how to do it...
I tried to put RestaurantsView(i created it), but it gives me error 'cuz i don't know where to put it more properly.
Right now it gives me an empty page, but this is not a view.
import Foundation
enum SideMenuText: Int, CaseIterable {
case restaurants
case coffeeShops
case groceryStores
case clothingStores
case electronicStores
case carDealers
var title: String {
switch self {
case .restaurants: return "Restaurants"
case .coffeeShops: return "Coffee Shops"
case .groceryStores: return "Grocery Stores"
case .clothingStores: return "Clothing Stores"
case .electronicStores: return "Electronic Stores"
case .carDealers: return "Car Dealers"
}
}
var imageName: String {
switch self {
case .restaurants: return "fork.knife"
case .coffeeShops: return "cup.and.saucer"
case .groceryStores: return "cart"
case .clothingStores: return "tshirt"
case .electronicStores: return "iphone"
case .carDealers: return "car"
}
}
}
And here:
import SwiftUI
struct SideMenuView: View {
#Binding var isShowing: Bool
var body: some View {
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .leading, endPoint: .bottom)
.ignoresSafeArea()
VStack {
SideMenuHeaderView(isShowing: $isShowing)
ForEach(SideMenuText.allCases, id: \.self) { option in
NavigationLink ( //
destination: Text(option.title),
label: { SideMenuOptions(viewModel: option)
})
}
Spacer()
}
}
.navigationBarHidden(true) //
}
}
struct SideMenuView_Previews: PreviewProvider {
static var previews: some View {
SideMenuView(isShowing: .constant(true))
}
}
I am building a login for my app which calls an API on my server and returns user information and a JWT token. I decided to split my views into separate code blocks with a view to moving them to their own files in the project later on. Since I have done this however, my button in the login form doesn't run the login function in my ViewModel, nor does it even print and output to the console.
I have included my ContentView.swift here to show my thinking and basic setup.
struct ContentView: View {
#StateObject private var loginVM = LoginViewModel()
#StateObject private var projectListVM = ProjectListViewModel()
#State private var isSecured: Bool = true
var body: some View {
Group {
if loginVM.isAuthenticated {
MainView()
} else {
LoginView()
}
}
}
}
struct LoginView: View {
#StateObject private var loginVM = LoginViewModel()
#StateObject private var projectListVM = ProjectListViewModel()
#State private var isPasswordVisible = false
var body: some View {
Form {
VStack(alignment: .center, spacing: 0.0) {
Spacer()
VStack(alignment: .leading, spacing: 10) {
Text("Email")
.font(.fieldLabel)
.foregroundColor(Color.secondary01Light)
.frame(alignment: .leading)
TextField("Username", text: $loginVM.username)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.stroke(Color(UIColor.systemGray4), lineWidth: 1)
}
}
VStack(alignment: .leading, spacing: 10) {
Spacer()
Text("Password")
.font(.fieldLabel)
.foregroundColor(Color.secondary01Light)
.frame(alignment: .leading)
SecureField("Password", text: $loginVM.password)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.stroke(Color(UIColor.systemGray4), lineWidth: 1)
}
}
}
VStack {
Button(action: {
print("Login Tapped")
loginVM.login()
}) {
Text("Sign In")
.frame(maxWidth: .infinity)
.padding(8)
.cornerRadius(8)
}
.padding(.top, 30)
.frame(height: nil)
.buttonStyle(.borderedProminent)
.tint(Color.primary01)
.controlSize(.regular)
.font(.custom("Poppins-ExtraBold", size: 16))
.foregroundColor(.white)
}
}
}
}
The above code shows the button inside a form and with an action and inside of the action a call to the login() method and a print statement.
When I tap the button in the simulator I am not even getting an output in the console and it doesn't seem to be running my login function either.
Below is my LoginViewModel which I have had working without a problem in the past.
class LoginViewModel: ObservableObject {
var username: String = ""
var password: String = ""
#Published var isAuthenticated: Bool = false
func login() {
print("Login Called")
Webservice().login(username: username, password: password) { result in
print("Login API Call Made")
switch result {
case .success(_):
print("Got User Data")
DispatchQueue.main.async {
self.isAuthenticated = true
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
I have the Form fields in place and the #StateObject in the view but doesn't seem to want to fire.
You initialize loginVM two times! Once in ContentView and again in LoginView (both using #StateObject) ... so you create two different states that are not connected.
What you want to do is create it only once in ContentView and pass it down (e.g. with .environmentObject) to LoginView:
struct ContentView: View {
// here you create/initialize the object
#StateObject private var loginVM = LoginViewModel()
var body: some View {
Group {
if loginVM.isAuthenticated {
MainView()
} else {
LoginView()
}
}
.environmentObject(loginVM) // pass the object to all child views
}
}
struct LoginView: View {
// here you get the passed Object from the environment
#EnvironmentObject private var loginVM: LoginViewModel
var body: some View {
// ...
}
}
The requirement is to display a banner above the tabbar. So that the banner does not disappear when the tabs are changed? How can I achieve it?
I can think of starting points, to help you see ways to approach this, but I don't think either idea really meets your requirements. It will depend on the details of whether the banner is permanent, where its content comes from, etc.
The first idea:
struct BannerView : View {
var text : String
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
Text(text)
Spacer()
}.background(Color.orange)
}
}
}
Then you can include this in a ZStack along with your tabs:
TabView {
ZStack {
Text("Tab 1")
BannerView("BANNER")
}.tabItem { Text("Home") }
ZStack {
Text("Tab 2")
BannerView("BANNER")
}.tabItem { Text("History") }
}
The second idea uses the BannerView from the first idea, but in a slightly cleaner way, still not great:
struct TabWrapperWithOptionalBanner<Content> : View where Content : View {
var showBanner : Bool
var content : Content
init(showBanner : Bool, #ViewBuilder content: () -> Content) {
self.showBanner = showBanner
self.content = content()
}
var body: some View {
ZStack {
content
if showBanner {
BannerView(text: "BANNER")
}
}
}
}
then your ContentView looks like this:
TabView {
TabWrapperWithOptionalBanner(showBanner: showBanner) {
Text("Tab 1")
}.tabItem { Text("Home") }
TabWrapperWithOptionalBanner(showBanner: showBanner) {
Text("Tab 2")
}.tabItem { Text("History") }
}
Update Try a pair of TabViews bound to the same State:
struct BanneredTabView: View {
#State private var selected = panels.one
var body: some View {
VStack {
TabView(selection: $selected) {
panels.one.label.tag(panels.one)
panels.two.label.tag(panels.two)
panels.three.label.tag(panels.three)
}
.tabViewStyle(PageTabViewStyle())
Text("Banner")
.frame(height: 40, alignment: .top)
TabView(selection: $selected) {
ForEach(panels.allCases) { panel in
Text("").tabItem {
panel.label
}
.tag(panel)
}
}
.frame(height: 30)
}
}
enum panels : Int, CaseIterable, Identifiable {
case one = 1
case two = 2
case three = 3
var label : some View {
switch self {
case .one:
return Label("Tab One", systemImage: "1.circle")
case .two:
return Label("Tab Two", systemImage: "2.square")
case .three:
return Label("Tab Three", systemImage: "asterisk.circle")
}
}
// so the enum can be identified when enumerated
var id : Int { self.rawValue }
}
}
I try to implement a dynamic created list into an tabview in SwiftUI using an EnvironmentObject and a Binding (which may not be needed in the example-code below but it is in my real project) for the SubView of the row.
The list itself (add item, delete item) is working fine as well as the switch to the other "page" of the tabview. But if you are deleting the last item of the list, and only the last (if you are deleting the first one or any item between it also works fine) the app crashes with the Error: "Thread 1: Fatal error: Index out of range" as soon you are trying to switch to the other tab after deleting this item.
Heres the (example)-code:
import SwiftUI
struct PickerRowData: Equatable, Identifiable, Hashable {
var id = UUID()
var pickerValueString = ""
}
class GlobalStates: ObservableObject, Identifiable {
#Published var pickerRowData = [PickerRowData]()
var id = UUID()
}
struct MeasurePickerRow: View, Identifiable {
#Binding var pickerRowData: PickerRowData
var id = UUID()
var body: some View {
return VStack {
Text("Just a test. ID: \(pickerRowData.id)")
}
}
}
struct MeasurePickerView: View {
#EnvironmentObject var globalStates: GlobalStates
var body: some View {
return NavigationView {
List {
ForEach(self.globalStates.pickerRowData) { rowData in
MeasurePickerRow(pickerRowData: self.$globalStates.pickerRowData[self.globalStates.pickerRowData.lastIndex(of: rowData) ?? 0])
}
.onDelete(perform: deleteMeasurePicker)
}
.listStyle(GroupedListStyle())
.padding(0)
.navigationBarTitle(Text("Input"), displayMode: .inline)
.navigationBarItems(
trailing: HStack {
Button(action: {
self.globalStates.pickerRowData.append(PickerRowData())
}) {
Image(systemName: "gauge.badge.plus")
}
})
}
}
func deleteMeasurePicker(at offsets: IndexSet) {
self.globalStates.pickerRowData.remove(atOffsets: offsets)
}
}
struct ContentView: View {
var body: some View {
TabView {
MeasurePickerView()
.tabItem {
Image(systemName: "gauge")
HStack {
Text("Tab 1")
}
}
Text("Tab 2 View")
.tabItem {
Image(systemName: "equal.circle")
HStack {
Text("Tab 2")
}
}
}
}
}
Did I miss something? I'm quite new in SwiftUI (and Swift) so I hope someone has a hint.
Regards,
DZ