MCPInstall

swiftdata-inheritance

SwiftData class inheritance patterns for hierarchical models with type-based querying, polymorphic relationships, and when to choose inheritance vs enums. Use when designing SwiftData model hierarchies.

Installs
78
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/rshankras/claude-code-apple-skills --skill swiftdata-inheritance
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads swiftdata-inheritance automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

SwiftData Class Inheritance

Guide for implementing class inheritance in SwiftData models. Covers when to use inheritance versus enums or protocols, how to annotate subclasses, query across hierarchies, and avoid common pitfalls with schema migrations and relationship modeling.

When This Skill Activates

  • User is designing a SwiftData model hierarchy with shared base properties
  • User asks about @Model on subclasses or how inheritance works in SwiftData
  • User needs to query across a type hierarchy (all trips vs only business trips)
  • User is deciding between inheritance, enums, or protocols for model variants
  • User has issues with polymorphic relationships or type casting in SwiftData
  • User is migrating a Core Data inheritance hierarchy to SwiftData

Decision Tree

Do your model variants share a common identity and most properties?
|
+-- YES: Clear IS-A relationship (BusinessTrip IS-A Trip)
|   |
|   +-- Subclasses add significant unique properties or behavior?
|   |   +-- YES --> Use class inheritance (this skill)
|   |   +-- NO, just 1-2 distinguishing fields --> Use enum property on base model
|   |
|   +-- Need to query "all trips" AND "only business trips"?
|       +-- YES --> Inheritance gives you both for free
|       +-- Only one type at a time --> Enum filter is simpler
|
+-- NO: Models share only a few properties
|   +-- Use protocol conformance (no SwiftData inheritance needed)
|
+-- UNCERTAIN: Could go either way
    +-- Prefer enum on base model (simpler schema, easier migrations)
    +-- Promote to inheritance later if variants diverge significantly

When to Use Inheritance

  • There is a meaningful IS-A relationship (a BusinessTrip fundamentally IS a Trip)
  • Subclasses add substantial unique stored properties
  • You need deep queries (fetch all Trip instances regardless of subtype) and shallow queries (fetch only BusinessTrip)
  • Polymorphic relationships are required (a [Trip] array holding mixed subtypes)

When to Avoid Inheritance

  • Subclasses share only a few properties -- use a protocol instead
  • A boolean flag or enum could represent the distinction without separate classes
  • You want to minimize schema migration complexity
  • The hierarchy would go deeper than two levels

API Patterns

Base Model Declaration

Apply @Model to the base class. All persistent properties live here.

@Model
class Trip {
    var name: String
    var startDate: Date
    var endDate: Date

    @Attribute(.preserveValueOnDeletion)
    var identifier: UUID

    @Relationship(deleteRule: .cascade, inverse: \Accommodation.trip)
    var accommodations: [Accommodation] = []

    init(name: String, startDate: Date, endDate: Date) {
        self.identifier = UUID()
        self.name = name
        self.startDate = startDate
        self.endDate = endDate
    }
}

Subclass Declaration

Apply @Model to each subclass. Call super.init() and add subclass-specific stored properties.

@Model
class BusinessTrip: Trip {
    var company: String
    var expenseReport: String?
    var meetingAgenda: String?

    init(name: String, startDate: Date, endDate: Date, company: String) {
        self.company = company
        super.init(name: name, startDate: startDate, endDate: endDate)
    }
}

@Model
class PersonalTrip: Trip {
    enum Reason: String, Codable {
        case vacation
        case family
        case adventure
    }

    var reason: Reason
    var companions: [String] = []

    init(name: String, startDate: Date, endDate: Date, reason: Reason) {
        self.reason = reason
        super.init(name: name, startDate: startDate, endDate: endDate)
    }
}

Relationships Across the Hierarchy

Relationships defined on the base class apply to all subclasses:

@Model
class Accommodation {
    var name: String

    // Points to Trip -- could be BusinessTrip or PersonalTrip at runtime
    @Relationship(inverse: \Trip.accommodations)
    var trip: Trip?

    init(name: String) { self.name = name }
}

ModelContainer Configuration

Register the base class. SwiftData discovers subclasses automatically.

// Register Trip -- BusinessTrip and PersonalTrip are included automatically
let container = try ModelContainer(for: Trip.self, Accommodation.self, Itinerary.self)

Querying Hierarchies

Deep Query (All Subclasses)

Querying the base class returns instances of every subclass.

// Returns Trip, BusinessTrip, and PersonalTrip instances
@Query(sort: \Trip.startDate)
var allTrips: [Trip]

Type Filtering with Predicate

Narrow results to a specific subclass using is or as? in a #Predicate.

// Only BusinessTrip instances
let businessOnly = #Predicate<Trip> { trip in
    trip is BusinessTrip
}

@Query(filter: #Predicate<Trip> { $0 is BusinessTrip }, sort: \Trip.startDate)
var businessTrips: [Trip]

Subclass Property Filtering

Access subclass-specific properties with conditional casting inside the predicate.

let vacationTrips = #Predicate<Trip> { trip in
    if let personal = trip as? PersonalTrip {
        personal.reason == .vacation
    } else {
        false
    }
}

Enum-Based Filter Switching in UI

A common pattern for filter controls that switch between all trips and a specific type.

enum TripFilter: String, CaseIterable, Identifiable {
    case all, business, personal
    var id: String { rawValue }
}

struct TripListView: View {
    @State private var filter: TripFilter = .all
    @Query(sort: \Trip.startDate) var allTrips: [Trip]

    var filteredTrips: [Trip] {
        switch filter {
        case .all: return allTrips
        case .business: return allTrips.filter { $0 is BusinessTrip }
        case .personal: return allTrips.filter { $0 is PersonalTrip }
        }
    }

    var body: some View {
        List {
            Picker("Filter", selection: $filter) {
                ForEach(TripFilter.allCases) { f in
                    Text(f.rawValue.capitalized).tag(f)
                }
            }
            .pickerStyle(.segmented)

            ForEach(filteredTrips) { trip in
                TripRowView(trip: trip)
            }
        }
    }
}

Type Casting at Runtime

Use standard Swift casting to access subclass-specific properties in views.

if let business = trip as? BusinessTrip {
    LabeledContent("Company", value: business.company)
}
if let personal = trip as? PersonalTrip {
    LabeledContent("Reason", value: personal.reason.rawValue)
}

Top Mistakes

1. Missing @Model on Subclass

The @Model macro must appear on both the base class and every subclass.

// WRONG -- subclass properties not persisted
class BusinessTrip: Trip {
    var company: String  // not saved
    ...
}

// RIGHT
@Model
class BusinessTrip: Trip {
    var company: String  // persisted correctly
    ...
}

2. Deep Hierarchies

Keep hierarchies to one level of subclassing (base + one tier).

// WRONG -- three levels deep
@Model class InternationalBusinessTrip: BusinessTrip { ... }  // avoid

// RIGHT -- flat: base + one level
@Model class Trip { ... }
@Model class BusinessTrip: Trip { ... }
@Model class PersonalTrip: Trip { ... }

3. Using Inheritance When an Enum Would Suffice

If the only difference is a type tag and one or two optional fields, an enum on the base model is simpler.

// WRONG -- inheritance just for a category label
@Model class DomesticTrip: Trip { }
@Model class InternationalTrip: Trip { var passportRequired: Bool = true }

// RIGHT -- enum property on the base model
@Model class Trip {
    enum Category: String, Codable { case domestic, international }
    var name: String
    var category: Category
    var passportRequired: Bool?
}

4. Forgetting super.init()

Subclass initializers must call super.init() with all required base properties. Missing this causes incomplete or corrupt records.

// WRONG -- base properties uninitialized
init(company: String) {
    self.company = company
    // Missing super.init(name:startDate:endDate:)
}

// RIGHT -- always call super.init()
init(name: String, startDate: Date, endDate: Date, company: String) {
    self.company = company
    super.init(name: name, startDate: startDate, endDate: endDate)
}

5. Registering Subclasses Separately in ModelContainer

Register only the base class -- see "ModelContainer Configuration" above.

// UNNECESSARY
let container = try ModelContainer(for: Trip.self, BusinessTrip.self, PersonalTrip.self)

// RIGHT
let container = try ModelContainer(for: Trip.self)

Review Checklist

When reviewing code that uses SwiftData class inheritance, verify each item:

  • @Model is applied to the base class AND every subclass
  • Each subclass calls super.init() with all required base properties
  • Hierarchy is shallow (base + one level of subclasses, no deeper)
  • The IS-A relationship is meaningful -- not just a type tag that could be an enum
  • @Attribute(.preserveValueOnDeletion) is used on fields needed after deletion (sync IDs, audit trails)
  • Relationships use inverse: parameters correctly, pointing to the base class property
  • @Relationship(deleteRule:) is specified on owning side (.cascade, .nullify, or .deny)
  • Deep queries (base class fetch) and shallow queries (type-filtered) both work as expected
  • Type casting (as? Subclass) is used safely with if let in views and logic
  • ModelContainer registers the base class (subclasses are auto-discovered)
  • Schema migration plan exists if the hierarchy will evolve (adding/removing subclasses)
  • Enum-based filter pattern is used for UI that switches between type views

Cross-Reference

  • For SwiftData repository and architecture patterns, see macos/swiftdata-architecture/
  • For SwiftData concurrency with @ModelActor, see swift/concurrency-patterns/
  • For persistence setup generator, see generators/persistence-setup/

References

FAQ

What does the swiftdata-inheritance skill do?

SwiftData class inheritance patterns for hierarchical models with type-based querying, polymorphic relationships, and when to choose inheritance vs enums. Use when designing SwiftData model hierarchies.

How do I install swiftdata-inheritance?

Run npx skills add https://github.com/rshankras/claude-code-apple-skills --skill swiftdata-inheritance in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/swiftdata-inheritance/ (personal) or .claude/skills/swiftdata-inheritance/ (project).

Which AI agents can use swiftdata-inheritance?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is swiftdata-inheritance safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains swiftdata-inheritance?

It is published by rshankras in rshankras/claude-code-apple-skills. It has 78 recorded installs.

More from rshankras/claude-code-apple-skills

View all →

macos-development

rshankras/claude-code-apple-skills

Comprehensive macOS development guidance including Swift 6+, SwiftUI, SwiftData, architecture patterns, AppKit bridging, and macOS 26 Tahoe APIs. Use for macOS code review, best practices, UI review, or platform-specific features.

ios-development

rshankras/claude-code-apple-skills

Comprehensive iOS development guidance including Swift best practices, SwiftUI patterns, UI/UX review against HIG, and app planning. Use for iOS code review, best practices, accessibility audits, or planning new iOS apps.

Mobile823 installs

design

rshankras/claude-code-apple-skills

Design skills for Apple platform UI — Liquid Glass, animations, game feel (haptics, sound, celebrations), UI prototyping, UX writing, SF Symbols, and typography. Use when implementing design language features, adding juice/feedback, writing interface copy, or choosing type and iconography.

Frontend & Design680 installs

app-store

rshankras/claude-code-apple-skills

App Store optimization and marketing skills for descriptions, screenshots, keywords, review responses, and comprehensive promotional strategy. Use when user needs help with App Store presence, ASO, marketing, or customer communication.

Mobile599 installs

watchos

rshankras/claude-code-apple-skills

watchOS development guidance including SwiftUI for Watch, Watch Connectivity, complications, and watch-specific UI patterns. Use for watchOS code review, best practices, or Watch app development.

product-development

rshankras/claude-code-apple-skills

End-to-end product development for iOS/macOS apps. Covers market research, competitive analysis, PRD generation, architecture specs, UX design, implementation guides, testing, and App Store release. Use for product planning, validation, or generating specification documents.

Testing & QA554 installs

Popular other skills

View category →

hyperframes-cli

heygen-com/hyperframes

The HyperFrames CLI by job - scaffold, check, look at frames, edit the timeline, preview, render locally or in the cloud, publish, project history - plus reading its JSON and recovering from build or render failures.

1.3M installs

triage

mattpocock/skills

Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.

1M installs

ask-matt

mattpocock/skills

Ask which skill or flow fits your situation. A router over the skills in this repo.

778.4K installs

to-questionnaire

mattpocock/skills

Turn a decision you can't fully answer into a questionnaire for someone else to fill in.

508.9K installs

wait-what

mattpocock/skills

Stop. That last message did not land: re-pitch it.

459.7K installs

loop-me

mattpocock/skills

Grill me about specs for the workflows I want to build, within this workspace.

447.9K installs