ForEach with NavigationLink(iOS 15) - ios

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))
}
}

Related

EmptyBody().onAppear not works when sheet present with item

When I present sheet with .sheet(isPresented... onAppear of EmptyView() triggered
but when I use .sheet(item... then onAppear doesn't trigger. I don't understand what mistake I am doing?
item:
enum ActiveSheet: Identifiable {
var id: String { UUID().uuidString }
case customA
case customB
}
Main View:
struct ContentView: View {
#State private var activeSheet: ActiveSheet?
var body: some View {
VStack {
Button(action: { activeSheet = .customA }) {
Text("View A")
}
Button(action: { activeSheet = .customB }) {
Text("View B")
}
}
.buttonStyle(.borderedProminent)
//If I use this .sheet(isPresented... then onAppear triggers, but not with item
.sheet(item: $activeSheet) { item in
switch item {
case .customA:
CustomViewA()
case .customB:
CustomViewB()
}
}
}
}
Empty Views:
struct CustomViewA: View {
var body: some View {
EmptyView()
.onAppear {
print("OnAppear")
}
}
}
struct CustomViewB: View {
var body: some View {
EmptyView()
.onAppear {
print("OnAppear")
}
}
}

Picker conflicts with NavigationLink gesture

I am trying to create the following card view.
With the following code to achieve it.
struct SimpleGame: Identifiable, Hashable {
var id = UUID()
let name: String
}
enum PlayingStatus: String {
case In = "I"
case Out = "O"
case Undecided = "U"
}
struct TestView: View {
let games: [SimpleGame] = [
.init(name: "First"),
.init(name: "Second")
]
#State private var currentStatus: PlayingStatus = .Undecided
var body: some View {
NavigationView {
List(games) { game in
Section {
VStack {
NavigationLink(value: game) {
Text("\(game.name)")
}
Divider()
Picker("Going?", selection: $currentStatus) {
Text("No Response")
.tag(PlayingStatus.Undecided)
Text("Going")
.tag(PlayingStatus.In)
Text("Not going")
.tag(PlayingStatus.Out)
}
.font(.body)
}
}
}
.navigationDestination(for: Game.self) { game in
Text("Detail View")
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Upcoming")
}
}
}
But a tap on element wrapped by NavigationLink is registering as a tap on the Picker. Anyone know of a way around this?
iOS 16/Xcode 14
you could try this:
List {
ForEach(games, id: \.name) { game in
Section {
NavigationLink(value: game) {
Text("\(game.name)")
}
// -- here
VStack {
Divider()
Picker("Going?", selection: $currentStatus) {
Text("No Response").tag(PlayingStatus.Undecided)
Text("Going").tag(PlayingStatus.In)
Text("Not going").tag(PlayingStatus.Out)
}
.font(.body)
}
}
}
}
What often works for me is extending the view.
struct TestView: View {
var body: some View {
List {
ForEach(games, id: \.name) { game in
Section {
NavigationLink(value: game) {
Text("\(game.name)")
}
// -- here
VStack {
Divider()
picker
}
}
}
}
}
}
Extension TestView {
private var picker: some View {
Picker("Going?", selection: $currentStatus) {
Text("No Response").tag(PlayingStatus.Undecided)
Text("Going").tag(PlayingStatus.In)
Text("Not going").tag(PlayingStatus.Out)
}
.font(.body)
}
}

SwiftUI - How to use enum switch to View other Screens

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)
}
}

SwiftUI get next item in ForEach loop

I'm trying to implement a VStack grid that needs to have a HStack when a condition is met on the next item in a ForEach loop. I have the code below that displays the items in an array but I don't know how to get the next item to check against.
Here's what I have so far.
VStack {
ForEach(item.cards, id: \.self) { item in
switch item.card.size {
case .Large:
LargeCard(card: item.card, viewModel: CardViewModel())
case .Medium:
MediumCard(card: item.card, viewModel: CardViewModel())
case .Small:
HStack(spacing: 16) {
SmallCard(card: item.card, viewModel: CardViewModel())
// This is where I think I need to check and I tried somthing like below, but the compiler crashes
if $0 + 1 < item.cards.count {
if item.card.size == .Small {
SmallCard(card: item.card, viewModel: CardViewModel())
}
}
}
case .none:
Text("No more.")
}
}
}
Here's the item struct:
struct Item: Decodable, Hashable {
let card: Card
}
Here's what I'm wanting to get.
you could try adding a id to your struct Item, such as:
struct Item: Identifiable, Decodable, Hashable {
let id = UUID()
let card: Card
}
and then use:
VStack {
ForEach(item.cards, id: \.self) { theItem in
switch theItem.card.size {
case .Large:
LargeCard(card: theItem.card, viewModel: CardViewModel())
case .Medium:
MediumCard(card: theItem.card, viewModel: CardViewModel())
case .Small:
HStack(spacing: 16) {
SmallCard(card: theItem.card, viewModel: CardViewModel())
// here use the id to find the next item
if let ndx = item.cards.firstIndex(where: {$0.id == theItem.id}) {
if ndx + 1 < item.cards.count {
let nextItem = item.cards[ndx + 1]
if nextItem.card.size == .Small {
SmallCard(card: nextItem.card, viewModel: CardViewModel())
}
}
}
}
case .none:
Text("No more.")
}
}
}
You could also use enumerated as mentioned in the comments, such as:
VStack {
ForEach(Array(item.cards.enumerated()), id: \.offset) { index, theItem in
switch theItem.card.size {
case .Large:
LargeCard(card: theItem.card, viewModel: CardViewModel())
case .Medium:
MediumCard(card: theItem.card, viewModel: CardViewModel())
case .Small:
HStack(spacing: 16) {
SmallCard(card: theItem.card, viewModel: CardViewModel())
// here use the index to find the next item
if index + 1 < item.cards.count {
let nextItem = item.cards[index + 1]
if nextItem.card.size == .Small {
SmallCard(card: nextItem.card, viewModel: CardViewModel())
}
}
}
case .none:
Text("No more.")
}
}
}
Note, it looks like you should be using .environmentObject(viewModel) to pass
a single CardViewModel() to the views, instead of creating a new CardViewModel() each time.

Swiftui how to add a view above a tabview?

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 }
}
}

Resources