Capvera: Building Portfolio Intelligence for Real Estate Investors with SwiftUI and AI

Sometimes you build an app because you see a market need. Other times, you build it as a technical exploration to see what's possible with modern SwiftUI and AI-assisted development. Capvera falls into both categories—a sophisticated iPad application for real estate portfolio management that I built with Claude Opus 4.5 in the Cursor IDE.

This app isn't ready for production. In fact, I'm not even sure if I'll ever deploy it publicly. My son has expressed interest in potentially using the concept for a college project, which might be where this ultimately goes. But the development journey was fascinating enough that I wanted to share the technical details and design decisions that went into creating a professional-grade real estate investment platform.

Capvera App Icon

Quick Links

The Vision: Portfolio Intelligence for Serious Investors

The idea behind Capvera was to build something that feels purpose-built for real estate investors—not a retrofitted spreadsheet, not a web app trying to be native, but a true iPad experience designed for the way investors actually work.

The target user is someone who owns anywhere from a few rental units to a portfolio of 30+ properties. They need to:

  • Track cash flow, occupancy, and NOI in real time
  • Underwrite new deals with professional-grade analysis
  • Organize documents (leases, receipts, insurance certificates)
  • Generate investor-ready reports for partners and lenders
  • Collaborate with bookkeepers and partners
  • Access everything on the go from their iPad

Key Features

Portfolio Dashboard: Real-time KPIs across your entire portfolio—net cash flow, occupancy rates, NOI calculations, and equity tracking. Everything updates automatically as you record transactions.

Deal Analyzer: Underwrite acquisitions with multiple scenario comparison, sensitivity analysis on key assumptions, and all the metrics that matter (cap rate, cash-on-cash returns, DSCR).

Smart Tracking: Every transaction categorized and organized. Income, expenses, receipts—all linked to properties, units, and time periods.

Document Management: Drag-and-drop uploads with smart linking. Find any lease, insurance certificate, or repair receipt in seconds with full-text search.

Professional Reports: Generate beautiful PDFs and spreadsheets for partners, lenders, and tax preparation.

The Technical Stack

Building Capvera gave me a chance to work with Apple's latest frameworks. The entire app is written in Swift 6 using SwiftUI for the interface and SwiftData for persistence.

SwiftData: Modern Persistence Layer

One of the most interesting technical aspects is the data model. Real estate portfolio management involves complex relationships between entities:

@Model
class PropertyAsset {
    var name: String
    var address: String
    var propertyType: PropertyType
    var purchaseDate: Date?
    var purchasePrice: Decimal?
    
    @Relationship(deleteRule: .cascade) 
    var spaces: [Space] = []
    
    @Relationship(deleteRule: .cascade) 
    var documents: [Document] = []
    
    @Relationship(deleteRule: .cascade) 
    var cashEvents: [CashEvent] = []
    
    var portfolio: Portfolio?
}

The data model includes:

  • Properties with spaces (units), documents, and financial events
  • Leases with charge schedules (rent, utilities, parking)
  • Deals with multiple scenarios for acquisition analysis
  • Cash events for income and expense tracking
  • Loans with payment schedules and amortization
  • Organizations and Parties for tenant and partner management
  • Tasks and Milestones for deal tracking

SwiftData makes this feel natural with declarative relationships and automatic change tracking. The @Relationship macro handles cascading deletes, and queries become beautifully simple:

@Query(filter: #Predicate<PropertyAsset> { property in
    property.portfolio?.id == portfolioId
}, sort: \PropertyAsset.name) 
var properties: [PropertyAsset]

Financial Calculations Service

Real estate investing requires precise financial calculations. Capvera includes a dedicated FinancialCalculationService that handles:

Property-Level Metrics:

  • Gross Scheduled Income (GSI)
  • Effective Gross Income (EGI)
  • Net Operating Income (NOI)
  • Cash Flow calculations
  • Cap Rate computation
  • Cash-on-Cash Return
  • Internal Rate of Return (IRR)

Deal Analysis:

  • Multiple scenario modeling
  • Sensitivity analysis on key assumptions
  • Debt Service Coverage Ratio (DSCR)
  • Loan amortization schedules
  • Return on Investment (ROI) projections

The service is structured to be testable and reusable across different views:

class FinancialCalculationService {
    static func calculateNOI(
        for property: PropertyAsset,
        in period: DateInterval
    ) -> Decimal {
        let income = calculateGrossIncome(property, period)
        let expenses = calculateOperatingExpenses(property, period)
        return income - expenses
    }
    
    static func calculateCapRate(
        noi: Decimal,
        propertyValue: Decimal
    ) -> Decimal {
        guard propertyValue > 0 else { return 0 }
        return (noi / propertyValue) * 100
    }
}

Document Intelligence with AI

One of the most ambitious features is document extraction powered by AI. The idea: drag in a lease PDF, and Capvera automatically extracts key information—tenant name, lease dates, rent amounts, security deposits.

The DocumentExtractionService uses a multi-step process:

  1. Text Extraction: Pull text from PDFs using PDFTextExtractorService or Word documents using WordTextExtractorService
  2. AI Analysis: Send extracted text to an LLM with structured prompts requesting specific fields
  3. Entity Creation: Parse the AI response and create structured data (leases, tenants, charge schedules)
class DocumentExtractionService {
    func extractLeaseInformation(
        from document: Document,
        modelContext: ModelContext
    ) async throws -> LeaseExtractionResult {
        // Extract text from PDF/Word
        let text = try await extractText(from: document)
        
        // Send to AI with structured prompt
        let prompt = DocumentExtractionPrompts.leaseExtraction(text)
        let response = try await llmService.complete(prompt)
        
        // Parse and create entities
        return try parseLeaseData(response, modelContext: modelContext)
    }
}

The prompts are carefully crafted to request JSON responses with specific schemas, making parsing reliable:

struct DocumentExtractionPrompts {
    static func leaseExtraction(_ text: String) -> String {
        """
        You are analyzing a residential or commercial lease document.
        Extract the following information and return it as JSON:
        
        {
            "tenant_name": "string",
            "landlord_name": "string",
            "property_address": "string",
            "lease_start_date": "YYYY-MM-DD",
            "lease_end_date": "YYYY-MM-DD",
            "monthly_rent": number,
            "security_deposit": number,
            "unit_number": "string"
        }
        
        Document text:
        \(text)
        """
    }
}

Component Architecture

The UI is built from reusable components that maintain consistency across the app:

Data Display:

  • DataTable: Sortable, filterable tables for rent rolls and transaction lists
  • MetricCard: KPI displays with trend indicators
  • FinancialSummaryCard: Income, expense, and NOI summaries
  • KPIPanel: Dashboard metrics with visual emphasis

Interactive Components:

  • DrillDownSheet: Hierarchical data exploration
  • SearchBar: Full-text search across documents and properties
  • PropertyCard: Property preview with key metrics
  • RentRollTable: Tenant and lease information display

Maps and Visualization:

  • PortfolioMapView: Geographic distribution of properties
  • AddressMapView: Individual property location with context
  • AddressAutocomplete: Smart address entry with validation

Document Handling:

  • PDFKitView: Native PDF viewing and annotation
  • QuickLookPreviewView: System-level document preview
  • ExtractionProgressView: AI extraction status with progress

Each component is designed to be composable and reusable. For example, MetricCard can show any KPI with appropriate formatting:

struct MetricCard: View {
    let title: String
    let value: String
    let change: Decimal?
    let trend: Trend
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(title)
                .font(.caption)
                .foregroundStyle(.secondary)
            
            HStack(alignment: .firstTextBaseline) {
                Text(value)
                    .font(.title2.bold())
                
                if let change = change {
                    Label {
                        Text(formatPercent(change))
                    } icon: {
                        Image(systemName: trend.icon)
                    }
                    .foregroundStyle(trend.color)
                }
            }
        }
    }
}

CloudKit Sync (Designed, Not Implemented)

The architecture includes CloudKitSyncService designed to synchronize data across devices, but this isn't fully implemented yet. The structure is there for:

  • Portfolio sharing between partners
  • Automatic backup to iCloud
  • Cross-device sync for iPad and Mac
  • Conflict resolution for collaborative editing

Design Tokens and Theming

Capvera uses a design system with centralized tokens for colors, spacing, typography, and effects:

struct DesignTokens {
    // Spacing scale
    static let spacing1: CGFloat = 4
    static let spacing2: CGFloat = 8
    static let spacing3: CGFloat = 12
    static let spacing4: CGFloat = 16
    
    // Color semantics
    static let accentColor = Color.blue
    static let successColor = Color.green
    static let warningColor = Color.orange
    static let errorColor = Color.red
    
    // Typography scale
    static let headingFont = Font.system(.largeTitle, design: .rounded)
    static let bodyFont = Font.system(.body, design: .default)
}

This makes the app feel cohesive and makes theme changes trivial—adjust the tokens, and the entire app updates.

Building with AI: The Cursor Experience

Developing Capvera with Claude Opus 4.5 in Cursor was a revelation. I could describe complex requirements in natural language and get production-quality SwiftUI code:

"Create a drill-down sheet that shows properties → spaces → leases with inline editing" → Complete implementation with navigation, state management, and data binding.

"Add a financial summary card showing income, expenses, and NOI with trend indicators" → Beautifully styled component with proper number formatting and color coding.

"Implement document extraction for leases using AI with structured JSON responses" → Complete service with error handling, progress tracking, and entity creation.

The AI understood SwiftUI idioms, SwiftData relationships, and even macOS design patterns. It maintained architectural consistency across features and made sensible technology choices without me needing to specify every detail.

Areas Where AI Struggled

Not everything was smooth:

  • Complex state management across multiple views sometimes required manual refinement
  • Swift concurrency patterns occasionally needed correction (actors, @MainActor, etc.)
  • Xcode project configuration and entitlements had to be manually verified
  • Performance optimization required human insight about lazy loading and caching

But overall, the AI accelerated development by an order of magnitude compared to writing everything manually.

The iPad Experience

On iPad, Capvera really shines. The larger screen makes the dashboard KPIs readable at a glance. Drag-and-drop document uploads feel natural. The pencil-friendly interface works beautifully for annotating PDFs.

The multi-column layouts adapt to portrait and landscape orientations. The navigation uses SwiftUI's NavigationSplitView for a sidebar + detail arrangement that feels right at home on iPadOS.

But here's the problem: in the US, you can't distribute or sideload iPad apps without going through the App Store or enterprise distribution. For a side project that's not production-ready, that's a dealbreaker.

The macOS Demo Workaround

Since Capvera is built entirely in SwiftUI, it compiles for macOS with minimal changes. The Mac version isn't ideal—some iPad-specific interactions don't translate perfectly, and the UI was optimized for touch rather than mouse—but it gives a reasonable preview of what the app does.

That's why I've made a macOS demo available for download. It's not the intended platform, but it lets people see the interface, explore the features, and get a sense of the design aesthetic.

What Works on macOS:

  • All views and navigation
  • Data entry and editing
  • Financial calculations
  • Document management (drag-and-drop PDFs)
  • Reports and exports

What Feels Different:

  • Hover states instead of touch feedback
  • Menu bar instead of iPad toolbars
  • Window management instead of full-screen sheets

Current State: Not Production-Ready

Let me be clear: Capvera is not ready for production. It's missing:

  • CloudKit sync implementation
  • Comprehensive error handling
  • Data import/export tools
  • User onboarding flow
  • Accessibility features
  • Performance optimization for large portfolios
  • Comprehensive testing
  • Documentation

It's a technical prototype that demonstrates what's possible, not a finished product ready for the App Store.

What's Next?

I'm genuinely unsure about Capvera's future. A few possibilities:

  1. College Project: My son is interested in the concept and might adapt it for academic work
  2. Open Source: Release the code as a learning resource for SwiftUI + SwiftData
  3. Continue Development: Polish it to production quality and pursue App Store release
  4. Archive It: Accept it as a valuable learning experience and move on

For now, it exists as a beautiful experiment in modern iOS development with AI assistance.

Technical Highlights Worth Noting

Soft Deletion Pattern

Capvera implements a SoftDeletable protocol for entities that shouldn't be permanently deleted immediately:

protocol SoftDeletable {
    var isDeleted: Bool { get set }
    var deletedAt: Date? { get set }
}

extension SoftDeletable {
    mutating func softDelete() {
        isDeleted = true
        deletedAt = Date()
    }
    
    mutating func restore() {
        isDeleted = false
        deletedAt = nil
    }
}

This allows "undo" functionality and prevents accidental data loss.

Schema Versioning

The app includes SchemaVersions to manage SwiftData migrations as the model evolves:

enum CapveraSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)
    
    static var models: [any PersistentModel.Type] {
        [PropertyAsset.self, Portfolio.self, Deal.self, /* ... */]
    }
}

This makes future schema changes safer and more predictable.

Extension Organization

The codebase uses extensions thoughtfully:

  • Date+Extensions: Fiscal quarter calculations, period generation
  • Decimal+Extensions: Currency formatting, percentage display
  • View+Accessibility: Consistent accessibility labels and hints
  • ModelContext+SafeSave: Error handling for save operations

Form Field Component

Data entry uses a reusable FormField component that handles labels, validation, and styling:

struct FormField<Content: View>: View {
    let label: String
    let required: Bool
    @ViewBuilder let content: () -> Content
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            HStack {
                Text(label)
                    .font(.subheadline.weight(.medium))
                if required {
                    Text("*").foregroundStyle(.red)
                }
            }
            content()
        }
    }
}

Design Aesthetic

The visual design was something I'm particularly pleased with. Clean, professional, with just enough visual interest to feel modern without being distracting.

The color scheme uses:

  • Blue accents for primary actions and emphasis
  • Green for positive metrics (income, gains)
  • Red for negative values (expenses, losses)
  • Gray scale for backgrounds and secondary content
  • Semantic colors for status indicators (warning, error, success)

Typography is clean and hierarchical. Spacing is consistent. Cards have subtle shadows and borders. The whole thing feels like it belongs on an iPad Pro.

Lessons Learned

Building Capvera taught me several things:

  1. SwiftData is powerful: The declarative syntax makes complex relationships feel simple
  2. AI accelerates development: Claude in Cursor made me 10x more productive
  3. iPad constraints are real: Sideloading limitations affect development workflow
  4. Financial modeling is complex: Real estate investment calculations have edge cases
  5. Component architecture matters: Reusable components save time and ensure consistency
  6. Design systems scale: Central design tokens make polish achievable
  7. SwiftUI is mature: Building professional apps is very feasible now

Try the Demo

If you're curious about what a SwiftUI real estate portfolio management app looks like, download the macOS demo. It's not perfect, but it gives you a sense of the interface and capabilities.

Or visit capvera.surge.sh to see the marketing site and learn more about the intended feature set.

Reflections

Whether Capvera becomes a real product or remains a prototype, the development journey was valuable. It pushed my SwiftUI skills forward, demonstrated the power of AI-assisted development, and resulted in something genuinely polished (even if incomplete).

Sometimes you build apps to solve problems. Sometimes you build them to learn. Capvera was definitely the latter—and I'm pleased with how it turned out.


Daniel Wanja is a developer and founder of Nouvelles Solutions, Inc. When not building experimental iPad apps, he's usually exploring the intersection of AI and software development.