I have been attempting to follow this checklist design. My approach was to divide the checklist into 2 parts: Checklist Header and Checklist Background. Below I have illustrated this part:
From there I created 2 swift files:
ChecklistHeader.swift :
import SwiftUI
struct ChecklistHeader: Shape {
var folder_color = #colorLiteral(red: 1, green: 0.9529411765, blue: 0.8235294118, alpha: 1)
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.00667*width, y: 0.28571*height))
path.addCurve(to: CGPoint(x: 0.04*width, y: 0.04762*height), control1: CGPoint(x: 0.00667*width, y: 0.15422*height), control2: CGPoint(x: 0.02159*width, y: 0.04762*height))
path.addLine(to: CGPoint(x: 0.96*width, y: 0.04762*height))
path.addCurve(to: CGPoint(x: 0.99333*width, y: 0.28571*height), control1: CGPoint(x: 0.97841*width, y: 0.04762*height), control2: CGPoint(x: 0.99333*width, y: 0.15422*height))
path.addLine(to: CGPoint(x: 0.99333*width, y: 0.95238*height))
path.addLine(to: CGPoint(x: 0.00667*width, y: 0.95238*height))
path.addLine(to: CGPoint(x: 0.00667*width, y: 0.28571*height))
path.closeSubpath()
return path
}
}
struct ChecklistHeaderStroke: 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.00667*width, y: 0.28571*height))
path.addCurve(to: CGPoint(x: 0.04*width, y: 0.04762*height), control1: CGPoint(x: 0.00667*width, y: 0.15422*height), control2: CGPoint(x: 0.02159*width, y: 0.04762*height))
path.addLine(to: CGPoint(x: 0.96*width, y: 0.04762*height))
path.addCurve(to: CGPoint(x: 0.99333*width, y: 0.28571*height), control1: CGPoint(x: 0.97841*width, y: 0.04762*height), control2: CGPoint(x: 0.99333*width, y: 0.15422*height))
path.addLine(to: CGPoint(x: 0.99333*width, y: 0.95238*height))
path.move(to: CGPoint(x: 0.00667*width, y: 0.95238*height))
path.addLine(to: CGPoint(x: 0.00667*width, y: 0.28571*height))
return path
}
}
#Preview {
ChecklistHeader()
.fill(Color(#colorLiteral(red: 1, green: 0.9529411765, blue: 0.8235294118, alpha: 1)))
.overlay(
ChecklistHeaderStroke()
.stroke(Color.black, lineWidth: 0.5)
)
.frame(width: 148, height: 19)
}
ChecklistBackground.swift:
import SwiftUI
struct ChecklistBackground: Shape {
var color_folder = #colorLiteral(red: 0.3764705882, green: 0.3764705882, blue: 0.3764705882, alpha: 1)
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, y: 0))
path.addLine(to: CGPoint(x: width, y: 0))
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.addLine(to: CGPoint(x: 0, y: 0))
path.closeSubpath()
return path
}
}
[](https://i.sstatic.net/82yJ2KHT.png)
#Preview {
ChecklistBackground()
.fill(Color(#colorLiteral(red: 1, green: 0.96009022, blue: 0.8565915227, alpha: 1)))
.stroke(.black)
.frame(width: 300, height: 300)
}
This is the file I use to preview the combined checklist, it’s called ChecklistView.swift :
import SwiftUI
struct ChecklistItem: Identifiable {
var id = UUID()
var title: String
var isChecked: Bool
}
struct ChecklistView: View {
@State private var checklistItems = [
ChecklistItem(title: "item 1", isChecked: false),
ChecklistItem(title: "item 2", isChecked: false),
ChecklistItem(title: "item 3", isChecked: false),
ChecklistItem(title: "item 4", isChecked: false),
ChecklistItem(title: "item 5", isChecked: false)
]
var body: some View {
ZStack {
VStack(spacing: 0) {
// Checklist
VStack {
ForEach($checklistItems) { $item in
HStack {
Text(item.title)
Spacer()
Image(systemName: item.isChecked ? "checkmark.square" : "square")
.onTapGesture {
item.isChecked.toggle()
}
}
.padding(.leading, 20)
.padding(.trailing, 20)
Divider().frame(width: 250)
.overlay(Color.black)
.opacity(item.id != checklistItems.last?.id ? 1 : 0)
}
}
.frame(width: 280, alignment: .leading)
.padding(.top, 10)
.padding(.bottom, 10)
.background(Color(#colorLiteral(red: 1, green: 0.96009022, blue: 0.8565915227, alpha: 1)), in: ChecklistBackground()) // Note: the folder shape is used here as a background
.overlay(
ChecklistBackground()
.stroke(Color.black, lineWidth: 1)
)
Spacer()
Button("Add Item") {
checklistItems.append(.init(title: "New Item", isChecked: false))
}
}
// Checklist header
VStack {
HStack(spacing: 0) {
ChecklistHeader()
.fill(Color(#colorLiteral(red: 1, green: 0.96009022, blue: 0.8565915227, alpha: 1)))
.overlay(
ChecklistHeaderStroke()
.stroke(Color.black, lineWidth: 1)
)
.frame(width: 148, height: 19)
.padding(.leading, 0)
Spacer()
}
.frame(width: 282, alignment: .leading)
Spacer()
}
.padding(.top, -17.5)
}
.padding(.top, 20)
}
}
#Preview {
ChecklistView()
}
My approach was just to create a ZStack and place the Checklist Header on top of the Checklist Background to hide its black stroke/outline. However, I really don’t think this approach I am using is great at all to create the overall shape of the checklist. How would an experienced UI/UX developer approach this? I am a beginner and have only coded Swift for a month.