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