Files
PlantGuide/Docs/IMPLEMENTATION_PLAN.md
Trey t 136dfbae33 Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow
- Add Core Data persistence for plants, care schedules, and cached API data
- Create collection view with grid/list layouts and filtering
- Build plant detail views with care information display
- Integrate Trefle botanical API for plant care data
- Add local image storage for captured plant photos
- Implement dependency injection container for testability
- Include accessibility support throughout the app

Bug fixes in this commit:
- Fix Trefle API decoding by removing duplicate CodingKeys
- Fix LocalCachedImage to load from correct PlantImages directory
- Set dateAdded when saving plants for proper collection sorting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:18:01 -06:00

10 KiB

Botanica - Plant Identification iOS App

Overview

Custom iOS 17+ app using SwiftUI that identifies plants via camera with care schedules.

Stack:

  • On-device ML: PlantNet-300K model converted to Core ML
  • Online API: Pl@ntNet (my.plantnet.org) for higher accuracy
  • Care data: Trefle API (open source botanical database)
  • Architecture: Clean Architecture + MVVM

Phase 1: Foundation (Week 1-2)

Goal: Core infrastructure with camera capture

Task Description
1.1 Create Xcode project (iOS 17+, SwiftUI)
1.2 Set up folder structure (App/, Core/, Domain/, Data/, ML/, Presentation/)
1.3 Implement DIContainer.swift for dependency injection
1.4 Create domain entities: Plant, PlantIdentification, PlantCareSchedule, CareTask
1.5 Define repository protocols in Domain/RepositoryInterfaces/
1.6 Build NetworkService.swift with async/await and multipart upload
1.7 Implement CameraView + CameraViewModel with AVFoundation
1.8 Set up Core Data stack for persistence
1.9 Create tab navigation (Camera, Collection, Care, Settings)

Deliverable: Working camera capture with photo preview


Phase 2: On-Device ML (Week 3-4)

Goal: Offline plant identification with Core ML

Task Description
2.1 Download PlantNet-300K pre-trained ResNet weights
2.2 Convert to Core ML using coremltools (Python script)
2.3 Add PlantNet300K.mlpackage to Xcode
2.4 Create PlantLabels.json with 1,081 species names
2.5 Implement PlantClassificationService.swift using Vision framework
2.6 Create ImagePreprocessor.swift for model input normalization
2.7 Build IdentifyPlantOnDeviceUseCase.swift
2.8 Create IdentificationView showing results with confidence scores
2.9 Build SpeciesMatchCard and ConfidenceIndicator components
2.10 Performance test on device (target: <500ms inference)

Deliverable: End-to-end offline identification flow


Phase 3: PlantNet API Integration (Week 5-6)

Goal: Hybrid identification with API fallback

Task Description
3.1 Register at my.plantnet.org for API key
3.2 Create PlantNetEndpoints.swift (POST /v2/identify/{project})
3.3 Implement PlantNetAPIService.swift with multipart image upload
3.4 Create DTOs: PlantNetIdentifyResponseDTO, PlantNetSpeciesDTO
3.5 Build PlantNetMapper.swift (DTO → Domain entity)
3.6 Implement IdentifyPlantOnlineUseCase.swift
3.7 Create HybridIdentificationUseCase.swift (on-device first, API for confirmation)
3.8 Add network reachability monitoring
3.9 Handle rate limiting (500 free requests/day)
3.10 Implement IdentificationCache.swift for previous results

Deliverable: Hybrid identification combining on-device + API


Phase 4: Trefle API & Plant Care (Week 7-8)

Goal: Complete care information and scheduling

Task Description
4.1 Register at trefle.io for API token
4.2 Create TrefleEndpoints.swift (GET /plants/search, GET /species/{slug})
4.3 Implement TrefleAPIService.swift
4.4 Create DTOs: TrefleSpeciesDTO, GrowthDataDTO
4.5 Build TrefleMapper.swift mapping growth data to care schedules
4.6 Implement FetchPlantCareUseCase.swift
4.7 Create CreateCareScheduleUseCase.swift
4.8 Build PlantDetailView with CareInformationSection
4.9 Implement CareScheduleView with upcoming tasks
4.10 Add local notifications for care reminders

Deliverable: Full plant care data with watering/fertilizer schedules


Phase 5: Plant Collection & Persistence (Week 9-10)

Goal: Saved plants with full offline support

Task Description
5.1 Define Core Data models (PlantMO, CareScheduleMO, IdentificationMO)
5.2 Implement CoreDataPlantStorage.swift
5.3 Build PlantCollectionRepository.swift
5.4 Create use cases: SavePlantUseCase, FetchCollectionUseCase
5.5 Build CollectionView with grid layout
5.6 Implement ImageCache.swift for offline images
5.7 Add search/filter in collection

Deliverable: Full plant collection management with offline support


Phase 6: Polish & Release (Week 11-12)

Goal: Production-ready application

Task Description
6.1 Build SettingsView (offline mode toggle, API status, cache clear)
6.2 Add comprehensive error handling with ErrorView
6.3 Implement loading states with shimmer effects
6.4 Add accessibility labels and Dynamic Type support
6.5 Performance optimization pass
6.6 Write unit tests for use cases and services
6.7 Write UI tests for critical flows
6.8 Final QA and bug fixes

Deliverable: App Store ready application


Project Structure

Botanica/
├── App/
│   ├── BotanicaApp.swift
│   └── Configuration/
│       ├── AppConfiguration.swift
│       └── APIKeys.swift
├── Core/
│   ├── DI/DIContainer.swift
│   ├── Extensions/
│   └── Utilities/
├── Domain/
│   ├── Entities/
│   │   ├── Plant.swift
│   │   ├── PlantIdentification.swift
│   │   ├── PlantCareSchedule.swift
│   │   └── CareTask.swift
│   ├── UseCases/
│   │   ├── Identification/
│   │   │   ├── IdentifyPlantOnDeviceUseCase.swift
│   │   │   ├── IdentifyPlantOnlineUseCase.swift
│   │   │   └── HybridIdentificationUseCase.swift
│   │   ├── PlantCare/
│   │   │   ├── FetchPlantCareUseCase.swift
│   │   │   └── CreateCareScheduleUseCase.swift
│   │   └── Collection/
│   └── RepositoryInterfaces/
├── Data/
│   ├── Repositories/
│   ├── DataSources/
│   │   ├── Remote/
│   │   │   ├── PlantNetAPI/
│   │   │   │   ├── PlantNetAPIService.swift
│   │   │   │   └── DTOs/
│   │   │   ├── TrefleAPI/
│   │   │   │   ├── TrefleAPIService.swift
│   │   │   │   └── DTOs/
│   │   │   └── NetworkService/
│   │   └── Local/
│   │       ├── CoreData/
│   │       └── Cache/
│   └── Mappers/
├── ML/
│   ├── Models/
│   │   └── PlantNet300K.mlpackage
│   ├── Services/
│   │   └── PlantClassificationService.swift
│   └── Preprocessing/
├── Presentation/
│   ├── Scenes/
│   │   ├── Camera/
│   │   ├── Identification/
│   │   ├── PlantDetail/
│   │   ├── Collection/
│   │   ├── CareSchedule/
│   │   └── Settings/
│   ├── Common/Components/
│   └── Navigation/
└── Resources/
    ├── PlantLabels.json
    └── Assets.xcassets

API Details

Pl@ntNet API

  • Base URL: https://my-api.plantnet.org
  • Endpoint: POST /v2/identify/{project}
  • Free tier: 500 requests/day
  • Coverage: 77,565 species
  • Documentation: my.plantnet.org/doc

Trefle API

  • Base URL: https://trefle.io/api/v1
  • Endpoints:
    • GET /plants/search?q={name}
    • GET /species/{slug}
  • Data: Light requirements, watering, soil, temperature, fertilizer, growth info
  • Documentation: docs.trefle.io

Core ML Conversion

Prerequisites

pip install torch torchvision coremltools pillow numpy

Conversion Script

# scripts/convert_plantnet_to_coreml.py
import torch
import torchvision.models as models
import coremltools as ct

# Load PlantNet-300K pre-trained ResNet
model = models.resnet50(weights=None)
model.fc = torch.nn.Linear(model.fc.in_features, 1081)
model.load_state_dict(torch.load("resnet50_weights_best_acc.tar", map_location='cpu')['state_dict'])
model.eval()

# Trace for conversion
traced = torch.jit.trace(model, torch.rand(1, 3, 224, 224))

# Convert to Core ML
image_input = ct.ImageType(
    name="image",
    shape=(1, 3, 224, 224),
    scale=1/255.0,
    bias=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
    color_layout=ct.colorlayout.RGB
)

mlmodel = ct.convert(
    traced,
    inputs=[image_input],
    convert_to="mlprogram",
    minimum_deployment_target=ct.target.iOS17,
    compute_precision=ct.precision.FLOAT16,
)

mlmodel.save("PlantNet300K.mlpackage")

Download Weights

# From Zenodo (PlantNet-300K official)
wget https://zenodo.org/records/4726653/files/resnet50_weights_best_acc.tar

Key Data Models

Plant Entity

struct Plant: Identifiable, Sendable {
    let id: UUID
    let scientificName: String
    let commonNames: [String]
    let family: String
    let genus: String
    let imageURLs: [URL]
    let dateIdentified: Date
    let identificationSource: IdentificationSource

    enum IdentificationSource: String {
        case onDevice, plantNetAPI, hybrid
    }
}

PlantCareSchedule Entity

struct PlantCareSchedule: Identifiable, Sendable {
    let id: UUID
    let plantID: UUID
    let lightRequirement: LightRequirement
    let wateringSchedule: WateringSchedule
    let temperatureRange: TemperatureRange
    let fertilizerSchedule: FertilizerSchedule?
    let tasks: [CareTask]
}

Verification Checklist

Test Expected Result
Camera capture Take photo → preview displays
On-device ML Photo → top 10 species with confidence scores (<500ms)
PlantNet API Photo → API results match/exceed on-device accuracy
Trefle API Scientific name → care data (watering, light, fertilizer)
Save plant Save to collection → persists after app restart
Offline mode Disable network → on-device identification still works
Care reminders Create schedule → notification fires at scheduled time

Resources