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
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.

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.

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
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)
}
}
}
}
}