├── 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 |
--------------------------------------------------------------------------------