I’ve got to the point where I can expand and collapse a card, showing the detail, but on to collapsing an expanded card when tabbing a different card? I haven’t figured out how to do that yet.
The expand is working but I need to add functionality so that when tapped, the expanded card should collapse while the card being tapped should expand.
ContentView:
struct ContentView: View {
@EnvironmentObject var model: BuyMyRideModel
var body: some View {
VStack {
Text(Constants.appTitle)
.bold()
.font(.title)
.foregroundColor(ColorManager.titleColor)
// List of vehicles
ScrollView {
// Loop through the list of vehicles and add them to the screen
ForEach(model.vehicles) { vehicle in
CardView(vehicle: vehicle, isExpanded: model.selection.contains(vehicle))
.onTapGesture {
// Expand/collapse the card when it's tapped
model.expandCollapse(vehicle)
}
// Animate the expand/collapse
.animation(.easeInOut(duration: 0.2), value: model.selection)
}
}
}
.padding()
.background(ColorManager.backgroundColor)
}
}
CardView:
struct CardView: View {
var vehicle: Vehicle
let isExpanded: Bool
var body: some View {
HStack {
// Card
ZStack(alignment: .leading) {
// Background
Rectangle()
.foregroundColor(.white)
.cornerRadius(Constants.cornerRadius)
HStack {
// Vehicle image
Image(vehicle.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: 175, minHeight: 125)
.clipped()
.cornerRadius(Constants.cornerRadius, corners: [.topLeft, .bottomLeft])
// Vehicle info
VStack(alignment: .leading) {
Text(vehicle.make + " " + vehicle.model)
.font(Font.system(size: Constants.headline, weight: .semibold))
.foregroundColor(ColorManager.titleColor)
Text(vehicle.price)
.font(Font.system(size: Constants.footnote))
.foregroundColor(ColorManager.lightColor)
// Rating
HStack {
// The number of stars rated
ForEach (1..<vehicle.rating+1) { i in
Image(systemName: "star.fill")
.font(Font.system(size: Constants.caption2))
.padding(.horizontal, -5)
}
.foregroundColor(ColorManager.titleColor)
// The number of stars not rated
if vehicle.rating < 5 {
ForEach(0..<5-vehicle.rating) { i in
Image(systemName: "star")
.font(Font.system(size: Constants.caption2))
.padding(.horizontal, -5)
}
.foregroundColor(ColorManager.lightColor)
}
}
// Pros and Cons
if isExpanded {
// Are there any Pros
if vehicle.pros != nil {
Text(Constants.pros + ":")
.bold()
.font(Font.system(size: Constants.footnote))
VStack(alignment: .leading) {
// Loop through the pros
ForEach(vehicle.pros!, id: \.self) { item in
HStack(alignment: .top) {
Text(" •")
Text(item)
}
.font(Font.system(size: Constants.footnote))
}
}
}
// Are the any Cons
if vehicle.cons != nil {
Text(Constants.cons + ":")
.bold()
.font(Font.system(size: Constants.footnote))
VStack(alignment: .leading) {
// Loop through the cons
ForEach(vehicle.cons!, id: \.self) { item in
HStack(alignment: .top) {
Text(" •")
Text(item)
}
.font(Font.system(size: Constants.footnote))
}
}
}
// There are no Pros or Cons
if vehicle.pros == nil && vehicle.cons == nil {
Text(Constants.noDetails)
.font(Font.system(size: Constants.footnote))
}
}
Spacer()
Text(Constants.ShowDetails)
.bold()
.font(Font.system(size: Constants.subhead))
.foregroundColor(.blue)
}
.padding(.vertical, 5)
.padding(.trailing, 5)
}
}
}
.contentShape(Rectangle())
}
}
// Extend View so we can apply radius to the individual corners of an object
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape( RoundedCorner(radius: radius, corners: corners) )
}
}
// Struct so individual corner radius can be done
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
return Path(path.cgPath)
}
}
ViewModel:
class BuyMyRideModel: ObservableObject {
@Published var vehicles = [Vehicle]()
@Published var selection: Set<Vehicle> = [] // local data that changes dynamically
init() {
// Create data service instance
let service = DataService()
// Get the data
self.vehicles = service.getLocalData()
}
// Expand/Collapse selected vehicle details
func expandCollapse(_ vehicle: Vehicle) {
// Keep track of vehicle selected
if selection.contains(vehicle) {
// Collapse
selection.remove(vehicle)
} else {
// Expand
selection.insert(vehicle)
}
}
}