add swiftUI code
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"assets" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Circular.imageset",
|
||||
"role" : "circular"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Extra Large.imageset",
|
||||
"role" : "extra-large"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Modular.imageset",
|
||||
"role" : "modular"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"filename" : "Utilitarian.imageset",
|
||||
"role" : "utilitarian"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{130,145}",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "watch",
|
||||
"screenWidth" : "{146,165}",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import WatchKit
|
||||
|
||||
class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
||||
|
||||
func applicationDidFinishLaunching() {
|
||||
// Perform any final initialization of your application.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive() {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillResignActive() {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, etc.
|
||||
}
|
||||
|
||||
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
|
||||
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
|
||||
for task in backgroundTasks {
|
||||
// Use a switch statement to check the task type
|
||||
switch task {
|
||||
case let backgroundTask as WKApplicationRefreshBackgroundTask:
|
||||
// Be sure to complete the background task once you’re done.
|
||||
backgroundTask.setTaskCompletedWithSnapshot(false)
|
||||
case let snapshotTask as WKSnapshotRefreshBackgroundTask:
|
||||
// Snapshot tasks have a unique completion call, make sure to set your expiration date
|
||||
snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
|
||||
case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
|
||||
// Be sure to complete the connectivity task once you’re done.
|
||||
connectivityTask.setTaskCompletedWithSnapshot(false)
|
||||
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
|
||||
// Be sure to complete the URL session task once you’re done.
|
||||
urlSessionTask.setTaskCompletedWithSnapshot(false)
|
||||
case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
|
||||
// Be sure to complete the relevant-shortcut task once you're done.
|
||||
relevantShortcutTask.setTaskCompletedWithSnapshot(false)
|
||||
case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
|
||||
// Be sure to complete the intent-did-run task once you're done.
|
||||
intentDidRunTask.setTaskCompletedWithSnapshot(false)
|
||||
default:
|
||||
// make sure to complete unhandled task types
|
||||
task.setTaskCompletedWithSnapshot(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>GRDBDemoWatchOS Extension</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>WKAppBundleIdentifier</key>
|
||||
<string>com.github.groue.GRDBDemoiOS2.watchkitapp</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.watchkit</string>
|
||||
</dict>
|
||||
<key>WKExtensionDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ExtensionDelegate</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
import WatchKit
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
class InterfaceController: WKInterfaceController {
|
||||
|
||||
@IBOutlet var versionLabel: WKInterfaceLabel!
|
||||
|
||||
override func awake(withContext context: Any?) {
|
||||
super.awake(withContext: context)
|
||||
|
||||
let sqliteVersion = String(cString: sqlite3_libversion(), encoding: .utf8)
|
||||
versionLabel.setText(sqliteVersion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "24x24",
|
||||
"idiom" : "watch",
|
||||
"scale" : "2x",
|
||||
"role" : "notificationCenter",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "27.5x27.5",
|
||||
"idiom" : "watch",
|
||||
"scale" : "2x",
|
||||
"role" : "notificationCenter",
|
||||
"subtype" : "42mm"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "watch",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "watch",
|
||||
"role" : "companionSettings",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "watch",
|
||||
"scale" : "2x",
|
||||
"role" : "appLauncher",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "86x86",
|
||||
"idiom" : "watch",
|
||||
"scale" : "2x",
|
||||
"role" : "quickLook",
|
||||
"subtype" : "38mm"
|
||||
},
|
||||
{
|
||||
"size" : "98x98",
|
||||
"idiom" : "watch",
|
||||
"scale" : "2x",
|
||||
"role" : "quickLook",
|
||||
"subtype" : "42mm"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="AgC-eL-Hgc">
|
||||
<device id="watch38" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="watchOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="12029"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Interface Controller-->
|
||||
<scene sceneID="aou-V4-d1y">
|
||||
<objects>
|
||||
<controller id="AgC-eL-Hgc" customClass="InterfaceController" customModule="GRDBDemoWatchOS" customModuleProvider="target">
|
||||
<items>
|
||||
<group width="1" alignment="left" verticalAlignment="center" layout="vertical" id="tkk-aG-QQF">
|
||||
<items>
|
||||
<label alignment="center" text="SQLite version" id="dGA-xX-x8V"/>
|
||||
<label alignment="center" text="Label" id="whi-N6-h8U"/>
|
||||
</items>
|
||||
</group>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="versionLabel" destination="whi-N6-h8U" id="5xN-Gj-yNt"/>
|
||||
</connections>
|
||||
</controller>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>GRDBDemoiOS</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>com.github.groue.GRDBDemoiOS2</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,912 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
56185BBF25B8036100B9C30F /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185BBE25B8036100B9C30F /* Persistence.swift */; };
|
||||
56185BF325B80B8900B9C30F /* AppDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */; };
|
||||
56185C0725B80CEC00B9C30F /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185C0625B80CEC00B9C30F /* PlayerTests.swift */; };
|
||||
567940C528BA33D2004A0298 /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; };
|
||||
567940C628BA33D2004A0298 /* GRDB.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
567940C728BA33DE004A0298 /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; };
|
||||
567940C828BA33DE004A0298 /* GRDB.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
568E5FC31E926430002582E0 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FC11E926430002582E0 /* Interface.storyboard */; };
|
||||
568E5FC51E926430002582E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FC41E926430002582E0 /* Assets.xcassets */; };
|
||||
568E5FCC1E926430002582E0 /* GRDBDemoWatchOS Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
568E5FD11E926430002582E0 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 568E5FD01E926430002582E0 /* InterfaceController.swift */; };
|
||||
568E5FD31E926430002582E0 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 568E5FD21E926430002582E0 /* ExtensionDelegate.swift */; };
|
||||
568E5FD51E926430002582E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FD41E926430002582E0 /* Assets.xcassets */; };
|
||||
568E5FD91E926430002582E0 /* GRDBDemoWatchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */; };
|
||||
56B036071E8D9EBE003B6DA4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */; };
|
||||
56B0360C1E8D9EBE003B6DA4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */; };
|
||||
56B0360E1E8D9EBE003B6DA4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */; };
|
||||
56B036111E8D9EBE003B6DA4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */; };
|
||||
56B0361B1E8D9F38003B6DA4 /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036191E8D9F38003B6DA4 /* AppDatabase.swift */; };
|
||||
56B0361C1E8D9F38003B6DA4 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B0361A1E8D9F38003B6DA4 /* Player.swift */; };
|
||||
56B036231E8D9F4C003B6DA4 /* PlayerEditionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */; };
|
||||
56B036241E8D9F4C003B6DA4 /* PlayerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */; };
|
||||
56FE6F2624A90CE400711EDF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FE6F2524A90CE400711EDF /* SceneDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
56185BF525B80B8900B9C30F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 56B036021E8D9EBE003B6DA4;
|
||||
remoteInfo = GRDBDemoiOS;
|
||||
};
|
||||
5642252F29A2390800D714BF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DC3773F219C8CBB3004FCF85;
|
||||
remoteInfo = GRDB;
|
||||
};
|
||||
5642253329A2391900D714BF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = DC3773F219C8CBB3004FCF85;
|
||||
remoteInfo = GRDB;
|
||||
};
|
||||
56606D092355931F00185962 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = DC3773F319C8CBB3004FCF85;
|
||||
remoteInfo = GRDBOSX;
|
||||
};
|
||||
56606D0B2355931F00185962 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 56E5D7F91B4D422D00430942;
|
||||
remoteInfo = GRDBOSXTests;
|
||||
};
|
||||
568E5FCD1E926430002582E0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 568E5FCA1E926430002582E0;
|
||||
remoteInfo = "GRDBDemoWatchOS Extension";
|
||||
};
|
||||
568E5FD71E926430002582E0 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 568E5FBE1E926430002582E0;
|
||||
remoteInfo = GRDBDemoWatchOS;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
568E5FDD1E926430002582E0 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
568E5FCC1E926430002582E0 /* GRDBDemoWatchOS Extension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FE11E926430002582E0 /* Embed Watch Content */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
568E5FD91E926430002582E0 /* GRDBDemoWatchOS.app in Embed Watch Content */,
|
||||
);
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FE81E926547002582E0 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
567940C828BA33DE004A0298 /* GRDB.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
56B036621E8D9FB1003B6DA4 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
567940C628BA33D2004A0298 /* GRDB.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
56185BBE25B8036100B9C30F /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBDemoiOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabaseTests.swift; sourceTree = "<group>"; };
|
||||
56185BF425B80B8900B9C30F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
56185C0625B80CEC00B9C30F /* PlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = "<group>"; };
|
||||
568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBDemoWatchOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
568E5FC21E926430002582E0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; };
|
||||
568E5FC41E926430002582E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
568E5FC61E926430002582E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "GRDBDemoWatchOS Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
568E5FD01E926430002582E0 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = "<group>"; };
|
||||
568E5FD21E926430002582E0 /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = "<group>"; };
|
||||
568E5FD41E926430002582E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
568E5FD61E926430002582E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBDemoiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
56B0360B1E8D9EBE003B6DA4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
56B036101E8D9EBE003B6DA4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
56B036121E8D9EBE003B6DA4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
56B036191E8D9F38003B6DA4 /* AppDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = "<group>"; };
|
||||
56B0361A1E8D9F38003B6DA4 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
|
||||
56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerEditionViewController.swift; sourceTree = "<group>"; };
|
||||
56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerListViewController.swift; sourceTree = "<group>"; };
|
||||
56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GRDB.xcodeproj; path = ../../../GRDB.xcodeproj; sourceTree = "<group>"; };
|
||||
56FE6F2524A90CE400711EDF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
56185BED25B80B8900B9C30F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FC81E926430002582E0 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
567940C728BA33DE004A0298 /* GRDB.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
56B036001E8D9EBE003B6DA4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
567940C528BA33D2004A0298 /* GRDB.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
56185BF125B80B8900B9C30F /* GRDBDemoiOSTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56185BF425B80B8900B9C30F /* Info.plist */,
|
||||
56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */,
|
||||
56185C0625B80CEC00B9C30F /* PlayerTests.swift */,
|
||||
);
|
||||
path = GRDBDemoiOSTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56606CFD2355931F00185962 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56606D0A2355931F00185962 /* GRDB.framework */,
|
||||
56606D0C2355931F00185962 /* GRDBTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56606D1F2355938A00185962 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
568E5FC01E926430002582E0 /* GRDBDemoWatchOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
568E5FC11E926430002582E0 /* Interface.storyboard */,
|
||||
568E5FC41E926430002582E0 /* Assets.xcassets */,
|
||||
568E5FC61E926430002582E0 /* Info.plist */,
|
||||
);
|
||||
path = GRDBDemoWatchOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
568E5FCF1E926430002582E0 /* GRDBDemoWatchOS Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
568E5FD01E926430002582E0 /* InterfaceController.swift */,
|
||||
568E5FD21E926430002582E0 /* ExtensionDelegate.swift */,
|
||||
568E5FD41E926430002582E0 /* Assets.xcassets */,
|
||||
568E5FD61E926430002582E0 /* Info.plist */,
|
||||
);
|
||||
path = "GRDBDemoWatchOS Extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56B035FA1E8D9EBE003B6DA4 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */,
|
||||
56B036051E8D9EBE003B6DA4 /* GRDBDemoiOS */,
|
||||
568E5FC01E926430002582E0 /* GRDBDemoWatchOS */,
|
||||
568E5FCF1E926430002582E0 /* GRDBDemoWatchOS Extension */,
|
||||
56185BF125B80B8900B9C30F /* GRDBDemoiOSTests */,
|
||||
56B036041E8D9EBE003B6DA4 /* Products */,
|
||||
56606D1F2355938A00185962 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56B036041E8D9EBE003B6DA4 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */,
|
||||
568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */,
|
||||
568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */,
|
||||
56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56B036051E8D9EBE003B6DA4 /* GRDBDemoiOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56B036121E8D9EBE003B6DA4 /* Info.plist */,
|
||||
56B036191E8D9F38003B6DA4 /* AppDatabase.swift */,
|
||||
56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */,
|
||||
56185BBE25B8036100B9C30F /* Persistence.swift */,
|
||||
56B0361A1E8D9F38003B6DA4 /* Player.swift */,
|
||||
56FE6F2524A90CE400711EDF /* SceneDelegate.swift */,
|
||||
56FE6F4E24A9112900711EDF /* Resources */,
|
||||
56FE6F4F24A9114200711EDF /* ViewControllers */,
|
||||
);
|
||||
path = GRDBDemoiOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56FE6F4E24A9112900711EDF /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */,
|
||||
56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */,
|
||||
56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56FE6F4F24A9114200711EDF /* ViewControllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */,
|
||||
56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
56185BEF25B80B8900B9C30F /* GRDBDemoiOSTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 56185BFF25B80B8900B9C30F /* Build configuration list for PBXNativeTarget "GRDBDemoiOSTests" */;
|
||||
buildPhases = (
|
||||
56185BEC25B80B8900B9C30F /* Sources */,
|
||||
56185BED25B80B8900B9C30F /* Frameworks */,
|
||||
56185BEE25B80B8900B9C30F /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
56185BF625B80B8900B9C30F /* PBXTargetDependency */,
|
||||
);
|
||||
name = GRDBDemoiOSTests;
|
||||
productName = GRDBDemoiOSTests;
|
||||
productReference = 56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 568E5FDE1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS" */;
|
||||
buildPhases = (
|
||||
568E5FBD1E926430002582E0 /* Resources */,
|
||||
568E5FDD1E926430002582E0 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
568E5FCE1E926430002582E0 /* PBXTargetDependency */,
|
||||
);
|
||||
name = GRDBDemoWatchOS;
|
||||
productName = GRDBDemoWatchOS;
|
||||
productReference = 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */;
|
||||
productType = "com.apple.product-type.application.watchapp2";
|
||||
};
|
||||
568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 568E5FDA1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS Extension" */;
|
||||
buildPhases = (
|
||||
568E5FC71E926430002582E0 /* Sources */,
|
||||
568E5FC81E926430002582E0 /* Frameworks */,
|
||||
568E5FC91E926430002582E0 /* Resources */,
|
||||
568E5FE81E926547002582E0 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5642253429A2391900D714BF /* PBXTargetDependency */,
|
||||
);
|
||||
name = "GRDBDemoWatchOS Extension";
|
||||
productName = "GRDBDemoWatchOS Extension";
|
||||
productReference = 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */;
|
||||
productType = "com.apple.product-type.watchkit2-extension";
|
||||
};
|
||||
56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 56B036151E8D9EBE003B6DA4 /* Build configuration list for PBXNativeTarget "GRDBDemoiOS" */;
|
||||
buildPhases = (
|
||||
56B035FF1E8D9EBE003B6DA4 /* Sources */,
|
||||
56B036001E8D9EBE003B6DA4 /* Frameworks */,
|
||||
56B036011E8D9EBE003B6DA4 /* Resources */,
|
||||
56B036621E8D9FB1003B6DA4 /* Embed Frameworks */,
|
||||
568E5FE11E926430002582E0 /* Embed Watch Content */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5642253029A2390800D714BF /* PBXTargetDependency */,
|
||||
568E5FD81E926430002582E0 /* PBXTargetDependency */,
|
||||
);
|
||||
name = GRDBDemoiOS;
|
||||
productName = GRDBDemoiOS;
|
||||
productReference = 56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
56B035FB1E8D9EBE003B6DA4 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1230;
|
||||
LastUpgradeCheck = 1400;
|
||||
ORGANIZATIONNAME = "Gwendal Roué";
|
||||
TargetAttributes = {
|
||||
56185BEF25B80B8900B9C30F = {
|
||||
CreatedOnToolsVersion = 12.3;
|
||||
DevelopmentTeam = AMD8W895CT;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 56B036021E8D9EBE003B6DA4;
|
||||
};
|
||||
568E5FBE1E926430002582E0 = {
|
||||
CreatedOnToolsVersion = 8.3;
|
||||
DevelopmentTeam = AMD8W895CT;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
568E5FCA1E926430002582E0 = {
|
||||
CreatedOnToolsVersion = 8.3;
|
||||
DevelopmentTeam = AMD8W895CT;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
56B036021E8D9EBE003B6DA4 = {
|
||||
CreatedOnToolsVersion = 8.3;
|
||||
DevelopmentTeam = AMD8W895CT;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 56B035FE1E8D9EBE003B6DA4 /* Build configuration list for PBXProject "GRDBDemoiOS" */;
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 56B035FA1E8D9EBE003B6DA4;
|
||||
productRefGroup = 56B036041E8D9EBE003B6DA4 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = 56606CFD2355931F00185962 /* Products */;
|
||||
ProjectRef = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */,
|
||||
568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */,
|
||||
568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */,
|
||||
56185BEF25B80B8900B9C30F /* GRDBDemoiOSTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
56606D0A2355931F00185962 /* GRDB.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = GRDB.framework;
|
||||
remoteRef = 56606D092355931F00185962 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
56606D0C2355931F00185962 /* GRDBTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = GRDBTests.xctest;
|
||||
remoteRef = 56606D0B2355931F00185962 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
56185BEE25B80B8900B9C30F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FBD1E926430002582E0 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
568E5FC51E926430002582E0 /* Assets.xcassets in Resources */,
|
||||
568E5FC31E926430002582E0 /* Interface.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FC91E926430002582E0 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
568E5FD51E926430002582E0 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
56B036011E8D9EBE003B6DA4 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
56B036111E8D9EBE003B6DA4 /* LaunchScreen.storyboard in Resources */,
|
||||
56B0360E1E8D9EBE003B6DA4 /* Assets.xcassets in Resources */,
|
||||
56B0360C1E8D9EBE003B6DA4 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
56185BEC25B80B8900B9C30F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
56185C0725B80CEC00B9C30F /* PlayerTests.swift in Sources */,
|
||||
56185BF325B80B8900B9C30F /* AppDatabaseTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
568E5FC71E926430002582E0 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
568E5FD31E926430002582E0 /* ExtensionDelegate.swift in Sources */,
|
||||
568E5FD11E926430002582E0 /* InterfaceController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
56B035FF1E8D9EBE003B6DA4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
56FE6F2624A90CE400711EDF /* SceneDelegate.swift in Sources */,
|
||||
56B0361C1E8D9F38003B6DA4 /* Player.swift in Sources */,
|
||||
56B0361B1E8D9F38003B6DA4 /* AppDatabase.swift in Sources */,
|
||||
56185BBF25B8036100B9C30F /* Persistence.swift in Sources */,
|
||||
56B036071E8D9EBE003B6DA4 /* AppDelegate.swift in Sources */,
|
||||
56B036241E8D9F4C003B6DA4 /* PlayerListViewController.swift in Sources */,
|
||||
56B036231E8D9F4C003B6DA4 /* PlayerEditionViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
56185BF625B80B8900B9C30F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */;
|
||||
targetProxy = 56185BF525B80B8900B9C30F /* PBXContainerItemProxy */;
|
||||
};
|
||||
5642253029A2390800D714BF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = GRDB;
|
||||
targetProxy = 5642252F29A2390800D714BF /* PBXContainerItemProxy */;
|
||||
};
|
||||
5642253429A2391900D714BF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = GRDB;
|
||||
targetProxy = 5642253329A2391900D714BF /* PBXContainerItemProxy */;
|
||||
};
|
||||
568E5FCE1E926430002582E0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */;
|
||||
targetProxy = 568E5FCD1E926430002582E0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
568E5FD81E926430002582E0 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */;
|
||||
targetProxy = 568E5FD71E926430002582E0 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
568E5FC11E926430002582E0 /* Interface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
568E5FC21E926430002582E0 /* Base */,
|
||||
);
|
||||
name = Interface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
56B0360B1E8D9EBE003B6DA4 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
56B036101E8D9EBE003B6DA4 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
56185BF725B80B8900B9C30F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = GRDBDemoiOSTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOSTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemoiOS.app/GRDBDemoiOS";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
56185BF825B80B8900B9C30F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = GRDBDemoiOSTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOSTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemoiOS.app/GRDBDemoiOS";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
568E5FDB1E926430002582E0 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
INFOPLIST_FILE = "GRDBDemoWatchOS Extension/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp.watchkitextension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
568E5FDC1E926430002582E0 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
INFOPLIST_FILE = "GRDBDemoWatchOS Extension/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp.watchkitextension;
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
568E5FDF1E926430002582E0 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
IBSC_MODULE = GRDBDemoWatchOS_Extension;
|
||||
INFOPLIST_FILE = GRDBDemoWatchOS/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
568E5FE01E926430002582E0 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
IBSC_MODULE = GRDBDemoWatchOS_Extension;
|
||||
INFOPLIST_FILE = GRDBDemoWatchOS/Info.plist;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
56B036131E8D9EBE003B6DA4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
56B036141E8D9EBE003B6DA4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 8.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
56B036161E8D9EBE003B6DA4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
INFOPLIST_FILE = GRDBDemoiOS/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
56B036171E8D9EBE003B6DA4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
DEVELOPMENT_TEAM = AMD8W895CT;
|
||||
INFOPLIST_FILE = GRDBDemoiOS/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
56185BFF25B80B8900B9C30F /* Build configuration list for PBXNativeTarget "GRDBDemoiOSTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
56185BF725B80B8900B9C30F /* Debug */,
|
||||
56185BF825B80B8900B9C30F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
568E5FDA1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
568E5FDB1E926430002582E0 /* Debug */,
|
||||
568E5FDC1E926430002582E0 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
568E5FDE1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
568E5FDF1E926430002582E0 /* Debug */,
|
||||
568E5FE01E926430002582E0 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
56B035FE1E8D9EBE003B6DA4 /* Build configuration list for PBXProject "GRDBDemoiOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
56B036131E8D9EBE003B6DA4 /* Debug */,
|
||||
56B036141E8D9EBE003B6DA4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
56B036151E8D9EBE003B6DA4 /* Build configuration list for PBXNativeTarget "GRDBDemoiOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
56B036161E8D9EBE003B6DA4 /* Debug */,
|
||||
56B036171E8D9EBE003B6DA4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "568E5FBE1E926430002582E0"
|
||||
BuildableName = "GRDBDemoWatchOS.app"
|
||||
BlueprintName = "GRDBDemoWatchOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56B036021E8D9EBE003B6DA4"
|
||||
BuildableName = "GRDBDemoiOS.app"
|
||||
BlueprintName = "GRDBDemoiOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "568E5FCA1E926430002582E0"
|
||||
BuildableName = "GRDBDemoWatchOS Extension.appex"
|
||||
BlueprintName = "GRDBDemoWatchOS Extension"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56185BEF25B80B8900B9C30F"
|
||||
BuildableName = "GRDBDemoiOSTests.xctest"
|
||||
BlueprintName = "GRDBDemoiOSTests"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "568E5FBE1E926430002582E0"
|
||||
BuildableName = "GRDBDemoWatchOS.app"
|
||||
BlueprintName = "GRDBDemoWatchOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "568E5FBE1E926430002582E0"
|
||||
BuildableName = "GRDBDemoWatchOS.app"
|
||||
BlueprintName = "GRDBDemoWatchOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56B036021E8D9EBE003B6DA4"
|
||||
BuildableName = "GRDBDemoiOS.app"
|
||||
BlueprintName = "GRDBDemoiOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56185BEF25B80B8900B9C30F"
|
||||
BuildableName = "GRDBDemoiOSTests.xctest"
|
||||
BlueprintName = "GRDBDemoiOSTests"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56B036021E8D9EBE003B6DA4"
|
||||
BuildableName = "GRDBDemoiOS.app"
|
||||
BlueprintName = "GRDBDemoiOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "SQL_TRACE"
|
||||
value = "1"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "56B036021E8D9EBE003B6DA4"
|
||||
BuildableName = "GRDBDemoiOS.app"
|
||||
BlueprintName = "GRDBDemoiOS"
|
||||
ReferencedContainer = "container:GRDBDemoiOS.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,200 @@
|
||||
import GRDB
|
||||
import os.log
|
||||
|
||||
/// `AppDatabase` lets the application access the database.
|
||||
///
|
||||
/// You create an `AppDatabase` with a connection to an SQLite database
|
||||
/// (see <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>).
|
||||
///
|
||||
/// Create those connections with a configuration returned from
|
||||
/// `AppDatabase/makeConfiguration(_:)`.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Create an in-memory AppDatabase
|
||||
/// let config = AppDatabase.makeConfiguration()
|
||||
/// let dbQueue = try DatabaseQueue(configuration: config)
|
||||
/// let appDatabase = try AppDatabase(dbQueue)
|
||||
/// ```
|
||||
struct AppDatabase {
|
||||
/// Creates an `AppDatabase`, and makes sure the database schema
|
||||
/// is ready.
|
||||
///
|
||||
/// - important: Create the `DatabaseWriter` with a configuration
|
||||
/// returned by ``makeConfiguration(_:)``.
|
||||
init(_ dbWriter: any DatabaseWriter) throws {
|
||||
self.dbWriter = dbWriter
|
||||
try migrator.migrate(dbWriter)
|
||||
}
|
||||
|
||||
/// Provides access to the database.
|
||||
///
|
||||
/// Application can use a `DatabasePool`, and tests can use a fast
|
||||
/// in-memory `DatabaseQueue`.
|
||||
///
|
||||
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
|
||||
private let dbWriter: any DatabaseWriter
|
||||
}
|
||||
|
||||
// MARK: - Database Configuration
|
||||
|
||||
extension AppDatabase {
|
||||
private static let sqlLogger = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SQL")
|
||||
|
||||
/// Returns a database configuration suited for `PlayerRepository`.
|
||||
///
|
||||
/// SQL statements are logged if the `SQL_TRACE` environment variable
|
||||
/// is set.
|
||||
///
|
||||
/// - parameter base: A base configuration.
|
||||
public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
|
||||
var config = base
|
||||
|
||||
// An opportunity to add required custom SQL functions or
|
||||
// collations, if needed:
|
||||
// config.prepareDatabase { db in
|
||||
// db.add(function: ...)
|
||||
// }
|
||||
|
||||
// Log SQL statements if the `SQL_TRACE` environment variable is set.
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/database/trace(options:_:)>
|
||||
if ProcessInfo.processInfo.environment["SQL_TRACE"] != nil {
|
||||
config.prepareDatabase { db in
|
||||
db.trace {
|
||||
// It's ok to log statements publicly. Sensitive
|
||||
// information (statement arguments) are not logged
|
||||
// unless config.publicStatementArguments is set
|
||||
// (see below).
|
||||
os_log("%{public}@", log: sqlLogger, type: .debug, String(describing: $0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// Protect sensitive information by enabling verbose debugging in
|
||||
// DEBUG builds only.
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/configuration/publicstatementarguments>
|
||||
config.publicStatementArguments = true
|
||||
#endif
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Database Migrations
|
||||
|
||||
extension AppDatabase {
|
||||
/// The DatabaseMigrator that defines the database schema.
|
||||
///
|
||||
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
||||
private var migrator: DatabaseMigrator {
|
||||
var migrator = DatabaseMigrator()
|
||||
|
||||
#if DEBUG
|
||||
// Speed up development by nuking the database when migrations change
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
||||
migrator.eraseDatabaseOnSchemaChange = true
|
||||
#endif
|
||||
|
||||
migrator.registerMigration("createPlayer") { db in
|
||||
// Create a table
|
||||
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
|
||||
try db.create(table: "player") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("name", .text).notNull()
|
||||
t.column("score", .integer).notNull()
|
||||
}
|
||||
}
|
||||
|
||||
// Migrations for future application versions will be inserted here:
|
||||
// migrator.registerMigration(...) { db in
|
||||
// ...
|
||||
// }
|
||||
|
||||
return migrator
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Database Access: Writes
|
||||
// The write methods execute invariant-preserving database transactions.
|
||||
|
||||
extension AppDatabase {
|
||||
/// Saves (inserts or updates) a player. When the method returns, the
|
||||
/// player is present in the database, and its id is not nil.
|
||||
func savePlayer(_ player: inout Player) throws {
|
||||
try dbWriter.write { db in
|
||||
try player.save(db)
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the specified players
|
||||
func deletePlayers(ids: [Int64]) throws {
|
||||
try dbWriter.write { db in
|
||||
_ = try Player.deleteAll(db, ids: ids)
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete all players
|
||||
func deleteAllPlayers() throws {
|
||||
try dbWriter.write { db in
|
||||
_ = try Player.deleteAll(db)
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh all players (by performing some random changes, for demo purpose).
|
||||
func refreshPlayers() throws {
|
||||
try dbWriter.write { db in
|
||||
if try Player.all().isEmpty(db) {
|
||||
// When database is empty, insert new random players
|
||||
try createRandomPlayers(db)
|
||||
} else {
|
||||
// Insert a player
|
||||
if Bool.random() {
|
||||
_ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
|
||||
}
|
||||
|
||||
// Delete a random player
|
||||
if Bool.random() {
|
||||
try Player.order(sql: "RANDOM()").limit(1).deleteAll(db)
|
||||
}
|
||||
|
||||
// Update some players
|
||||
for var player in try Player.fetchAll(db) where Bool.random() {
|
||||
try player.updateChanges(db) {
|
||||
$0.score = Player.randomScore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create random players if the database is empty.
|
||||
func createRandomPlayersIfEmpty() throws {
|
||||
try dbWriter.write { db in
|
||||
if try Player.all().isEmpty(db) {
|
||||
try createRandomPlayers(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Support for `createRandomPlayersIfEmpty()` and `refreshPlayers()`.
|
||||
private func createRandomPlayers(_ db: Database) throws {
|
||||
for _ in 0..<8 {
|
||||
_ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Database Access: Reads
|
||||
|
||||
// This demo app does not provide any specific reading method, and instead
|
||||
// gives an unrestricted read-only access to the rest of the application.
|
||||
// In your app, you are free to choose another path, and define focused
|
||||
// reading methods.
|
||||
extension AppDatabase {
|
||||
/// Provides a read-only access to the database
|
||||
var reader: DatabaseReader {
|
||||
dbWriter
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,49 @@
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
extension AppDatabase {
|
||||
/// The database for the application
|
||||
static let shared = makeShared()
|
||||
|
||||
private static func makeShared() -> AppDatabase {
|
||||
do {
|
||||
// Apply recommendations from
|
||||
// <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
|
||||
//
|
||||
// Create the "Application Support/Database" directory if needed
|
||||
let fileManager = FileManager.default
|
||||
let appSupportURL = try fileManager.url(
|
||||
for: .applicationSupportDirectory, in: .userDomainMask,
|
||||
appropriateFor: nil, create: true)
|
||||
let directoryURL = appSupportURL.appendingPathComponent("Database", isDirectory: true)
|
||||
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
||||
|
||||
// Open or create the database
|
||||
let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
|
||||
NSLog("Database stored at \(databaseURL.path)")
|
||||
let dbPool = try DatabasePool(
|
||||
path: databaseURL.path,
|
||||
// Use default AppDatabase configuration
|
||||
configuration: AppDatabase.makeConfiguration())
|
||||
|
||||
// Create the AppDatabase
|
||||
let appDatabase = try AppDatabase(dbPool)
|
||||
|
||||
// Populate the database if it is empty, for better demo purpose.
|
||||
try appDatabase.createRandomPlayersIfEmpty()
|
||||
|
||||
return appDatabase
|
||||
} catch {
|
||||
// Replace this implementation with code to handle the error appropriately.
|
||||
// fatalError() causes the application to generate a crash log and terminate.
|
||||
//
|
||||
// Typical reasons for an error here include:
|
||||
// * The parent directory cannot be created, or disallows writing.
|
||||
// * The database is not accessible, due to permissions or data protection when the device is locked.
|
||||
// * The device is out of space.
|
||||
// * The database could not be migrated to its latest schema version.
|
||||
// Check the error message to determine what the actual problem was.
|
||||
fatalError("Unresolved error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import GRDB
|
||||
|
||||
/// The Player struct.
|
||||
///
|
||||
/// Identifiable conformance supports type-safe GRDB primary key methods.
|
||||
/// Hashable conformance supports table view updates
|
||||
struct Player: Identifiable, Hashable {
|
||||
/// The player id.
|
||||
///
|
||||
/// Int64 is the recommended type for auto-incremented database ids.
|
||||
/// Use nil for players that are not inserted yet in the database.
|
||||
var id: Int64?
|
||||
var name: String
|
||||
var score: Int
|
||||
}
|
||||
|
||||
extension Player {
|
||||
private static let names = [
|
||||
"Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
|
||||
"Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
|
||||
"Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
|
||||
"Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
|
||||
"Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
|
||||
"Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
|
||||
"Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
|
||||
"Zazie", "Zoé"]
|
||||
|
||||
/// Creates a new player with empty name and zero score
|
||||
static func new() -> Player {
|
||||
Player(id: nil, name: "", score: 0)
|
||||
}
|
||||
|
||||
/// Creates a new player with random name and random score
|
||||
static func makeRandom() -> Player {
|
||||
Player(id: nil, name: randomName(), score: randomScore())
|
||||
}
|
||||
|
||||
/// Returns a random name
|
||||
static func randomName() -> String {
|
||||
names.randomElement()!
|
||||
}
|
||||
|
||||
/// Returns a random score
|
||||
static func randomScore() -> Int {
|
||||
10 * Int.random(in: 0...100)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
/// Make Player a Codable Record.
|
||||
///
|
||||
/// See <https://github.com/groue/GRDB.swift/blob/master/README.md#records>
|
||||
extension Player: Codable, FetchableRecord, MutablePersistableRecord {
|
||||
// Define database columns from CodingKeys
|
||||
fileprivate enum Columns {
|
||||
static let name = Column(CodingKeys.name)
|
||||
static let score = Column(CodingKeys.score)
|
||||
}
|
||||
|
||||
/// Updates a player id after it has been inserted in the database.
|
||||
mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
id = inserted.rowID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Player Database Requests
|
||||
|
||||
/// Define some player requests used by the application.
|
||||
///
|
||||
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices>
|
||||
extension DerivableRequest<Player> {
|
||||
/// A request of players ordered by name.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let players: [Player] = try dbWriter.read { db in
|
||||
/// try Player.all().orderedByName().fetchAll(db)
|
||||
/// }
|
||||
func orderedByName() -> Self {
|
||||
// Sort by name in a localized case insensitive fashion
|
||||
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
}
|
||||
|
||||
/// A request of players ordered by score.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let players: [Player] = try dbWriter.read { db in
|
||||
/// try Player.all().orderedByScore().fetchAll(db)
|
||||
/// }
|
||||
/// let bestPlayer: Player? = try dbWriter.read { db in
|
||||
/// try Player.all().orderedByScore().fetchOne(db)
|
||||
/// }
|
||||
func orderedByScore() -> Self {
|
||||
// Sort by descending score, and then by name, in a
|
||||
// localized case insensitive fashion
|
||||
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
|
||||
order(
|
||||
Player.Columns.score.desc,
|
||||
Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_20pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_29pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_40pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "icon_60pt@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_20pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_29pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_40pt@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_76pt@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "icon_83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LaunchIcon.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchIcon" translatesAutoresizingMaskIntoConstraints="NO" id="10v-qH-Qer">
|
||||
<rect key="frame" x="123.5" y="334.5" width="167" height="237"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="10v-qH-Qer" firstAttribute="centerX" secondItem="5Q0-b7-anu" secondAttribute="centerX" id="CgU-Ob-iCK"/>
|
||||
<constraint firstItem="10v-qH-Qer" firstAttribute="centerY" secondItem="5Q0-b7-anu" secondAttribute="centerY" id="l2h-yP-koC"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="5Q0-b7-anu"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchIcon" width="167" height="237"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,213 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="UAh-8N-CM8">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Player Edition View Controller-->
|
||||
<scene sceneID="Juk-Ad-tZ2">
|
||||
<objects>
|
||||
<tableViewController id="iNr-Oe-muq" customClass="PlayerEditionViewController" customModule="GRDBDemoiOS" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" dataMode="static" style="insetGrouped" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" id="vlV-lc-eQA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<sections>
|
||||
<tableViewSection headerTitle="Name" id="Nzq-eO-jT3">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="2gb-gB-918">
|
||||
<rect key="frame" x="20" y="32" width="374" height="40.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2gb-gB-918" id="iyM-TJ-JB4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="40.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalCompressionResistancePriority="250" contentHorizontalAlignment="left" contentVerticalAlignment="center" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="m5m-Zs-j0z">
|
||||
<rect key="frame" x="20" y="11" width="334" height="18.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words" autocorrectionType="no" spellCheckingType="no" returnKeyType="next"/>
|
||||
<connections>
|
||||
<action selector="textFieldDidChange:" destination="iNr-Oe-muq" eventType="editingChanged" id="EmC-Rp-f6S"/>
|
||||
<outlet property="delegate" destination="iNr-Oe-muq" id="K2p-7d-teD"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="m5m-Zs-j0z" firstAttribute="leading" secondItem="iyM-TJ-JB4" secondAttribute="leadingMargin" id="9cK-nj-70V"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="m5m-Zs-j0z" secondAttribute="trailing" id="JsE-8v-XAS"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="m5m-Zs-j0z" secondAttribute="bottom" id="KbI-Yl-Las"/>
|
||||
<constraint firstItem="m5m-Zs-j0z" firstAttribute="top" secondItem="iyM-TJ-JB4" secondAttribute="topMargin" id="v2S-en-21g"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="Score" id="Urb-uF-sj1">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" id="vp3-Ft-Gth">
|
||||
<rect key="frame" x="20" y="122" width="374" height="40.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vp3-Ft-Gth" id="ccO-0O-u72">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="40.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalCompressionResistancePriority="250" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="0" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="rZ9-Fa-cyr">
|
||||
<rect key="frame" x="20" y="11" width="334" height="18.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="numberPad" returnKeyType="done"/>
|
||||
<connections>
|
||||
<action selector="textFieldDidChange:" destination="iNr-Oe-muq" eventType="editingChanged" id="ebo-2z-Z9P"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="rZ9-Fa-cyr" secondAttribute="bottom" id="3Ya-Po-TQ3"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="rZ9-Fa-cyr" secondAttribute="trailing" id="Gso-FU-Ct1"/>
|
||||
<constraint firstItem="rZ9-Fa-cyr" firstAttribute="leading" secondItem="ccO-0O-u72" secondAttribute="leadingMargin" id="WrY-St-72v"/>
|
||||
<constraint firstItem="rZ9-Fa-cyr" firstAttribute="top" secondItem="ccO-0O-u72" secondAttribute="topMargin" id="mmE-f2-e9r"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="iNr-Oe-muq" id="67O-XE-MeV"/>
|
||||
<outlet property="delegate" destination="iNr-Oe-muq" id="vLD-QQ-zR2"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<toolbarItems/>
|
||||
<navigationItem key="navigationItem" id="U4g-xG-W8b">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="VIN-Hi-Twt">
|
||||
<connections>
|
||||
<segue destination="lTz-K0-aRY" kind="unwind" unwindAction="cancelPlayerEdition:" id="fOs-ol-qJL"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="2um-u5-BhV">
|
||||
<connections>
|
||||
<segue destination="lTz-K0-aRY" kind="unwind" identifier="Commit" unwindAction="commitPlayerEdition:" id="Y1E-dI-wcl"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="cancelButtonItem" destination="VIN-Hi-Twt" id="dET-mF-ZXe"/>
|
||||
<outlet property="nameCell" destination="2gb-gB-918" id="ch2-MB-uRm"/>
|
||||
<outlet property="nameTextField" destination="m5m-Zs-j0z" id="A6a-pw-q8s"/>
|
||||
<outlet property="saveButtonItem" destination="2um-u5-BhV" id="Jym-us-zdP"/>
|
||||
<outlet property="scoreCell" destination="vp3-Ft-Gth" id="b8A-42-8Dw"/>
|
||||
<outlet property="scoreTextField" destination="rZ9-Fa-cyr" id="4kw-gV-uih"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fNC-Ls-uRw" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
<exit id="lTz-K0-aRY" userLabel="Exit" sceneMemberID="exit"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1134" y="2891"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="2KX-IU-Z5Q">
|
||||
<objects>
|
||||
<navigationController id="ein-Sr-i8w" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="zIT-dB-Mfo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="iNr-Oe-muq" kind="relationship" relationship="rootViewController" destinationCreationSelector="makePlayerCreationViewController:" id="e5b-3U-9Vz"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kNr-vk-I5l" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1134" y="2071"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="N1X-4j-Re4">
|
||||
<objects>
|
||||
<navigationController id="UAh-8N-CM8" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" largeTitles="YES" id="U7d-1C-GnL">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="PJp-Eq-ZjA" kind="relationship" relationship="rootViewController" id="klT-ZP-gtX"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="sVt-e9-ghQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-459" y="2071"/>
|
||||
</scene>
|
||||
<!--Hall Of Fame-->
|
||||
<scene sceneID="9su-ms-vdJ">
|
||||
<objects>
|
||||
<tableViewController id="PJp-Eq-ZjA" customClass="PlayerListViewController" customModule="GRDBDemoiOS" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" id="tX0-ON-WSI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Player" textLabel="G8y-VE-YRu" detailTextLabel="NNc-J4-xiE" style="IBUITableViewCellStyleValue1" id="bFj-Hd-fwd">
|
||||
<rect key="frame" x="0.0" y="44.5" width="414" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="bFj-Hd-fwd" id="kDf-TZ-xhb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="384.5" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="G8y-VE-YRu">
|
||||
<rect key="frame" x="20" y="14" width="28.5" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="NNc-J4-xiE">
|
||||
<rect key="frame" x="339" y="14" width="37.5" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="iNr-Oe-muq" kind="show" identifier="" destinationCreationSelector="makePlayerEditionViewController:" id="Lgu-Yd-HSy"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="PJp-Eq-ZjA" id="MkQ-WQ-sTh"/>
|
||||
<outlet property="delegate" destination="PJp-Eq-ZjA" id="quj-Sp-DuL"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<toolbarItems>
|
||||
<barButtonItem title="Item" id="hMF-Rv-dAe"/>
|
||||
</toolbarItems>
|
||||
<navigationItem key="navigationItem" title="Hall Of Fame" id="QIv-M6-kT1">
|
||||
<barButtonItem key="leftBarButtonItem" systemItem="add" id="2bK-Fl-55Q">
|
||||
<connections>
|
||||
<segue destination="ein-Sr-i8w" kind="presentation" identifier="" id="ehP-ZN-Nt4"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<connections>
|
||||
<outlet property="newPlayerButtonItem" destination="2bK-Fl-55Q" id="O9m-8s-oRL"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="af5-Qo-AkD" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="348" y="2071"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="Lgu-Yd-HSy"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
<resources>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,40 @@
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
guard let _ = (scene as? UIWindowScene) else { return }
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import UIKit
|
||||
|
||||
class PlayerEditionViewController: UITableViewController {
|
||||
enum Mode {
|
||||
/// Edition ends with the "Commit" unwind segue.
|
||||
case creation
|
||||
|
||||
/// Edition ends when user hits the back button.
|
||||
case edition
|
||||
}
|
||||
|
||||
/// The edited player
|
||||
private(set) var player: Player
|
||||
|
||||
/// The presentation mode
|
||||
let mode: Mode
|
||||
|
||||
@IBOutlet private weak var cancelButtonItem: UIBarButtonItem!
|
||||
@IBOutlet private weak var saveButtonItem: UIBarButtonItem!
|
||||
@IBOutlet private weak var nameCell: UITableViewCell!
|
||||
@IBOutlet private weak var nameTextField: UITextField!
|
||||
@IBOutlet private weak var scoreCell: UITableViewCell!
|
||||
@IBOutlet private weak var scoreTextField: UITextField!
|
||||
|
||||
init?(_ coder: NSCoder, mode: Mode, player: Player) {
|
||||
self.mode = mode
|
||||
self.player = player
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
configureNavigationItem()
|
||||
configureForm()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
extension PlayerEditionViewController {
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
// Force keyboard to dismiss early
|
||||
view.endEditing(true)
|
||||
return true
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "Commit" {
|
||||
saveChanges()
|
||||
}
|
||||
}
|
||||
|
||||
override func willMove(toParent parent: UIViewController?) {
|
||||
super.willMove(toParent: parent)
|
||||
|
||||
if mode == .edition, parent == nil {
|
||||
// Self is popping from its navigation controller
|
||||
saveChanges()
|
||||
}
|
||||
}
|
||||
|
||||
private func configureNavigationItem() {
|
||||
switch mode {
|
||||
case .creation:
|
||||
navigationItem.title = "New Player"
|
||||
navigationItem.leftBarButtonItem = cancelButtonItem
|
||||
navigationItem.rightBarButtonItem = saveButtonItem
|
||||
case .edition:
|
||||
navigationItem.title = player.name
|
||||
navigationItem.leftBarButtonItem = nil
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Form
|
||||
|
||||
extension PlayerEditionViewController: UITextFieldDelegate {
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
nameTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: false)
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
if cell === nameCell {
|
||||
nameTextField.becomeFirstResponder()
|
||||
} else if cell === scoreCell {
|
||||
scoreTextField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField == nameTextField {
|
||||
scoreTextField.becomeFirstResponder()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@IBAction func textFieldDidChange(_ textField: UITextField) {
|
||||
// User has edited the player: prevent interactive dismissal
|
||||
isModalInPresentation = true
|
||||
}
|
||||
|
||||
private func configureForm() {
|
||||
nameTextField.text = player.name
|
||||
|
||||
if player.score == 0 && player.id == nil {
|
||||
scoreTextField.text = ""
|
||||
} else {
|
||||
scoreTextField.text = "\(player.score)"
|
||||
}
|
||||
}
|
||||
|
||||
private func saveChanges() {
|
||||
var player = self.player
|
||||
player.name = nameTextField.text ?? ""
|
||||
player.score = scoreTextField.text.flatMap { Int($0) } ?? 0
|
||||
try! AppDatabase.shared.savePlayer(&player)
|
||||
self.player = player
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import UIKit
|
||||
import GRDB
|
||||
|
||||
/// PlayerListViewController displays the list of players.
|
||||
class PlayerListViewController: UITableViewController {
|
||||
private enum PlayerOrdering {
|
||||
case byName
|
||||
case byScore
|
||||
}
|
||||
|
||||
@IBOutlet private weak var newPlayerButtonItem: UIBarButtonItem!
|
||||
private var dataSource: PlayerDataSource!
|
||||
private var playersCancellable: DatabaseCancellable?
|
||||
private var playerOrdering: PlayerOrdering = .byScore {
|
||||
didSet {
|
||||
configureOrderingBarButtonItem()
|
||||
observePlayers()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
configureToolbar()
|
||||
configureNavigationItem()
|
||||
configureDataSource()
|
||||
observePlayers()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.isToolbarHidden = false
|
||||
}
|
||||
|
||||
private func configureToolbar() {
|
||||
toolbarItems = [
|
||||
UIBarButtonItem(systemItem: .trash, primaryAction: UIAction { [unowned self] _ in
|
||||
setEditing(false, animated: true)
|
||||
try! AppDatabase.shared.deleteAllPlayers()
|
||||
}),
|
||||
UIBarButtonItem(systemItem: .flexibleSpace),
|
||||
UIBarButtonItem(systemItem: .refresh, primaryAction: UIAction { [unowned self] _ in
|
||||
setEditing(false, animated: true)
|
||||
try! AppDatabase.shared.refreshPlayers()
|
||||
}),
|
||||
UIBarButtonItem(systemItem: .flexibleSpace),
|
||||
UIBarButtonItem(image: UIImage(systemName: "tornado"), primaryAction: UIAction { [unowned self] _ in
|
||||
setEditing(false, animated: true)
|
||||
for _ in 0..<50 {
|
||||
DispatchQueue.global().async {
|
||||
try! AppDatabase.shared.refreshPlayers()
|
||||
}
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
private func configureNavigationItem() {
|
||||
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Players")
|
||||
navigationItem.leftBarButtonItems = [editButtonItem, newPlayerButtonItem]
|
||||
configureOrderingBarButtonItem()
|
||||
}
|
||||
|
||||
private func configureOrderingBarButtonItem() {
|
||||
switch playerOrdering {
|
||||
case .byScore:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: "Score ▼",
|
||||
primaryAction: UIAction { [unowned self] _ in
|
||||
setEditing(false, animated: true)
|
||||
playerOrdering = .byName
|
||||
})
|
||||
case .byName:
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
title: "Name ▲",
|
||||
primaryAction: UIAction { [unowned self] _ in
|
||||
setEditing(false, animated: true)
|
||||
playerOrdering = .byScore
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func configureDataSource() {
|
||||
dataSource = PlayerDataSource(tableView: tableView) { (tableView, indexPath, player) in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Player", for: indexPath)
|
||||
if player.name.isEmpty {
|
||||
cell.textLabel?.text = "(anonymous)"
|
||||
} else {
|
||||
cell.textLabel?.text = player.name
|
||||
}
|
||||
cell.detailTextLabel?.text = abs(player.score) > 1 ? "\(player.score) points" : "0 point"
|
||||
return cell
|
||||
}
|
||||
dataSource.defaultRowAnimation = .fade
|
||||
tableView.dataSource = dataSource
|
||||
}
|
||||
|
||||
private func configureTitle(from players: [Player]) {
|
||||
switch players.count {
|
||||
case 0:
|
||||
navigationItem.title = "No Player"
|
||||
case 1:
|
||||
navigationItem.title = "1 Player"
|
||||
case let count:
|
||||
navigationItem.title = "\(count) Players"
|
||||
}
|
||||
}
|
||||
|
||||
private func configureDataSource(from players: [Player]) {
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Int, Player>()
|
||||
snapshot.appendSections([0])
|
||||
snapshot.appendItems(players, toSection: 0)
|
||||
|
||||
// Remember selection
|
||||
let selectedPlayerId = tableView.indexPathForSelectedRow.flatMap {
|
||||
dataSource.itemIdentifier(for: $0)?.id
|
||||
}
|
||||
|
||||
// Avoid a UIKit warning; don't animate when popping from edition
|
||||
let animated = view.window != nil
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: animated, completion: {
|
||||
// Restore selection
|
||||
if let index = players.firstIndex(where: { $0.id == selectedPlayerId }) {
|
||||
self.tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func observePlayers() {
|
||||
let request: QueryInterfaceRequest<Player>
|
||||
switch playerOrdering {
|
||||
case .byName:
|
||||
request = Player.all().orderedByName()
|
||||
case .byScore:
|
||||
request = Player.all().orderedByScore()
|
||||
}
|
||||
|
||||
playersCancellable = ValueObservation
|
||||
.tracking(request.fetchAll(_:))
|
||||
.start(
|
||||
in: AppDatabase.shared.reader,
|
||||
// Immediate scheduling feeds the data source right on subscription,
|
||||
// and avoids an undesired animation when the application starts.
|
||||
scheduling: .immediate,
|
||||
onError: { error in fatalError("Unexpected error: \(error)") },
|
||||
onChange: { [weak self] players in
|
||||
guard let self else { return }
|
||||
self.configureTitle(from: players)
|
||||
self.configureDataSource(from: players)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Navigation
|
||||
|
||||
extension PlayerListViewController {
|
||||
@IBSegueAction func makePlayerEditionViewController(_ coder: NSCoder) -> PlayerEditionViewController? {
|
||||
guard let indexPath = tableView.indexPathForSelectedRow,
|
||||
let player = dataSource.itemIdentifier(for: indexPath)
|
||||
else { return nil }
|
||||
return PlayerEditionViewController(coder, mode: .edition, player: player)
|
||||
}
|
||||
|
||||
@IBSegueAction func makePlayerCreationViewController(_ coder: NSCoder) -> PlayerEditionViewController? {
|
||||
let player = Player(id: nil, name: "", score: 0)
|
||||
return PlayerEditionViewController(coder, mode: .creation, player: player)
|
||||
}
|
||||
|
||||
@IBAction func cancelPlayerEdition(_ segue: UIStoryboardSegue) {
|
||||
// Player creation cancelled
|
||||
}
|
||||
|
||||
@IBAction func commitPlayerEdition(_ segue: UIStoryboardSegue) {
|
||||
// Player creation committed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - UITableViewDataSource
|
||||
|
||||
/// Subclass of UITableViewDiffableDataSource that supports row deletion
|
||||
private class PlayerDataSource: UITableViewDiffableDataSource<Int, Player> {
|
||||
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
// Delete the player
|
||||
if let player = itemIdentifier(for: indexPath), let id = player.id {
|
||||
try! AppDatabase.shared.deletePlayers(ids: [id])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import XCTest
|
||||
import GRDB
|
||||
@testable import GRDBDemoiOS
|
||||
|
||||
class AppDatabaseTests: XCTestCase {
|
||||
func test_database_schema() throws {
|
||||
// Given an empty database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
|
||||
// When we instantiate an AppDatabase
|
||||
_ = try AppDatabase(dbQueue)
|
||||
|
||||
// Then the player table exists, with id, name & score columns
|
||||
try dbQueue.read { db in
|
||||
try XCTAssert(db.tableExists("player"))
|
||||
let columns = try db.columns(in: "player")
|
||||
let columnNames = Set(columns.map { $0.name })
|
||||
XCTAssertEqual(columnNames, ["id", "name", "score"])
|
||||
}
|
||||
}
|
||||
|
||||
func test_savePlayer_inserts() throws {
|
||||
// Given an empty players database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
|
||||
// When we save a new player
|
||||
var player = Player(id: nil, name: "Arthur", score: 100)
|
||||
try appDatabase.savePlayer(&player)
|
||||
|
||||
// Then the player exists in the database
|
||||
try XCTAssertTrue(dbQueue.read(player.exists))
|
||||
}
|
||||
|
||||
func test_savePlayer_updates() throws {
|
||||
// Given a players database that contains a player
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
var player = Player(id: nil, name: "Arthur", score: 100)
|
||||
try dbQueue.write { db in
|
||||
try player.insert(db)
|
||||
}
|
||||
|
||||
// When we modify and save the player
|
||||
player.name = "Barbara"
|
||||
player.score = 1000
|
||||
try appDatabase.savePlayer(&player)
|
||||
|
||||
// Then the player has been updated in the database
|
||||
let fetchedPlayer = try dbQueue.read { db in
|
||||
try XCTUnwrap(Player.fetchOne(db, key: player.id))
|
||||
}
|
||||
XCTAssertEqual(fetchedPlayer, player)
|
||||
}
|
||||
|
||||
func test_deletePlayers() throws {
|
||||
// Given a players database that contains four players
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
var player1 = Player(id: nil, name: "Arthur", score: 100)
|
||||
var player2 = Player(id: nil, name: "Barbara", score: 200)
|
||||
var player3 = Player(id: nil, name: "Craig", score: 150)
|
||||
var player4 = Player(id: nil, name: "David", score: 120)
|
||||
try dbQueue.write { db in
|
||||
try player1.insert(db)
|
||||
try player2.insert(db)
|
||||
try player3.insert(db)
|
||||
try player4.insert(db)
|
||||
}
|
||||
|
||||
// When we delete two players
|
||||
try appDatabase.deletePlayers(ids: [player1.id!, player3.id!])
|
||||
|
||||
// Then the deleted players no longer exist
|
||||
try dbQueue.read { db in
|
||||
try XCTAssertFalse(player1.exists(db))
|
||||
try XCTAssertFalse(player3.exists(db))
|
||||
}
|
||||
|
||||
// Then the database still contains two players
|
||||
try XCTAssertEqual(dbQueue.read(Player.fetchCount), 2)
|
||||
}
|
||||
|
||||
func test_deleteAllPlayers() throws {
|
||||
// Given a players database that contains players
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
var player1 = Player(id: nil, name: "Arthur", score: 100)
|
||||
var player2 = Player(id: nil, name: "Barbara", score: 200)
|
||||
var player3 = Player(id: nil, name: "Craig", score: 150)
|
||||
var player4 = Player(id: nil, name: "David", score: 120)
|
||||
try dbQueue.write { db in
|
||||
try player1.insert(db)
|
||||
try player2.insert(db)
|
||||
try player3.insert(db)
|
||||
try player4.insert(db)
|
||||
}
|
||||
|
||||
// When we delete all players
|
||||
try appDatabase.deleteAllPlayers()
|
||||
|
||||
// Then the database does not contain any player
|
||||
try XCTAssertEqual(dbQueue.read(Player.fetchCount), 0)
|
||||
}
|
||||
|
||||
func test_refreshPlayers_populates_an_empty_database() throws {
|
||||
// Given an empty players database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
|
||||
// When we refresh players
|
||||
try appDatabase.refreshPlayers()
|
||||
|
||||
// Then the database is not empty
|
||||
try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
|
||||
}
|
||||
|
||||
func test_createRandomPlayersIfEmpty_populates_an_empty_database() throws {
|
||||
// Given an empty players database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
|
||||
// When we create random players
|
||||
try appDatabase.createRandomPlayersIfEmpty()
|
||||
|
||||
// Then the database is not empty
|
||||
try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
|
||||
}
|
||||
|
||||
func test_createRandomPlayersIfEmpty_does_not_modify_a_non_empty_database() throws {
|
||||
// Given a players database that contains one player
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
let appDatabase = try AppDatabase(dbQueue)
|
||||
var player = Player(id: nil, name: "Arthur", score: 100)
|
||||
try dbQueue.write { db in
|
||||
try player.insert(db)
|
||||
}
|
||||
|
||||
// When we create random players
|
||||
try appDatabase.createRandomPlayersIfEmpty()
|
||||
|
||||
// Then the database still only contains the original player
|
||||
let players = try dbQueue.read(Player.fetchAll)
|
||||
XCTAssertEqual(players, [player])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,108 @@
|
||||
import XCTest
|
||||
import GRDB
|
||||
@testable import GRDBDemoiOS
|
||||
|
||||
class PlayerTests: XCTestCase {
|
||||
// MARK: - CRUD
|
||||
// Test that our Player type properly talks to GRDB.
|
||||
|
||||
func testInsert() throws {
|
||||
// Given an empty players database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
_ = try AppDatabase(dbQueue)
|
||||
|
||||
// When we insert a player
|
||||
var player = Player(id: nil, name: "Arthur", score: 100)
|
||||
try dbQueue.write { db in
|
||||
try player.insert(db)
|
||||
}
|
||||
|
||||
// Then the player gets a non-nil id
|
||||
XCTAssertNotNil(player.id)
|
||||
}
|
||||
|
||||
func testRoundtrip() throws {
|
||||
// Given an empty players database
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
_ = try AppDatabase(dbQueue)
|
||||
|
||||
// When we insert a player and fetch the player with the same id
|
||||
var insertedPlayer = Player(id: nil, name: "Arthur", score: 100)
|
||||
let fetchedPlayer: Player? = try dbQueue.write { db in
|
||||
try insertedPlayer.insert(db)
|
||||
return try Player.fetchOne(db, key: insertedPlayer.id)
|
||||
}
|
||||
|
||||
// Then the fetched player is equal to the inserted player
|
||||
XCTAssertEqual(insertedPlayer, fetchedPlayer)
|
||||
}
|
||||
|
||||
// MARK: - Requests
|
||||
// Test that requests defined on the Player type behave as expected.
|
||||
|
||||
func testOrderedByScore() throws {
|
||||
// Given a players database that contains players with distinct scores
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
_ = try AppDatabase(dbQueue)
|
||||
var player1 = Player(id: 1, name: "Arthur", score: 100)
|
||||
var player2 = Player(id: 2, name: "Barbara", score: 200)
|
||||
var player3 = Player(id: 3, name: "Craig", score: 150)
|
||||
var player4 = Player(id: 4, name: "David", score: 120)
|
||||
try dbQueue.write { db in
|
||||
try player1.insert(db)
|
||||
try player2.insert(db)
|
||||
try player3.insert(db)
|
||||
try player4.insert(db)
|
||||
}
|
||||
|
||||
// When we fetch players ordered by score
|
||||
let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
|
||||
|
||||
// Then fetched players are ordered by score descending
|
||||
XCTAssertEqual(players, [player2, player3, player4, player1])
|
||||
}
|
||||
|
||||
func testOrderedByScoreSortsIdenticalScoresByName() throws {
|
||||
// Given a players database that contains players with common scores
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
_ = try AppDatabase(dbQueue)
|
||||
var player1 = Player(id: 1, name: "Arthur", score: 100)
|
||||
var player2 = Player(id: 2, name: "Barbara", score: 200)
|
||||
var player3 = Player(id: 3, name: "Craig", score: 200)
|
||||
var player4 = Player(id: 4, name: "David", score: 200)
|
||||
try dbQueue.write { db in
|
||||
try player1.insert(db)
|
||||
try player2.insert(db)
|
||||
try player3.insert(db)
|
||||
try player4.insert(db)
|
||||
}
|
||||
|
||||
// When we fetch players ordered by score
|
||||
let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
|
||||
|
||||
// Then fetched players are ordered by score descending and by name
|
||||
XCTAssertEqual(players, [player2, player3, player4, player1])
|
||||
}
|
||||
|
||||
func testOrderedByName() throws {
|
||||
// Given a players database that contains players with distinct names
|
||||
let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
|
||||
_ = try AppDatabase(dbQueue)
|
||||
var player1 = Player(id: 1, name: "Arthur", score: 100)
|
||||
var player2 = Player(id: 2, name: "Barbara", score: 200)
|
||||
var player3 = Player(id: 3, name: "Craig", score: 150)
|
||||
var player4 = Player(id: 4, name: "David", score: 120)
|
||||
try dbQueue.write { db in
|
||||
try player1.insert(db)
|
||||
try player2.insert(db)
|
||||
try player3.insert(db)
|
||||
try player4.insert(db)
|
||||
}
|
||||
|
||||
// When we fetch players ordered by name
|
||||
let players = try dbQueue.read(Player.all().orderedByName().fetchAll)
|
||||
|
||||
// Then fetched players are ordered by name
|
||||
XCTAssertEqual(players, [player1, player2, player3, player4])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
UIKit Demo Application
|
||||
======================
|
||||
|
||||
<img align="right" src="https://github.com/groue/GRDB.swift/raw/master/Documentation/DemoApps/GRDBDemoiOS/Screenshot.png" width="50%">
|
||||
|
||||
**This demo application is a storyboard-based UIKit application.** For a demo application that uses Combine + SwiftUI, see [GRDBCombineDemo](../GRDBCombineDemo/README.md), and for Async/Await + SwiftUI, see [GRDBAsyncDemo](../GRDBAsyncDemo/README.md).
|
||||
|
||||
**Requirements**: iOS 15.0+ / Xcode 12+
|
||||
|
||||
> **Note**: This demo app is not a project template. Do not copy it as a starting point for your application. Instead, create a new project, choose a GRDB [installation method](../../../README.md#installation), and use the demo as an inspiration.
|
||||
|
||||
The topics covered in this demo are:
|
||||
|
||||
- How to setup a database in an iOS app.
|
||||
- How to define a simple [Codable Record](../../../README.md#codable-records).
|
||||
- How to track database changes and animate a table view with [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation).
|
||||
- How to apply the recommendations of [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices).
|
||||
|
||||
**Files of interest:**
|
||||
|
||||
- [AppDatabase.swift](GRDBDemoiOS/AppDatabase.swift)
|
||||
|
||||
`AppDatabase` is the type that grants database access. It uses [DatabaseMigrator](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasemigrator) in order to setup the database schema, and [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation) in order to let the application observe database changes.
|
||||
|
||||
- [Persistence.swift](GRDBDemoiOS/Persistence.swift)
|
||||
|
||||
This file defines the `AppDatabase` instance used by the application.
|
||||
|
||||
- [Player.swift](GRDBDemoiOS/Player.swift)
|
||||
|
||||
`Player` is a [Record](../../../README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](../../../README.md#codable-records). It defines the database requests used by the application.
|
||||
|
||||
- [PlayerListViewController.swift](GRDBDemoiOS/ViewControllers/PlayerListViewController.swift)
|
||||
|
||||
`PlayerListViewController` displays the list of players.
|
||||
|
||||
- [PlayerEditionViewController.swift](GRDBDemoiOS/ViewControllers/PlayerEditionViewController.swift)
|
||||
|
||||
`PlayerEditionViewController` can create or edit a player, and save it in the database.
|
||||
|
||||
- [GRDBDemoiOSTests](GRDBDemoiOSTests)
|
||||
|
||||
- Test the database schema
|
||||
- Test the `Player` record and its requests
|
||||
- Test the `AppDatabase` methods that let the app access the database.
|
||||
|
After Width: | Height: | Size: 887 KiB |