mx6001
1
Hi guys - I am very new to Swift and I am trying to recreate a shopping app programmatically. As shopping apps go, there's a category for everything, like All, Bestsellers, Best Deal etc. And each such category has its own scrollable image gallery to go with it, whether a horizontal or a vertical one.
Building a single screen with a headline, categories (UIButtons) a ScrollView featuring an image gallery for one of the categories is straightforward enough. It's when I try to add more image galleries for other categories that things fall apart. ScrollView HStacks just start piling up on a screen and not conform to their intended UIButtons.
Clearly, another approach is needed. Do I create a Model/Data file for all image galleries and make each Button upload the relevant one on-screen? Or do I create a ReusableCell layout and make it display different image galleries when a UIButton is pressed?
I've been running around this beginner issue in circles, any pointing into the right direction will be appreciated.
mattcurtis
(Matt Curtis)
2
It's difficult to point you in the right direction from this description alone — if you have any minimal example code demonstrating the issue you're running into that would help!
mx6001
3
Hi Matt,
The issue I'm running into is that I can't add a separate image carousel for another item category. It's the same image carousel for all categories, and if I try to add another struct with images, it just piles underneath the first one. There must be a solution to tie an image carousel to a category, but I keep missing it.
Here's the code:
import SwiftUI
struct HomeScreen: View {
@State private var selectedIndex: Int = 0
private let categories = ["All", "Recommended", "Popular", "Just In", "Best Buy"]
var body: some View {
NavigationView {
ZStack {
Color.white
.ignoresSafeArea()
ScrollView (showsIndicators: false) {
VStack (alignment: .leading) {
TagLineView()
.padding()
ScrollView (.horizontal, showsIndicators: false) {
HStack {
ForEach(0 ..< categories.count) { i in
Button(action: {selectedIndex = i}) {
CategoryView(isActive: selectedIndex == i, text: categories[i])
}
}
}
.padding()
}
ScrollView (.horizontal, showsIndicators: false) {
HStack (spacing: 0) {
ForEach(0 ..< 4) { i in
NavigationLink(
destination: ItemScreen(),
label: {
ItemView(image: Image("item_\(i+1)"), size: 200)
})
.navigationBarHidden(true)
.foregroundColor(.black)
}
.padding(.leading)
}
}
.padding(.bottom)
}
}
}
}
}
}
struct HomeScreen_Previews: PreviewProvider {
static var previews: some View {
HomeScreen()
}
}
struct TagLineView: View {
var body: some View {
Text("Headline")
.font.title3
.foregroundColor(Color.black)
}
}
struct CategoryView: View {
let isActive: Bool
let text: String
var body: some View {
VStack (alignment: .leading, spacing: 0) {
Text(text)
.font(.system(size: 18))
.fontWeight(.medium)
.foregroundColor(isActive ? Color.black : Color.black.opacity(0.5))
if (isActive) { Color.black
.frame(width: 25, height: 2)
.clipShape(Capsule())
}
}
.padding(.trailing)
}
}
struct ItemView: View {
let image: Image
let size: CGFloat
var body: some View {
ZStack {
image
.resizable()
.aspectRatio(contentMode: .fill)
Text(“Placeholder").font(.title3).fontWeight(.bold).foregroundColor(Color.white)
HStack (spacing: 2) {
Spacer()
Text("Placeholder text description")
.font(.body)
.fontWeight(.medium)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
OpenButton()
} }
.frame()
.padding()
.background(Color.white)
.cornerRadius(20.0)
}
}
mattcurtis
(Matt Curtis)
4
Looking at your code, it seems right now you essentially have:
ScrollView {
VStack {
// a header
ScrollView {
HStack {
// a button for each category, where
// tapping a category sets that category as selected
// (using the @State selectedIndex property)
}
}
ScrollView {
HStack {
// 4 navigation links to the same ItemScreen
}
}
}
}
If I'm understanding you correctly and you want
// 4 navigation links to the same ItemScreen
to instead be
// 4 navigation links to ItemScreens for the current category
Data modeling suggestions aside, could you share why referencing the value of selectedIndex to build out your 4 navigation links isn't accomplishing what you want?
mx6001
5
Data modeling suggestions aside, could you share why referencing the value of selectedIndex to build out your 4 navigation links isn't accomplishing what you want?
Hi Matt,
Thank you so much for pointing out that multiple navigation links can do the job. By referencing a selectedIndex inside a NavigationLink struct, may I ask how it works? A selectedIndex is referenced in a destination block, or its own block of code like ForEach struct?
mattcurtis
(Matt Curtis)
6
So a simple, bruteforce approach could be something like this — referencing the product index and selected category to decide what the destination view should be. I'm using a Group here because it's a quick and easy way (for demonstration purposes) to return views based on conditional logic. (Group and View.body can do this because they're annotated with @ViewBuilder)
ForEach(0..<4) {
productIndex in
NavigationLink(
destination: Group {
if selectedCategory == 0 {
if productIndex == 0 {
// Item screen for product 0 in category 0
} else if productIndex == 1 {
// Item screen for product 1 in category 0
}
} else if selectedCategory == 1 {
// and so on
}
},
label: {
ItemView(image: Image("item_\(i + 1)"), size: 200)
}
)
}
But it's easy to make a mistake while writing out what products should appear when, and would require a lot of work to maintain anytime you changed a category or product. One possible way to model the category-product relationship might be like this (note that this isn't functional, but an example:)
// Models:
struct Category {
let name: String
let products: Product
}
struct Product {
let name: String
let description: String
}
// Somewhere in your view:
let categories = [
Category(
name: "Electronics",
products: [
Product(name: "Phone", description: "It's a phone! Wow!")
]
),
Category(
name: "Beauty",
products: [
Product(name: "Lotion", description: "It's lotion! Amazing!")
]
)
]
@State var selectedCategoryIndex = 0
var body: some View {
// Show a button for each category
ForEach(categories.indices) {
categoryIndex in
let isSelected = categoryIndex == selectedCategoryIndex
// Display category button, styled based on isSelected
}
// Display link to each product in selected category
let selectedCategory = categories[selectedCategoryIndex]
ForEach(selectedCategory.products.indices) {
productIndex in
let product = selectedCategory.products[productIndex]
NavigationLink(
destination: ProductView(product: product)
label: Text(product.name)
)
}
}
mx6001
7
Hi Matt,
Thank you so much for explaining the code behind navigation links/selected index reference!
1 Like
Karl
(👑🦆)
8
Hello,
This forum is only about the Swift language. Apple has requested that questions/comments about their proprietary frameworks should be asked on the Apple developer forums .
It's great that a member of the community helped you regardless, but in future please direct SwiftUI-related questions to the Apple developer forums.
Thank you.