├── SwiftDataTCA ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Info.plist ├── SwiftDataTCA.entitlements ├── SwiftDataTCAApp.swift ├── Service │ ├── Database.swift │ └── MovieDatabase.swift └── View │ ├── Migrations │ └── MovieMigration.swift │ ├── QueryView.swift │ └── ContentView.swift ├── SwiftDataTCA.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── rodrigosouza.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── LICENSE.MD ├── README.md └── CONTRIBUTING.md /SwiftDataTCA/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftDataTCA/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftDataTCA/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/xcuserdata/rodrigosouza.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftDataTCA/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIBackgroundModes 6 | 7 | remote-notification 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/xcuserdata/rodrigosouza.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftDataTCA.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftDataTCA/SwiftDataTCA.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.aps-environment 8 | development 9 | com.apple.developer.icloud-container-identifiers 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.app-sandbox 16 | 17 | com.apple.security.files.user-selected.read-only 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] [RODRIGO SANTOS DE SOUZA] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /SwiftDataTCA/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SwiftDataTCA/SwiftDataTCAApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftDataTCAApp.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Rodrigo Souza on 03/10/23. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftData 10 | import Dependencies 11 | 12 | @main 13 | struct SwiftDataTCAApp: App { 14 | @Dependency(\.databaseService) var databaseService 15 | var modelContext: ModelContext { 16 | guard let modelContext = try? self.databaseService.context() else { 17 | fatalError("Could not find modelcontext") 18 | } 19 | return modelContext 20 | } 21 | 22 | var body: some Scene { 23 | WindowGroup { 24 | TabView { 25 | TCAContentView(store: .init(initialState: TCAContentView.Feature.State(), reducer: { 26 | TCAContentView.Feature() 27 | ._printChanges() 28 | })) 29 | .tabItem { 30 | Label("TCAContentView", systemImage: "1.circle") 31 | } 32 | 33 | QueryView(store: .init(initialState: .init(), reducer: { 34 | QueryReducer()._printChanges() 35 | })) 36 | .modelContext(self.modelContext) 37 | .tabItem { 38 | Label("QueryView", systemImage: "2.circle") 39 | } 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SwiftDataTCA/Service/Database.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Database.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Rodrigo Souza on 05/10/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | import Dependencies 11 | 12 | extension DependencyValues { 13 | var databaseService: Database { 14 | get { self[Database.self] } 15 | set { self[Database.self] = newValue } 16 | } 17 | } 18 | 19 | fileprivate let appContext: ModelContext = { 20 | do { 21 | 22 | let url = URL.applicationSupportDirectory.appending(path: "Model.sqlite") 23 | let config = ModelConfiguration(url: url) 24 | 25 | let container = try ModelContainer(for: Movie.self, migrationPlan: MovieMigrationPlan.self, configurations: config) 26 | return ModelContext(container) 27 | } catch { 28 | fatalError("Failed to create container.") 29 | } 30 | }() 31 | 32 | struct Database { 33 | var context: () throws -> ModelContext 34 | } 35 | 36 | extension Database: DependencyKey { 37 | public static let liveValue = Self( 38 | context: { appContext } 39 | ) 40 | } 41 | 42 | extension Database: TestDependencyKey { 43 | public static var previewValue = Self.noop 44 | 45 | public static let testValue = Self( 46 | context: unimplemented("\(Self.self).context") 47 | ) 48 | 49 | static let noop = Self( 50 | context: unimplemented("\(Self.self).context") 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /SwiftDataTCA/View/Migrations/MovieMigration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelMigration.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Rodrigo Souza on 07/10/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | 11 | enum MovieSchemaV1: VersionedSchema { 12 | static var versionIdentifier = Schema.Version(1, 0, 0) 13 | 14 | static var models: [any PersistentModel.Type] { 15 | [Movie.self] 16 | } 17 | 18 | @Model 19 | class Movie: Identifiable { 20 | var id: UUID 21 | var title: String 22 | var cast: [String] 23 | 24 | init(title: String, cast: [String]) { 25 | self.id = UUID() 26 | self.title = title 27 | self.cast = cast 28 | } 29 | } 30 | } 31 | 32 | enum MovieSchemaV2: VersionedSchema { 33 | static var versionIdentifier = Schema.Version(2, 0, 0) 34 | 35 | static var models: [any PersistentModel.Type] { 36 | [Movie.self] 37 | } 38 | 39 | @Model 40 | class Movie: Identifiable { 41 | var id: UUID 42 | var title: String 43 | var cast: [String] 44 | var favorite: Bool = false 45 | 46 | init(title: String, cast: [String], favorite: Bool = false) { 47 | self.id = UUID() 48 | self.title = title 49 | self.cast = cast 50 | self.favorite = favorite 51 | } 52 | } 53 | } 54 | 55 | typealias Movie = MovieSchemaV2.Movie 56 | 57 | enum MovieMigrationPlan: SchemaMigrationPlan { 58 | static var stages: [MigrationStage] { 59 | [migrateV1toV2] 60 | } 61 | 62 | static var schemas: [any VersionedSchema.Type] { 63 | [MovieSchemaV1.self, MovieSchemaV2.self] 64 | } 65 | 66 | 67 | static let migrateV1toV2 = MigrationStage.lightweight( 68 | fromVersion: MovieSchemaV1.self, 69 | toVersion: MovieSchemaV2.self 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Movie App with SwiftData and PointFree Composable Architecture 2 | 3 | This is a sample application that demonstrates the power of the PointFree Composable Architecture (TCA) in combination with Apple's SwiftData for data persistence. 4 | 5 | ## Table of Contents 6 | - [Introduction](#introduction) 7 | - [Features](#features) 8 | - [Getting Started](#getting-started) 9 | - [Usage](#usage) 10 | - [Contributing](#contributing) 11 | - [License](#license) 12 | 13 | ## Introduction 14 | 15 | This project is a demonstration of how to implement SwiftData and the PointFree Composable Architecture (TCA) in an iOS application. It provides a clear and concise example of how to integrate these two powerful tools to build efficient and maintainable iOS apps. 16 | 17 | ## Features 18 | 19 | - **Sample Implementation**: The project is a simple implementation that highlights the integration of SwiftData for data persistence and TCA for a functional and predictable architecture. 20 | 21 | - **SwiftData Integration**: SwiftData is used to illustrate the ease of data persistence in iOS applications, making it a valuable addition to your toolbox. 22 | 23 | - **TCA Showcase**: The project demonstrates how TCA can be used to structure your app's architecture, making it easier to understand and manage the application's behavior and state. 24 | 25 | 26 | ## Getting Started 27 | 28 | To get started with this project, follow these steps: 29 | 30 | 1. **Clone the Repository**: 31 | 2. **Open Xcode**: 32 | 3. **Build and Run**: 33 | Build and run the project in Xcode to see the Movie app in action. 34 | 35 | ## Usage 36 | 37 | You are encouraged to use this project as a reference to understand how to implement SwiftData and TCA in your own iOS applications. The sample code provided here can help you kickstart your projects and build efficient, maintainable apps. 38 | 39 | 40 | ## Contributing 41 | 42 | If you'd like to contribute to this project, please follow the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines. 43 | 44 | ## License 45 | 46 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SwiftData + TCA Sample Implementation 2 | 3 | Thank you for considering contributing to this project. Your contributions help improve and grow the SwiftData + TCA community. We appreciate your interest in making this project better. 4 | 5 | Before contributing, please review this document to understand the guidelines and best practices for contributing to this project. 6 | 7 | ## Code of Conduct 8 | 9 | We expect all contributors to follow our [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive community. 10 | 11 | ## How to Contribute 12 | 13 | 1. Fork the Repository. 14 | 15 | 2. Clone your forked repository to your local machine. 16 | 17 | 3. Create a new branch for your work. 18 | `git clone https://github.com/your-username/swiftdata-tca-sample.git` 19 | 20 | 4. Make your changes, ensuring that your code follows best practices and is well-documented. 21 | `git checkout -b feature/your-feature-name` 22 | 23 | 5. Run tests if applicable and ensure that your changes do not introduce any issues. 24 | 25 | 6. Commit your changes with clear, concise commit messages. 26 | `git commit -m "feat: your feature name"` 27 | 28 | 7. Push your changes to your forked repository. 29 | `git push origin feature/your-feature-name` 30 | 31 | 8. Create a Pull Request: 32 | 33 | - Go to the [Pull Requests](https://github.com/your-username/swiftdata-tca-sample/pulls) section of the original repository. 34 | - Click "New Pull Request." 35 | - Select the branch containing your changes. 36 | - Provide a descriptive title and comment, explaining your changes. 37 | 38 | 9. Collaborate with reviewers to address feedback and make any necessary revisions. 39 | 40 | 10. Once your Pull Request is approved, it will be merged into the main branch. 41 | 42 | Please note that your contributions should align with the project's goals and guidelines. We appreciate your effort in contributing to the community. 43 | 44 | ## Reporting Issues 45 | 46 | If you encounter a bug, have a feature request, or need assistance, please open an issue on the [Issues](https://github.com/your-username/swiftdata-tca-sample/issues) page. 47 | 48 | ## Licensing 49 | 50 | By contributing to this project, you agree that your contributions will be licensed under the project's [MIT License](LICENSE). 51 | 52 | Thank you for your contributions and support! 🚀 53 | -------------------------------------------------------------------------------- /SwiftDataTCA/Service/MovieDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalDatabase.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Rodrigo Souza on 05/10/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftData 10 | import Dependencies 11 | 12 | extension DependencyValues { 13 | var swiftData: MovieDatabase { 14 | get { self[MovieDatabase.self] } 15 | set { self[MovieDatabase.self] = newValue } 16 | } 17 | } 18 | 19 | struct MovieDatabase { 20 | var fetchAll: @Sendable () throws -> [Movie] 21 | var fetch: @Sendable (FetchDescriptor) throws -> [Movie] 22 | var add: @Sendable (Movie) throws -> Void 23 | var delete: @Sendable (Movie) throws -> Void 24 | 25 | enum MovieError: Error { 26 | case add 27 | case delete 28 | } 29 | } 30 | 31 | extension MovieDatabase: DependencyKey { 32 | public static let liveValue = Self( 33 | fetchAll: { 34 | do { 35 | @Dependency(\.databaseService.context) var context 36 | let movieContext = try context() 37 | 38 | let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.title)]) 39 | return try movieContext.fetch(descriptor) 40 | } catch { 41 | return [] 42 | } 43 | }, 44 | fetch: { descriptor in 45 | do { 46 | @Dependency(\.databaseService.context) var context 47 | let movieContext = try context() 48 | return try movieContext.fetch(descriptor) 49 | } catch { 50 | return [] 51 | } 52 | }, 53 | add: { model in 54 | do { 55 | @Dependency(\.databaseService.context) var context 56 | let movieContext = try context() 57 | 58 | movieContext.insert(model) 59 | } catch { 60 | throw MovieError.add 61 | } 62 | }, 63 | delete: { model in 64 | do { 65 | @Dependency(\.databaseService.context) var context 66 | let movieContext = try context() 67 | 68 | let modelToBeDelete = model 69 | movieContext.delete(modelToBeDelete) 70 | } catch { 71 | throw MovieError.delete 72 | } 73 | } 74 | ) 75 | } 76 | 77 | extension MovieDatabase: TestDependencyKey { 78 | public static var previewValue = Self.noop 79 | 80 | public static let testValue = Self( 81 | fetchAll: unimplemented("\(Self.self).fetch"), 82 | fetch: unimplemented("\(Self.self).fetchDescriptor"), 83 | add: unimplemented("\(Self.self).add"), 84 | delete: unimplemented("\(Self.self).delete") 85 | ) 86 | 87 | static let noop = Self( 88 | fetchAll: { [] }, 89 | fetch: { _ in [] }, 90 | add: { _ in }, 91 | delete: { _ in } 92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "combine-schedulers", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/pointfreeco/combine-schedulers", 7 | "state" : { 8 | "revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb", 9 | "version" : "1.0.0" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-case-paths", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/pointfreeco/swift-case-paths", 16 | "state" : { 17 | "revision" : "5da6989aae464f324eef5c5b52bdb7974725ab81", 18 | "version" : "1.0.0" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-clocks", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/pointfreeco/swift-clocks", 25 | "state" : { 26 | "revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb", 27 | "version" : "1.0.0" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-collections", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/apple/swift-collections", 34 | "state" : { 35 | "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", 36 | "version" : "1.0.5" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-composable-architecture", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/pointfreeco/swift-composable-architecture", 43 | "state" : { 44 | "revision" : "a7c1f799b55ecb418f85094b142565834f7ee7c7", 45 | "version" : "1.2.0" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-concurrency-extras", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/pointfreeco/swift-concurrency-extras", 52 | "state" : { 53 | "revision" : "ea631ce892687f5432a833312292b80db238186a", 54 | "version" : "1.0.0" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-custom-dump", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/pointfreeco/swift-custom-dump", 61 | "state" : { 62 | "revision" : "3efbfba0e4e56c7187cc19137ee16b7c95346b79", 63 | "version" : "1.1.0" 64 | } 65 | }, 66 | { 67 | "identity" : "swift-dependencies", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/pointfreeco/swift-dependencies", 70 | "state" : { 71 | "revision" : "4e1eb6e28afe723286d8cc60611237ffbddba7c5", 72 | "version" : "1.0.0" 73 | } 74 | }, 75 | { 76 | "identity" : "swift-identified-collections", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/pointfreeco/swift-identified-collections", 79 | "state" : { 80 | "revision" : "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8", 81 | "version" : "1.0.0" 82 | } 83 | }, 84 | { 85 | "identity" : "swiftui-navigation", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/pointfreeco/swiftui-navigation", 88 | "state" : { 89 | "revision" : "6eb293c49505d86e9e24232cb6af6be7fff93bd5", 90 | "version" : "1.0.2" 91 | } 92 | }, 93 | { 94 | "identity" : "xctest-dynamic-overlay", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", 97 | "state" : { 98 | "revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631", 99 | "version" : "1.0.2" 100 | } 101 | } 102 | ], 103 | "version" : 2 104 | } 105 | -------------------------------------------------------------------------------- /SwiftDataTCA/View/QueryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QueryView.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Daniel Lyons on 11/8/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import ComposableArchitecture 11 | import SwiftData 12 | import Dependencies 13 | 14 | struct QueryView: View { 15 | let store: StoreOf 16 | 17 | @Query(FetchDescriptor()) var moviesQuery: [Movie] 18 | 19 | var body: some View { 20 | WithViewStore(store, observe: \.movies) { viewStore in 21 | NavigationStack { 22 | List { 23 | ForEach(viewStore.state) { movie in 24 | VStack { 25 | Text("\(movie.title)").font(.title) 26 | Text("\(movie.id)") 27 | } 28 | .background(movie.favorite ? .blue : .clear) 29 | .swipeActions { 30 | Button("Delete", systemImage: "trash", role: .destructive) { 31 | viewStore.send(.delete(movie), animation: .snappy) 32 | } 33 | Button("Toggle Favorite", systemImage: "star") { 34 | viewStore.send(.favorite(movie), animation: .snappy) 35 | } 36 | } 37 | } 38 | } 39 | .navigationTitle("Movies Query") 40 | .toolbar { 41 | Button("Add sample", systemImage: "plus") { 42 | viewStore.send(.addMovie, animation: .snappy) 43 | } 44 | } 45 | .onChange(of: self.moviesQuery, initial: true) { _, newValue in 46 | viewStore.send(.queryChangedMovies(newValue)) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | struct QueryReducer: Reducer { 54 | struct State: Equatable { 55 | var movies: [Movie] = [] 56 | } 57 | 58 | enum Action: Equatable { 59 | case queryChangedMovies([Movie]) 60 | case addMovie 61 | case delete(Movie) 62 | case favorite(Movie) 63 | } 64 | 65 | @Dependency(\.swiftData) var context 66 | @Dependency(\.databaseService) var databaseService 67 | 68 | var body: some ReducerOf { 69 | Reduce { state, action in 70 | switch action { 71 | case .queryChangedMovies(let newMovies): 72 | state.movies = newMovies 73 | return .none 74 | case .addMovie: 75 | do { 76 | let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! 77 | try context.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) 78 | } catch { } 79 | return .none 80 | case .delete(let movieToDelete): 81 | do { 82 | try context.delete(movieToDelete) 83 | } catch { } 84 | return .none 85 | case .favorite(let movieToFavorite): 86 | movieToFavorite.favorite.toggle() 87 | guard let context = try? self.databaseService.context() else { 88 | print("Failed to find context") 89 | return .none 90 | } 91 | do { 92 | try context.save() 93 | } catch { 94 | print("Failed to save") 95 | } 96 | return .none 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /SwiftDataTCA/View/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TCAContentView.swift 3 | // SwiftDataTCA 4 | // 5 | // Created by Rodrigo Souza on 03/10/23. 6 | // 7 | 8 | import SwiftUI 9 | import ComposableArchitecture 10 | import SwiftData 11 | import Dependencies 12 | 13 | struct TCAContentView: View { 14 | let store: StoreOf 15 | 16 | struct ViewState: Equatable { 17 | let movies: [Movie] 18 | let searchString: String 19 | let titleSort: TCAContentView.Feature.State.TitleSort? 20 | let uuidSort: TCAContentView.Feature.State.UUIDSort? 21 | 22 | init(_ state: Feature.State) { 23 | self.movies = state.movies 24 | self.searchString = state.searchString 25 | self.titleSort = state.titleSort 26 | self.uuidSort = state.uuidSort 27 | } 28 | } 29 | 30 | var body: some View { 31 | WithViewStore(store, observe: ViewState.init) { viewStore in 32 | NavigationStack { 33 | Menu("Sort", systemImage: "arrow.up.arrow.down") { 34 | Picker("Title", selection: viewStore.binding(get: \.titleSort, send: { .titleSortChanged($0) }).animation()) { 35 | Text("A -> Z").tag(TCAContentView_TitleSort?.some(.forward)) 36 | Text("Z -> A").tag(TCAContentView_TitleSort?.some(.reverse)) 37 | Text("❌").tag(TCAContentView_TitleSort?.none) 38 | }.pickerStyle(.menu) 39 | Picker("UUID", selection: viewStore.binding(get: \.uuidSort, send: { .uuidSortChanged($0) }).animation()) { 40 | Text("A -> Z").tag(TCAContentView.Feature.State.UUIDSort?.some(.forward)) 41 | Text("Z -> A").tag(TCAContentView.Feature.State.UUIDSort?.some(.reverse)) 42 | Text("❌").tag(TCAContentView.Feature.State.UUIDSort?.none) 43 | }.pickerStyle(.menu) 44 | if viewStore.titleSort != nil || viewStore.uuidSort != nil { 45 | Button("No sorting") { viewStore.send(.clearAllSorting, animation: .default) } 46 | } 47 | } 48 | Text( 49 | "Title: \(self.description(viewStore.titleSort)), UUID: \(self.description(viewStore.uuidSort))" 50 | ) 51 | 52 | List(viewStore.movies) { movie in 53 | VStack(alignment: .leading) { 54 | Text(movie.id.uuidString) 55 | 56 | Text(movie.title) 57 | .font(.headline) 58 | 59 | Text(movie.cast.formatted(.list(type: .and))) 60 | } 61 | .background(movie.favorite ? .blue : .clear) 62 | .swipeActions { 63 | Button(role: .destructive) { 64 | store.send(.delete(movie), animation: .snappy) 65 | } label: { 66 | Label("Delete", systemImage: "trash") 67 | } 68 | Button { 69 | store.send(.favorite(movie), animation: .bouncy) 70 | } label: { 71 | Label( 72 | movie.favorite ? "Unfavorite" : "Favorite", 73 | systemImage: "star" 74 | ).foregroundStyle(.blue) 75 | } 76 | } 77 | } 78 | .navigationTitle("SwiftData") 79 | .searchable(text: viewStore.binding(get: \.searchString, 80 | send: { .searchStringChanged($0) } 81 | ).animation(), // the animation on this Binding is not currently working 82 | prompt: "Title" 83 | ) 84 | .toolbar { 85 | Button("Add Sample") { 86 | store.send(.add) 87 | } 88 | } 89 | } 90 | .onAppear { 91 | store.send(.onAppear) 92 | } 93 | } 94 | } 95 | 96 | func description(_ titleSort: TCAContentView_TitleSort?) -> String { 97 | switch titleSort { 98 | case .forward: "Forward" 99 | case .reverse: "Reverse" 100 | case .none: "None" 101 | } 102 | } 103 | 104 | func description(_ uuidSort: TCAContentView_UUIDSort?) -> String { 105 | switch uuidSort { 106 | case .forward: "Forward" 107 | case .reverse: "Reverse" 108 | case .none: "None" 109 | } 110 | } 111 | } 112 | 113 | typealias TCAContentView_TitleSort = TCAContentView.Feature.State.TitleSort 114 | typealias TCAContentView_UUIDSort = TCAContentView.Feature.State.UUIDSort 115 | 116 | extension TCAContentView { 117 | struct Feature: Reducer { 118 | struct State: Equatable { 119 | var movies: [Movie] = [] 120 | var searchString: String = "" 121 | var fetchDescriptor: FetchDescriptor { 122 | return .init(predicate: self.predicate, sortBy: self.sort) 123 | } 124 | var predicate: Predicate { 125 | guard !searchString.isEmpty else { return #Predicate { _ in true } } 126 | 127 | return #Predicate { 128 | $0.title.localizedStandardContains(searchString) 129 | 130 | } 131 | } 132 | var sort: [SortDescriptor] { 133 | return [ 134 | self.titleSort?.descriptor, 135 | self.uuidSort?.descriptor 136 | ].compactMap { $0 } 137 | } 138 | var titleSort: TitleSort? 139 | public enum TitleSort { 140 | case forward, reverse 141 | var descriptor: SortDescriptor { 142 | switch self { 143 | case .forward: 144 | return .init(\.title, order: .forward) 145 | case .reverse: 146 | return .init(\.title, order: .reverse) 147 | } 148 | } 149 | } 150 | var uuidSort: UUIDSort? 151 | enum UUIDSort { 152 | case forward, reverse 153 | var descriptor: SortDescriptor { 154 | switch self { 155 | case .forward: return .init(\.id, order: .forward) 156 | case .reverse: return .init(\.id, order: .reverse) 157 | } 158 | } 159 | } 160 | 161 | 162 | mutating func refetchMovies() { 163 | @Dependency(\.swiftData) var context 164 | do { 165 | self.movies = try context.fetch(self.fetchDescriptor) 166 | } catch {} 167 | } 168 | } 169 | 170 | enum Action: Equatable { 171 | case onAppear 172 | 173 | case add 174 | case delete(Movie) 175 | case favorite(Movie) 176 | 177 | case searchStringChanged(String) 178 | 179 | case titleSortChanged(TCAContentView.Feature.State.TitleSort?) 180 | case uuidSortChanged(TCAContentView.Feature.State.UUIDSort?) 181 | case clearAllSorting 182 | } 183 | 184 | @Dependency(\.swiftData) var context 185 | 186 | var body: some Reducer { 187 | Reduce { state, action in 188 | switch action { 189 | case .onAppear: 190 | state.refetchMovies() 191 | return .none 192 | case .add: 193 | do { 194 | let randomMovieName = ["Star Wars", "Harry Potter", "Hunger Games", "Lord of the Rings"].randomElement()! 195 | try context.add(.init(title: randomMovieName, cast: ["Sam Worthington", "Zoe Saldaña", "Stephen Lang", "Michelle Rodriguez"])) 196 | } catch { } 197 | return .run { @MainActor send in 198 | send(.onAppear, animation: .default) 199 | } 200 | case .delete(let movie): 201 | do { 202 | try context.delete(movie) 203 | } catch { 204 | 205 | } 206 | 207 | return .run { @MainActor send in 208 | send(.onAppear, animation: .default) 209 | } 210 | case .favorite(let movie): 211 | movie.favorite.toggle() 212 | 213 | return .none 214 | case .searchStringChanged(let newString): 215 | guard newString != state.searchString else { return .none } 216 | 217 | state.searchString = newString 218 | // state.refetchMovies() 219 | 220 | // Not sure why but animation doesn't appear to work unless I .run another action 221 | return .run { @MainActor send in 222 | send(.onAppear, animation: .default) 223 | } 224 | case .titleSortChanged(let newSort): 225 | state.titleSort = newSort 226 | state.refetchMovies() 227 | return .none 228 | case .uuidSortChanged(let newSort): 229 | state.uuidSort = newSort 230 | state.refetchMovies() 231 | return .none 232 | case .clearAllSorting: 233 | state.uuidSort = nil 234 | state.titleSort = nil 235 | state.refetchMovies() 236 | return .none 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | #Preview { 244 | TCAContentView( 245 | store: .init(initialState: .init(), 246 | reducer: TCAContentView.Feature.init) 247 | ) 248 | } 249 | -------------------------------------------------------------------------------- /SwiftDataTCA.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 23A64E8A2ACC5312005B6344 /* SwiftDataTCAApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A64E892ACC5312005B6344 /* SwiftDataTCAApp.swift */; }; 11 | 23A64E902ACC5313005B6344 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23A64E8F2ACC5313005B6344 /* Assets.xcassets */; }; 12 | 23A64E942ACC5313005B6344 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23A64E932ACC5313005B6344 /* Preview Assets.xcassets */; }; 13 | 23A64E9D2ACC5DFE005B6344 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 23A64E9C2ACC5DFE005B6344 /* ComposableArchitecture */; }; 14 | 23A64E9F2ACC5E16005B6344 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A64E9E2ACC5E16005B6344 /* ContentView.swift */; }; 15 | 23A64EA72ACF5812005B6344 /* MovieDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A64EA62ACF5812005B6344 /* MovieDatabase.swift */; }; 16 | 23A64EA92ACF5855005B6344 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A64EA82ACF5855005B6344 /* Database.swift */; }; 17 | 23A64EAD2AD1AB46005B6344 /* MovieMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A64EAC2AD1AB46005B6344 /* MovieMigration.swift */; }; 18 | 80180C422AFBECC900ADAB03 /* QueryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80180C412AFBECC900ADAB03 /* QueryView.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 23A64E862ACC5312005B6344 /* SwiftDataTCA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftDataTCA.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 23A64E892ACC5312005B6344 /* SwiftDataTCAApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDataTCAApp.swift; sourceTree = ""; }; 24 | 23A64E8F2ACC5313005B6344 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 23A64E912ACC5313005B6344 /* SwiftDataTCA.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SwiftDataTCA.entitlements; sourceTree = ""; }; 26 | 23A64E932ACC5313005B6344 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | 23A64E952ACC5313005B6344 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 23A64E9E2ACC5E16005B6344 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 29 | 23A64EA62ACF5812005B6344 /* MovieDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDatabase.swift; sourceTree = ""; }; 30 | 23A64EA82ACF5855005B6344 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = ""; }; 31 | 23A64EAC2AD1AB46005B6344 /* MovieMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieMigration.swift; sourceTree = ""; }; 32 | 80180C412AFBECC900ADAB03 /* QueryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryView.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 23A64E832ACC5312005B6344 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | 23A64E9D2ACC5DFE005B6344 /* ComposableArchitecture in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 23A64E7D2ACC5312005B6344 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 23A64E882ACC5312005B6344 /* SwiftDataTCA */, 51 | 23A64E872ACC5312005B6344 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 23A64E872ACC5312005B6344 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 23A64E862ACC5312005B6344 /* SwiftDataTCA.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 23A64E882ACC5312005B6344 /* SwiftDataTCA */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 23A64EAA2AD0CCE4005B6344 /* View */, 67 | 23A64EA52ACF5803005B6344 /* Service */, 68 | 23A64E892ACC5312005B6344 /* SwiftDataTCAApp.swift */, 69 | 23A64E8F2ACC5313005B6344 /* Assets.xcassets */, 70 | 23A64E912ACC5313005B6344 /* SwiftDataTCA.entitlements */, 71 | 23A64E952ACC5313005B6344 /* Info.plist */, 72 | 23A64E922ACC5313005B6344 /* Preview Content */, 73 | ); 74 | path = SwiftDataTCA; 75 | sourceTree = ""; 76 | }; 77 | 23A64E922ACC5313005B6344 /* Preview Content */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 23A64E932ACC5313005B6344 /* Preview Assets.xcassets */, 81 | ); 82 | path = "Preview Content"; 83 | sourceTree = ""; 84 | }; 85 | 23A64EA52ACF5803005B6344 /* Service */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 23A64EA62ACF5812005B6344 /* MovieDatabase.swift */, 89 | 23A64EA82ACF5855005B6344 /* Database.swift */, 90 | ); 91 | path = Service; 92 | sourceTree = ""; 93 | }; 94 | 23A64EAA2AD0CCE4005B6344 /* View */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 23A64EAB2AD1AB36005B6344 /* Migrations */, 98 | 23A64E9E2ACC5E16005B6344 /* ContentView.swift */, 99 | 80180C412AFBECC900ADAB03 /* QueryView.swift */, 100 | ); 101 | path = View; 102 | sourceTree = ""; 103 | }; 104 | 23A64EAB2AD1AB36005B6344 /* Migrations */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 23A64EAC2AD1AB46005B6344 /* MovieMigration.swift */, 108 | ); 109 | path = Migrations; 110 | sourceTree = ""; 111 | }; 112 | /* End PBXGroup section */ 113 | 114 | /* Begin PBXNativeTarget section */ 115 | 23A64E852ACC5312005B6344 /* SwiftDataTCA */ = { 116 | isa = PBXNativeTarget; 117 | buildConfigurationList = 23A64E982ACC5313005B6344 /* Build configuration list for PBXNativeTarget "SwiftDataTCA" */; 118 | buildPhases = ( 119 | 23A64E822ACC5312005B6344 /* Sources */, 120 | 23A64E832ACC5312005B6344 /* Frameworks */, 121 | 23A64E842ACC5312005B6344 /* Resources */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = SwiftDataTCA; 128 | packageProductDependencies = ( 129 | 23A64E9C2ACC5DFE005B6344 /* ComposableArchitecture */, 130 | ); 131 | productName = SwiftDataTCA; 132 | productReference = 23A64E862ACC5312005B6344 /* SwiftDataTCA.app */; 133 | productType = "com.apple.product-type.application"; 134 | }; 135 | /* End PBXNativeTarget section */ 136 | 137 | /* Begin PBXProject section */ 138 | 23A64E7E2ACC5312005B6344 /* Project object */ = { 139 | isa = PBXProject; 140 | attributes = { 141 | BuildIndependentTargetsInParallel = 1; 142 | LastSwiftUpdateCheck = 1500; 143 | LastUpgradeCheck = 1500; 144 | TargetAttributes = { 145 | 23A64E852ACC5312005B6344 = { 146 | CreatedOnToolsVersion = 15.0; 147 | }; 148 | }; 149 | }; 150 | buildConfigurationList = 23A64E812ACC5312005B6344 /* Build configuration list for PBXProject "SwiftDataTCA" */; 151 | compatibilityVersion = "Xcode 14.0"; 152 | developmentRegion = en; 153 | hasScannedForEncodings = 0; 154 | knownRegions = ( 155 | en, 156 | Base, 157 | ); 158 | mainGroup = 23A64E7D2ACC5312005B6344; 159 | packageReferences = ( 160 | 23A64E9B2ACC5DFE005B6344 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, 161 | ); 162 | productRefGroup = 23A64E872ACC5312005B6344 /* Products */; 163 | projectDirPath = ""; 164 | projectRoot = ""; 165 | targets = ( 166 | 23A64E852ACC5312005B6344 /* SwiftDataTCA */, 167 | ); 168 | }; 169 | /* End PBXProject section */ 170 | 171 | /* Begin PBXResourcesBuildPhase section */ 172 | 23A64E842ACC5312005B6344 /* Resources */ = { 173 | isa = PBXResourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 23A64E942ACC5313005B6344 /* Preview Assets.xcassets in Resources */, 177 | 23A64E902ACC5313005B6344 /* Assets.xcassets in Resources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXResourcesBuildPhase section */ 182 | 183 | /* Begin PBXSourcesBuildPhase section */ 184 | 23A64E822ACC5312005B6344 /* Sources */ = { 185 | isa = PBXSourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | 23A64EA72ACF5812005B6344 /* MovieDatabase.swift in Sources */, 189 | 80180C422AFBECC900ADAB03 /* QueryView.swift in Sources */, 190 | 23A64E9F2ACC5E16005B6344 /* ContentView.swift in Sources */, 191 | 23A64EAD2AD1AB46005B6344 /* MovieMigration.swift in Sources */, 192 | 23A64EA92ACF5855005B6344 /* Database.swift in Sources */, 193 | 23A64E8A2ACC5312005B6344 /* SwiftDataTCAApp.swift in Sources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXSourcesBuildPhase section */ 198 | 199 | /* Begin XCBuildConfiguration section */ 200 | 23A64E962ACC5313005B6344 /* Debug */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 205 | CLANG_ANALYZER_NONNULL = YES; 206 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_ENABLE_OBJC_WEAK = YES; 211 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_COMMA = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 218 | CLANG_WARN_EMPTY_BODY = YES; 219 | CLANG_WARN_ENUM_CONVERSION = YES; 220 | CLANG_WARN_INFINITE_RECURSION = YES; 221 | CLANG_WARN_INT_CONVERSION = YES; 222 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 224 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 227 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 228 | CLANG_WARN_STRICT_PROTOTYPES = YES; 229 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 230 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 231 | CLANG_WARN_UNREACHABLE_CODE = YES; 232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = dwarf; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | ENABLE_TESTABILITY = YES; 237 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 238 | GCC_C_LANGUAGE_STANDARD = gnu17; 239 | GCC_DYNAMIC_NO_PIC = NO; 240 | GCC_NO_COMMON_BLOCKS = YES; 241 | GCC_OPTIMIZATION_LEVEL = 0; 242 | GCC_PREPROCESSOR_DEFINITIONS = ( 243 | "DEBUG=1", 244 | "$(inherited)", 245 | ); 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 253 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 254 | MTL_FAST_MATH = YES; 255 | ONLY_ACTIVE_ARCH = YES; 256 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 257 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 258 | }; 259 | name = Debug; 260 | }; 261 | 23A64E972ACC5313005B6344 /* Release */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_ENABLE_OBJC_WEAK = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 289 | CLANG_WARN_STRICT_PROTOTYPES = YES; 290 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 291 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 292 | CLANG_WARN_UNREACHABLE_CODE = YES; 293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 294 | COPY_PHASE_STRIP = NO; 295 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 296 | ENABLE_NS_ASSERTIONS = NO; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu17; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | MTL_FAST_MATH = YES; 310 | SWIFT_COMPILATION_MODE = wholemodule; 311 | }; 312 | name = Release; 313 | }; 314 | 23A64E992ACC5313005B6344 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 319 | CODE_SIGN_ENTITLEMENTS = SwiftDataTCA/SwiftDataTCA.entitlements; 320 | CODE_SIGN_STYLE = Automatic; 321 | CURRENT_PROJECT_VERSION = 1; 322 | DEVELOPMENT_ASSET_PATHS = "\"SwiftDataTCA/Preview Content\""; 323 | DEVELOPMENT_TEAM = 2KMN827G7Y; 324 | ENABLE_PREVIEWS = YES; 325 | GENERATE_INFOPLIST_FILE = YES; 326 | INFOPLIST_FILE = SwiftDataTCA/Info.plist; 327 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 328 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 329 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 330 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 331 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 332 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 333 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 334 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 335 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 336 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 337 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 338 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 339 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 340 | MACOSX_DEPLOYMENT_TARGET = 14.0; 341 | MARKETING_VERSION = 1.0; 342 | PRODUCT_BUNDLE_IDENTIFIER = io.DandyLyons.SwiftDataTCA; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SDKROOT = auto; 345 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 346 | SWIFT_EMIT_LOC_STRINGS = YES; 347 | SWIFT_VERSION = 5.0; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | }; 350 | name = Debug; 351 | }; 352 | 23A64E9A2ACC5313005B6344 /* Release */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 357 | CODE_SIGN_ENTITLEMENTS = SwiftDataTCA/SwiftDataTCA.entitlements; 358 | CODE_SIGN_STYLE = Automatic; 359 | CURRENT_PROJECT_VERSION = 1; 360 | DEVELOPMENT_ASSET_PATHS = "\"SwiftDataTCA/Preview Content\""; 361 | DEVELOPMENT_TEAM = 2KMN827G7Y; 362 | ENABLE_PREVIEWS = YES; 363 | GENERATE_INFOPLIST_FILE = YES; 364 | INFOPLIST_FILE = SwiftDataTCA/Info.plist; 365 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 366 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 367 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 368 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 369 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 370 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 371 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 372 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 373 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 374 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 375 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 376 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 377 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 378 | MACOSX_DEPLOYMENT_TARGET = 14.0; 379 | MARKETING_VERSION = 1.0; 380 | PRODUCT_BUNDLE_IDENTIFIER = io.DandyLyons.SwiftDataTCA; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SDKROOT = auto; 383 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 384 | SWIFT_EMIT_LOC_STRINGS = YES; 385 | SWIFT_VERSION = 5.0; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | }; 388 | name = Release; 389 | }; 390 | /* End XCBuildConfiguration section */ 391 | 392 | /* Begin XCConfigurationList section */ 393 | 23A64E812ACC5312005B6344 /* Build configuration list for PBXProject "SwiftDataTCA" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | 23A64E962ACC5313005B6344 /* Debug */, 397 | 23A64E972ACC5313005B6344 /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | 23A64E982ACC5313005B6344 /* Build configuration list for PBXNativeTarget "SwiftDataTCA" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | 23A64E992ACC5313005B6344 /* Debug */, 406 | 23A64E9A2ACC5313005B6344 /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | /* End XCConfigurationList section */ 412 | 413 | /* Begin XCRemoteSwiftPackageReference section */ 414 | 23A64E9B2ACC5DFE005B6344 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { 415 | isa = XCRemoteSwiftPackageReference; 416 | repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; 417 | requirement = { 418 | kind = upToNextMajorVersion; 419 | minimumVersion = 1.2.0; 420 | }; 421 | }; 422 | /* End XCRemoteSwiftPackageReference section */ 423 | 424 | /* Begin XCSwiftPackageProductDependency section */ 425 | 23A64E9C2ACC5DFE005B6344 /* ComposableArchitecture */ = { 426 | isa = XCSwiftPackageProductDependency; 427 | package = 23A64E9B2ACC5DFE005B6344 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; 428 | productName = ComposableArchitecture; 429 | }; 430 | /* End XCSwiftPackageProductDependency section */ 431 | }; 432 | rootObject = 23A64E7E2ACC5312005B6344 /* Project object */; 433 | } 434 | --------------------------------------------------------------------------------