├── GRDBDemoApp
├── Sources
│ └── Born
│ │ ├── Exported.swift
│ │ ├── Model
│ │ ├── Dependencies.swift
│ │ ├── Persistence.swift
│ │ ├── Player.swift
│ │ └── AppDatabase.swift
│ │ └── Reducers
│ │ ├── EditFeature.swift
│ │ ├── NewFeature.swift
│ │ └── ListFeature.swift
├── Apps
│ ├── Apple
│ │ ├── Xcode
│ │ │ ├── HappyDevLife
│ │ │ │ ├── Assets.xcassets
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── AccentColor.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── AppIcon.appiconset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Preview Content
│ │ │ │ │ └── Preview Assets.xcassets
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── HappyDevLife.entitlements
│ │ │ │ ├── HappyDevLifeApp.swift
│ │ │ │ └── SampleCode.xcconfig
│ │ │ ├── HappyDevLife.xcodeproj
│ │ │ │ ├── project.xcworkspace
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata
│ │ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ │ ├── xcshareddata
│ │ │ │ │ └── xcschemes
│ │ │ │ │ │ └── HappyDevLife.xcscheme
│ │ │ │ └── project.pbxproj
│ │ │ ├── Package.swift
│ │ │ └── Workspace.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── Lib
│ │ │ └── MainApp.swift
│ └── Windows
│ │ ├── Util.swift
│ │ ├── AppDelegate.swift
│ │ └── ListView.swift
├── .gitignore
├── README.md
├── .vscode
│ └── launch.json
├── Tests
│ └── BornTests
│ │ ├── ListFeatureTests.swift
│ │ ├── PlayerTests.swift
│ │ └── AppDatabaseTests.swift
└── Package.swift
├── SimpleCounter
├── Sources
│ └── Born
│ │ ├── Exported.swift
│ │ ├── Counter.swift
│ │ └── Navigation.swift
├── Apps
│ ├── Apple
│ │ ├── Xcode
│ │ │ ├── HappyDevLife
│ │ │ │ ├── Assets.xcassets
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── AccentColor.colorset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── AppIcon.appiconset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Preview Content
│ │ │ │ │ └── Preview Assets.xcassets
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── HappyDevLifeApp.swift
│ │ │ │ ├── HappyDevLife.entitlements
│ │ │ │ └── SampleCode.xcconfig
│ │ │ ├── HappyDevLife.xcodeproj
│ │ │ │ ├── project.xcworkspace
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata
│ │ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ │ └── project.pbxproj
│ │ │ ├── Package.swift
│ │ │ └── Workspace.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── Lib
│ │ │ └── MainApp.swift
│ ├── Windows
│ │ ├── Navigation.swift
│ │ ├── AppDelegate.swift
│ │ └── Counter.swift
│ └── WinUITests
│ │ └── CounterUITests.swift
├── .gitignore
├── README.md
├── .vscode
│ └── launch.json
├── Tests
│ └── BornTests
│ │ └── BornTests.swift
└── Package.swift
├── .gitignore
├── requirement.ps1
├── LICENSE
└── README.md
/GRDBDemoApp/Sources/Born/Exported.swift:
--------------------------------------------------------------------------------
1 | @_exported import ComposableArchitecture
2 |
--------------------------------------------------------------------------------
/SimpleCounter/Sources/Born/Exported.swift:
--------------------------------------------------------------------------------
1 | @_exported import ComposableArchitecture
2 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
10 | build
11 | *.build
12 | *.nupkg
13 | Package.resolved
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/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 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Xcode"
8 | )
9 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/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 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Xcode"
8 | )
9 |
--------------------------------------------------------------------------------
/GRDBDemoApp/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
10 | build
11 | *.build
12 | *.nupkg
13 | Package.resolved
--------------------------------------------------------------------------------
/SimpleCounter/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
10 | build
11 | *.build
12 | *.nupkg
13 | Package.resolved
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/Workspace.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/Workspace.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/Workspace.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/Workspace.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/HappyDevLifeApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HappyDevLifeApp.swift
3 | // HappyDevLife
4 | //
5 | // Created by arasan01 on 2024/03/01.
6 | //
7 |
8 | import SwiftUI
9 | import AppleApp
10 |
11 | @main
12 | struct HappyDevLifeApp: App {
13 | var body: some Scene {
14 | WindowGroup {
15 | ContentView()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/HappyDevLife.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/HappyDevLife.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Windows/Util.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | func configure(_ source: T, _ setup: (T) -> Void) -> T {
4 | setup(source)
5 | return source
6 | }
7 |
8 | extension Array where Element: ObservationToken {
9 | func cancelAll() {
10 | for token in self {
11 | token.cancel()
12 | }
13 | }
14 | }
15 |
16 | extension ObservationToken {
17 | func store(in array: inout [ObservationToken]) {
18 | array.append(self)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/HappyDevLifeApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HappyDevLifeApp.swift
3 | // HappyDevLife
4 | //
5 | // Created by arasan01 on 2024/03/01.
6 | //
7 |
8 | import SwiftUI
9 | import AppleApp
10 | import Born
11 |
12 | @main
13 | struct HappyDevLifeApp: App {
14 | var body: some Scene {
15 | WindowGroup {
16 | ContentView(
17 | store: Store(
18 | initialState: ListFeature.State(),
19 | reducer: { ListFeature()._printChanges() }
20 | )
21 | )
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SimpleCounter/README.md:
--------------------------------------------------------------------------------
1 | # Simple counter
2 |
3 | Learn how to create a development environment and make sure TCA and GRDB.swift can actually build.
4 |
5 | ## Windows UITest
6 |
7 | swift-webdriver communicates with the Windows Application Driver's WebDriver to handle UITest commands. The name WebDriver is based on the protocol designed by W3C based on Selenium, and it is not limited to the web, as it is used by Windows and Appium.
8 |
9 | https://github.com/thebrowsercompany/swift-webdriver
10 |
11 | **You need to install the WinAppDriver application when running the test.**
12 | https://github.com/microsoft/WinAppDriver
13 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Model/Dependencies.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | extension DependencyValues {
4 | public var appDatabase: AppDatabase {
5 | get { self[AppDatabase.self] }
6 | set { self[AppDatabase.self] = newValue }
7 | }
8 | }
9 |
10 | extension AppDatabase: TestDependencyKey {
11 | public static var testValue: AppDatabase {
12 | AppDatabase.empty()
13 | }
14 |
15 | public static var previewValue: AppDatabase {
16 | AppDatabase.random()
17 | }
18 | }
19 |
20 | extension AppDatabase: DependencyKey {
21 | public static var liveValue: AppDatabase {
22 | AppDatabase.shared
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/GRDBDemoApp/README.md:
--------------------------------------------------------------------------------
1 | # GRDBDemoApp
2 |
3 | This is a modified version of the demo app provided in the GRDB.swift official website to work on Windows.
4 |
5 | ref: https://github.com/groue/GRDB.swift/tree/master/Documentation/DemoApps
6 |
7 | The demo app provided in the GRDB.swift official website only works on macOS, but I modified it to work on Windows.
8 |
9 | This app demonstrates how to create a database, create a table, insert data, and retrieve data using SQLite.
10 |
11 | This app also demonstrates how to create a database, create a table, insert data, and retrieve data using `GRDB.swift`.
12 |
13 | This app is written in the Swift language. with The Composable Architecture 1.8.2
14 |
15 |
16 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/SampleCode.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // See LICENSE folder for this sample’s licensing information.
3 | //
4 | // SampleCode.xcconfig
5 | //
6 |
7 | // The `SAMPLE_CODE_DISAMBIGUATOR` configuration is to make it easier to build
8 | // and run a sample code project. Once you set your project's development team,
9 | // you'll have a unique bundle identifier. This is because the bundle identifier
10 | // is derived based on the 'SAMPLE_CODE_DISAMBIGUATOR' value. Do not use this
11 | // approach in your own projects—it's only useful for sample code projects because
12 | // they are frequently downloaded and don't have a development team set.
13 | SAMPLE_CODE_DISAMBIGUATOR=${DEVELOPMENT_TEAM}
14 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/SampleCode.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // See LICENSE folder for this sample’s licensing information.
3 | //
4 | // SampleCode.xcconfig
5 | //
6 |
7 | // The `SAMPLE_CODE_DISAMBIGUATOR` configuration is to make it easier to build
8 | // and run a sample code project. Once you set your project's development team,
9 | // you'll have a unique bundle identifier. This is because the bundle identifier
10 | // is derived based on the 'SAMPLE_CODE_DISAMBIGUATOR' value. Do not use this
11 | // approach in your own projects—it's only useful for sample code projects because
12 | // they are frequently downloaded and don't have a development team set.
13 | SAMPLE_CODE_DISAMBIGUATOR=${DEVELOPMENT_TEAM}
14 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Lib/MainApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import Born
3 |
4 | public struct ContentView: View {
5 | @State var store = Store(initialState: Counter.State(), reducer: { Counter() })
6 |
7 | public init() {}
8 |
9 | public var body: some View {
10 | HStack {
11 | Button(action: {
12 | store.send(.decrementButtonTapped)
13 | }) {
14 | Text("-1")
15 | }
16 | Text("\(store.count)")
17 | .font(.system(size: 24))
18 | Button(action: {
19 | store.send(.incrementButtonTapped)
20 | }) {
21 | Text("+1")
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SimpleCounter/Sources/Born/Counter.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | @Reducer
4 | public struct Counter: Reducer {
5 | public init() {}
6 |
7 | @ObservableState
8 | public struct State: Equatable {
9 | public var count = 0
10 |
11 | public init(count: Int = 0) {
12 | self.count = count
13 | }
14 | }
15 |
16 | public enum Action {
17 | case decrementButtonTapped
18 | case incrementButtonTapped
19 | }
20 |
21 | public var body: some ReducerOf {
22 | Reduce { state, action in
23 | switch action {
24 | case .decrementButtonTapped:
25 | state.count -= 1
26 | case .incrementButtonTapped:
27 | state.count += 1
28 | }
29 | return .none
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/GRDBDemoApp/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "type": "lldb",
5 | "request": "launch",
6 | "sourceLanguages": [
7 | "swift"
8 | ],
9 | "args": [],
10 | "cwd": "${workspaceFolder:GRDBDemoApp}",
11 | "name": "Debug WindowsApp",
12 | "program": "${workspaceFolder:GRDBDemoApp}\\.build\\debug\\WindowsApp.exe",
13 | "preLaunchTask": "swift: Build Debug WindowsApp"
14 | },
15 | {
16 | "type": "lldb",
17 | "request": "launch",
18 | "sourceLanguages": [
19 | "swift"
20 | ],
21 | "args": [],
22 | "cwd": "${workspaceFolder:GRDBDemoApp}",
23 | "name": "Release WindowsApp",
24 | "program": "${workspaceFolder:GRDBDemoApp}\\.build\\release\\WindowsApp.exe",
25 | "preLaunchTask": "swift: Build Release WindowsApp"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/SimpleCounter/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "type": "lldb",
5 | "request": "launch",
6 | "sourceLanguages": [
7 | "swift"
8 | ],
9 | "args": [],
10 | "cwd": "${workspaceFolder:SimpleCounter}",
11 | "name": "Debug WindowsApp",
12 | "program": "${workspaceFolder:SimpleCounter}\\.build\\debug\\WindowsApp.exe",
13 | "preLaunchTask": "swift: Build Debug WindowsApp"
14 | },
15 | {
16 | "type": "lldb",
17 | "request": "launch",
18 | "sourceLanguages": [
19 | "swift"
20 | ],
21 | "args": [],
22 | "cwd": "${workspaceFolder:SimpleCounter}",
23 | "name": "Release WindowsApp",
24 | "program": "${workspaceFolder:SimpleCounter}\\.build\\release\\WindowsApp.exe",
25 | "preLaunchTask": "swift: Build Release WindowsApp"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/SimpleCounter/Sources/Born/Navigation.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 |
3 | @Reducer
4 | public struct Navigation: Reducer {
5 | public init() {}
6 |
7 | @ObservableState
8 | public struct State: Equatable {
9 | @Presents public var destination: Counter.State?
10 |
11 | public init(destination: Counter.State? = nil) {
12 | self.destination = destination
13 | }
14 | }
15 |
16 | public enum Action {
17 | case destination(PresentationAction)
18 | case toggleButtonTapped
19 | }
20 |
21 | public var body: some ReducerOf {
22 | Reduce { state, action in
23 | switch action {
24 | case .toggleButtonTapped:
25 | state.destination = state.destination == nil ? Counter.State() : nil
26 | return .none
27 |
28 | case .destination(let presentedAction):
29 | print(presentedAction)
30 | return .none
31 | }
32 | }
33 | .ifLet(\.$destination, action: \.destination) {
34 | Counter()
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Reducers/EditFeature.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ComposableArchitecture
3 |
4 | @Reducer
5 | public struct EditFeature: Reducer {
6 | public init() {}
7 |
8 | @ObservableState
9 | public struct State: Equatable {
10 | var player: Player
11 | }
12 |
13 | public enum Action {
14 | case inputName(String)
15 | case inputScore(String)
16 | case tappedUpdate
17 | case delegate(Delegate)
18 |
19 | @CasePathable
20 | public enum Delegate {
21 | case updatedPlayer(Player)
22 | }
23 | }
24 |
25 | public var body: some ReducerOf {
26 | Reduce { state, action in
27 | switch action {
28 | case let .inputName(name):
29 | state.player.name = name
30 | return .none
31 |
32 | case let .inputScore(score):
33 | state.player.score = Int(score) ?? 0
34 | return .none
35 |
36 | case .tappedUpdate:
37 | return .send(.delegate(.updatedPlayer(state.player)))
38 |
39 | case .delegate(.updatedPlayer):
40 | return .none
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Reducers/NewFeature.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ComposableArchitecture
3 |
4 | @Reducer
5 | public struct NewFeature: Reducer {
6 | public init() {}
7 |
8 | @ObservableState
9 | public struct State: Equatable {
10 | var player: Player = .init(name: "", score: 0)
11 | }
12 |
13 | public enum Action {
14 | case inputName(String)
15 | case inputScore(String)
16 | case tappedCreate
17 | case delegate(Delegate)
18 |
19 | @CasePathable
20 | public enum Delegate {
21 | case createdPlayer(Player)
22 | }
23 | }
24 |
25 | public var body: some ReducerOf {
26 | Reduce { state, action in
27 | switch action {
28 | case let .inputName(name):
29 | state.player.name = name
30 | return .none
31 |
32 | case let .inputScore(score):
33 | state.player.score = Int(score) ?? 0
34 | return .none
35 |
36 | case .tappedCreate:
37 | return .send(.delegate(.createdPlayer(state.player)))
38 |
39 | case .delegate:
40 | return .none
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SimpleCounter/Tests/BornTests/BornTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Born
3 |
4 | @MainActor
5 | final class BornTests: XCTestCase {
6 | func testCounter() async throws {
7 | let store = TestStore(initialState: Counter.State()) {
8 | Counter()
9 | }
10 | await store.send(.incrementButtonTapped) {
11 | $0.count = 1
12 | }
13 |
14 | await store.send(.incrementButtonTapped) {
15 | $0.count = 2
16 | }
17 |
18 | await store.send(.decrementButtonTapped) {
19 | $0.count = 1
20 | }
21 |
22 | await store.send(.decrementButtonTapped) {
23 | $0.count = 0
24 | }
25 |
26 | await store.send(.decrementButtonTapped) {
27 | $0.count = -1
28 | }
29 | }
30 |
31 | func testNavigation() async throws {
32 | let store = TestStore(initialState: Navigation.State()) {
33 | Navigation()
34 | }
35 | await store.send(.toggleButtonTapped) {
36 | $0.destination = Counter.State()
37 | }
38 |
39 | await store.send(.toggleButtonTapped) {
40 | $0.destination = nil
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife/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 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife/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 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Lib/MainApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import Born
3 |
4 | @ViewAction(for: ListFeature.self)
5 | public struct ContentView: View {
6 | public init(store: StoreOf) {
7 | self.store = store
8 | }
9 |
10 | public var store: StoreOf
11 |
12 | public var body: some View {
13 | NavigationStack {
14 | List {
15 | ForEach(store.players) { player in
16 | Text("\(player.name): \(player.score)")
17 | }
18 | }
19 | .onAppear {
20 | send(.viewAppeared)
21 | }
22 | .onDisappear {
23 | send(.viewDisappeared)
24 | }
25 | .toolbar {
26 | ToolbarItem(placement: .primaryAction) {
27 | Button("Refresh") { send(.tappedRefreshUser) }
28 | }
29 | ToolbarItem(placement: .primaryAction) {
30 | Button("Tornado") { send(.tappedTornado)}
31 | }
32 | ToolbarItem(placement: .navigation) {
33 | Button("Order") { send(.tappedChangeOrdering(store.ordering == .byName ? .byScore : .byName))}
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 | #Preview("ListView") {
41 | let store = Store(
42 | initialState: ListFeature.State(),
43 | reducer: { ListFeature()._printChanges() },
44 | withDependencies: {
45 | $0.appDatabase = .random()
46 | }
47 | )
48 | return ContentView(store: store)
49 | }
50 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Windows/Navigation.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WindowsFoundation
3 | import WinAppSDK
4 | import WinUI
5 | import Born
6 |
7 | final class NavigationView: WinUI.StackPanel {
8 | let store: StoreOf
9 | var observables: [ObservationToken] = []
10 | var counterView: CounterView?
11 |
12 | public init(store: StoreOf) {
13 | self.store = store
14 | super.init()
15 | self.orientation = .vertical
16 | self.spacing = 10
17 | self.horizontalAlignment = .center
18 | self.verticalAlignment = .center
19 |
20 | self.loaded.addHandler { [weak self] sender, args in
21 | guard let self else { return }
22 | let toggleButton = Button()
23 | toggleButton.content = "Toggle"
24 | toggleButton.click.addHandler { [store] _, _ in
25 | store.send(.toggleButtonTapped)
26 | }
27 | self.children.append(toggleButton)
28 | let token = observe { [weak self] in
29 | guard let self else { return }
30 |
31 | if let counter = store.scope(state: \.destination, action: \.destination.presented),
32 | counterView == nil {
33 | self.counterView = CounterView(store: counter)
34 | self.children.append(self.counterView!)
35 | } else if store.destination == nil, let counterView = self.counterView {
36 | if let index = self.children.index(of: counterView) {
37 | _ = self.children.remove(at: index)
38 | }
39 | self.counterView = nil
40 | }
41 | }
42 | observables.append(token)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Windows/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WinAppSDK
3 | import WindowsFoundation
4 | import WinUI
5 | import Born
6 |
7 | @main
8 | public class PreviewApp: SwiftApplication {
9 | /// A required initializer for the application. Non-UI setup for your application can be done here.
10 | /// Subscribing to unhandledException is a good place to handle any unhandled exceptions that may occur
11 | /// in your application.
12 | public required init() {
13 | super.init()
14 | unhandledException.addHandler { (_, args:UnhandledExceptionEventArgs!) in
15 | print("Unhandled exception: \(args.message)")
16 | }
17 | }
18 |
19 | /// onShutdown is called once Application.start returns. This is a good place to do any cleanup
20 | /// that is necessary for your application before it terminates.
21 | override public func onShutdown() { }
22 |
23 | /// onLaunched is called when the application is launched. This is the main entry point for your
24 | /// application and when you can create a window and display UI.s
25 | override public func onLaunched(_ args: WinUI.LaunchActivatedEventArgs) {
26 | let window = Window()
27 | window.title = "GRDBDemoApp"
28 |
29 | try! window.activate()
30 | let listView = ListView(
31 | store: Store(
32 | initialState: ListFeature.State(),
33 | reducer: { ListFeature()._printChanges() }
34 | )
35 | )
36 | let panel = ScrollView()
37 | panel.content = listView
38 | panel.contentOrientation = .vertical
39 | window.content = panel
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Windows/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WinAppSDK
3 | import WindowsFoundation
4 | import WinUI
5 | import Born
6 |
7 | @main
8 | public class PreviewApp: SwiftApplication {
9 | /// A required initializer for the application. Non-UI setup for your application can be done here.
10 | /// Subscribing to unhandledException is a good place to handle any unhandled exceptions that may occur
11 | /// in your application.
12 | public required init() {
13 | super.init()
14 | unhandledException.addHandler { (_, args:UnhandledExceptionEventArgs!) in
15 | print("Unhandled exception: \(args.message)")
16 | }
17 | }
18 |
19 | /// onShutdown is called once Application.start returns. This is a good place to do any cleanup
20 | /// that is necessary for your application before it terminates.
21 | override public func onShutdown() { }
22 |
23 | /// onLaunched is called when the application is launched. This is the main entry point for your
24 | /// application and when you can create a window and display UI.s
25 | override public func onLaunched(_ args: WinUI.LaunchActivatedEventArgs) {
26 | let window = Window()
27 | window.title = "SimpleCounter"
28 |
29 | try! window.activate()
30 | let navigationView = NavigationView(
31 | store: Store(
32 | initialState: Navigation.State(),
33 | reducer: { Navigation() }
34 | )
35 | )
36 | let panel = StackPanel()
37 | panel.orientation = .vertical
38 | panel.spacing = 10
39 | panel.horizontalAlignment = .center
40 | panel.verticalAlignment = .center
41 | panel.children.append(navigationView)
42 | window.content = panel
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/WinUITests/CounterUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import WinAppDriver
3 |
4 | @MainActor
5 | final class SimpleCounterUITests: XCTestCase {
6 | var session: Session!
7 | override func setUp() async throws {
8 | self.session = try Session(
9 | webDriver: WinAppDriver.start(),
10 | desiredCapabilities: WinAppDriver.Capabilities.startApp(name: ".build\\debug\\WindowsApp.exe")
11 | )
12 | }
13 |
14 | override func tearDown() async throws {
15 | try session.close(window: "SimpleCounter")
16 | session = nil
17 | }
18 |
19 | func testCounterNavigation() async throws {
20 | try XCTUnwrap(session.findElement(byName: "Toggle"), "Toggle button not found").click()
21 | try XCTAssertNotNil(session.findElement(byName: "0"), "no open navigation")
22 | for v in 0..<4 {
23 | try XCTUnwrap(session.findElement(byName: "+1"), "Increment button not found").click()
24 | try XCTAssertNotNil(session.findElement(byName: "\(v + 1)"), "missing incremented value")
25 | }
26 | for v in 0..<8 {
27 | try XCTUnwrap(session.findElement(byName: "-1"), "Decrement button not found").click()
28 | try XCTAssertNotNil(session.findElement(byName: "\(3 - v)"), "missing decremented value")
29 | }
30 | for v in 0..<4 {
31 | try XCTUnwrap(session.findElement(byName: "+1"), "Increment button not found").click()
32 | try XCTAssertNotNil(session.findElement(byName: "\(v + -3)"), "missing incremented value")
33 | }
34 | }
35 |
36 | func testNavigationOpenClose() async throws {
37 | try XCTUnwrap(session.findElement(byName: "Toggle"), "Toggle button not found").click()
38 | try XCTAssertNotNil(session.findElement(byName: "0"), "no open navigation")
39 | try XCTUnwrap(session.findElement(byName: "Toggle"), "Toggle button not found").click()
40 | try XCTAssertNil(session.findElement(byName: "0"), "navigation still open")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/requirement.ps1:
--------------------------------------------------------------------------------
1 | cd C:\
2 |
3 | if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
4 | $progressPreference = 'silentlyContinue'
5 | Write-Information "Downloading WinGet and its dependencies..."
6 | Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
7 | Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
8 | Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx -OutFile Microsoft.UI.Xaml.2.8.x64.appx
9 | Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx
10 | Add-AppxPackage Microsoft.UI.Xaml.2.8.x64.appx
11 | Add-AppxPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
12 | }
13 |
14 | winget install --id Microsoft.VisualStudio.2022.Community --exact --force --custom "--add Microsoft.VisualStudio.Component.Windows11SDK.22621 --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
15 | winget install --id Git.Git -e
16 | winget install --id Microsoft.VCRedist.2015+.x64 -e
17 | winget install --id Python.Python.3.9 -e
18 | winget install --id Kitware.CMake -e
19 | winget install --id Ninja-build.Ninja -e
20 | winget install --id bloodrock.pkg-config-lite -e
21 | winget install --id Microsoft.WindowsApplicationDriver -e
22 |
23 | $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
24 |
25 | git clone https://github.com/arasan01/vcpkg.git
26 | .\vcpkg\bootstrap-vcpkg.bat
27 | .\vcpkg\vcpkg install sqlite3[dbstat,fts3,fts4,fts5,geopoly,json1,limit,math,memsys3,memsys5,omit-load-extension,rtree,session,snapshot,soundex,tool,zlib]
28 | .\vcpkg\vcpkg install sqlcipher[fts5,geopoly,json1,tool]
29 | .\vcpkg\vcpkg integrate install
30 |
31 | [System.Environment]::SetEnvironmentVariable('PKG_CONFIG_PATH', 'C:\vcpkg\installed\x64-windows\lib\pkgconfig\', [System.EnvironmentVariableTarget]::Machine)
32 | [System.Environment]::SetEnvironmentVariable('PATH', "$env:PATH;C:\vcpkg\installed\x64-windows\bin\;$env:USERPROFILE\AppData\Local\Programs\Python\Python39\ ", [System.EnvironmentVariableTarget]::Machine)
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 arasan01
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 | ---
24 |
25 | MIT License
26 |
27 | Copyright (c) 2023 The Browser Company
28 |
29 | Permission is hereby granted, free of charge, to any person obtaining a copy
30 | of this software and associated documentation files (the "Software"), to deal
31 | in the Software without restriction, including without limitation the rights
32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33 | copies of the Software, and to permit persons to whom the Software is
34 | furnished to do so, subject to the following conditions:
35 |
36 | The above copyright notice and this permission notice shall be included in all
37 | copies or substantial portions of the Software.
38 |
39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45 | SOFTWARE.
46 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Windows/Counter.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WindowsFoundation
3 | import WinAppSDK
4 | import WinUI
5 | import Born
6 |
7 | final class CounterView: WinUI.StackPanel {
8 | let store: StoreOf
9 | var observables: [ObservationToken] = []
10 |
11 | public init(store: StoreOf) {
12 | self.store = store
13 | super.init()
14 | self.orientation = .horizontal
15 | self.spacing = 10
16 | self.horizontalAlignment = .center
17 | self.verticalAlignment = .center
18 |
19 | self.loaded.addHandler { [weak self] sender, args in
20 | guard let self = self else { return }
21 | let decrementButton = Button()
22 | decrementButton.content = "-1"
23 | decrementButton.click.addHandler { [store] _, _ in
24 | store.send(.decrementButtonTapped)
25 | }
26 | let incrementButton = Button()
27 | incrementButton.content = "+1"
28 | incrementButton.click.addHandler { [store] _, _ in
29 | store.send(.incrementButtonTapped)
30 | }
31 |
32 | let countTextBlock = TextBlock()
33 | countTextBlock.text = "0"
34 | countTextBlock.fontSize = 24
35 |
36 | self.children.append(decrementButton)
37 | self.children.append(countTextBlock)
38 | self.children.append(incrementButton)
39 |
40 | let token = observe { [weak self] in
41 | guard let self else { return }
42 | countTextBlock.text = "\(store.count)"
43 | let scaleValue = 1 + (min(max(Float(store.count) / 10.0, -1.0), 1.0) / 2.0)
44 | springAnimation.finalValue = Vector3(x: scaleValue, y: scaleValue, z: scaleValue)
45 | try? countTextBlock.startAnimation(springAnimation)
46 | }
47 | observables.append(token)
48 | }
49 | }
50 |
51 | deinit {
52 | observables.forEach { $0.cancel() }
53 | }
54 |
55 | lazy var compositor: WinAppSDK.Compositor = WinUI.CompositionTarget.getCompositorForCurrentThread()
56 | lazy var springAnimation: WinAppSDK.SpringVector3NaturalMotionAnimation = {
57 | // swiftlint:disable:next force_try
58 | let animation: WinAppSDK.SpringVector3NaturalMotionAnimation = try! compositor.createSpringVector3Animation()
59 | animation.target = "Scale"
60 | animation.dampingRatio = 0.6
61 | animation.period = TimeSpan(duration: 100000)
62 | return animation
63 | }()
64 | }
65 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Tests/BornTests/ListFeatureTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Born
3 |
4 | @MainActor
5 | final class ListFeatureTests: XCTestCase {
6 | func testObserveDatabase() async throws {
7 | let store = TestStore(initialState: ListFeature.State()) {
8 | ListFeature()
9 | } withDependencies: {
10 | $0.appDatabase = .empty()
11 | }
12 |
13 | store.exhaustivity = .off
14 |
15 | let observeTask = await store.send(.view(.viewAppeared))
16 | await store.receive(\.observePlayers)
17 | await store.receive(\.setPlayers) {
18 | $0.players = []
19 | }
20 | await store.send(.view(.tappedRefreshUser)).finish()
21 | await store.receive(\.setPlayers)
22 | await store.send(.view(.tappedAllSwipe)).finish()
23 | await store.receive(\.setPlayers) {
24 | $0.players = []
25 | }
26 | let nextObserveTask = await store.send(.view(.tappedChangeOrdering(.byScore))) {
27 | $0.ordering = .byScore
28 | }
29 | await store.receive(\.observePlayers)
30 | await store.receive(\.setPlayers) {
31 | $0.players = []
32 | }
33 | await store.send(.view(.tappedRefreshUser)).finish()
34 | await store.receive(\.setPlayers)
35 | await store.send(.view(.viewDisappeared))
36 | await observeTask.cancel()
37 | await nextObserveTask.cancel()
38 | }
39 |
40 | func testCreateNewPlayer() async throws {
41 | let store = TestStore(initialState: ListFeature.State()) {
42 | ListFeature()
43 | } withDependencies: {
44 | $0.appDatabase = .empty()
45 | }
46 |
47 | store.exhaustivity = .off
48 |
49 | let observeTask = await store.send(.view(.viewAppeared))
50 | await store.send(.view(.tappedNewUser)) {
51 | $0.destination = .new(NewFeature.State())
52 | }
53 | await store.send(.destination(.presented(.new(.inputName("Arthur"))))) {
54 | $0.$destination[case: \.new]?.player.name = "Arthur"
55 | }
56 | await store.send(.destination(.presented(.new(.inputScore("100"))))) {
57 | $0.$destination[case: \.new]?.player.score = 100
58 | }
59 |
60 | await store.send(.destination(.presented(.new(.tappedCreate))))
61 |
62 | await store.receive(\.destination.new.delegate.createdPlayer) {
63 | $0.destination = nil
64 | }
65 | await store.receive(\.setPlayers) {
66 | $0.players = [Player(id: 1, name: "Arthur", score: 100)]
67 | }
68 | await observeTask.cancel()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Swift on Windows Samples Apps by arasan01
2 |
3 | Sample apps for Swift on Windows, showcasing how to build Windows Apps using the [Windows App SDK](https://github.com/microsoft/windowsappsdk) through the [Swift/WinRT](https://github.com/thebrowsercompany/swift-winrt) language projection.
4 |
5 | It discloses how to standardize code on iOS, macOS, and Windows, and how to use TCA and GRDB.swift in a multi-platform environment.
6 |
7 | # Technologies included
8 |
9 | - [x] How to standardize code on Windows, iOS, and macOS
10 | - [x] How to set up Swift on Windows easily
11 | - [x] VSCode settings
12 | - [x] How to build Windows applications using The Composable Architecture (TCA)
13 | - [x] How to use SQLite with GRDB.swift
14 | - [x] How to create a Windows application using the demo app included in GRDB.swift
15 | - [ ] How to app that [microsoft's official tutorial](https://blogs.windows.com/windowsdeveloper/2022/01/28/build-your-first-winui-3-app-part-1/)
16 | - [ ] How to build practical image processing apps in collaboration with more external libraries
17 |
18 | ## Setup
19 |
20 | ### Requirements
21 |
22 | Run requirement.ps1. Set powershell policy `Set-ExecutionPolicy Bypass` if can not run script.
23 |
24 | **Note that this adds a development environment to the overall system, so be careful if you have already created a development environment. It is not a powerful script.**
25 |
26 | Install latest Swift SDK from [thebrowsercompany/swift-build](https://github.com/thebrowsercompany/swift-build/releases)
27 |
28 | Make sure to have the appropriate version of the Windows App Runtime installed as mentioned [here](https://github.com/thebrowsercompany/swift-windowsappsdk?tab=readme-ov-file#using-windows-app-sdk)
29 |
30 |
31 | ### VSCode
32 |
33 | VSCode is the editor of choice for developing Windows apps on Swift. You can install it from https://code.visualstudio.com/download.
34 |
35 | If you choose to use Visual Studio Code, you'll need to install these extensions:
36 | - [Swift VSCode Extension](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)
37 |
38 | ## Building
39 |
40 | The build of the Windows applications is done through SPM. This can be done on the command line with `swift build` or in Visual Studio Code with `Ctrl+Shift+B`.
41 |
42 | ### Debugging in VSCode
43 |
44 | Debugging in VSCode is supported through LLDB. You can simply press `F5` or navigate to the `Run and Debug` (`Ctrl+Shift+D`) pane.
45 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Windows/ListView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import WindowsFoundation
3 | import WinAppSDK
4 | import WinUI
5 | import UWP
6 | import WinSDK
7 | import Born
8 |
9 | @ViewAction(for: ListFeature.self)
10 | final class ListView: StackPanel {
11 | let store: StoreOf
12 | var observables: [ObservationToken] = []
13 |
14 | public init(store: StoreOf) {
15 | self.store = store
16 | super.init()
17 | self.orientation = .vertical
18 | self.spacing = 10
19 | self.horizontalAlignment = .left
20 | self.verticalAlignment = .top
21 | let listView = configure(WinUI.ListView()) {
22 | $0.selectionMode = .single
23 | }
24 |
25 | let header = configure(WinUI.TextBlock()) {
26 | $0.text = "Players"
27 | $0.fontSize = 20
28 | }
29 |
30 | var changeOrderingButton: Button!
31 | let buttons = configure(StackPanel()) {
32 | $0.orientation = .horizontal
33 | $0.spacing = 10
34 | $0.horizontalAlignment = .left
35 | let randomButton = configure(WinUI.Button()) {
36 | $0.content = "Refresh"
37 | $0.click.addHandler { [weak self] _, _ in
38 | guard let self else { return }
39 | send(.tappedRefreshUser)
40 | }
41 | }
42 | $0.children.append(randomButton)
43 | let tornadoButton = configure(WinUI.Button()) {
44 | $0.content = "Tornado"
45 | $0.click.addHandler { [weak self] _, _ in
46 | guard let self else { return }
47 | send(.tappedTornado)
48 | }
49 | }
50 | $0.children.append(tornadoButton)
51 | changeOrderingButton = configure(WinUI.Button()) {
52 | $0.content = ""
53 | $0.click.addHandler { [weak self] _, _ in
54 | guard let self else { return }
55 | send(.tappedChangeOrdering(store.ordering == .byName ? .byScore : .byName))
56 | }
57 | }
58 | $0.children.append(changeOrderingButton)
59 | }
60 |
61 | self.children.append(header)
62 | self.children.append(buttons)
63 | self.children.append(listView)
64 |
65 |
66 | self.loaded.addHandler { _, _ in
67 | self.send(.viewAppeared)
68 |
69 | observe { [weak self] in
70 | guard let self else { return }
71 | listView.items.clear()
72 | store.players.forEach { listView.items.append("\($0.name): \($0.score)") }
73 | }.store(in: &self.observables)
74 |
75 | observe { [weak self] in
76 | guard let self else { return }
77 | switch store.ordering {
78 | case .byName:
79 | changeOrderingButton.content = "Order by name"
80 | case .byScore:
81 | changeOrderingButton.content = "Order by score"
82 | }
83 | }.store(in: &self.observables)
84 | }
85 | }
86 |
87 | deinit {
88 | self.send(.viewDisappeared)
89 | observables.cancelAll()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife.xcodeproj/xcshareddata/xcschemes/HappyDevLife.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
67 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 |
3 | import Foundation
4 | import PackageDescription
5 |
6 | var swiftSettings: [SwiftSetting] = [
7 | .define("SQL_TRACE", .when(configuration: .debug))
8 | ]
9 |
10 | let package = Package(
11 | name: "multiplatform-app",
12 | products: [
13 | .library(name: "Born", targets: ["Born"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/arasan01/swift-composable-architecture", branch: "windows/1.8.2"),
17 | .package(url: "https://github.com/arasan01/GRDB.swift", branch: "master"),
18 | .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0")
19 | ],
20 | targets: [
21 | .target(
22 | name: "Born",
23 | dependencies: [
24 | .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
25 | .product(name: "GRDB", package: "GRDB.swift"),
26 | .product(name: "Logging", package: "swift-log")
27 | ],
28 | swiftSettings: swiftSettings
29 | ),
30 | .testTarget(
31 | name: "BornTests",
32 | dependencies: ["Born"]
33 | ),
34 | ]
35 | )
36 |
37 | #if os(Windows)
38 |
39 | // MARK: - Windows
40 |
41 | let GUILinkerSettings: [LinkerSetting] = [
42 | .unsafeFlags(["-Xlinker", "/SUBSYSTEM:WINDOWS"], .when(configuration: .release)),
43 | // Update the entry point to point to the generated swift function, this lets us keep the same main method
44 | // for debug/release
45 | .unsafeFlags(["-Xlinker", "/ENTRY:mainCRTStartup"], .when(configuration: .release)),
46 | ]
47 |
48 | let products: [Product] = [
49 | .executable(name: "WindowsApp", targets: ["WindowsApp"]),
50 | ]
51 |
52 | let dependencies: [Package.Dependency] = [
53 | .package(url: "https://github.com/thebrowsercompany/swift-windowsappsdk", branch: "main"),
54 | .package(url: "https://github.com/thebrowsercompany/swift-windowsfoundation", branch: "main"),
55 | .package(url: "https://github.com/thebrowsercompany/swift-winui", branch: "main"),
56 | ]
57 |
58 | let targets: [Target] = [
59 | .executableTarget(
60 | name: "WindowsApp",
61 | dependencies: [
62 | "Born",
63 | .product(name: "WinUI", package: "swift-winui"),
64 | .product(name: "WinAppSDK", package: "swift-windowsappsdk"),
65 | .product(name: "WindowsFoundation", package: "swift-windowsfoundation"),
66 | ],
67 | path: "Apps/Windows",
68 | linkerSettings: GUILinkerSettings
69 | ),
70 | ]
71 |
72 | package.products.append(contentsOf: products)
73 | package.dependencies.append(contentsOf: dependencies)
74 | package.targets.append(contentsOf: targets)
75 |
76 | #elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
77 |
78 | // MARK: - Apple
79 |
80 | package.platforms = [
81 | .macOS(.v14),
82 | .iOS(.v17),
83 | .tvOS(.v17),
84 | .watchOS(.v10)
85 | ]
86 |
87 |
88 | let products: [Product] = [
89 | .library(name: "AppleApp", targets: ["AppleApp"]),
90 | ]
91 |
92 | let dependencies: [Package.Dependency] = [
93 | // 何もなし
94 | ]
95 |
96 | let targets: [Target] = [
97 | .target(
98 | name: "AppleApp",
99 | dependencies: [
100 | "Born"
101 | ],
102 | path: "Apps/Apple/Lib"
103 | )
104 | ]
105 |
106 | package.products.append(contentsOf: products)
107 | package.dependencies.append(contentsOf: dependencies)
108 | package.targets.append(contentsOf: targets)
109 |
110 | #endif
111 |
--------------------------------------------------------------------------------
/SimpleCounter/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "multiplatform-app",
7 | products: [
8 | .library(name: "Born", targets: ["Born"]),
9 | ],
10 | dependencies: [
11 | .package(url: "https://github.com/arasan01/swift-composable-architecture", revision: "adfa61b02b833c1ab2dccbc6badef909639cedc3"),
12 | .package(url: "https://github.com/arasan01/GRDB.swift", revision: "584d867eb22d35d0746302be5d43d34fed3a895b")
13 | ],
14 | targets: [
15 | .target(
16 | name: "Born",
17 | dependencies: [
18 | .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
19 | .product(name: "GRDB", package: "GRDB.swift")
20 | ]),
21 | .testTarget(
22 | name: "BornTests",
23 | dependencies: ["Born"]
24 | ),
25 | ]
26 | )
27 |
28 | #if os(Windows)
29 |
30 | // MARK: - Windows
31 |
32 | let GUILinkerSettings: [LinkerSetting] = [
33 | .unsafeFlags(["-Xlinker", "/SUBSYSTEM:WINDOWS"], .when(configuration: .release)),
34 | // Update the entry point to point to the generated swift function, this lets us keep the same main method
35 | // for debug/release
36 | .unsafeFlags(["-Xlinker", "/ENTRY:mainCRTStartup"], .when(configuration: .release)),
37 | ]
38 |
39 | let products: [Product] = [
40 | .executable(name: "WindowsApp", targets: ["WindowsApp"]),
41 | ]
42 |
43 | let dependencies: [Package.Dependency] = [
44 | .package(url: "https://github.com/thebrowsercompany/swift-windowsappsdk", branch: "main"),
45 | .package(url: "https://github.com/thebrowsercompany/swift-windowsfoundation", branch: "main"),
46 | .package(url: "https://github.com/thebrowsercompany/swift-winui", branch: "main"),
47 | .package(url: "https://github.com/thebrowsercompany/swift-webdriver", branch: "main")
48 | ]
49 |
50 | let targets: [Target] = [
51 | .executableTarget(
52 | name: "WindowsApp",
53 | dependencies: [
54 | "Born",
55 | .product(name: "WinUI", package: "swift-winui"),
56 | .product(name: "WinAppSDK", package: "swift-windowsappsdk"),
57 | .product(name: "WindowsFoundation", package: "swift-windowsfoundation"),
58 | ],
59 | path: "Apps/Windows",
60 | linkerSettings: GUILinkerSettings
61 | ),
62 | .testTarget(
63 | name: "WinUITests",
64 | dependencies: [
65 | .product(name: "WinAppDriver", package: "swift-webdriver"),
66 | ],
67 | path: "Apps/WinUITests"
68 | )
69 | ]
70 |
71 | package.products.append(contentsOf: products)
72 | package.dependencies.append(contentsOf: dependencies)
73 | package.targets.append(contentsOf: targets)
74 |
75 | #elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
76 |
77 | // MARK: - Apple
78 |
79 | package.platforms = [
80 | .macOS(.v14),
81 | .iOS(.v17),
82 | .tvOS(.v17),
83 | .watchOS(.v10)
84 | ]
85 |
86 |
87 | let products: [Product] = [
88 | .library(name: "AppleApp", targets: ["AppleApp"]),
89 | ]
90 |
91 | let dependencies: [Package.Dependency] = [
92 | // 何もなし
93 | ]
94 |
95 | let targets: [Target] = [
96 | .target(
97 | name: "AppleApp",
98 | dependencies: [
99 | "Born"
100 | ],
101 | path: "Apps/Apple/Lib"
102 | )
103 | ]
104 |
105 | package.products.append(contentsOf: products)
106 | package.dependencies.append(contentsOf: dependencies)
107 | package.targets.append(contentsOf: targets)
108 |
109 | #endif
110 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Model/Persistence.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015-2024 Gwendal Roué
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 | */
10 |
11 | import Foundation
12 | import GRDB
13 |
14 | extension AppDatabase {
15 | /// The database for the application
16 | public static let shared = makeShared()
17 |
18 | private static func makeShared() -> AppDatabase {
19 | do {
20 | // Apply recommendations from
21 | //
22 | //
23 | // Create the "Application Support/Database" directory if needed
24 | let fileManager = FileManager.default
25 | let appSupportURL = try fileManager.url(
26 | for: .applicationSupportDirectory, in: .userDomainMask,
27 | appropriateFor: nil, create: true)
28 | let directoryURL = appSupportURL.appendingPathComponent("Database", isDirectory: true)
29 |
30 | // Support for tests: delete the database if requested
31 | if CommandLine.arguments.contains("-reset") {
32 | try? fileManager.removeItem(at: directoryURL)
33 | }
34 |
35 | // Create the database folder if needed
36 | try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
37 |
38 | // Open or create the database
39 | let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
40 | print("Database stored at \(databaseURL.path)")
41 | let dbPool = try DatabasePool(
42 | path: "\(databaseURL.path)",
43 | // Use default AppDatabase configuration
44 | configuration: AppDatabase.makeConfiguration())
45 |
46 | // Create the AppDatabase
47 | let appDatabase = try AppDatabase(dbPool)
48 |
49 | // Prepare the database with test fixtures if requested
50 | if CommandLine.arguments.contains("-fixedTestData") {
51 | try appDatabase.createPlayersForUITests()
52 | } else {
53 | // Otherwise, populate the database if it is empty, for better
54 | // demo purpose.
55 | // try appDatabase.createRandomPlayersIfEmpty()
56 | }
57 |
58 | return appDatabase
59 | } catch {
60 | // Replace this implementation with code to handle the error appropriately.
61 | // fatalError() causes the application to generate a crash log and terminate.
62 | //
63 | // Typical reasons for an error here include:
64 | // * The parent directory cannot be created, or disallows writing.
65 | // * The database is not accessible, due to permissions or data protection when the device is locked.
66 | // * The device is out of space.
67 | // * The database could not be migrated to its latest schema version.
68 | // Check the error message to determine what the actual problem was.
69 | fatalError("Unresolved error \(error)")
70 | }
71 | }
72 |
73 | /// Creates an empty database for SwiftUI previews
74 | public static func empty() -> AppDatabase {
75 | // Connect to an in-memory database
76 | // See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
77 | let dbQueue = try! DatabaseQueue(configuration: AppDatabase.makeConfiguration())
78 | return try! AppDatabase(dbQueue)
79 | }
80 |
81 | /// Creates a database full of random players for SwiftUI previews
82 | public static func random() -> AppDatabase {
83 | let appDatabase = empty()
84 | try! appDatabase.createRandomPlayersIfEmpty()
85 | return appDatabase
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Reducers/ListFeature.swift:
--------------------------------------------------------------------------------
1 | import ComposableArchitecture
2 | import GRDB
3 |
4 | @Reducer
5 | public struct ListFeature: Reducer {
6 | public init() {}
7 |
8 | @ObservableState
9 | public struct State: Equatable {
10 | public var players: IdentifiedArrayOf
11 | public var ordering: PlayerOrdering
12 | @Presents public var destination: Destination.State?
13 |
14 | public init(players: [Player] = [], ordering: PlayerOrdering = .byName) {
15 | self.players = .init(uniqueElements: players)
16 | self.ordering = ordering
17 | }
18 | }
19 |
20 | @Reducer(state: .equatable)
21 | public enum Destination {
22 | case new(NewFeature)
23 | case edit(EditFeature)
24 | }
25 |
26 | public enum PlayerOrdering {
27 | case byName
28 | case byScore
29 | }
30 |
31 | public enum Action: ViewAction {
32 | case destination(PresentationAction)
33 | case setPlayers([Player])
34 | case observePlayers
35 | case view(View)
36 |
37 | @CasePathable
38 | public enum View: Equatable {
39 | case viewAppeared
40 | case viewDisappeared
41 | case tappedChangeOrdering(PlayerOrdering)
42 | case tappedAllSwipe
43 | case tappedRefreshUser
44 | case tappedTornado
45 | case tappedNewUser
46 | case swipePlayer([Player.ID])
47 | case tappedExistingUser(Player.ID)
48 | }
49 | }
50 |
51 | @Dependency(\.appDatabase) var appDatabase
52 | @Dependency(\.mainQueue) var mainQueue
53 |
54 | public enum CancelID {
55 | case databaseObserve
56 | }
57 |
58 | public var body: some ReducerOf {
59 | Reduce { state, action in
60 | switch action {
61 | case .view(.viewAppeared):
62 | return .send(.observePlayers)
63 |
64 | case .view(.viewDisappeared):
65 | return .cancel(id: CancelID.databaseObserve)
66 |
67 | case let .view(.tappedChangeOrdering(ordering)):
68 | state.ordering = ordering
69 | return .send(.observePlayers)
70 |
71 | case .view(.tappedAllSwipe):
72 | return .run { [appDatabase] send in
73 | try await appDatabase.deleteAllPlayers()
74 | }
75 |
76 | case .view(.tappedRefreshUser):
77 | return .run { [appDatabase] send in
78 | try await appDatabase.refreshPlayers()
79 | }
80 |
81 | case .view(.tappedTornado):
82 | return .run { [appDatabase] send in
83 | try? await withThrowingTaskGroup(of: Void.self) { group in
84 | for idx in 0..<10 {
85 | _ = group.addTask {
86 | try await mainQueue.sleep(for: .milliseconds(idx * 250))
87 | try await appDatabase.refreshPlayers()
88 | }
89 | }
90 | try await group.waitForAll()
91 | }
92 | }
93 |
94 | case .view(.tappedNewUser):
95 | state.destination = .new(NewFeature.State())
96 | return .none
97 |
98 | case let .view(.swipePlayer(ids)):
99 | return .run { [appDatabase] send in
100 | try await appDatabase.deletePlayers(ids: ids.compactMap { $0 })
101 | }
102 |
103 | case let .view(.tappedExistingUser(id)):
104 | guard let player = state.players[id: id] else { return .none }
105 | state.destination = .edit(EditFeature.State(player: player))
106 | return .none
107 |
108 | case let .destination(.presented(.new(.delegate(.createdPlayer(player))))),
109 | let .destination(.presented(.edit(.delegate(.updatedPlayer(player))))):
110 | state.destination = nil
111 | return .run { [appDatabase, player] send in
112 | var player = player
113 | try await appDatabase.savePlayer(&player)
114 | }
115 |
116 | case .destination:
117 | return .none
118 |
119 | case .observePlayers:
120 | return .run { [ordering = state.ordering] send in
121 | let playersStream = ValueObservation.tracking { db in
122 | switch ordering {
123 | case .byName:
124 | return try Player.all().orderedByName().fetchAll(db)
125 | case .byScore:
126 | return try Player.all().orderedByScore().fetchAll(db)
127 | }
128 | }
129 | .values(in: appDatabase.reader)
130 |
131 | for try await newPlayers in playersStream {
132 | await send(.setPlayers(newPlayers))
133 | }
134 | }
135 | .cancellable(id: CancelID.databaseObserve, cancelInFlight: true)
136 |
137 | case let .setPlayers(newPlayers):
138 | state.players = .init(uniqueElements: newPlayers)
139 | return .none
140 | }
141 | }
142 | .ifLet(\.$destination, action: \.destination)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Model/Player.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015-2024 Gwendal Roué
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 | */
10 | import GRDB
11 |
12 | /// The Player struct.
13 | ///
14 | /// Identifiable conformance supports SwiftUI list animations, and type-safe
15 | /// GRDB primary key methods.
16 | /// Equatable conformance supports tests.
17 | public struct Player: Identifiable, Equatable {
18 | public init(id: Int64? = nil, name: String, score: Int) {
19 | self.id = id
20 | self.name = name
21 | self.score = score
22 | }
23 |
24 | /// The player id.
25 | ///
26 | /// Int64 is the recommended type for auto-incremented database ids.
27 | /// Use nil for players that are not inserted yet in the database.
28 | public var id: Int64?
29 | public var name: String
30 | public var score: Int
31 | }
32 |
33 | extension Player {
34 | private static let names = [
35 | "Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
36 | "Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
37 | "Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
38 | "Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
39 | "Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
40 | "Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
41 | "Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
42 | "Zazie", "Zoé"]
43 |
44 | /// Creates a new player with empty name and zero score
45 | public static func new() -> Player {
46 | Player(id: nil, name: "", score: 0)
47 | }
48 |
49 | /// Creates a new player with random name and random score
50 | public static func makeRandom() -> Player {
51 | Player(id: nil, name: randomName(), score: randomScore())
52 | }
53 |
54 | /// Returns a random name
55 | public static func randomName() -> String {
56 | names.randomElement()!
57 | }
58 |
59 | /// Returns a random score
60 | public static func randomScore() -> Int {
61 | 10 * Int.random(in: 0...100)
62 | }
63 | }
64 |
65 | // MARK: - Persistence
66 |
67 | /// Make Player a Codable Record.
68 | ///
69 | /// See
70 | extension Player: Codable, FetchableRecord, MutablePersistableRecord {
71 | // Define database columns from CodingKeys
72 | fileprivate enum Columns {
73 | static let name = Column(CodingKeys.name)
74 | static let score = Column(CodingKeys.score)
75 | }
76 |
77 | /// Updates a player id after it has been inserted in the database.
78 | mutating public func didInsert(_ inserted: InsertionSuccess) {
79 | id = inserted.rowID
80 | }
81 | }
82 |
83 | // MARK: - Player Database Requests
84 |
85 | /// Define some player requests used by the application.
86 | ///
87 | /// See
88 | extension DerivableRequest {
89 | /// A request of players ordered by name.
90 | ///
91 | /// For example:
92 | ///
93 | /// let players: [Player] = try dbWriter.read { db in
94 | /// try Player.all().orderedByName().fetchAll(db)
95 | /// }
96 | public func orderedByName() -> Self {
97 | // Sort by name in a localized case insensitive fashion
98 | // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
99 | order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
100 | }
101 |
102 | /// A request of players ordered by score.
103 | ///
104 | /// For example:
105 | ///
106 | /// let players: [Player] = try dbWriter.read { db in
107 | /// try Player.all().orderedByScore().fetchAll(db)
108 | /// }
109 | /// let bestPlayer: Player? = try dbWriter.read { db in
110 | /// try Player.all().orderedByScore().fetchOne(db)
111 | /// }
112 | public func orderedByScore() -> Self {
113 | // Sort by descending score, and then by name, in a
114 | // localized case insensitive fashion
115 | // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
116 | order(
117 | Player.Columns.score.desc,
118 | Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Tests/BornTests/PlayerTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015-2024 Gwendal Roué
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 | */
10 |
11 | import XCTest
12 | import GRDB
13 | @testable import Born
14 |
15 | class PlayerTests: XCTestCase {
16 | // MARK: - CRUD
17 | // Test that our Player type properly talks to GRDB.
18 |
19 | func testInsert() throws {
20 | // Given an empty players database
21 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
22 | _ = try AppDatabase(dbQueue)
23 |
24 | // When we insert a player
25 | var player = Player(id: nil, name: "Arthur", score: 100)
26 | try dbQueue.write { db in
27 | try player.insert(db)
28 | }
29 |
30 | // Then the player gets a non-nil id
31 | XCTAssertNotNil(player.id)
32 | }
33 |
34 | func testRoundtrip() throws {
35 | // Given an empty players database
36 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
37 | _ = try AppDatabase(dbQueue)
38 |
39 | // When we insert a player and fetch the player with the same id
40 | var insertedPlayer = Player(id: nil, name: "Arthur", score: 100)
41 | let fetchedPlayer: Player? = try dbQueue.write { db in
42 | try insertedPlayer.insert(db)
43 | return try Player.fetchOne(db, key: insertedPlayer.id)
44 | }
45 |
46 | // Then the fetched player is equal to the inserted player
47 | XCTAssertEqual(insertedPlayer, fetchedPlayer)
48 | }
49 |
50 | // MARK: - Requests
51 | // Test that requests defined on the Player type behave as expected.
52 |
53 | func testOrderedByScore() throws {
54 | // Given a players database that contains players with distinct scores
55 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
56 | _ = try AppDatabase(dbQueue)
57 | var player1 = Player(id: 1, name: "Arthur", score: 100)
58 | var player2 = Player(id: 2, name: "Barbara", score: 200)
59 | var player3 = Player(id: 3, name: "Craig", score: 150)
60 | var player4 = Player(id: 4, name: "David", score: 120)
61 | try dbQueue.write { db in
62 | try player1.insert(db)
63 | try player2.insert(db)
64 | try player3.insert(db)
65 | try player4.insert(db)
66 | }
67 |
68 | // When we fetch players ordered by score
69 | let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
70 |
71 | // Then fetched players are ordered by score descending
72 | XCTAssertEqual(players, [player2, player3, player4, player1])
73 | }
74 |
75 | func testOrderedByScoreSortsIdenticalScoresByName() throws {
76 | // Given a players database that contains players with common scores
77 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
78 | _ = try AppDatabase(dbQueue)
79 | var player1 = Player(id: 1, name: "Arthur", score: 100)
80 | var player2 = Player(id: 2, name: "Barbara", score: 200)
81 | var player3 = Player(id: 3, name: "Craig", score: 200)
82 | var player4 = Player(id: 4, name: "David", score: 200)
83 | try dbQueue.write { db in
84 | try player1.insert(db)
85 | try player2.insert(db)
86 | try player3.insert(db)
87 | try player4.insert(db)
88 | }
89 |
90 | // When we fetch players ordered by score
91 | let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
92 |
93 | // Then fetched players are ordered by score descending and by name
94 | XCTAssertEqual(players, [player2, player3, player4, player1])
95 | }
96 |
97 | func testOrderedByName() throws {
98 | // Given a players database that contains players with distinct names
99 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
100 | _ = try AppDatabase(dbQueue)
101 | var player1 = Player(id: 1, name: "Arthur", score: 100)
102 | var player2 = Player(id: 2, name: "Barbara", score: 200)
103 | var player3 = Player(id: 3, name: "Craig", score: 150)
104 | var player4 = Player(id: 4, name: "David", score: 120)
105 | try dbQueue.write { db in
106 | try player1.insert(db)
107 | try player2.insert(db)
108 | try player3.insert(db)
109 | try player4.insert(db)
110 | }
111 |
112 | // When we fetch players ordered by name
113 | let players = try dbQueue.read(Player.all().orderedByName().fetchAll)
114 |
115 | // Then fetched players are ordered by name
116 | XCTAssertEqual(players, [player1, player2, player3, player4])
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Tests/BornTests/AppDatabaseTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015-2024 Gwendal Roué
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 | */
10 |
11 | import XCTest
12 | import GRDB
13 | @testable import Born
14 |
15 | class AppDatabaseTests: XCTestCase {
16 | func test_database_schema() throws {
17 | // Given an empty database
18 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
19 |
20 | // When we instantiate an AppDatabase
21 | _ = try AppDatabase(dbQueue)
22 |
23 | // Then the player table exists, with id, name & score columns
24 | try dbQueue.read { db in
25 | try XCTAssert(db.tableExists("player"))
26 | let columns = try db.columns(in: "player")
27 | let columnNames = Set(columns.map { $0.name })
28 | XCTAssertEqual(columnNames, ["id", "name", "score"])
29 | }
30 | }
31 |
32 | func test_savePlayer_inserts() async throws {
33 | // Given an empty players database
34 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
35 | let appDatabase = try AppDatabase(dbQueue)
36 |
37 | // When we save a new player
38 | var player = Player(id: nil, name: "Arthur", score: 100)
39 | try await appDatabase.savePlayer(&player)
40 |
41 | // Then the player exists in the database
42 | let playerExists = try await dbQueue.read { [player] in try player.exists($0) }
43 | XCTAssertTrue(playerExists)
44 | }
45 |
46 | func test_savePlayer_updates() async throws {
47 | // Given a players database that contains a player
48 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
49 | let appDatabase = try AppDatabase(dbQueue)
50 | var player = try await dbQueue.write { db in
51 | try Player(id: nil, name: "Arthur", score: 100).inserted(db)
52 | }
53 |
54 | // When we modify and save the player
55 | player.name = "Barbara"
56 | player.score = 1000
57 | try await appDatabase.savePlayer(&player)
58 |
59 | // Then the player has been updated in the database
60 | let fetchedPlayer = try await dbQueue.read { [player] db in
61 | try XCTUnwrap(Player.fetchOne(db, key: player.id))
62 | }
63 | XCTAssertEqual(fetchedPlayer, player)
64 | }
65 |
66 | func test_deletePlayers() async throws {
67 | // Given a players database that contains four players
68 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
69 | let appDatabase = try AppDatabase(dbQueue)
70 | let playerIds: [Int64] = try await dbQueue.write { db in
71 | _ = try Player(id: nil, name: "Arthur", score: 100).inserted(db)
72 | _ = try Player(id: nil, name: "Barbara", score: 200).inserted(db)
73 | _ = try Player(id: nil, name: "Craig", score: 150).inserted(db)
74 | _ = try Player(id: nil, name: "David", score: 120).inserted(db)
75 | return try Player.selectPrimaryKey().fetchAll(db)
76 | }
77 |
78 | // When we delete two players
79 | let deletedId1 = playerIds[0]
80 | let deletedId2 = playerIds[2]
81 | try await appDatabase.deletePlayers(ids: [deletedId1, deletedId2])
82 |
83 | // Then the deleted players no longer exist
84 | try await dbQueue.read { db in
85 | try XCTAssertFalse(Player.exists(db, id: deletedId1))
86 | try XCTAssertFalse(Player.exists(db, id: deletedId2))
87 | }
88 |
89 | // Then the database still contains two players
90 | let count = try await dbQueue.read { try Player.fetchCount($0) }
91 | XCTAssertEqual(count, 2)
92 | }
93 |
94 | func test_deleteAllPlayers() async throws {
95 | // Given a players database that contains players
96 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
97 | let appDatabase = try AppDatabase(dbQueue)
98 | try await dbQueue.write { db in
99 | _ = try Player(id: nil, name: "Arthur", score: 100).inserted(db)
100 | _ = try Player(id: nil, name: "Barbara", score: 200).inserted(db)
101 | _ = try Player(id: nil, name: "Craig", score: 150).inserted(db)
102 | _ = try Player(id: nil, name: "David", score: 120).inserted(db)
103 | }
104 |
105 | // When we delete all players
106 | try await appDatabase.deleteAllPlayers()
107 |
108 | // Then the database does not contain any player
109 | let count = try await dbQueue.read { try Player.fetchCount($0) }
110 | XCTAssertEqual(count, 0)
111 | }
112 |
113 | func test_refreshPlayers_populates_an_empty_database() async throws {
114 | // Given an empty players database
115 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
116 | let appDatabase = try AppDatabase(dbQueue)
117 |
118 | // When we refresh players
119 | try await appDatabase.refreshPlayers()
120 |
121 | // Then the database is not empty
122 | let count = try await dbQueue.read { try Player.fetchCount($0) }
123 | XCTAssert(count > 0)
124 | }
125 |
126 | func test_createRandomPlayersIfEmpty_populates_an_empty_database() throws {
127 | // Given an empty players database
128 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
129 | let appDatabase = try AppDatabase(dbQueue)
130 |
131 | // When we create random players
132 | try appDatabase.createRandomPlayersIfEmpty()
133 |
134 | // Then the database is not empty
135 | try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
136 | }
137 |
138 | func test_createRandomPlayersIfEmpty_does_not_modify_a_non_empty_database() throws {
139 | // Given a players database that contains one player
140 | let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
141 | let appDatabase = try AppDatabase(dbQueue)
142 | var player = Player(id: nil, name: "Arthur", score: 100)
143 | try dbQueue.write { db in
144 | try player.insert(db)
145 | }
146 |
147 | // When we create random players
148 | try appDatabase.createRandomPlayersIfEmpty()
149 |
150 | // Then the database still only contains the original player
151 | let players = try dbQueue.read(Player.fetchAll)
152 | XCTAssertEqual(players, [player])
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Sources/Born/Model/AppDatabase.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015-2024 Gwendal Roué
3 |
4 | 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:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | 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.
9 | */
10 |
11 | import Foundation
12 | import GRDB
13 | import Logging
14 |
15 | /// A database of players.
16 | ///
17 | /// You create an `AppDatabase` with a connection to an SQLite database
18 | /// (see ).
19 | ///
20 | /// Create those connections with a configuration returned from
21 | /// `AppDatabase/makeConfiguration(_:)`.
22 | ///
23 | /// For example:
24 | ///
25 | /// ```swift
26 | /// // Create an in-memory AppDatabase
27 | /// let config = AppDatabase.makeConfiguration()
28 | /// let dbQueue = try DatabaseQueue(configuration: config)
29 | /// let appDatabase = try AppDatabase(dbQueue)
30 | /// ```
31 | public struct AppDatabase {
32 | /// Creates an `AppDatabase`, and makes sure the database schema
33 | /// is ready.
34 | ///
35 | /// - important: Create the `DatabaseWriter` with a configuration
36 | /// returned by ``makeConfiguration(_:)``.
37 | public init(_ dbWriter: any DatabaseWriter) throws {
38 | self.dbWriter = dbWriter
39 | try migrator.migrate(dbWriter)
40 | }
41 |
42 | /// Provides access to the database.
43 | ///
44 | /// Application can use a `DatabasePool`, while SwiftUI previews and tests
45 | /// can use a fast in-memory `DatabaseQueue`.
46 | ///
47 | /// See
48 | private let dbWriter: any DatabaseWriter
49 | }
50 |
51 | // MARK: - Database Configuration
52 |
53 | extension AppDatabase {
54 | private static let sqlLogger = Logger(label: "com.example.grdb-swift.SQL")
55 |
56 | /// Returns a database configuration suited for `PlayerRepository`.
57 | ///
58 | /// SQL statements are logged if the `SQL_TRACE` environment variable
59 | /// is set.
60 | ///
61 | /// - parameter base: A base configuration.
62 | public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
63 | var config = base
64 |
65 | // An opportunity to add required custom SQL functions or
66 | // collations, if needed:
67 | // config.prepareDatabase { db in
68 | // db.add(function: ...)
69 | // }
70 |
71 | // Log SQL statements if the `SQL_TRACE` environment variable is set.
72 | // See
73 | #if SQL_TRACE
74 | config.prepareDatabase { db in
75 | db.trace {
76 | sqlLogger.debug("\($0)")
77 | }
78 | }
79 | #endif
80 |
81 | #if DEBUG
82 | // Protect sensitive information by enabling verbose debugging in
83 | // DEBUG builds only.
84 | // See
85 | config.publicStatementArguments = true
86 | #endif
87 |
88 | return config
89 | }
90 | }
91 |
92 | // MARK: - Database Migrations
93 |
94 | extension AppDatabase {
95 | /// The DatabaseMigrator that defines the database schema.
96 | ///
97 | /// See
98 | private var migrator: DatabaseMigrator {
99 | var migrator = DatabaseMigrator()
100 |
101 | #if DEBUG
102 | // Speed up development by nuking the database when migrations change
103 | // See
104 | migrator.eraseDatabaseOnSchemaChange = true
105 | #endif
106 |
107 | migrator.registerMigration("createPlayer") { db in
108 | // Create a table
109 | // See
110 | try db.create(table: "player") { t in
111 | t.autoIncrementedPrimaryKey("id")
112 | t.column("name", .text).notNull()
113 | t.column("score", .integer).notNull()
114 | }
115 | }
116 |
117 | // Migrations for future application versions will be inserted here:
118 | // migrator.registerMigration(...) { db in
119 | // ...
120 | // }
121 |
122 | return migrator
123 | }
124 | }
125 |
126 | // MARK: - Database Access: Writes
127 | // The write methods execute invariant-preserving database transactions.
128 |
129 | extension AppDatabase {
130 | /// A validation error that prevents some players from being saved into
131 | /// the database.
132 | public enum ValidationError: LocalizedError {
133 | case missingName
134 |
135 | public var errorDescription: String? {
136 | switch self {
137 | case .missingName:
138 | return "Please provide a name"
139 | }
140 | }
141 | }
142 |
143 | /// Saves (inserts or updates) a player. When the method returns, the
144 | /// player is present in the database, and its id is not nil.
145 | public func savePlayer(_ player: inout Player) async throws {
146 | if player.name.isEmpty {
147 | throw ValidationError.missingName
148 | }
149 | player = try await dbWriter.write { [player] db in
150 | try player.saved(db)
151 | }
152 | }
153 |
154 | /// Delete the specified players
155 | public func deletePlayers(ids: [Int64]) async throws {
156 | try await dbWriter.write { db in
157 | _ = try Player.deleteAll(db, ids: ids)
158 | }
159 | }
160 |
161 | /// Delete all players
162 | public func deleteAllPlayers() async throws {
163 | try await dbWriter.write { db in
164 | _ = try Player.deleteAll(db)
165 | }
166 | }
167 |
168 | /// Refresh all players (by performing some random changes, for demo purpose).
169 | public func refreshPlayers() async throws {
170 | try await dbWriter.write { db in
171 | if try Player.all().isEmpty(db) {
172 | // When database is empty, insert new random players
173 | try createRandomPlayers(db)
174 | } else {
175 | // Insert a player
176 | for _ in 0..<4 {
177 | _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
178 | }
179 |
180 | // Delete a random player
181 | // try Player.order(sql: "RANDOM()").limit(4).deleteAll(db)
182 | try db.execute(sql: #"""
183 | DELETE FROM player WHERE ROWID IN (
184 | SELECT ROWID FROM player ORDER BY random() LIMIT 4
185 | );
186 | """#)
187 |
188 | // Update some players
189 | for var player in try Player.fetchAll(db) where Bool.random() {
190 | try player.updateChanges(db) {
191 | $0.score = Player.randomScore()
192 | }
193 | }
194 | }
195 | }
196 | }
197 |
198 | /// Create random players if the database is empty.
199 | public func createRandomPlayersIfEmpty() throws {
200 | try dbWriter.write { db in
201 | if try Player.all().isEmpty(db) {
202 | try createRandomPlayers(db)
203 | }
204 | }
205 | }
206 |
207 | private static let uiTestPlayers = [
208 | Player(id: nil, name: "Arthur", score: 5),
209 | Player(id: nil, name: "Barbara", score: 6),
210 | Player(id: nil, name: "Craig", score: 8),
211 | Player(id: nil, name: "David", score: 4),
212 | Player(id: nil, name: "Elena", score: 1),
213 | Player(id: nil, name: "Frederik", score: 2),
214 | Player(id: nil, name: "Gilbert", score: 7),
215 | Player(id: nil, name: "Henriette", score: 3)]
216 |
217 | public func createPlayersForUITests() throws {
218 | try dbWriter.write { db in
219 | try AppDatabase.uiTestPlayers.forEach { player in
220 | _ = try player.inserted(db) // insert but ignore inserted id
221 | }
222 | }
223 | }
224 |
225 | /// Support for `createRandomPlayersIfEmpty()` and `refreshPlayers()`.
226 | private func createRandomPlayers(_ db: Database) throws {
227 | for _ in 0..<8 {
228 | _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
229 | }
230 | }
231 | }
232 |
233 | // MARK: - Database Access: Reads
234 |
235 | // This demo app does not provide any specific reading method, and instead
236 | // gives an unrestricted read-only access to the rest of the application.
237 | // In your app, you are free to choose another path, and define focused
238 | // reading methods.
239 | extension AppDatabase {
240 | /// Provides a read-only access to the database
241 | public var reader: DatabaseReader {
242 | dbWriter
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/GRDBDemoApp/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6F0DD5C32B9212420043B825 /* HappyDevLifeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */; };
11 | 6F0DD5C72B9212440043B825 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F0DD5C62B9212440043B825 /* Assets.xcassets */; };
12 | 6F0DD5CB2B9212440043B825 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */; };
13 | 6F4A32B82B924F67009F3599 /* SampleCode.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */; };
14 | 6FE0F9C42B92166900DE82E0 /* AppleApp in Frameworks */ = {isa = PBXBuildFile; productRef = 6FE0F9C32B92166900DE82E0 /* AppleApp */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HappyDevLife.app; sourceTree = BUILT_PRODUCTS_DIR; };
19 | 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HappyDevLifeApp.swift; sourceTree = ""; };
20 | 6F0DD5C62B9212440043B825 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
21 | 6F0DD5C82B9212440043B825 /* HappyDevLife.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HappyDevLife.entitlements; sourceTree = ""; };
22 | 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
23 | 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SampleCode.xcconfig; sourceTree = ""; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | 6F0DD5BC2B9212420043B825 /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | 6FE0F9C42B92166900DE82E0 /* AppleApp in Frameworks */,
32 | );
33 | runOnlyForDeploymentPostprocessing = 0;
34 | };
35 | /* End PBXFrameworksBuildPhase section */
36 |
37 | /* Begin PBXGroup section */
38 | 6F0DD5B62B9212410043B825 = {
39 | isa = PBXGroup;
40 | children = (
41 | 6F0DD5C12B9212420043B825 /* HappyDevLife */,
42 | 6F0DD5C02B9212420043B825 /* Products */,
43 | 6FE0F9C22B92166900DE82E0 /* Frameworks */,
44 | );
45 | sourceTree = "";
46 | };
47 | 6F0DD5C02B9212420043B825 /* Products */ = {
48 | isa = PBXGroup;
49 | children = (
50 | 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */,
51 | );
52 | name = Products;
53 | sourceTree = "";
54 | };
55 | 6F0DD5C12B9212420043B825 /* HappyDevLife */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */,
59 | 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */,
60 | 6F0DD5C62B9212440043B825 /* Assets.xcassets */,
61 | 6F0DD5C82B9212440043B825 /* HappyDevLife.entitlements */,
62 | 6F0DD5C92B9212440043B825 /* Preview Content */,
63 | );
64 | path = HappyDevLife;
65 | sourceTree = "";
66 | };
67 | 6F0DD5C92B9212440043B825 /* Preview Content */ = {
68 | isa = PBXGroup;
69 | children = (
70 | 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */,
71 | );
72 | path = "Preview Content";
73 | sourceTree = "";
74 | };
75 | 6FE0F9C22B92166900DE82E0 /* Frameworks */ = {
76 | isa = PBXGroup;
77 | children = (
78 | );
79 | name = Frameworks;
80 | sourceTree = "";
81 | };
82 | /* End PBXGroup section */
83 |
84 | /* Begin PBXNativeTarget section */
85 | 6F0DD5BE2B9212420043B825 /* HappyDevLife */ = {
86 | isa = PBXNativeTarget;
87 | buildConfigurationList = 6F0DD5CE2B9212440043B825 /* Build configuration list for PBXNativeTarget "HappyDevLife" */;
88 | buildPhases = (
89 | 6F0DD5BB2B9212420043B825 /* Sources */,
90 | 6F0DD5BC2B9212420043B825 /* Frameworks */,
91 | 6F0DD5BD2B9212420043B825 /* Resources */,
92 | );
93 | buildRules = (
94 | );
95 | dependencies = (
96 | );
97 | name = HappyDevLife;
98 | packageProductDependencies = (
99 | 6FE0F9C32B92166900DE82E0 /* AppleApp */,
100 | );
101 | productName = HappyDevLife;
102 | productReference = 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */;
103 | productType = "com.apple.product-type.application";
104 | };
105 | /* End PBXNativeTarget section */
106 |
107 | /* Begin PBXProject section */
108 | 6F0DD5B72B9212410043B825 /* Project object */ = {
109 | isa = PBXProject;
110 | attributes = {
111 | BuildIndependentTargetsInParallel = 1;
112 | LastSwiftUpdateCheck = 1530;
113 | LastUpgradeCheck = 1530;
114 | TargetAttributes = {
115 | 6F0DD5BE2B9212420043B825 = {
116 | CreatedOnToolsVersion = 15.3;
117 | };
118 | };
119 | };
120 | buildConfigurationList = 6F0DD5BA2B9212410043B825 /* Build configuration list for PBXProject "HappyDevLife" */;
121 | compatibilityVersion = "Xcode 14.0";
122 | developmentRegion = en;
123 | hasScannedForEncodings = 0;
124 | knownRegions = (
125 | en,
126 | Base,
127 | );
128 | mainGroup = 6F0DD5B62B9212410043B825;
129 | productRefGroup = 6F0DD5C02B9212420043B825 /* Products */;
130 | projectDirPath = "";
131 | projectRoot = "";
132 | targets = (
133 | 6F0DD5BE2B9212420043B825 /* HappyDevLife */,
134 | );
135 | };
136 | /* End PBXProject section */
137 |
138 | /* Begin PBXResourcesBuildPhase section */
139 | 6F0DD5BD2B9212420043B825 /* Resources */ = {
140 | isa = PBXResourcesBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 6F0DD5CB2B9212440043B825 /* Preview Assets.xcassets in Resources */,
144 | 6F4A32B82B924F67009F3599 /* SampleCode.xcconfig in Resources */,
145 | 6F0DD5C72B9212440043B825 /* Assets.xcassets in Resources */,
146 | );
147 | runOnlyForDeploymentPostprocessing = 0;
148 | };
149 | /* End PBXResourcesBuildPhase section */
150 |
151 | /* Begin PBXSourcesBuildPhase section */
152 | 6F0DD5BB2B9212420043B825 /* Sources */ = {
153 | isa = PBXSourcesBuildPhase;
154 | buildActionMask = 2147483647;
155 | files = (
156 | 6F0DD5C32B9212420043B825 /* HappyDevLifeApp.swift in Sources */,
157 | );
158 | runOnlyForDeploymentPostprocessing = 0;
159 | };
160 | /* End PBXSourcesBuildPhase section */
161 |
162 | /* Begin XCBuildConfiguration section */
163 | 6F0DD5CC2B9212440043B825 /* Debug */ = {
164 | isa = XCBuildConfiguration;
165 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
166 | buildSettings = {
167 | ALWAYS_SEARCH_USER_PATHS = NO;
168 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
169 | CLANG_ANALYZER_NONNULL = YES;
170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
172 | CLANG_ENABLE_MODULES = YES;
173 | CLANG_ENABLE_OBJC_ARC = YES;
174 | CLANG_ENABLE_OBJC_WEAK = YES;
175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
176 | CLANG_WARN_BOOL_CONVERSION = YES;
177 | CLANG_WARN_COMMA = YES;
178 | CLANG_WARN_CONSTANT_CONVERSION = YES;
179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
182 | CLANG_WARN_EMPTY_BODY = YES;
183 | CLANG_WARN_ENUM_CONVERSION = YES;
184 | CLANG_WARN_INFINITE_RECURSION = YES;
185 | CLANG_WARN_INT_CONVERSION = YES;
186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
192 | CLANG_WARN_STRICT_PROTOTYPES = YES;
193 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
195 | CLANG_WARN_UNREACHABLE_CODE = YES;
196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
197 | COPY_PHASE_STRIP = NO;
198 | DEBUG_INFORMATION_FORMAT = dwarf;
199 | ENABLE_STRICT_OBJC_MSGSEND = YES;
200 | ENABLE_TESTABILITY = YES;
201 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
202 | GCC_C_LANGUAGE_STANDARD = gnu17;
203 | GCC_DYNAMIC_NO_PIC = NO;
204 | GCC_NO_COMMON_BLOCKS = YES;
205 | GCC_OPTIMIZATION_LEVEL = 0;
206 | GCC_PREPROCESSOR_DEFINITIONS = (
207 | "DEBUG=1",
208 | "$(inherited)",
209 | );
210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
212 | GCC_WARN_UNDECLARED_SELECTOR = YES;
213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
214 | GCC_WARN_UNUSED_FUNCTION = YES;
215 | GCC_WARN_UNUSED_VARIABLE = YES;
216 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
217 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
218 | MTL_FAST_MATH = YES;
219 | ONLY_ACTIVE_ARCH = YES;
220 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
222 | };
223 | name = Debug;
224 | };
225 | 6F0DD5CD2B9212440043B825 /* Release */ = {
226 | isa = XCBuildConfiguration;
227 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
228 | buildSettings = {
229 | ALWAYS_SEARCH_USER_PATHS = NO;
230 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
231 | CLANG_ANALYZER_NONNULL = YES;
232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
234 | CLANG_ENABLE_MODULES = YES;
235 | CLANG_ENABLE_OBJC_ARC = YES;
236 | CLANG_ENABLE_OBJC_WEAK = YES;
237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
238 | CLANG_WARN_BOOL_CONVERSION = YES;
239 | CLANG_WARN_COMMA = YES;
240 | CLANG_WARN_CONSTANT_CONVERSION = YES;
241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
244 | CLANG_WARN_EMPTY_BODY = YES;
245 | CLANG_WARN_ENUM_CONVERSION = YES;
246 | CLANG_WARN_INFINITE_RECURSION = YES;
247 | CLANG_WARN_INT_CONVERSION = YES;
248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
252 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
254 | CLANG_WARN_STRICT_PROTOTYPES = YES;
255 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
257 | CLANG_WARN_UNREACHABLE_CODE = YES;
258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
259 | COPY_PHASE_STRIP = NO;
260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
261 | ENABLE_NS_ASSERTIONS = NO;
262 | ENABLE_STRICT_OBJC_MSGSEND = YES;
263 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
264 | GCC_C_LANGUAGE_STANDARD = gnu17;
265 | GCC_NO_COMMON_BLOCKS = YES;
266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
268 | GCC_WARN_UNDECLARED_SELECTOR = YES;
269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
270 | GCC_WARN_UNUSED_FUNCTION = YES;
271 | GCC_WARN_UNUSED_VARIABLE = YES;
272 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
273 | MTL_ENABLE_DEBUG_INFO = NO;
274 | MTL_FAST_MATH = YES;
275 | SWIFT_COMPILATION_MODE = wholemodule;
276 | };
277 | name = Release;
278 | };
279 | 6F0DD5CF2B9212440043B825 /* Debug */ = {
280 | isa = XCBuildConfiguration;
281 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
282 | buildSettings = {
283 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
284 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
285 | CODE_SIGN_ENTITLEMENTS = HappyDevLife/HappyDevLife.entitlements;
286 | CODE_SIGN_STYLE = Automatic;
287 | CURRENT_PROJECT_VERSION = 1;
288 | DEVELOPMENT_ASSET_PATHS = "\"HappyDevLife/Preview Content\"";
289 | DEVELOPMENT_TEAM = "";
290 | ENABLE_HARDENED_RUNTIME = YES;
291 | ENABLE_PREVIEWS = YES;
292 | GENERATE_INFOPLIST_FILE = YES;
293 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
294 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
295 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
296 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
297 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
298 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
299 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
300 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
301 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
302 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
303 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
304 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
305 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
306 | MACOSX_DEPLOYMENT_TARGET = 14.0;
307 | MARKETING_VERSION = 1.0;
308 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.arasan01-${SAMPLE_CODE_DISAMBIGUATOR}.HappyDevLife";
309 | PRODUCT_NAME = "$(TARGET_NAME)";
310 | SDKROOT = auto;
311 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
312 | SWIFT_EMIT_LOC_STRINGS = YES;
313 | SWIFT_VERSION = 5.0;
314 | TARGETED_DEVICE_FAMILY = "1,2";
315 | };
316 | name = Debug;
317 | };
318 | 6F0DD5D02B9212440043B825 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
324 | CODE_SIGN_ENTITLEMENTS = HappyDevLife/HappyDevLife.entitlements;
325 | CODE_SIGN_STYLE = Automatic;
326 | CURRENT_PROJECT_VERSION = 1;
327 | DEVELOPMENT_ASSET_PATHS = "\"HappyDevLife/Preview Content\"";
328 | DEVELOPMENT_TEAM = "";
329 | ENABLE_HARDENED_RUNTIME = YES;
330 | ENABLE_PREVIEWS = YES;
331 | GENERATE_INFOPLIST_FILE = YES;
332 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
333 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
334 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
335 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
336 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
337 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
338 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
339 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
340 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
341 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
342 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
343 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
344 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
345 | MACOSX_DEPLOYMENT_TARGET = 14.0;
346 | MARKETING_VERSION = 1.0;
347 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.arasan01-${SAMPLE_CODE_DISAMBIGUATOR}.HappyDevLife";
348 | PRODUCT_NAME = "$(TARGET_NAME)";
349 | SDKROOT = auto;
350 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
351 | SWIFT_EMIT_LOC_STRINGS = YES;
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Release;
356 | };
357 | /* End XCBuildConfiguration section */
358 |
359 | /* Begin XCConfigurationList section */
360 | 6F0DD5BA2B9212410043B825 /* Build configuration list for PBXProject "HappyDevLife" */ = {
361 | isa = XCConfigurationList;
362 | buildConfigurations = (
363 | 6F0DD5CC2B9212440043B825 /* Debug */,
364 | 6F0DD5CD2B9212440043B825 /* Release */,
365 | );
366 | defaultConfigurationIsVisible = 0;
367 | defaultConfigurationName = Release;
368 | };
369 | 6F0DD5CE2B9212440043B825 /* Build configuration list for PBXNativeTarget "HappyDevLife" */ = {
370 | isa = XCConfigurationList;
371 | buildConfigurations = (
372 | 6F0DD5CF2B9212440043B825 /* Debug */,
373 | 6F0DD5D02B9212440043B825 /* Release */,
374 | );
375 | defaultConfigurationIsVisible = 0;
376 | defaultConfigurationName = Release;
377 | };
378 | /* End XCConfigurationList section */
379 |
380 | /* Begin XCSwiftPackageProductDependency section */
381 | 6FE0F9C32B92166900DE82E0 /* AppleApp */ = {
382 | isa = XCSwiftPackageProductDependency;
383 | productName = AppleApp;
384 | };
385 | /* End XCSwiftPackageProductDependency section */
386 | };
387 | rootObject = 6F0DD5B72B9212410043B825 /* Project object */;
388 | }
389 |
--------------------------------------------------------------------------------
/SimpleCounter/Apps/Apple/Xcode/HappyDevLife.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6F0DD5C32B9212420043B825 /* HappyDevLifeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */; };
11 | 6F0DD5C72B9212440043B825 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F0DD5C62B9212440043B825 /* Assets.xcassets */; };
12 | 6F0DD5CB2B9212440043B825 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */; };
13 | 6F4A32B82B924F67009F3599 /* SampleCode.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */; };
14 | 6FE0F9C42B92166900DE82E0 /* AppleApp in Frameworks */ = {isa = PBXBuildFile; productRef = 6FE0F9C32B92166900DE82E0 /* AppleApp */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXFileReference section */
18 | 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HappyDevLife.app; sourceTree = BUILT_PRODUCTS_DIR; };
19 | 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HappyDevLifeApp.swift; sourceTree = ""; };
20 | 6F0DD5C62B9212440043B825 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
21 | 6F0DD5C82B9212440043B825 /* HappyDevLife.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HappyDevLife.entitlements; sourceTree = ""; };
22 | 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
23 | 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SampleCode.xcconfig; sourceTree = ""; };
24 | /* End PBXFileReference section */
25 |
26 | /* Begin PBXFrameworksBuildPhase section */
27 | 6F0DD5BC2B9212420043B825 /* Frameworks */ = {
28 | isa = PBXFrameworksBuildPhase;
29 | buildActionMask = 2147483647;
30 | files = (
31 | 6FE0F9C42B92166900DE82E0 /* AppleApp in Frameworks */,
32 | );
33 | runOnlyForDeploymentPostprocessing = 0;
34 | };
35 | /* End PBXFrameworksBuildPhase section */
36 |
37 | /* Begin PBXGroup section */
38 | 6F0DD5B62B9212410043B825 = {
39 | isa = PBXGroup;
40 | children = (
41 | 6F0DD5C12B9212420043B825 /* HappyDevLife */,
42 | 6F0DD5C02B9212420043B825 /* Products */,
43 | 6FE0F9C22B92166900DE82E0 /* Frameworks */,
44 | );
45 | sourceTree = "";
46 | };
47 | 6F0DD5C02B9212420043B825 /* Products */ = {
48 | isa = PBXGroup;
49 | children = (
50 | 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */,
51 | );
52 | name = Products;
53 | sourceTree = "";
54 | };
55 | 6F0DD5C12B9212420043B825 /* HappyDevLife */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */,
59 | 6F0DD5C22B9212420043B825 /* HappyDevLifeApp.swift */,
60 | 6F0DD5C62B9212440043B825 /* Assets.xcassets */,
61 | 6F0DD5C82B9212440043B825 /* HappyDevLife.entitlements */,
62 | 6F0DD5C92B9212440043B825 /* Preview Content */,
63 | );
64 | path = HappyDevLife;
65 | sourceTree = "";
66 | };
67 | 6F0DD5C92B9212440043B825 /* Preview Content */ = {
68 | isa = PBXGroup;
69 | children = (
70 | 6F0DD5CA2B9212440043B825 /* Preview Assets.xcassets */,
71 | );
72 | path = "Preview Content";
73 | sourceTree = "";
74 | };
75 | 6FE0F9C22B92166900DE82E0 /* Frameworks */ = {
76 | isa = PBXGroup;
77 | children = (
78 | );
79 | name = Frameworks;
80 | sourceTree = "";
81 | };
82 | /* End PBXGroup section */
83 |
84 | /* Begin PBXNativeTarget section */
85 | 6F0DD5BE2B9212420043B825 /* HappyDevLife */ = {
86 | isa = PBXNativeTarget;
87 | buildConfigurationList = 6F0DD5CE2B9212440043B825 /* Build configuration list for PBXNativeTarget "HappyDevLife" */;
88 | buildPhases = (
89 | 6F0DD5BB2B9212420043B825 /* Sources */,
90 | 6F0DD5BC2B9212420043B825 /* Frameworks */,
91 | 6F0DD5BD2B9212420043B825 /* Resources */,
92 | );
93 | buildRules = (
94 | );
95 | dependencies = (
96 | );
97 | name = HappyDevLife;
98 | packageProductDependencies = (
99 | 6FE0F9C32B92166900DE82E0 /* AppleApp */,
100 | );
101 | productName = HappyDevLife;
102 | productReference = 6F0DD5BF2B9212420043B825 /* HappyDevLife.app */;
103 | productType = "com.apple.product-type.application";
104 | };
105 | /* End PBXNativeTarget section */
106 |
107 | /* Begin PBXProject section */
108 | 6F0DD5B72B9212410043B825 /* Project object */ = {
109 | isa = PBXProject;
110 | attributes = {
111 | BuildIndependentTargetsInParallel = 1;
112 | LastSwiftUpdateCheck = 1530;
113 | LastUpgradeCheck = 1530;
114 | TargetAttributes = {
115 | 6F0DD5BE2B9212420043B825 = {
116 | CreatedOnToolsVersion = 15.3;
117 | };
118 | };
119 | };
120 | buildConfigurationList = 6F0DD5BA2B9212410043B825 /* Build configuration list for PBXProject "HappyDevLife" */;
121 | compatibilityVersion = "Xcode 14.0";
122 | developmentRegion = en;
123 | hasScannedForEncodings = 0;
124 | knownRegions = (
125 | en,
126 | Base,
127 | );
128 | mainGroup = 6F0DD5B62B9212410043B825;
129 | productRefGroup = 6F0DD5C02B9212420043B825 /* Products */;
130 | projectDirPath = "";
131 | projectRoot = "";
132 | targets = (
133 | 6F0DD5BE2B9212420043B825 /* HappyDevLife */,
134 | );
135 | };
136 | /* End PBXProject section */
137 |
138 | /* Begin PBXResourcesBuildPhase section */
139 | 6F0DD5BD2B9212420043B825 /* Resources */ = {
140 | isa = PBXResourcesBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 6F0DD5CB2B9212440043B825 /* Preview Assets.xcassets in Resources */,
144 | 6F4A32B82B924F67009F3599 /* SampleCode.xcconfig in Resources */,
145 | 6F0DD5C72B9212440043B825 /* Assets.xcassets in Resources */,
146 | );
147 | runOnlyForDeploymentPostprocessing = 0;
148 | };
149 | /* End PBXResourcesBuildPhase section */
150 |
151 | /* Begin PBXSourcesBuildPhase section */
152 | 6F0DD5BB2B9212420043B825 /* Sources */ = {
153 | isa = PBXSourcesBuildPhase;
154 | buildActionMask = 2147483647;
155 | files = (
156 | 6F0DD5C32B9212420043B825 /* HappyDevLifeApp.swift in Sources */,
157 | );
158 | runOnlyForDeploymentPostprocessing = 0;
159 | };
160 | /* End PBXSourcesBuildPhase section */
161 |
162 | /* Begin XCBuildConfiguration section */
163 | 6F0DD5CC2B9212440043B825 /* Debug */ = {
164 | isa = XCBuildConfiguration;
165 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
166 | buildSettings = {
167 | ALWAYS_SEARCH_USER_PATHS = NO;
168 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
169 | CLANG_ANALYZER_NONNULL = YES;
170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
172 | CLANG_ENABLE_MODULES = YES;
173 | CLANG_ENABLE_OBJC_ARC = YES;
174 | CLANG_ENABLE_OBJC_WEAK = YES;
175 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
176 | CLANG_WARN_BOOL_CONVERSION = YES;
177 | CLANG_WARN_COMMA = YES;
178 | CLANG_WARN_CONSTANT_CONVERSION = YES;
179 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
182 | CLANG_WARN_EMPTY_BODY = YES;
183 | CLANG_WARN_ENUM_CONVERSION = YES;
184 | CLANG_WARN_INFINITE_RECURSION = YES;
185 | CLANG_WARN_INT_CONVERSION = YES;
186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
187 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
188 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
190 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
192 | CLANG_WARN_STRICT_PROTOTYPES = YES;
193 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
195 | CLANG_WARN_UNREACHABLE_CODE = YES;
196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
197 | COPY_PHASE_STRIP = NO;
198 | DEBUG_INFORMATION_FORMAT = dwarf;
199 | ENABLE_STRICT_OBJC_MSGSEND = YES;
200 | ENABLE_TESTABILITY = YES;
201 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
202 | GCC_C_LANGUAGE_STANDARD = gnu17;
203 | GCC_DYNAMIC_NO_PIC = NO;
204 | GCC_NO_COMMON_BLOCKS = YES;
205 | GCC_OPTIMIZATION_LEVEL = 0;
206 | GCC_PREPROCESSOR_DEFINITIONS = (
207 | "DEBUG=1",
208 | "$(inherited)",
209 | );
210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
212 | GCC_WARN_UNDECLARED_SELECTOR = YES;
213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
214 | GCC_WARN_UNUSED_FUNCTION = YES;
215 | GCC_WARN_UNUSED_VARIABLE = YES;
216 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
217 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
218 | MTL_FAST_MATH = YES;
219 | ONLY_ACTIVE_ARCH = YES;
220 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
222 | };
223 | name = Debug;
224 | };
225 | 6F0DD5CD2B9212440043B825 /* Release */ = {
226 | isa = XCBuildConfiguration;
227 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
228 | buildSettings = {
229 | ALWAYS_SEARCH_USER_PATHS = NO;
230 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
231 | CLANG_ANALYZER_NONNULL = YES;
232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
234 | CLANG_ENABLE_MODULES = YES;
235 | CLANG_ENABLE_OBJC_ARC = YES;
236 | CLANG_ENABLE_OBJC_WEAK = YES;
237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
238 | CLANG_WARN_BOOL_CONVERSION = YES;
239 | CLANG_WARN_COMMA = YES;
240 | CLANG_WARN_CONSTANT_CONVERSION = YES;
241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
244 | CLANG_WARN_EMPTY_BODY = YES;
245 | CLANG_WARN_ENUM_CONVERSION = YES;
246 | CLANG_WARN_INFINITE_RECURSION = YES;
247 | CLANG_WARN_INT_CONVERSION = YES;
248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
252 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
254 | CLANG_WARN_STRICT_PROTOTYPES = YES;
255 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
257 | CLANG_WARN_UNREACHABLE_CODE = YES;
258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
259 | COPY_PHASE_STRIP = NO;
260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
261 | ENABLE_NS_ASSERTIONS = NO;
262 | ENABLE_STRICT_OBJC_MSGSEND = YES;
263 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
264 | GCC_C_LANGUAGE_STANDARD = gnu17;
265 | GCC_NO_COMMON_BLOCKS = YES;
266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
268 | GCC_WARN_UNDECLARED_SELECTOR = YES;
269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
270 | GCC_WARN_UNUSED_FUNCTION = YES;
271 | GCC_WARN_UNUSED_VARIABLE = YES;
272 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
273 | MTL_ENABLE_DEBUG_INFO = NO;
274 | MTL_FAST_MATH = YES;
275 | SWIFT_COMPILATION_MODE = wholemodule;
276 | };
277 | name = Release;
278 | };
279 | 6F0DD5CF2B9212440043B825 /* Debug */ = {
280 | isa = XCBuildConfiguration;
281 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
282 | buildSettings = {
283 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
284 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
285 | CODE_SIGN_ENTITLEMENTS = HappyDevLife/HappyDevLife.entitlements;
286 | CODE_SIGN_STYLE = Automatic;
287 | CURRENT_PROJECT_VERSION = 1;
288 | DEVELOPMENT_ASSET_PATHS = "\"HappyDevLife/Preview Content\"";
289 | DEVELOPMENT_TEAM = "";
290 | ENABLE_HARDENED_RUNTIME = YES;
291 | ENABLE_PREVIEWS = YES;
292 | GENERATE_INFOPLIST_FILE = YES;
293 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
294 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
295 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
296 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
297 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
298 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
299 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
300 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
301 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
302 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
303 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
304 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
305 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
306 | MACOSX_DEPLOYMENT_TARGET = 14.0;
307 | MARKETING_VERSION = 1.0;
308 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.arasan01-${SAMPLE_CODE_DISAMBIGUATOR}.HappyDevLife";
309 | PRODUCT_NAME = "$(TARGET_NAME)";
310 | SDKROOT = auto;
311 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
312 | SWIFT_EMIT_LOC_STRINGS = YES;
313 | SWIFT_VERSION = 5.0;
314 | TARGETED_DEVICE_FAMILY = "1,2";
315 | };
316 | name = Debug;
317 | };
318 | 6F0DD5D02B9212440043B825 /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | baseConfigurationReference = 6F4A32B72B924F67009F3599 /* SampleCode.xcconfig */;
321 | buildSettings = {
322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
323 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
324 | CODE_SIGN_ENTITLEMENTS = HappyDevLife/HappyDevLife.entitlements;
325 | CODE_SIGN_STYLE = Automatic;
326 | CURRENT_PROJECT_VERSION = 1;
327 | DEVELOPMENT_ASSET_PATHS = "\"HappyDevLife/Preview Content\"";
328 | DEVELOPMENT_TEAM = "";
329 | ENABLE_HARDENED_RUNTIME = YES;
330 | ENABLE_PREVIEWS = YES;
331 | GENERATE_INFOPLIST_FILE = YES;
332 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
333 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
334 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
335 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
336 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
337 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
338 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
339 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
340 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
341 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
342 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
343 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
344 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
345 | MACOSX_DEPLOYMENT_TARGET = 14.0;
346 | MARKETING_VERSION = 1.0;
347 | PRODUCT_BUNDLE_IDENTIFIER = "com.example.arasan01-${SAMPLE_CODE_DISAMBIGUATOR}.HappyDevLife";
348 | PRODUCT_NAME = "$(TARGET_NAME)";
349 | SDKROOT = auto;
350 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
351 | SWIFT_EMIT_LOC_STRINGS = YES;
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Release;
356 | };
357 | /* End XCBuildConfiguration section */
358 |
359 | /* Begin XCConfigurationList section */
360 | 6F0DD5BA2B9212410043B825 /* Build configuration list for PBXProject "HappyDevLife" */ = {
361 | isa = XCConfigurationList;
362 | buildConfigurations = (
363 | 6F0DD5CC2B9212440043B825 /* Debug */,
364 | 6F0DD5CD2B9212440043B825 /* Release */,
365 | );
366 | defaultConfigurationIsVisible = 0;
367 | defaultConfigurationName = Release;
368 | };
369 | 6F0DD5CE2B9212440043B825 /* Build configuration list for PBXNativeTarget "HappyDevLife" */ = {
370 | isa = XCConfigurationList;
371 | buildConfigurations = (
372 | 6F0DD5CF2B9212440043B825 /* Debug */,
373 | 6F0DD5D02B9212440043B825 /* Release */,
374 | );
375 | defaultConfigurationIsVisible = 0;
376 | defaultConfigurationName = Release;
377 | };
378 | /* End XCConfigurationList section */
379 |
380 | /* Begin XCSwiftPackageProductDependency section */
381 | 6FE0F9C32B92166900DE82E0 /* AppleApp */ = {
382 | isa = XCSwiftPackageProductDependency;
383 | productName = AppleApp;
384 | };
385 | /* End XCSwiftPackageProductDependency section */
386 | };
387 | rootObject = 6F0DD5B72B9212410043B825 /* Project object */;
388 | }
389 |
--------------------------------------------------------------------------------