I have created the background using a shape struct called VectorShape and created a func path that draws out the VectorShape. Below is the code:
import SwiftUI
struct VectorShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.size.width
let height = rect.size.height
path.move(to: CGPoint(x: 0.083771389*width, y: 0.0048507464*height))
path.addLine(to: CGPoint(x: 0.0625162683*width, y: 0.0048507464*height))
path.addCurve(to: CGPoint(x: 0.0579251837*width, y: 0.0703358247*height), control1: CGPoint(x: 0.0548233261*width, y: 0.0097014928*height), control2: CGPoint(x: 0.058639587*width, y: 0.0562963474*height))
path.addCurve(to: CGPoint(x: 0.0625162683*width, y: 0.7512437811*height), control1: CGPoint(x: 0.0565676091*width, y: 0.0970149301*height), control2: CGPoint(x: 0.0625162683*width, y: 0.5410447761*height))
path.addLine(to: CGPoint(x: 0, y: 0.7512437811*height))
path.addCurve(to: CGPoint(x: 0.0021402598*width, y: 1.3582089552*height), control1: CGPoint(x: 0, y: 0.7512437811*height), control2: CGPoint(x: 0.0021406803*width, y: 1.1144278607*height))
path.addLine(to: CGPoint(x: 0.083771389*width, y: 1.3582089552*height))
path.addLine(to: CGPoint(x: 0.7362637363*width, y: 1.3582089552*height))
path.addLine(to: CGPoint(x: 0.7362637363*width, y: 0))
path.addLine(to: CGPoint(x: 0.083771389*width, y: 0.0048507464*height))
path.closeSubpath()
return path
}
}
#Preview{
VectorShape().fill().frame(width: 273, height: 201)
}
Currently, I am not sure:
- How do I contain and create a list within the VectorShape and make the list dynamic such that the VectorShape vertically moves downwards when there are new items in the list
I have tried several ideas such as creating a zstack, and then within the zstack, I added my Vector shape and a vstack for the list section. However, the list would not be contained within the VectorShape. Hence, I am not sure how to make the list dynamic such that the VectorShape vertically moves downwards as there are new items in the list. Do I need to use another software for the dynamic part?
Below is what I have done as a structure:
import SwiftUI
struct ChecklistItem: Identifiable {
var id = UUID()
var title: String
var isChecked: Bool
}
struct ChecklistTest: View {
@State private var checklistItems = [
ChecklistItem(title: "Item 1", isChecked: false),
ChecklistItem(title: "Item 2", isChecked: false),
ChecklistItem(title: "Item 3", isChecked: false)
]
var body: some View {
ZStack{
VectorShape()
.fill(Color(#colorLiteral(red: 1, green: 0.96009022, blue: 0.8565915227, alpha: 1)))
.stroke(.black)
.frame(width: 300, height: 300)
VStack {
ForEach($checklistItems) { $item in
HStack {
Text(item.title)
Spacer()
Image(systemName: item.isChecked ? "checkmark.square" : "square")
.onTapGesture {
item.isChecked.toggle()
}
}
.padding(5)
if item.id != checklistItems.last?.id {
Divider()
.overlay(.black)
}
}
}
.frame(width: 280, height: 150, alignment: .leading)
.padding(.top, 60)
}
}
}
#Preview {
ChecklistTest()
}
First, fix the drawing code for the shape so that the height of the “sticking out” part does not depend on the height of the shape. Here I added a parameter to the shape, so that callers can pass in a fixed height for the “sticking out” part.
struct VectorShape: Shape {
let insetHeight: CGFloat
func path(in rect: CGRect) -> Path {
// Here I simply subtracting a smaller rectangle from 'rect'
Path(roundedRect: rect, cornerRadius: 0)
.subtracting(
Path(
roundedRect: .init(x: rect.midX, y: rect.minY, width: rect.width / 2, height: insetHeight),
cornerRadius: 0
)
)
// More customisable ways to draw this shape is out of the scope of this question
}
}
Second, remove all the height:
parameters you pass to .frame
. We want a dynamic height after all.
Third, use the shape as a background
of the VStack
, so that its size matches the size of the VStack
.
VStack {
VStack {
// I have added invisible dividers to the top and bottom of the list,
// so that the first and last items look better
Divider().opacity(0)
ForEach($checklistItems) { $item in
HStack {
Text(item.title)
Spacer()
Image(systemName: item.isChecked ? "checkmark.square" : "square")
.onTapGesture {
item.isChecked.toggle()
}
}
.padding(5)
Divider()
.overlay(.black)
.opacity(item.id != checklistItems.last?.id ? 1 : 0)
}
}
.frame(width: 280, alignment: .leading)
.padding(.top, 40)
.background(.gray, in: VectorShape(insetHeight: 40)) // the shape is used here!
Spacer()
// I've also added a button to add new items
Button("Add Item") {
checklistItems.append(.init(title: "New Item", isChecked: false))
}
}