Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a1f84c4d3e
commit
6742e63105
@ -14,9 +14,12 @@ This app provides interactive demos for all LocalData storage options:
|
|||||||
| **Encrypted** | Encrypted logs (AES or ChaCha20) | Encrypted File System |
|
| **Encrypted** | Encrypted logs (AES or ChaCha20) | Encrypted File System |
|
||||||
| **Sync** | Platform availability & sync policies | Multiple |
|
| **Sync** | Platform availability & sync policies | Multiple |
|
||||||
|
|
||||||
|
The project also includes a watchOS companion app target for watch-specific demos.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- iOS 17.0+
|
- iOS 17.0+
|
||||||
|
- watchOS 10.0+ (companion app target)
|
||||||
- Xcode 15+
|
- Xcode 15+
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@ -38,6 +41,11 @@ SecureStorgageSample/
|
|||||||
├── FileSystemDemo.swift
|
├── FileSystemDemo.swift
|
||||||
├── EncryptedStorageDemo.swift
|
├── EncryptedStorageDemo.swift
|
||||||
└── PlatformSyncDemo.swift
|
└── PlatformSyncDemo.swift
|
||||||
|
SecureStorageSample Watch App/
|
||||||
|
├── SecureStorageSampleApp.swift
|
||||||
|
├── ContentView.swift
|
||||||
|
└── Services/
|
||||||
|
└── WatchConnectivityService.swift
|
||||||
```
|
```
|
||||||
|
|
||||||
## Storage Key Examples
|
## Storage Key Examples
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "watchos",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
45
SecureStorageSample Watch App/ContentView.swift
Normal file
45
SecureStorageSample Watch App/ContentView.swift
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// SecureStorageSample Watch App
|
||||||
|
//
|
||||||
|
// Created by Matt Bruce on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
@State private var connectivity = WatchConnectivityService.shared
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
if let profile = connectivity.profile {
|
||||||
|
Text(profile.name)
|
||||||
|
.bold()
|
||||||
|
Text(profile.email)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Text("Age: \(profile.ageDescription)")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Text(profile.createdAt, format: .dateTime)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
} else {
|
||||||
|
Text("No profile synced yet")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connectivity.statusMessage.isEmpty {
|
||||||
|
Text(connectivity.statusMessage)
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
15
SecureStorageSample Watch App/Models/UserProfile.swift
Normal file
15
SecureStorageSample Watch App/Models/UserProfile.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
nonisolated(unsafe)
|
||||||
|
struct UserProfile: Codable, Sendable {
|
||||||
|
static let storageKeyName = "user_profile.json"
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let email: String
|
||||||
|
let age: Int?
|
||||||
|
let createdAt: Date
|
||||||
|
|
||||||
|
var ageDescription: String {
|
||||||
|
age.map(String.init) ?? "n/a"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
SecureStorageSample Watch App/SecureStorageSampleApp.swift
Normal file
21
SecureStorageSample Watch App/SecureStorageSampleApp.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// SecureStorageSampleApp.swift
|
||||||
|
// SecureStorageSample Watch App
|
||||||
|
//
|
||||||
|
// Created by Matt Bruce on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct SecureStorageSample_Watch_AppApp: App {
|
||||||
|
init() {
|
||||||
|
_ = WatchConnectivityService.shared
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import Foundation
|
||||||
|
import Observation
|
||||||
|
import WatchConnectivity
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
final class WatchConnectivityService: NSObject, WCSessionDelegate {
|
||||||
|
static let shared = WatchConnectivityService()
|
||||||
|
|
||||||
|
private(set) var profile: UserProfile?
|
||||||
|
private(set) var statusMessage: String = ""
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
activateIfSupported()
|
||||||
|
loadCurrentContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func activateIfSupported() {
|
||||||
|
guard WCSession.isSupported() else {
|
||||||
|
statusMessage = "WatchConnectivity not supported"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadCurrentContext() {
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
updateProfile(from: WCSession.default.applicationContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateProfile(from context: [String: Any]) {
|
||||||
|
guard let data = context[UserProfile.storageKeyName] as? Data else {
|
||||||
|
statusMessage = "No profile received"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
profile = try JSONDecoder().decode(UserProfile.self, from: data)
|
||||||
|
statusMessage = "Profile synced"
|
||||||
|
} catch {
|
||||||
|
statusMessage = "Failed to decode profile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func session(
|
||||||
|
_ session: WCSession,
|
||||||
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
|
error: Error?
|
||||||
|
) {
|
||||||
|
loadCurrentContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
|
||||||
|
updateProfile(from: applicationContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// SecureStorageSample_Watch_AppTests.swift
|
||||||
|
// SecureStorageSample Watch AppTests
|
||||||
|
//
|
||||||
|
// Created by Matt Bruce on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Testing
|
||||||
|
@testable import SecureStorageSample_Watch_App
|
||||||
|
|
||||||
|
struct SecureStorageSample_Watch_AppTests {
|
||||||
|
|
||||||
|
@Test func example() async throws {
|
||||||
|
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// SecureStorageSample_Watch_AppUITests.swift
|
||||||
|
// SecureStorageSample Watch AppUITests
|
||||||
|
//
|
||||||
|
// Created by Matt Bruce on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class SecureStorageSample_Watch_AppUITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
|
||||||
|
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||||
|
continueAfterFailure = false
|
||||||
|
|
||||||
|
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testExample() throws {
|
||||||
|
// UI tests must launch the application that they test.
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testLaunchPerformance() throws {
|
||||||
|
// This measures how long it takes to launch your application.
|
||||||
|
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||||
|
XCUIApplication().launch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// SecureStorageSample_Watch_AppUITestsLaunchTests.swift
|
||||||
|
// SecureStorageSample Watch AppUITests
|
||||||
|
//
|
||||||
|
// Created by Matt Bruce on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class SecureStorageSample_Watch_AppUITestsLaunchTests: XCTestCase {
|
||||||
|
|
||||||
|
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
continueAfterFailure = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testLaunch() throws {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
|
||||||
|
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||||
|
// such as logging into a test account or navigating somewhere in the app
|
||||||
|
|
||||||
|
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||||
|
attachment.name = "Launch Screen"
|
||||||
|
attachment.lifetime = .keepAlways
|
||||||
|
add(attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
|
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
|
||||||
|
EA65D70D2F17DDEB00C48466 /* SecureStorageSample Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -25,12 +26,50 @@
|
|||||||
remoteGlobalIDString = EA179D002F1722BB00B1D54A;
|
remoteGlobalIDString = EA179D002F1722BB00B1D54A;
|
||||||
remoteInfo = SecureStorgageSample;
|
remoteInfo = SecureStorgageSample;
|
||||||
};
|
};
|
||||||
|
EA65D6F22F17DD6800C48466 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = EA179CF92F1722BB00B1D54A /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = EA65D6E42F17DD6700C48466;
|
||||||
|
remoteInfo = "SecureStorageSample Watch App";
|
||||||
|
};
|
||||||
|
EA65D6FC2F17DD6800C48466 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = EA179CF92F1722BB00B1D54A /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = EA65D6E42F17DD6700C48466;
|
||||||
|
remoteInfo = "SecureStorageSample Watch App";
|
||||||
|
};
|
||||||
|
EA65D70E2F17DDEB00C48466 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = EA179CF92F1722BB00B1D54A /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = EA65D6E42F17DD6700C48466;
|
||||||
|
remoteInfo = "SecureStorageSample Watch App";
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
EA179D312F1722BC00B1D54A /* Embed Watch Content */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||||
|
dstSubfolderSpec = 16;
|
||||||
|
files = (
|
||||||
|
EA65D70D2F17DDEB00C48466 /* SecureStorageSample Watch App.app in Embed Watch Content */,
|
||||||
|
);
|
||||||
|
name = "Embed Watch Content";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecureStorgageSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecureStorgageSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
EA179D0E2F1722BC00B1D54A /* SecureStorgageSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecureStorgageSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
EA179D0E2F1722BC00B1D54A /* SecureStorgageSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecureStorgageSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecureStorgageSampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecureStorgageSampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SecureStorageSample Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
EA65D6F12F17DD6800C48466 /* SecureStorageSample Watch AppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SecureStorageSample Watch AppTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
EA65D6FB2F17DD6800C48466 /* SecureStorageSample Watch AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SecureStorageSample Watch AppUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
@ -49,6 +88,21 @@
|
|||||||
path = SecureStorgageSampleUITests;
|
path = SecureStorgageSampleUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EA65D6E62F17DD6700C48466 /* SecureStorageSample Watch App */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = "SecureStorageSample Watch App";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EA65D6F42F17DD6800C48466 /* SecureStorageSample Watch AppTests */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = "SecureStorageSample Watch AppTests";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EA65D6FE2F17DD6800C48466 /* SecureStorageSample Watch AppUITests */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = "SecureStorageSample Watch AppUITests";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -74,6 +128,27 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
EA65D6E22F17DD6700C48466 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6EE2F17DD6800C48466 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6F82F17DD6800C48466 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
@ -83,6 +158,10 @@
|
|||||||
EA179D032F1722BB00B1D54A /* SecureStorgageSample */,
|
EA179D032F1722BB00B1D54A /* SecureStorgageSample */,
|
||||||
EA179D112F1722BC00B1D54A /* SecureStorgageSampleTests */,
|
EA179D112F1722BC00B1D54A /* SecureStorgageSampleTests */,
|
||||||
EA179D1B2F1722BC00B1D54A /* SecureStorgageSampleUITests */,
|
EA179D1B2F1722BC00B1D54A /* SecureStorgageSampleUITests */,
|
||||||
|
EA65D6E62F17DD6700C48466 /* SecureStorageSample Watch App */,
|
||||||
|
EA65D6F42F17DD6800C48466 /* SecureStorageSample Watch AppTests */,
|
||||||
|
EA65D6FE2F17DD6800C48466 /* SecureStorageSample Watch AppUITests */,
|
||||||
|
EA65D70C2F17DDEB00C48466 /* Frameworks */,
|
||||||
EA179D022F1722BB00B1D54A /* Products */,
|
EA179D022F1722BB00B1D54A /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -93,10 +172,20 @@
|
|||||||
EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */,
|
EA179D012F1722BB00B1D54A /* SecureStorgageSample.app */,
|
||||||
EA179D0E2F1722BC00B1D54A /* SecureStorgageSampleTests.xctest */,
|
EA179D0E2F1722BC00B1D54A /* SecureStorgageSampleTests.xctest */,
|
||||||
EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */,
|
EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */,
|
||||||
|
EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */,
|
||||||
|
EA65D6F12F17DD6800C48466 /* SecureStorageSample Watch AppTests.xctest */,
|
||||||
|
EA65D6FB2F17DD6800C48466 /* SecureStorageSample Watch AppUITests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EA65D70C2F17DDEB00C48466 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -107,10 +196,12 @@
|
|||||||
EA179CFD2F1722BB00B1D54A /* Sources */,
|
EA179CFD2F1722BB00B1D54A /* Sources */,
|
||||||
EA179CFE2F1722BB00B1D54A /* Frameworks */,
|
EA179CFE2F1722BB00B1D54A /* Frameworks */,
|
||||||
EA179CFF2F1722BB00B1D54A /* Resources */,
|
EA179CFF2F1722BB00B1D54A /* Resources */,
|
||||||
|
EA179D312F1722BC00B1D54A /* Embed Watch Content */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
EA65D70F2F17DDEB00C48466 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
EA179D032F1722BB00B1D54A /* SecureStorgageSample */,
|
EA179D032F1722BB00B1D54A /* SecureStorgageSample */,
|
||||||
@ -169,6 +260,74 @@
|
|||||||
productReference = EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */;
|
productReference = EA179D182F1722BC00B1D54A /* SecureStorgageSampleUITests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.ui-testing";
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
};
|
};
|
||||||
|
EA65D6E42F17DD6700C48466 /* SecureStorageSample Watch App */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = EA65D7032F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch App" */;
|
||||||
|
buildPhases = (
|
||||||
|
EA65D6E12F17DD6700C48466 /* Sources */,
|
||||||
|
EA65D6E22F17DD6700C48466 /* Frameworks */,
|
||||||
|
EA65D6E32F17DD6700C48466 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
EA65D6E62F17DD6700C48466 /* SecureStorageSample Watch App */,
|
||||||
|
);
|
||||||
|
name = "SecureStorageSample Watch App";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = "SecureStorageSample Watch App";
|
||||||
|
productReference = EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
EA65D6F02F17DD6800C48466 /* SecureStorageSample Watch AppTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = EA65D7062F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch AppTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
EA65D6ED2F17DD6800C48466 /* Sources */,
|
||||||
|
EA65D6EE2F17DD6800C48466 /* Frameworks */,
|
||||||
|
EA65D6EF2F17DD6800C48466 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
EA65D6F32F17DD6800C48466 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
EA65D6F42F17DD6800C48466 /* SecureStorageSample Watch AppTests */,
|
||||||
|
);
|
||||||
|
name = "SecureStorageSample Watch AppTests";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = "SecureStorageSample Watch AppTests";
|
||||||
|
productReference = EA65D6F12F17DD6800C48466 /* SecureStorageSample Watch AppTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
EA65D6FA2F17DD6800C48466 /* SecureStorageSample Watch AppUITests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = EA65D7092F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch AppUITests" */;
|
||||||
|
buildPhases = (
|
||||||
|
EA65D6F72F17DD6800C48466 /* Sources */,
|
||||||
|
EA65D6F82F17DD6800C48466 /* Frameworks */,
|
||||||
|
EA65D6F92F17DD6800C48466 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
EA65D6FD2F17DD6800C48466 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
EA65D6FE2F17DD6800C48466 /* SecureStorageSample Watch AppUITests */,
|
||||||
|
);
|
||||||
|
name = "SecureStorageSample Watch AppUITests";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = "SecureStorageSample Watch AppUITests";
|
||||||
|
productReference = EA65D6FB2F17DD6800C48466 /* SecureStorageSample Watch AppUITests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
@ -190,6 +349,17 @@
|
|||||||
CreatedOnToolsVersion = 26.0;
|
CreatedOnToolsVersion = 26.0;
|
||||||
TestTargetID = EA179D002F1722BB00B1D54A;
|
TestTargetID = EA179D002F1722BB00B1D54A;
|
||||||
};
|
};
|
||||||
|
EA65D6E42F17DD6700C48466 = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
};
|
||||||
|
EA65D6F02F17DD6800C48466 = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
TestTargetID = EA65D6E42F17DD6700C48466;
|
||||||
|
};
|
||||||
|
EA65D6FA2F17DD6800C48466 = {
|
||||||
|
CreatedOnToolsVersion = 26.0;
|
||||||
|
TestTargetID = EA65D6E42F17DD6700C48466;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = EA179CFC2F1722BB00B1D54A /* Build configuration list for PBXProject "SecureStorgageSample" */;
|
buildConfigurationList = EA179CFC2F1722BB00B1D54A /* Build configuration list for PBXProject "SecureStorgageSample" */;
|
||||||
@ -212,6 +382,9 @@
|
|||||||
EA179D002F1722BB00B1D54A /* SecureStorgageSample */,
|
EA179D002F1722BB00B1D54A /* SecureStorgageSample */,
|
||||||
EA179D0D2F1722BC00B1D54A /* SecureStorgageSampleTests */,
|
EA179D0D2F1722BC00B1D54A /* SecureStorgageSampleTests */,
|
||||||
EA179D172F1722BC00B1D54A /* SecureStorgageSampleUITests */,
|
EA179D172F1722BC00B1D54A /* SecureStorgageSampleUITests */,
|
||||||
|
EA65D6E42F17DD6700C48466 /* SecureStorageSample Watch App */,
|
||||||
|
EA65D6F02F17DD6800C48466 /* SecureStorageSample Watch AppTests */,
|
||||||
|
EA65D6FA2F17DD6800C48466 /* SecureStorageSample Watch AppUITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -238,6 +411,27 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
EA65D6E32F17DD6700C48466 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6EF2F17DD6800C48466 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6F92F17DD6800C48466 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@ -262,6 +456,27 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
EA65D6E12F17DD6700C48466 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6ED2F17DD6800C48466 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
EA65D6F72F17DD6800C48466 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
@ -275,6 +490,21 @@
|
|||||||
target = EA179D002F1722BB00B1D54A /* SecureStorgageSample */;
|
target = EA179D002F1722BB00B1D54A /* SecureStorgageSample */;
|
||||||
targetProxy = EA179D192F1722BC00B1D54A /* PBXContainerItemProxy */;
|
targetProxy = EA179D192F1722BC00B1D54A /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
EA65D6F32F17DD6800C48466 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = EA65D6E42F17DD6700C48466 /* SecureStorageSample Watch App */;
|
||||||
|
targetProxy = EA65D6F22F17DD6800C48466 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
EA65D6FD2F17DD6800C48466 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = EA65D6E42F17DD6700C48466 /* SecureStorageSample Watch App */;
|
||||||
|
targetProxy = EA65D6FC2F17DD6800C48466 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
EA65D70F2F17DDEB00C48466 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = EA65D6E42F17DD6700C48466 /* SecureStorageSample Watch App */;
|
||||||
|
targetProxy = EA65D70E2F17DDEB00C48466 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
@ -543,6 +773,162 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
EA65D7042F17DD6800C48466 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecureStorageSample;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.mbrucedogs.SecureStorgageSample;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.SecureStorgageSample.watchkitapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
EA65D7052F17DD6800C48466 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SecureStorageSample;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.mbrucedogs.SecureStorgageSample;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.mbrucedogs.SecureStorgageSample.watchkitapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
EA65D7072F17DD6800C48466 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mbrucedogs.SecureStorageSample-Watch-AppTests";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SecureStorageSample Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SecureStorageSample Watch App";
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
EA65D7082F17DD6800C48466 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mbrucedogs.SecureStorageSample-Watch-AppTests";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SecureStorageSample Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SecureStorageSample Watch App";
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
EA65D70A2F17DD6800C48466 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mbrucedogs.SecureStorageSample-Watch-AppUITests";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
TEST_TARGET_NAME = "SecureStorageSample Watch App";
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
EA65D70B2F17DD6800C48466 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = 6R7KLBPBLZ;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mbrucedogs.SecureStorageSample-Watch-AppUITests";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
TEST_TARGET_NAME = "SecureStorageSample Watch App";
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@ -582,6 +968,33 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
EA65D7032F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch App" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
EA65D7042F17DD6800C48466 /* Debug */,
|
||||||
|
EA65D7052F17DD6800C48466 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
EA65D7062F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch AppTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
EA65D7072F17DD6800C48466 /* Debug */,
|
||||||
|
EA65D7082F17DD6800C48466 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
EA65D7092F17DD6800C48466 /* Build configuration list for PBXNativeTarget "SecureStorageSample Watch AppUITests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
EA65D70A2F17DD6800C48466 /* Debug */,
|
||||||
|
EA65D70B2F17DD6800C48466 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCLocalSwiftPackageReference section */
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
|||||||
@ -4,10 +4,25 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>SchemeUserState</key>
|
<key>SchemeUserState</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>SecureStorageSample Watch App.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
</dict>
|
||||||
|
<key>SecureStorageWatch Watch App.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
<key>SecureStorgageSample.xcscheme_^#shared#^_</key>
|
<key>SecureStorgageSample.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>SecureStorgageSampleWatch.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>3</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
15
SecureStorgageSample/Models/UserProfile.swift
Normal file
15
SecureStorgageSample/Models/UserProfile.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
nonisolated(unsafe)
|
||||||
|
struct UserProfile: Codable, Sendable {
|
||||||
|
static let storageKeyName = "user_profile.json"
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let email: String
|
||||||
|
let age: Int?
|
||||||
|
let createdAt: Date
|
||||||
|
|
||||||
|
var ageDescription: String {
|
||||||
|
age.map(String.init) ?? "n/a"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,10 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct SecureStorgageSampleApp: App {
|
struct SecureStorgageSampleApp: App {
|
||||||
|
init() {
|
||||||
|
_ = WatchConnectivityService.shared
|
||||||
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
|||||||
35
SecureStorgageSample/Services/WatchConnectivityService.swift
Normal file
35
SecureStorgageSample/Services/WatchConnectivityService.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Foundation
|
||||||
|
import WatchConnectivity
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class WatchConnectivityService: NSObject, WCSessionDelegate {
|
||||||
|
static let shared = WatchConnectivityService()
|
||||||
|
|
||||||
|
private override init() {
|
||||||
|
super.init()
|
||||||
|
activateIfSupported()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func activateIfSupported() {
|
||||||
|
guard WCSession.isSupported() else { return }
|
||||||
|
let session = WCSession.default
|
||||||
|
session.delegate = self
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func session(
|
||||||
|
_ session: WCSession,
|
||||||
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||||
|
error: Error?
|
||||||
|
) {
|
||||||
|
// Intentionally empty: activation state is handled by WCSession.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionDidBecomeInactive(_ session: WCSession) {
|
||||||
|
// No-op; required for iOS WCSessionDelegate.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sessionDidDeactivate(_ session: WCSession) {
|
||||||
|
session.activate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -123,15 +123,19 @@ extension StorageKeys {
|
|||||||
|
|
||||||
/// Stores user profile as JSON file in documents.
|
/// Stores user profile as JSON file in documents.
|
||||||
struct UserProfileFileKey: StorageKey {
|
struct UserProfileFileKey: StorageKey {
|
||||||
typealias Value = [String: AnyCodable]
|
typealias Value = UserProfile
|
||||||
|
|
||||||
let name = "user_profile.json"
|
let name = UserProfile.storageKeyName
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
let domain: StorageDomain
|
||||||
let security: SecurityPolicy = .none
|
let security: SecurityPolicy = .none
|
||||||
let serializer: Serializer<[String: AnyCodable]> = .json
|
let serializer: Serializer<UserProfile> = .json
|
||||||
let owner = "SampleApp"
|
let owner = "SampleApp"
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
let availability: PlatformAvailability = .phoneWithWatchSync
|
||||||
let syncPolicy: SyncPolicy = .never
|
let syncPolicy: SyncPolicy = .automaticSmall
|
||||||
|
|
||||||
|
init(directory: FileDirectory = .documents) {
|
||||||
|
self.domain = .fileSystem(directory: directory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores cached data files.
|
/// Stores cached data files.
|
||||||
@ -264,3 +268,4 @@ extension StorageKeys {
|
|||||||
let syncPolicy: SyncPolicy = .never
|
let syncPolicy: SyncPolicy = .never
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ struct FileSystemDemo: View {
|
|||||||
@State private var profileName = ""
|
@State private var profileName = ""
|
||||||
@State private var profileEmail = ""
|
@State private var profileEmail = ""
|
||||||
@State private var profileAge = ""
|
@State private var profileAge = ""
|
||||||
@State private var storedProfile: [String: AnyCodable]?
|
@State private var storedProfile: UserProfile?
|
||||||
@State private var statusMessage = ""
|
@State private var statusMessage = ""
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var selectedDirectory: FileDirectory = .documents
|
@State private var selectedDirectory: FileDirectory = .documents
|
||||||
@ -81,9 +81,10 @@ struct FileSystemDemo: View {
|
|||||||
|
|
||||||
if let profile = storedProfile {
|
if let profile = storedProfile {
|
||||||
Section("Retrieved Profile") {
|
Section("Retrieved Profile") {
|
||||||
ForEach(Array(profile.keys.sorted()), id: \.self) { key in
|
LabeledContent("Name", value: profile.name)
|
||||||
LabeledContent(key.capitalized, value: String(describing: profile[key]?.value ?? "nil"))
|
LabeledContent("Email", value: profile.email)
|
||||||
}
|
LabeledContent("Age", value: profile.ageDescription)
|
||||||
|
LabeledContent("Created", value: profile.createdAt.formatted(date: .abbreviated, time: .shortened))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ struct FileSystemDemo: View {
|
|||||||
LabeledContent("Domain", value: "File System")
|
LabeledContent("Domain", value: "File System")
|
||||||
LabeledContent("Security", value: "None")
|
LabeledContent("Security", value: "None")
|
||||||
LabeledContent("Serializer", value: "JSON")
|
LabeledContent("Serializer", value: "JSON")
|
||||||
LabeledContent("Platform", value: "Phone Only")
|
LabeledContent("Platform", value: "Phone + Watch Sync")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("File System")
|
.navigationTitle("File System")
|
||||||
@ -118,13 +119,13 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey()
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
||||||
let profile: [String: AnyCodable] = [
|
let profile = UserProfile(
|
||||||
"name": AnyCodable(profileName),
|
name: profileName,
|
||||||
"email": AnyCodable(profileEmail),
|
email: profileEmail,
|
||||||
"age": AnyCodable(Int(profileAge) ?? 0),
|
age: Int(profileAge),
|
||||||
"createdAt": AnyCodable(Date().ISO8601Format())
|
createdAt: Date()
|
||||||
]
|
)
|
||||||
try await StorageRouter.shared.set(profile, for: key)
|
try await StorageRouter.shared.set(profile, for: key)
|
||||||
statusMessage = "✓ Saved to \(selectedDirectory == .documents ? "Documents" : "Caches")"
|
statusMessage = "✓ Saved to \(selectedDirectory == .documents ? "Documents" : "Caches")"
|
||||||
} catch {
|
} catch {
|
||||||
@ -138,7 +139,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey()
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
||||||
storedProfile = try await StorageRouter.shared.get(key)
|
storedProfile = try await StorageRouter.shared.get(key)
|
||||||
statusMessage = "✓ Loaded from file system"
|
statusMessage = "✓ Loaded from file system"
|
||||||
} catch StorageError.notFound {
|
} catch StorageError.notFound {
|
||||||
@ -155,7 +156,7 @@ struct FileSystemDemo: View {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let key = StorageKeys.UserProfileFileKey()
|
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
|
||||||
try await StorageRouter.shared.remove(key)
|
try await StorageRouter.shared.remove(key)
|
||||||
storedProfile = nil
|
storedProfile = nil
|
||||||
statusMessage = "✓ File deleted"
|
statusMessage = "✓ File deleted"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user