I've been working on a problem for the last couple of days and I can't seem to figure out how to approach it.
I'd like to achieve an hierarichal system of material-groups where the leaf would be a specific material.
I basically have two CoreData entities, Material
and MaterialGroup
.
MaterialGroup
has 3 relationships: subGroup
(MaterialGroups that belong to a specific MaterialGroup), superGroup
(the inverse of subGroup) and subMaterial
(the leaf of the hierarchy).
The ideal scenario which I'd like to accomplish is a selectable list of MaterialGroups
, for every selection I'd have 3 options:
(1) - Create MaterialGroup
on the same level of the hierarchy
(2) - Create subGroup
for selection
(3) - Create a Material
for MaterialGroup
A the moment I can create these hierarchies but I cannot display them, I just have a plain selectable list with no depth. I cannot figure out how to make the lists expandable and selectable at the same time.
Kind of like
this.
I've also wondered how to then select the Material
when I worked my way to a leaf. If I were to use something like List(materialGroupArray, selection: $materialGroupSelection)
, I couldn't select the Material
.
I'd be grateful for any tips.
Have a nice day.
Here is a possible approach for a hierarchical list:
import SwiftUI
class Material {
let name: String
init(name: String) {
self.name = name
}
}
class MaterialGroup: Identifiable {
let id: String
var subGroup: MaterialGroup?
let subMaterial: Material?
init(id: String, subGroup: MaterialGroup?, subMaterial: Material?) {
self.id = id
self.subGroup = subGroup
self.subMaterial = subMaterial
}
}
let group1 = MaterialGroup(id: "G1",subGroup: nil, subMaterial: Material(name: "M1"))
let group2 = MaterialGroup(id: "G2",subGroup: nil, subMaterial: Material(name: "M2"))
let group3 = MaterialGroup(id: "G3",subGroup: group2, subMaterial: nil)
let group4 = MaterialGroup(id: "G4",subGroup: group3, subMaterial: nil)
let demoData: [MaterialGroup] = [
group1,
group4,
MaterialGroup(id: "G5",subGroup: nil, subMaterial: Material(name: "M5"))
]
struct GroupView: View {
let group: MaterialGroup
@State var showChildRow = false
@State var selected = false
var hasChildren: Bool {
group.subGroup != nil
}
var displayName: String {
if let material = group.subMaterial {
return material.name
} else {
return group.id
}
}
var body: some View {
HStack {
if hasChildren {
Image(systemName: showChildRow ? "chevron.down" : "chevron.right")
}
Text("\(displayName)").font(.caption2)
Spacer()
}.padding()
.transition(.scale)
.background(RoundedRectangle(cornerRadius: 15).fill(selected ? Color.red.opacity(0.1) : Color.black.opacity(0.1)))
.onTapGesture {
if hasChildren {
withAnimation {
showChildRow.toggle()
}
} else {
// You could use a closure here
// onSelect: (Material) -> Void
selected.toggle()
}
}
if hasChildren && showChildRow {
GroupView(group: group.subGroup!)
.padding(.leading)
}
}
}
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(demoData) { group in
VStack(alignment: .leading) {
GroupView(group: group)
}
}
}.padding(.horizontal)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}