I have issue with the code to get both category and subCategory name from hierarchical list when it selected via onTapGesture. For example when I select housing category which is without any subcategory, The onTapGesture is correctly displaying the category name and its icon in the Selected Category Section. However when I select the category list with the subCategory, the onTapGesture is only selecting subCategory item name from the list .
However I am looking for the updated code so whenever I select the subCategory , it will also select the category name and icon for this particular subCategory.
For example if I select the Housing category I want to display following Items only in the the Selected Category Section:
HStack {
Image(systemName: item.icon) // this will be housing icon
Text(item.name) // this will be housing name
}
And if I select the the telephone subcategory from the Bills Category , I want to display the following items only in the Selected Category Section.
HStack {
Image(systemName: item.icon) // this will be bills icon
Text(item.name) // this will be bills category name
Text(item.subCategoryName) // this will be Telephone SubCategory name which I have the issue with the code to get subCategory name.
}
Following is the code:
import SwiftUI
struct Category: Hashable, Identifiable {
var name: String
let icon: String
var subCategory: [Category]? = nil
let id = UUID()
static func preview() -> [Category] {
[Category(name: "Bills",
icon: "lightbulb",
subCategory: [Category(name: "Electricity",
icon: "lightbulb"),
Category(name: "Telephone",
icon: "lightbulb")]),
Category(name: "Transport",
icon: "car",
subCategory: [Category(name: "Taxi",
icon: "car")]),
Category(name: "Housing", icon: "house")]
}
}
struct ContentView: View {
@State private var items = Category.preview()
@State private var name: String = ""
@State private var icon: String = ""
var body: some View {
NavigationView {
Form {
Section {
List(items, children: \.subCategory) { item in
HStack {
Image(systemName: item.icon)
Text(item.name)
}
.onTapGesture {
icon = item.icon
name = item.name
}
}
} header: {
Text("Category List")
}
Section {
HStack {
Image(systemName: icon) // Category icon
Text(name) // Category Name
// Code to display subCategory name here for the selected
}
} header: {
Text("Selected Category")
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Based on your edits, you want to show all subcategories for your main categories. You need to track the selected Category
and not Category's
parameters. Because Category
has an infinite amount of levels, you need to create a view that displays name
and icon
and, if subcategories exist, calls itself recursively to handle each level.
struct ContentView: View {
@State private var items = Category.preview()
// You need to keep track of the Category, not the parameters on the Category.
// This is nil until a Category is selected.
@State private var selectedCategory: Category?
var body: some View {
NavigationView {
Form {
Section {
List(items, children: \.subCategory) { item in
HStack {
Image(systemName: item.icon)
Text(item.name)
}
.onTapGesture {
selectedCategory = item
}
}
} header: {
Text("Category List")
}
Section {
// this unwraps the optional selectedCategory
if let selectedCategory {
CategoryView(selectedCategory: selectedCategory)
} else {
Text("Please select a category")
}
} header: {
Text("Selected Category")
}
}
}
}
}
struct CategoryView: View {
let selectedCategory: Category
var body: some View {
VStack(alignment: .leading) {
// this handles the top level.
HStack {
Image(systemName: selectedCategory.icon)
Text(selectedCategory.name)
}
if let subCategories = selectedCategory.subCategory {
// this handles the subcategories
ForEach(subCategories) { subCategory in
CategoryView(selectedCategory: subCategory)
}
}
}
}
}
Edit:
I created this for demonstration purposes only. In order to do what you want, you will need to change your underlying data structure. I would recommend implementing this with CoreData where you can more easily establish relationships between your objects. Otherwise, you are looking at a linked list. I updated your model to handle the UI, and changed the preview around to set it up as well, but hhis really is not a great data model and is for demonstration only.
struct ContentView: View {
@State private var items = Category.preview()
// You need to keep track of the Category, not the parameters on the Category.
// This is nil until a Category is selected.
@State private var selectedCategory: Category?
var body: some View {
NavigationView {
Form {
Section {
List(items, children: \.subCategory) { item in
HStack {
Image(systemName: item.icon)
Text(item.name)
}
.onTapGesture {
selectedCategory = item
}
}
} header: {
Text("Category List")
}
Section {
// this unwraps the optional selectedCategory
if let selectedCategory {
// THis will only go up one node
if let superCategory = selectedCategory.superCategory?.first {
CategoryView(selectedCategory: superCategory)
} else {
CategoryView(selectedCategory: selectedCategory)
}
} else {
Text("Please select a category")
}
} header: {
Text("Selected Category")
}
}
}
}
}
struct CategoryView: View {
let selectedCategory: Category
var body: some View {
VStack(alignment: .leading) {
// this handles the top level.
HStack {
Image(systemName: selectedCategory.icon)
Text(selectedCategory.name)
}
if let subCategories = selectedCategory.subCategory {
// this handles the subcategories
ForEach(subCategories) { subCategory in
CategoryView(selectedCategory: subCategory)
}
}
}
}
}
struct Category: Hashable, Identifiable {
var name: String
let icon: String
private (set) var superCategory: [Category]?
var subCategory: [Category]?
let id = UUID()
static func preview() -> [Category] {
[Category(name: "Bills",
icon: "creditcard",
subCategory: [Category(name: "Electricity",
icon: "lightbulb",
superCategory: [Category(name: "Bills",
icon: "creditcard",
subCategory: [Category(name: "Electricity",
icon: "lightbulb")])]
),
Category(name: "Telephone",
icon: "phone",
superCategory: [Category(name: "Bills",
icon: "creditcard",
subCategory: [Category(name: "Telephone",
icon: "phone")])]
)]),
Category(name: "Transport",
icon: "airplane",
subCategory: [Category(name: "Taxi",
icon: "car",
superCategory: [Category(name: "Transport",
icon: "airplane",
subCategory: [Category(name: "Taxi",
icon: "car")])
]
)]),
Category(name: "Housing", icon: "house")]
}
mutating func setSupercategory(_ superCategory: Category) {
// We don't want the actual Category as it would cause UI issues
self.superCategory = [
Category(name: superCategory.name,
icon: superCategory.icon,
superCategory: superCategory.superCategory,
subCategory: [self])]
}
}