Thanks Claude31 and OOPer. Here's my full code: (quite long and may be a little bit confusing)
(1). My custom class:
class BudgetItem {
var name: String
var date: Date
var category: String
var amount: Double
var note: String
init(name: String = "New Item", date: Date = Date(), category: String = "Unknown", amount: Double = 0.0, note: String = "A new budget item") {
self.name = name
self.date = date
self.category = category
self.amount = amount
self.note = note
}
}
class BudgetBook {
var title: String
var color: String
var items: [BudgetItem]
init(title: String = "New Book", color: String = "blue", items: [BudgetItem] = []) {
self.title = title
self.color = color
self.items = items
}
}
(2). BookItemView.swift
struct BookItemView: View {
var title: String
var color: Color
var body: some View {
ZStack {
BookPageView(color: color)
BookArcView()
VStack {
Text(title).font(.title).lineLimit(2)
Image(systemName: "\(title.first?.lowercased() ?? "a").circle").scaleEffect(2)
}
}
}
}
struct BookPageView: View {
var color: Color
var body: some View {
GeometryReader { geometry in
Path { path in
path.move(to: .zero)
path.addLine(to: CGPoint(x: geometry.size.width - 40, y: 0))
path.addQuadCurve(to: CGPoint(x: geometry.size.width, y: 40), control: CGPoint(x: geometry.size.width, y: 0))
path.addLine(to: CGPoint(x: geometry.size.width, y: geometry.size.height - 40))
path.addQuadCurve(to: CGPoint(x: geometry.size.width - 40, y: geometry.size.height), control: CGPoint(x: geometry.size.width, y: geometry.size.height))
path.addLine(to: CGPoint(x: 0, y: geometry.size.height))
path.addLine(to: .zero)
}.fill(self.color)
}
}
}
struct BookArcView: View {
var body: some View {
GeometryReader { geometry in
VStack {
ForEach(0 ..< 5) { _ in
ArcView(
startPoint: CGPoint(x: 0, y: 5),
diameter: geometry.size.height / 10,
length: 10
)
}
}
}
}
}
struct ArcView: View {
var startPoint: CGPoint
var diameter: CGFloat
var length: CGFloat
var body: some View {
Path { path in
path.move(to: self.startPoint)
path.addCurve(
to: CGPoint(x: self.startPoint.x, y: self.startPoint.y + self.diameter),
control1: CGPoint(x: self.startPoint.x - self.diameter * 0.6, y: self.startPoint.y),
control2: CGPoint(x: self.startPoint.x - self.diameter * 0.6, y: self.startPoint.y + self.diameter))
path.addLine(to: CGPoint(x: self.startPoint.x + self.length, y: self.startPoint.y + self.diameter))
}.stroke(lineWidth: 3)
}
}
(3). BudgetView.swift
struct BudgetView: View {
@State var showPersonalCenter = false
@State var books: [BudgetBook] = [
BudgetBook(title: "Daily", color: "blue", items: []),
BudgetBook(title: "Electronic", color: "red", items: []),
BudgetBook(title: "Grocery", color: "green", items: [])
]
private var numberOfBooks: Int {
return books.count
}
var body: some View {
NavigationView {
ScrollView {
VStack {
Button(action: { self.createNewBook() }) {
Text("+ Create a New Book")
.foregroundColor(.white)
.padding()
}.background(Color.gray).cornerRadius(10)
VStack(alignment: .leading) {
BookItemGroupVertical(items: self.books)
}
}
}
.navigationBarTitle("Budget")
.navigationBarItems(trailing:
Image(systemName: "person.crop.circle")
.scaleEffect(1.5)
.foregroundColor(.blue)
.onTapGesture {
self.showPersonalCenter.toggle()
}
.sheet(isPresented: $showPersonalCenter) { PersonalCenterView() }
)
}
}
func createNewBook() {
//TODO: Create a new budget book
}
}
struct BookItemGroupHorizontal: View {
var items: [BudgetBook]
private var number: Int {
return items.count
}
var body: some View {
HStack {
ForEach(0 ..< number) { index in
NavigationLink(destination: BudgetDetailView()) {
BookItemView(title: self.items[index].title, color: .color(from: self.items[index].color))
.frame(width: 150, height: 200)
.padding()
}.accentColor(.primary)
}
}
}
}
struct BookItemGroupVertical: View {
var items: [BudgetBook]
private var halfNumber: Int {
return Int(items.count / 2)
}
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< self.halfNumber) { index in
BookItemGroupHorizontal(items: [self.items[index * 2], self.items[index * 2 + 1]])
}
if items.count % 2 == 1 {
Text("it")
}
}
}
}
And here's is the complier error (line 84 in BudgetView.swift):
I did decompose my custom view into smaller subviews but the compile still cannot type-check the view.