Explore Your Card Collection

Navigate and interact with your credit card library through an intuitive grid-based interface, complete with dynamic filtering and responsive design.

Grid view of credit cards

Grid View

The card library is presented using a LazyVGrid, allowing the layout to adjust automatically as the window resizes. This provides a scalable way to display many cards while maintaining consistent spacing and responsiveness across different screen sizes.

| |

var body: some View {
    ScrollView {
        LazyVGrid(columns: columns, spacing: 5) {
            ForEach(filteredCreditCards, id: \.id) { card in
                CardCellView(creditCard: card)
                    //...
            }
        }
    }
}

Search & Sort

A .searchable modifier enables quick filtering of the card list by name. Sorting is handled through a Picker embedded in the toolbar menu, allowing users to reorder by open date, credit limit, or name. The setup keeps the UI simple while giving users control over how they view their data.

| |

.toolbar {
    ToolbarItemGroup(placement: .automatic) {
        Menu("Sort", systemImage: "arrow.up.arrow.down") {
            Picker("Open Date", selection: $sortOrder) {
                Text("Newest").tag([SortDescriptor(\CreditCard.openDate, order: .reverse)])
                Text("Oldest").tag(//...)
            }
            
            Picker("Credit Limit", selection: $sortOrder) {
                Text("Highest").tag(//...)
                Text("Lowest").tag(//...)
            }
            
            Picker("Name", selection: $sortOrder) {
                Text("A-Z").tag(//...)
                Text("Z-A").tag(//...)
            }
        }
    }
}

Credit Card Categories

The sidebar uses an enum-based model to categorize cards as Personal, Business, Closed, or All. Filtering is handled using straightforward logic that checks card properties, allowing users to quickly switch contexts without additional filtering UI.

| |

enum SideBarCategories: Hashable {
    case business
    case personal
    case all
    case closed
    case charts
    //...
    
    func sidebarFilter(_ creditCard: CreditCard) -> Bool {
        switch self {
        case .all:
            return creditCard.closed == nil
        case .personal:
            return !creditCard.isBusiness && creditCard.closed == nil
        case .business:
            return creditCard.isBusiness && creditCard.closed == nil
        case .closed:
            return creditCard.closed != nil
        case .charts:
            return false
        }
    }
}

Dive Into Card Details

Access a detailed view of each credit card, showcasing key attributes and history in a structured layout, leveraging SwiftUI’s navigation and data binding.

Card detail view

Detail View

Double-clicking a card opens a custom DetailView, displaying a structured breakdown of each card’s properties, including its name, type, credit limit, and associated bank. Information is grouped visually using GroupBox and VStack to keep the layout organized and easy to scan. The view is scrollable and designed to scale with the amount of content.

| |

var body: some View {
    ScrollView(.vertical, showsIndicators: false) {
        VStack {
            CardImageView(creditCard: creditCard)
                //...
            GroupBox {
                VStack{
                    DetailRowView(label: "Card Name", value: creditCard.name)
                    DetailRowView(label: "Type", value: creditCard.isBusiness ? "Business" : "Personal")
                    //...
                }
            } label: {
                Label("Card Details", systemImage: "creditcard")
                    //...
            }
            //...
        }
    }
    .navigationTitle("Credit Card Details")
}

Show History

The history section displays a chronological list of changes associated with a card. This includes modifications, promotions, and status updates, stored in a related SwiftData model. It gives users a clear record of how a card’s data has evolved over time.

| |

var body: some View {
    VStack {
        HStack {
            if creditCard.history.isEmpty {
                Text("No history available")
                    //...
            } else {
                List {
                    ForEach(creditCard.history, id: \.self) { history in
                        Text("\(history.date, style: .date)  \(history.entry)")
                            //...
                    }
                }
            }
        }
    }
}

Add and Manage Cards

Streamline credit card management with forms for adding, editing, and deleting entries, designed with SwiftUI’s Form and SwiftData for persistent storage.

Card management form

Adding, Editing, Deleting

Card management is handled via modals that open when adding or editing a card. The data is stored using SwiftData and updates are reflected in real time. Deletions prompt a confirmation alert, using SwiftUI’s native .alert and .sheet behaviors.

| |

.toolbar {
    ToolbarItemGroup(placement: .automatic) {
        //...
        Button(action: addCard) {//...}
        Button(action: toggleEditing) {//...}
        Button(action: deleteCard) {//...}
    }
}
.alert(item: $cardToDelete) { card in
    Alert(title: Text("Delete Card"), message: Text("Are you sure you want to delete \(card.name)?"), 
        primaryButton: .destructive(Text("Delete")) {
            deleteCard(card)
        },
        secondaryButton: .cancel()
    )
}
.sheet(isPresented: $isEditing) {
    if let selectedCard = selectedCard {
        FormEditView(creditCard: selectedCard)
    }
}
.sheet(isPresented: $newCard) {
    FormAddView()
}

Form Breakdown

The form includes fields for basic card details like name, credit limit, and bank. Toggles are used for business and charge card status. The layout is built with SwiftUI’s Form, using @Binding to connect user input directly to the model.

| |

struct FormView: View {
    //...
    @Binding var creditCard: CreditCard
    @Binding var //...
    var body: some View {
        ScrollView {
            Form {
                Section("Card Details") {
                    CustomTextField(title: "Card Name", text: $creditCard.name, width: .infinity, prompt: "Sapphire Reserve", borderColor: .accent)
                    //...
                }
                Section("Promotions") {
                    //...
                }
                //...
            }
        }
    }
}
| |
modelContext.insert(newCard)
modelContext.delete(cardToRemove)
| |

Form {
    Toggle("Is Business", isOn: $isBusiness)
    Picker("Bank", selection: $selectedBank) {
        ForEach(banks) { bank in Text(bank.name) }
    }
}

Visualize Your Data

Transform raw credit card data into meaningful charts, providing insights into limits, age, and bank distribution, built with SwiftUI’s potential for custom visualizations.

Charts view

Charts View

The Charts view summarizes credit card data visually, using bar and pie charts to show limit distribution, card age, and bank breakdowns. Charts are placed in a grid layout and backed by queries on the SwiftData model for live data access.

| |

struct ChartsContentView: View {
    @Query(sort: \CreditCard.openDate) private var cards: [CreditCard]
    @Query(sort: \Bank.name) private var banks: [Bank]
    //...
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                BarChartView {
                    CreditLimitBreakdownChart(cards: cards)
                }
                BarChartView {
                    CreditAgeChartView(cards: cards)
                }
                PieChartView {
                    BankBreakdownChart(banks: banks)
                }
            }
        }
    }
}