{"id":"2b187279-1b98-4772-9e9c-caac4b238ae8","shortId":"sD6J3j","kind":"skill","title":"swift-architecture","tagline":"Select, implement, or migrate between app architecture patterns for Apple platform apps. Use when choosing between MV (Model-View with @Observable), MVVM, MVI, TCA (The Composable Architecture), Clean Architecture, VIPER, or Coordinator patterns; when evaluating architecture fit ","description":"# Swift Architecture\n\nSelect and implement the right architecture pattern for Apple platform apps\nbuilt with Swift 6.3 and SwiftUI or UIKit.\n\n## Contents\n\n- [Architecture Selection](#architecture-selection)\n- [MV Pattern (Model-View with @Observable)](#mv-pattern)\n- [MVVM](#mvvm)\n- [MVI (Model-View-Intent)](#mvi)\n- [TCA (The Composable Architecture)](#tca)\n- [Clean Architecture](#clean-architecture)\n- [Coordinator Pattern](#coordinator-pattern)\n- [Migration Between Patterns](#migration-between-patterns)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n\n## Architecture Selection\n\nChoose based on feature complexity, team size, and testing requirements.\n\n| Pattern | Best For | Complexity | Testability |\n|---------|----------|-----------|-------------|\n| **MV** | Small-to-medium SwiftUI apps, rapid iteration | Low | Moderate |\n| **MVVM** | Medium apps, teams familiar with reactive patterns | Medium | High |\n| **MVI** | Complex state machines, predictable state flow | Medium-High | High |\n| **TCA** | Large apps needing composable features, strong testing | High | Very High |\n| **Clean Architecture** | Enterprise apps, strict separation of concerns | High | Very High |\n| **Coordinator** | Apps with complex navigation flows (UIKit or hybrid) | Medium | High |\n\n**Default recommendation for new SwiftUI apps:** Start with MV (Model-View\nwith `@Observable`). Escalate to MVVM or TCA only when the feature's complexity\ndemands it.\n\n### Decision Framework\n\n1. **Is the feature a simple CRUD screen?** → MV pattern\n2. **Does the screen have complex business logic separate from the view?** → MVVM\n3. **Do you need deterministic state transitions and side-effect management?** → MVI or TCA\n4. **Is the app large with many independent feature modules?** → TCA or Clean Architecture\n5. **Is navigation complex with deep linking and conditional flows?** → Add Coordinator pattern\n\n## MV Pattern\n\nThe simplest SwiftUI architecture. The view observes `@Observable` models\ndirectly. No intermediate view model layer.\n\nDocs: [@Observable](https://sosumi.ai/documentation/observation/observable())\n\n```swift\nimport Observation\nimport SwiftUI\n\n@Observable\nclass TripStore {\n    var trips: [Trip] = []\n    var isLoading = false\n    var error: Error?\n\n    private let service: TripService\n\n    init(service: TripService) {\n        self.service = service\n    }\n\n    func loadTrips() async {\n        isLoading = true\n        defer { isLoading = false }\n        do {\n            trips = try await service.fetchTrips()\n        } catch {\n            self.error = error\n        }\n    }\n\n    func deleteTrip(_ trip: Trip) async throws {\n        try await service.delete(trip)\n        trips.removeAll { $0.id == trip.id }\n    }\n}\n\nstruct TripsView: View {\n    @State private var store = TripStore(service: .live)\n\n    var body: some View {\n        List(store.trips) { trip in\n            TripRow(trip: trip)\n        }\n        .task { await store.loadTrips() }\n    }\n}\n```\n\n**When MV is enough:** Single-screen features, prototype/MVP, small teams,\nstraightforward data flow.\n\n**When to upgrade:** Business logic grows complex, unit testing the view's\nbehavior becomes difficult, multiple views need to share and transform the\nsame state differently.\n\n## MVVM\n\nSeparates view logic into a `ViewModel` that the view observes. The view model\ntransforms model data for display and handles user actions.\n\n```swift\n@Observable\nclass TripListViewModel {\n    private(set) var trips: [TripRowItem] = []\n    private(set) var isLoading = false\n    var searchText = \"\"\n\n    var filteredTrips: [TripRowItem] {\n        guard !searchText.isEmpty else { return trips }\n        return trips.filter { $0.name.localizedStandardContains(searchText) }\n    }\n\n    private let repository: TripRepository\n\n    init(repository: TripRepository) {\n        self.repository = repository\n    }\n\n    func loadTrips() async {\n        isLoading = true\n        defer { isLoading = false }\n        let models = (try? await repository.fetchAll()) ?? []\n        trips = models.map { TripRowItem(from: $0) }\n    }\n\n    func delete(at offsets: IndexSet) async {\n        let toDelete = offsets.map { filteredTrips[$0] }\n        for item in toDelete {\n            try? await repository.delete(id: item.id)\n        }\n        await loadTrips()\n    }\n}\n\nstruct TripRowItem: Identifiable {\n    let id: UUID\n    let name: String\n    let dateRange: String\n\n    init(from trip: Trip) {\n        self.id = trip.id\n        self.name = trip.name\n        self.dateRange = trip.startDate.formatted(.dateTime.month().day())\n            + \" – \" + trip.endDate.formatted(.dateTime.month().day())\n    }\n}\n\nstruct TripListView: View {\n    @State private var viewModel: TripListViewModel\n\n    init(repository: TripRepository) {\n        _viewModel = State(initialValue: TripListViewModel(repository: repository))\n    }\n\n    var body: some View {\n        List {\n            ForEach(viewModel.filteredTrips) { item in\n                Text(item.name)\n            }\n            .onDelete { offsets in\n                Task { await viewModel.delete(at: offsets) }\n            }\n        }\n        .searchable(text: $viewModel.searchText)\n        .task { await viewModel.loadTrips() }\n    }\n}\n```\n\n**Testing a ViewModel:**\n\n```swift\n@Test func filteredTripsMatchesSearch() async {\n    let repo = MockTripRepository(trips: [\n        Trip(name: \"Paris\"), Trip(name: \"Tokyo\"), Trip(name: \"Paris TX\")\n    ])\n    let vm = TripListViewModel(repository: repo)\n    await vm.loadTrips()\n    vm.searchText = \"Paris\"\n    #expect(vm.filteredTrips.count == 2)\n}\n```\n\n## MVI\n\nUnidirectional data flow: views dispatch **intents**, a **reducer** produces\nnew **state**, and **side effects** are handled explicitly.\n\n```swift\n@Observable\nclass TripListStore {\n    private(set) var state = State()\n\n    struct State {\n        var trips: [Trip] = []\n        var isLoading = false\n        var error: String?\n    }\n\n    enum Intent {\n        case loadTrips\n        case deleteTrip(Trip)\n        case clearError\n    }\n\n    private let service: TripService\n\n    init(service: TripService) {\n        self.service = service\n    }\n\n    func send(_ intent: Intent) {\n        Task { await handle(intent) }\n    }\n\n    @MainActor\n    private func handle(_ intent: Intent) async {\n        switch intent {\n        case .loadTrips:\n            state.isLoading = true\n            do {\n                state.trips = try await service.fetchTrips()\n            } catch {\n                state.error = error.localizedDescription\n            }\n            state.isLoading = false\n\n        case .deleteTrip(let trip):\n            try? await service.delete(trip)\n            state.trips.removeAll { $0.id == trip.id }\n\n        case .clearError:\n            state.error = nil\n        }\n    }\n}\n```\n\n**Advantages:** Predictable state transitions, easy to log/replay intents,\nclear separation of \"what happened\" from \"what changed.\"\n\n## TCA\n\nThe Composable Architecture (Point-Free) provides composable reducers,\ndependency injection, exhaustive testing, and structured side effects.\n\nDocs: [TCA](https://sosumi.ai/external/https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)\n\n```swift\nimport ComposableArchitecture\n\n@Reducer\nstruct TripList {\n    @ObservableState\n    struct State: Equatable {\n        var trips: IdentifiedArrayOf<Trip> = []\n        var isLoading = false\n    }\n\n    enum Action {\n        case onAppear\n        case tripsLoaded([Trip])\n        case deleteTrip(Trip.ID)\n    }\n\n    @Dependency(\\.tripClient) var tripClient\n\n    var body: some ReducerOf<Self> {\n        Reduce { state, action in\n            switch action {\n            case .onAppear:\n                state.isLoading = true\n                return .run { send in\n                    let trips = try await tripClient.fetchAll()\n                    await send(.tripsLoaded(trips))\n                }\n            case .tripsLoaded(let trips):\n                state.trips = IdentifiedArray(uniqueElements: trips)\n                state.isLoading = false\n                return .none\n            case .deleteTrip(let id):\n                state.trips.remove(id: id)\n                return .run { _ in try await tripClient.delete(id) }\n            }\n        }\n    }\n}\n```\n\n**Use TCA when:** Large team needs consistent patterns, exhaustive test\ncoverage is a priority, features compose from smaller features, you need\nstructured dependency injection across the app.\n\n## Clean Architecture\n\nLayers: **Domain** (entities, use cases, repository protocols) → **Data**\n(repository implementations, network, persistence) → **Presentation** (views,\nview models). Dependencies point inward.\n\n```swift\n// Domain layer\nprotocol TripRepository: Sendable {\n    func fetchAll() async throws -> [Trip]\n    func save(_ trip: Trip) async throws\n    func delete(id: UUID) async throws\n}\n\nstruct FetchUpcomingTripsUseCase: Sendable {\n    private let repository: TripRepository\n\n    init(repository: TripRepository) {\n        self.repository = repository\n    }\n\n    func execute() async throws -> [Trip] {\n        try await repository.fetchAll()\n            .filter { $0.startDate > .now }\n            .sorted { $0.startDate < $1.startDate }\n    }\n}\n\n// Data layer\nstruct RemoteTripRepository: TripRepository {\n    private let client: APIClient\n\n    func fetchAll() async throws -> [Trip] {\n        try await client.request(.get, \"/trips\")\n    }\n    // ...\n}\n\n// Presentation layer\n@Observable\nclass UpcomingTripsViewModel {\n    private(set) var trips: [Trip] = []\n    private let useCase: FetchUpcomingTripsUseCase\n\n    init(useCase: FetchUpcomingTripsUseCase) {\n        self.useCase = useCase\n    }\n\n    func load() async {\n        trips = (try? await useCase.execute()) ?? []\n    }\n}\n```\n\n**Use Clean Architecture when:** Strict separation is required (enterprise,\nregulated domains), the domain layer must be testable without any framework\ndependencies, or multiple presentation targets share the same business logic.\n\n## Coordinator Pattern\n\nSeparates navigation logic from views. Especially useful in UIKit or hybrid\napps with complex navigation flows.\n\n```swift\n@MainActor\nprotocol Coordinator: AnyObject {\n    var navigationController: UINavigationController { get }\n    func start()\n}\n\n@MainActor\nfinal class TripCoordinator: Coordinator {\n    let navigationController: UINavigationController\n    private let repository: TripRepository\n\n    init(navigationController: UINavigationController, repository: TripRepository) {\n        self.navigationController = navigationController\n        self.repository = repository\n    }\n\n    func start() {\n        let vm = TripListViewModel(repository: repository)\n        vm.onSelectTrip = { [weak self] trip in\n            self?.showDetail(for: trip)\n        }\n        let vc = TripListViewController(viewModel: vm)\n        navigationController.pushViewController(vc, animated: false)\n    }\n\n    private func showDetail(for trip: Trip) {\n        let vm = TripDetailViewModel(trip: trip, repository: repository)\n        vm.onEdit = { [weak self] trip in self?.showEditor(for: trip) }\n        let vc = TripDetailViewController(viewModel: vm)\n        navigationController.pushViewController(vc, animated: true)\n    }\n\n    private func showEditor(for trip: Trip) {\n        // ...\n    }\n}\n```\n\nIn pure SwiftUI apps, `NavigationStack` with path-based routing often\nreplaces the Coordinator pattern. Use Coordinators when you need UIKit\nintegration or shared navigation logic across platforms.\n\n## Migration Between Patterns\n\n### ObservableObject → @Observable\n\n```swift\n// Before (iOS 16)\nclass TripStore: ObservableObject {\n    @Published var trips: [Trip] = []\n}\n// View uses @ObservedObject or @StateObject\n\n// After (iOS 17+)\n@Observable\nclass TripStore {\n    var trips: [Trip] = []\n}\n// View uses @State for owned, plain property for injected\n```\n\n### MVVM → MV (simplifying)\n\nIf a view model only passes through model data without transforming it,\nremove the view model and let the view observe the model directly.\n\n### MV → MVVM (scaling up)\n\nExtract business logic and data transformation into a view model when:\n- The view's `body` contains conditional logic for data formatting\n- Multiple views need different projections of the same model\n- You need to test logic without instantiating views\n\n### Any → TCA\n\nTCA adoption is typically incremental: wrap one feature's state and actions\nin a `Reducer`, migrate its dependencies to `@Dependency`, and test.\n\n## Common Mistakes\n\n| Mistake | Fix |\n|---------|-----|\n| Using `ObservableObject` in new iOS 17+ code | Use `@Observable` instead |\n| View model that only forwards model properties | Remove the view model; use MV pattern |\n| Massive view model with navigation, networking, and formatting | Split into focused collaborators (coordinator, service, formatter) |\n| Choosing TCA for a two-screen app | Start with MV; adopt TCA when composition and testing demands justify it |\n| Protocol-heavy Clean Architecture for a simple feature | Match architecture complexity to feature complexity |\n| Coordinator pattern in pure SwiftUI without UIKit needs | Use `NavigationStack` path-based routing instead |\n| Mixing architecture patterns inconsistently within a module | One pattern per feature module; different modules can use different patterns |\n\n## Review Checklist\n\n- [ ] Architecture choice is justified by feature complexity and team needs\n- [ ] `@Observable` used instead of `ObservableObject` for iOS 17+ targets\n- [ ] Dependencies are injected, not created internally (testability)\n- [ ] Navigation logic is separated from business logic\n- [ ] State mutations happen in a clear, auditable location\n- [ ] View models (if present) are testable without views\n- [ ] No god objects — responsibilities are distributed appropriately\n- [ ] Pattern is consistent within each feature module\n\n## References\n\n- Apple docs: [Observation](https://sosumi.ai/documentation/observation) | [Observable](https://sosumi.ai/documentation/observation/observable())","tags":["swift","architecture","ios","skills","dpearson2699","accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills"],"capabilities":["skill","source-dpearson2699","skill-swift-architecture","topic-accessibility","topic-agent-skills","topic-ai-coding","topic-apple","topic-claude-code","topic-codex-skills","topic-cursor-skills","topic-ios","topic-ios-development","topic-liquid-glass","topic-localization","topic-mapkit"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swift-architecture","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dpearson2699/swift-ios-skills","source_repo":"https://github.com/dpearson2699/swift-ios-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 599 github stars · SKILL.md body (14,068 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:44.605Z","embedding":null,"createdAt":"2026-04-23T00:53:25.637Z","updatedAt":"2026-05-18T18:53:44.605Z","lastSeenAt":"2026-05-18T18:53:44.605Z","tsv":"'/documentation/observation)':1489 '/documentation/observation/observable())':316,1493 '/external/https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)':779 '/trips':978 '0':513,524 '0.id':370,735 '0.name.localizedstandardcontains':485 '0.startdate':955,958 '1':230 '1.startdate':959 '16':1183 '17':1198,1316,1437 '2':240,638 '3':253 '4':268 '5':282 '6.3':58 'across':887,1173 'action':458,797,816,819,1296 'add':292 'adopt':1286,1361 'advantag':741 'anim':1108,1139 'anyobject':1057 'apicli':968 'app':9,15,54,142,149,170,182,191,206,271,889,1048,1150,1357 'appl':13,52,1484 'appropri':1475 'architectur':3,10,31,33,40,43,49,64,67,90,93,96,119,180,281,300,760,891,1007,1374,1380,1401,1420 'architecture-select':66 'async':345,363,498,519,612,709,919,926,932,948,971,1000 'audit':1459 'await':354,366,394,507,530,534,595,603,632,700,719,731,831,833,860,952,975,1003 'base':122,1155,1397 'becom':423 'behavior':422 'best':132 'bodi':383,581,811,1259 'built':55 'busi':246,413,1033,1246,1451 'case':679,681,684,712,726,737,798,800,803,820,837,849,896 'catch':356,721 'chang':756 'checklist':115,118,1419 'choic':1421 'choos':18,121,1350 'class':323,461,659,982,1066,1184,1200 'clean':32,92,95,179,280,890,1006,1373 'clean-architectur':94 'clear':749,1458 'clearerror':685,738 'client':967 'client.request':976 'code':1317 'collabor':1346 'common':109,112,1307 'common-mistak':111 'complex':125,134,158,193,225,245,285,416,1050,1381,1384,1426 'compos':30,89,172,759,765,878 'composablearchitectur':782 'composit':1364 'concern':186 'condit':290,1261 'consist':869,1478 'contain':1260 'content':63 'coordin':36,97,100,190,293,1035,1056,1068,1160,1163,1347,1385 'coordinator-pattern':99 'coverag':873 'creat':1443 'crud':236 'data':408,452,641,899,960,1225,1249,1264 'daterang':546 'datetime.month':558,561 'day':559,562 'decis':228 'deep':287 'default':201 'defer':348,501 'delet':515,929 'deletetrip':360,682,727,804,850 'demand':226,1367 'depend':767,806,885,908,1025,1302,1304,1439 'determinist':257 'differ':435,1269,1412,1416 'difficult':424 'direct':306,1240 'dispatch':644 'display':454 'distribut':1474 'doc':312,775,1485 'domain':893,912,1015,1017 'easi':745 'effect':263,653,774 'els':480 'enough':399 'enterpris':181,1013 'entiti':894 'enum':677,796 'equat':789 'error':332,333,358,675 'error.localizeddescription':723 'escal':215 'especi':1042 'evalu':39 'execut':947 'exhaust':769,871 'expect':636 'explicit':656 'extract':1245 'fals':330,350,472,503,673,725,795,846,1109 'familiar':151 'featur':124,173,223,233,276,403,877,881,1292,1378,1383,1410,1425,1481 'fetchal':918,970 'fetchupcomingtripsusecas':935,992,995 'filter':954 'filteredtrip':476,523 'filteredtripsmatchessearch':611 'final':1065 'fit':41 'fix':1310 'flow':163,195,291,409,642,1052 'focus':1345 'foreach':585 'format':1265,1342 'formatt':1349 'forward':1325 'framework':229,1024 'free':763 'func':343,359,496,514,610,695,705,917,922,928,946,969,998,1062,1085,1111,1142 'get':977,1061 'god':1470 'grow':415 'guard':478 'handl':456,655,701,706 'happen':753,1455 'heavi':1372 'high':156,166,167,176,178,187,189,200 'hybrid':198,1047 'id':532,540,852,854,855,862,930 'identifi':538 'identifiedarray':842 'identifiedarrayof':792 'implement':5,46,901 'import':318,320,781 'inconsist':1403 'increment':1289 'independ':275 'indexset':518 'init':338,491,548,571,690,941,993,1076 'initialvalu':576 'inject':768,886,1213,1441 'instanti':1281 'instead':1320,1399,1432 'integr':1168 'intent':85,645,678,697,698,702,707,708,711,748 'intermedi':308 'intern':1444 'inward':910 'io':1182,1197,1315,1436 'isload':329,346,349,471,499,502,672,794 'item':526,587 'item.id':533 'item.name':590 'iter':144 'justifi':1368,1423 'larg':169,272,866 'layer':311,892,913,961,980,1018 'let':335,488,504,520,539,542,545,613,627,687,728,828,839,851,938,966,990,1069,1073,1087,1101,1116,1132,1234 'link':288 'list':386,584 'live':381 'load':999 'loadtrip':344,497,535,680,713 'locat':1460 'log/replay':747 'logic':247,414,439,1034,1039,1172,1247,1262,1279,1447,1452 'low':145 'machin':160 'mainactor':703,1054,1064 'manag':264 'mani':274 'massiv':1335 'match':1379 'medium':140,148,155,165,199 'medium-high':164 'migrat':7,102,106,1175,1300 'migration-between-pattern':105 'mistak':110,113,1308,1309 'mix':1400 'mocktriprepositori':615 'model':22,72,83,211,305,310,449,451,505,907,1220,1224,1232,1239,1254,1274,1322,1326,1331,1337,1462 'model-view':21,71,210 'model-view-int':82 'models.map':510 'moder':146 'modul':277,1406,1411,1413,1482 'multipl':425,1027,1266 'must':1019 'mutat':1454 'mv':20,69,77,136,209,238,295,397,1215,1241,1333,1360 'mv-pattern':76 'mvi':27,81,86,157,265,639 'mvvm':26,79,80,147,217,252,436,1214,1242 'name':543,618,621,624 'navig':194,284,1038,1051,1171,1339,1446 'navigationcontrol':1059,1070,1077,1082 'navigationcontroller.pushviewcontroller':1106,1137 'navigationstack':1151,1394 'need':171,256,427,868,883,1166,1268,1276,1392,1429 'network':902,1340 'new':204,649,1314 'nil':740 'none':848 'object':1471 'observ':25,75,214,303,304,313,319,322,446,460,658,981,1179,1199,1237,1319,1430,1486,1490 'observableobject':1178,1186,1312,1434 'observablest':786 'observedobject':1193 'offset':517,592,598 'offsets.map':522 'often':1157 'onappear':799,821 'ondelet':591 'one':1291,1407 'own':1209 'pari':619,625,635 'pass':1222 'path':1154,1396 'path-bas':1153,1395 'pattern':11,37,50,70,78,98,101,104,108,131,154,239,294,296,870,1036,1161,1177,1334,1386,1402,1408,1417,1476 'per':1409 'persist':903 'plain':1210 'platform':14,53,1174 'point':762,909 'point-fre':761 'predict':161,742 'present':904,979,1028,1464 'prioriti':876 'privat':334,376,463,468,487,567,661,686,704,937,965,984,989,1072,1110,1141 'produc':648 'project':1270 'properti':1211,1327 'protocol':898,914,1055,1371 'protocol-heavi':1370 'prototype/mvp':404 'provid':764 'publish':1187 'pure':1148,1388 'rapid':143 'reactiv':153 'recommend':202 'reduc':647,766,783,814,1299 'reducerof':813 'refer':1483 'regul':1014 'remotetriprepositori':963 'remov':1229,1328 'replac':1158 'repo':614,631 'repositori':489,492,495,572,578,579,630,897,900,939,942,945,1074,1079,1084,1090,1091,1121,1122 'repository.delete':531 'repository.fetchall':508,953 'requir':130,1012 'respons':1472 'return':481,483,824,847,856 'review':114,117,1418 'review-checklist':116 'right':48 'rout':1156,1398 'run':825,857 'save':923 'scale':1243 'screen':237,243,402,1356 'searchabl':599 'searchtext':474,486 'searchtext.isempty':479 'select':4,44,65,68,120 'self':1094,1097,1125,1128 'self.daterange':556 'self.error':357 'self.id':552 'self.name':554 'self.navigationcontroller':1081 'self.repository':494,944,1083 'self.service':341,693 'self.usecase':996 'send':696,826,834 'sendabl':916,936 'separ':184,248,437,750,1010,1037,1449 'servic':336,339,342,380,688,691,694,1348 'service.delete':367,732 'service.fetchtrips':355,720 'set':464,469,662,985 'share':429,1030,1170 'showdetail':1098,1112 'showeditor':1129,1143 'side':262,652,773 'side-effect':261 'simpl':235,1377 'simplest':298 'simplifi':1216 'singl':401 'single-screen':400 'size':127 'skill' 'skill-swift-architecture' 'small':138,405 'small-to-medium':137 'smaller':880 'sort':957 'sosumi.ai':315,778,1488,1492 'sosumi.ai/documentation/observation)':1487 'sosumi.ai/documentation/observation/observable())':314,1491 'sosumi.ai/external/https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)':777 'source-dpearson2699' 'split':1343 'start':207,1063,1086,1358 'state':159,162,258,375,434,566,575,650,664,665,667,743,788,815,1207,1294,1453 'state.error':722,739 'state.isloading':714,724,822,845 'state.trips':717,841 'state.trips.remove':853 'state.trips.removeall':734 'stateobject':1195 'store':378 'store.loadtrips':395 'store.trips':387 'straightforward':407 'strict':183,1009 'string':544,547,676 'strong':174 'struct':372,536,563,666,784,787,934,962 'structur':772,884 'swift':2,42,57,317,459,608,657,780,911,1053,1180 'swift-architectur':1 'swiftui':60,141,205,299,321,1149,1389 'switch':710,818 'target':1029,1438 'task':393,594,602,699 'tca':28,87,91,168,219,267,278,757,776,864,1284,1285,1351,1362 'team':126,150,406,867,1428 'test':129,175,418,605,609,770,872,1278,1306,1366 'testabl':135,1021,1445,1466 'text':589,600 'throw':364,920,927,933,949,972 'todelet':521,528 'tokyo':622 'topic-accessibility' 'topic-agent-skills' 'topic-ai-coding' 'topic-apple' 'topic-claude-code' 'topic-codex-skills' 'topic-cursor-skills' 'topic-ios' 'topic-ios-development' 'topic-liquid-glass' 'topic-localization' 'topic-mapkit' 'transform':431,450,1227,1250 'transit':259,744 'tri':353,365,506,529,718,730,830,859,951,974,1002 'trip':326,327,352,361,362,368,388,391,392,466,482,509,550,551,616,617,620,623,669,670,683,729,733,791,802,829,836,840,844,921,924,925,950,973,987,988,1001,1095,1100,1114,1115,1119,1120,1126,1131,1145,1146,1189,1190,1203,1204 'trip.enddate.formatted':560 'trip.id':371,553,736,805 'trip.name':555 'trip.startdate.formatted':557 'tripclient':807,809 'tripclient.delete':861 'tripclient.fetchall':832 'tripcoordin':1067 'tripdetailviewcontrol':1134 'tripdetailviewmodel':1118 'triplist':785 'tripliststor':660 'triplistview':564 'triplistviewcontrol':1103 'triplistviewmodel':462,570,577,629,1089 'triprepositori':490,493,573,915,940,943,964,1075,1080 'triprow':390 'triprowitem':467,477,511,537 'trips.filter':484 'trips.removeall':369 'tripservic':337,340,689,692 'tripsload':801,835,838 'tripstor':324,379,1185,1201 'tripsview':373 'true':347,500,715,823,1140 'two':1355 'two-screen':1354 'tx':626 'typic':1288 'uikit':62,196,1045,1167,1391 'uinavigationcontrol':1060,1071,1078 'unidirect':640 'uniqueel':843 'unit':417 'upcomingtripsviewmodel':983 'upgrad':412 'use':16,863,895,1005,1043,1162,1192,1206,1311,1318,1332,1393,1415,1431 'usecas':991,994,997 'usecase.execute':1004 'user':457 'uuid':541,931 'var':325,328,331,377,382,465,470,473,475,568,580,663,668,671,674,790,793,808,810,986,1058,1188,1202 'vc':1102,1107,1133,1138 'view':23,73,84,212,251,302,309,374,385,420,426,438,445,448,565,583,643,905,906,1041,1191,1205,1219,1231,1236,1253,1257,1267,1282,1321,1330,1336,1461,1468 'viewmodel':442,569,574,607,1104,1135 'viewmodel.delete':596 'viewmodel.filteredtrips':586 'viewmodel.loadtrips':604 'viewmodel.searchtext':601 'viper':34 'vm':628,1088,1105,1117,1136 'vm.filteredtrips.count':637 'vm.loadtrips':633 'vm.onedit':1123 'vm.onselecttrip':1092 'vm.searchtext':634 'weak':1093,1124 'within':1404,1479 'without':1022,1226,1280,1390,1467 'wrap':1290","prices":[{"id":"e0b9fb96-750b-4cd9-9234-f60352f6ca16","listingId":"2b187279-1b98-4772-9e9c-caac4b238ae8","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-23T00:53:25.637Z"}],"sources":[{"listingId":"2b187279-1b98-4772-9e9c-caac4b238ae8","source":"github","sourceId":"dpearson2699/swift-ios-skills/swift-architecture","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swift-architecture","isPrimary":false,"firstSeenAt":"2026-04-23T00:53:25.637Z","lastSeenAt":"2026-05-18T18:53:44.605Z"},{"listingId":"2b187279-1b98-4772-9e9c-caac4b238ae8","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/swift-architecture","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swift-architecture","isPrimary":true,"firstSeenAt":"2026-05-07T20:41:58.525Z","lastSeenAt":"2026-05-07T22:41:19.323Z"}],"details":{"listingId":"2b187279-1b98-4772-9e9c-caac4b238ae8","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"swift-architecture","github":{"repo":"dpearson2699/swift-ios-skills","stars":599,"topics":["accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills","ios","ios-development","liquid-glass","localization","mapkit","networking","storekit","swift","swift-concurrency","swiftdata","swiftui","widgetkit","xcode"],"license":"other","html_url":"https://github.com/dpearson2699/swift-ios-skills","pushed_at":"2026-04-26T21:04:17Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"ec1dac5f6854b4cb3726f1970e16b66c4b496711","skill_md_path":"skills/swift-architecture/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swift-architecture"},"layout":"multi","source":"github","category":"swift-ios-skills","frontmatter":{"name":"swift-architecture","description":"Select, implement, or migrate between app architecture patterns for Apple platform apps. Use when choosing between MV (Model-View with @Observable), MVVM, MVI, TCA (The Composable Architecture), Clean Architecture, VIPER, or Coordinator patterns; when evaluating architecture fit for a feature's complexity; when migrating from one pattern to another; or when reviewing whether an app's current architecture is appropriate. Scoped to Apple-platform patterns using Swift 6.3, SwiftUI, and UIKit."},"skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swift-architecture"},"updatedAt":"2026-05-18T18:53:44.605Z"}}