diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23d77f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.xccheckout +*.xcuserstate +*.xcscmblueprint +**/xcuserdata/* +.DS_Store + diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..37fbace --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,6 @@ +disabled_rules: + - line_length + - cyclomatic_complexity + - multiple_closures_with_trailing_closure + - function_body_length + - file_length diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6090c2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Nick Romano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d00285 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# ⌚️WatchSync + +[![CocoaPods](https://img.shields.io/cocoapods/v/WatchSync.svg)](http://cocoadocs.org/docsets/WatchSync/) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +WatchConnectivity wrapper with typed messages, better error handling, and simplified subscription APIs. + +## Example + +### Send messages + +Create a new message type that conforms to the `SyncableMessage` protocol. Uses `Codable` under the hood. + +```swift +import WatchSync + +struct MyMessage: SyncableMessage { + var myString: String? + var myDate: Date? +} +``` + +Send the message from anywhere in the iOS or watchOS app. + +```swift +let myMessage = MyMessage(myString: "Test", myDate: Date()) + +WatchSync.shared.sendMessage(myMessage) { result in +} +``` + +You can also send a simple dictionary as well. + +```swift +WatchSync.shared.sendMessage(["test": "message"]) { result in +} +``` + +### Subscribe to new messages + +Listen for changes from the paired device (iOS or watchOS) + +```swift +class ViewController: UIViewController { + var subscriptionToken: SubscriptionToken? + + override func viewDidLoad() { + super.viewDidLoad() + + subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in + print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) + } + } +} +``` + +### Update application context + +```swift +WatchSync.shared.update(applicationContext: ["test": "context"]) { result in +} +``` + +### Subscribe to application context updates + +```swift +appDelegateObserver = + +class ViewController: UIViewController { + var subscriptionToken: SubscriptionToken? + + override func viewDidLoad() { + super.viewDidLoad() + + subscriptionToken = WatchSync.shared.subscribeToApplicationContext { applicationContext in + print(applicationContext) + } + } +} +``` + +## How it works + +* If the paired device is reachable, `WatchSync` will try to send using an interactive message with `session.sendMessage()`. +* If the paired device is unreachable, it will fall back to using `sendUserInfo()` instead. +* All messages conforming to `SyncableMessage` will be JSON serialized to reduce the size of the payload. This is to reduce the likelyhood of running into a `WCErrorCodePayloadTooLarge` error. +* For interactive messages it uses the `replyHandler` for delivery acknowledgments. + +## Installation & Setup + +In your `AppDelegate` (iOS) and `ExtensionDelegate` (watchOS) under `applicationDidFinishLaunching` you will need to activate the Watch Connectivity session. + +```swift +WatchSync.shared.activateSession { error in + if let error = error { + print("Error activating session \(error.localizedDescription)") + return + } + print("Activated") +} +``` + +## Error handling + +The `sendMessage` method returns a closure with a result to switch on that reduces the number of possible states and [errors](https://developer.apple.com/documentation/watchconnectivity/wcerror) your app can end up in. + +```swift +WatchSync.shared.sendMessage(myMessage) { result in + switch result { + case .failure(let failure): + switch failure { + case .sessionNotActivated: + break + case .watchConnectivityNotAvailable: + break + case .unableToSerializeMessageAsJSON(let error): + break + case .watchAppNotPaired: + break + case .watchAppNotInstalled: + break + case .unhandledError(let error): + break + case .badPayloadError(let error): + break + case failedToDeliver(let error): + break + } + case .sent: + break + case .delivered: + break + } +} +``` diff --git a/Sources/Info-iOS.plist b/Sources/Info-iOS.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/Sources/Info-iOS.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Sources/Info-iOSTests.plist b/Sources/Info-iOSTests.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/Sources/Info-iOSTests.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Sources/Info-watchOS.plist b/Sources/Info-watchOS.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/Sources/Info-watchOS.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Sources/WatchSync.h b/Sources/WatchSync.h new file mode 100644 index 0000000..fedd09c --- /dev/null +++ b/Sources/WatchSync.h @@ -0,0 +1,18 @@ +// +// WatchSync.h +// WatchSync +// +// Created by Nicholas Romano on 3/15/18. +// + +#import + +//! Project version number for WatchSync. +FOUNDATION_EXPORT double WatchSyncVersionNumber; + +//! Project version string for WatchSync. +FOUNDATION_EXPORT const unsigned char WatchSyncVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Sources/WatchSync/Extensions/Codable+JSON.swift b/Sources/WatchSync/Extensions/Codable+JSON.swift new file mode 100644 index 0000000..e721571 --- /dev/null +++ b/Sources/WatchSync/Extensions/Codable+JSON.swift @@ -0,0 +1,24 @@ +// +// Codable+JSON.swift +// WatchConnectivityExample +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import Foundation + +extension Encodable { + func toJSONString() throws -> String { + let encoder = JSONEncoder() + let data = try encoder.encode(self) + return String(data: data, encoding: .utf8)! + } +} + +extension Decodable { + static func fromJSONString(_ string: String) throws -> Self { + let decoder = JSONDecoder() + return try decoder.decode(Self.self, from: string.data(using: .utf8)!) + } +} diff --git a/Sources/WatchSync/Results/FileTransferResult.swift b/Sources/WatchSync/Results/FileTransferResult.swift new file mode 100644 index 0000000..5e385bd --- /dev/null +++ b/Sources/WatchSync/Results/FileTransferResult.swift @@ -0,0 +1,29 @@ +// +// FileTransferResult.swift +// WatchSync iOS +// +// Created by Nick Romano on 3/26/18. +// + +import Foundation + +public typealias FileTransferCallback = (FileTransferResult) -> Void + +public enum FileTransferFailure { + /// `WatchSync.shared.activateSession()` must finish before transfering files + case sessionNotActivated + case watchConnectivityNotAvailable + + #if os(iOS) + case watchAppNotPaired + case watchAppNotInstalled + #endif + + case failedToSend(Error) +} + +public enum FileTransferResult { + case failure(FileTransferFailure) + case sent + case delivered +} diff --git a/Sources/WatchSync/Results/SendResult.swift b/Sources/WatchSync/Results/SendResult.swift new file mode 100644 index 0000000..ddb91a0 --- /dev/null +++ b/Sources/WatchSync/Results/SendResult.swift @@ -0,0 +1,36 @@ +// +// SendResult.swift +// WatchSync +// +// Created by Nicholas Romano on 3/15/18. +// + +import Foundation + +public typealias SendResultCallback = (SendResult) -> Void + +public enum SendResultFailure { + /// `WatchSync.shared.activateSession()` must finish before sending messages + case sessionNotActivated + case watchConnectivityNotAvailable + /// The `WatchSyncable` message could not be encoded as JSON + case unableToSerializeMessageAsJSON(Error) + + #if os(iOS) + case watchAppNotPaired + case watchAppNotInstalled + #endif + + /// Can be timeouts or general connectivity failures, could retry + case failedToDeliver(Error) + + case unhandledError(Error) + case badPayloadError(Error) +} + +/// Return codes for sending a message +public enum SendResult { + case failure(SendResultFailure) + case sent + case delivered +} diff --git a/Sources/WatchSync/Results/UpdateContextResult.swift b/Sources/WatchSync/Results/UpdateContextResult.swift new file mode 100644 index 0000000..e3b2de9 --- /dev/null +++ b/Sources/WatchSync/Results/UpdateContextResult.swift @@ -0,0 +1,29 @@ +// +// UpdateContextResult.swift +// WatchSync iOS +// +// Created by Nick Romano on 3/21/18. +// + +import Foundation + +public typealias UpdateContextCallback = (UpdateContextResult) -> Void + +public enum UpdateContextFailure { + /// `WatchSync.shared.activateSession()` must finish before updating application context + case sessionNotActivated + case watchConnectivityNotAvailable + + #if os(iOS) + case watchAppNotPaired + case watchAppNotInstalled + #endif + + case unhandledError(Error) + case badPayloadError(Error) +} + +public enum UpdateContextResult { + case failure(UpdateContextFailure) + case success +} diff --git a/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift b/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift new file mode 100644 index 0000000..a84c75e --- /dev/null +++ b/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift @@ -0,0 +1,26 @@ +// +// ApplicationContextSubscription.swift +// WatchSync iOS +// +// Created by Nick Romano on 3/21/18. +// + +import Foundation + +public typealias ApplicationContextListener = ([String: Any]) -> Void + +class ApplicationContextSubscription { + private var callback: ApplicationContextListener? + private var dispatchQueue: DispatchQueue + + func callCallback(_ message: [String: Any]) { + dispatchQueue.async { [weak self] in + self?.callback?(message) + } + } + + init(callback: ApplicationContextListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } +} diff --git a/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift b/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift new file mode 100644 index 0000000..784f3a6 --- /dev/null +++ b/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift @@ -0,0 +1,27 @@ +// +// FileTransferSubscription.swift +// WatchSync iOS +// +// Created by Nick Romano on 3/26/18. +// + +import Foundation +import WatchConnectivity + +public typealias FileTransferListener = (WCSessionFile) -> Void + +class FileTransferSubscription { + private var callback: FileTransferListener? + private var dispatchQueue: DispatchQueue + + func callCallback(_ message: WCSessionFile) { + dispatchQueue.async { [weak self] in + self?.callback?(message) + } + } + + init(callback: FileTransferListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } +} diff --git a/Sources/WatchSync/Subscriptions/MessageSubscription.swift b/Sources/WatchSync/Subscriptions/MessageSubscription.swift new file mode 100644 index 0000000..543c7d9 --- /dev/null +++ b/Sources/WatchSync/Subscriptions/MessageSubscription.swift @@ -0,0 +1,26 @@ +// +// MessageSubscription.swift +// WatchSync iOS +// +// Created by Nicholas Romano on 3/15/18. +// + +import Foundation + +public typealias MessageListener = ([String: Any]) -> Void + +class MessageSubscription { + private var callback: MessageListener? + private var dispatchQueue: DispatchQueue + + func callCallback(_ message: [String: Any]) { + dispatchQueue.async { [weak self] in + self?.callback?(message) + } + } + + init(callback: MessageListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } +} diff --git a/Sources/WatchSync/Subscriptions/SubscriptionToken.swift b/Sources/WatchSync/Subscriptions/SubscriptionToken.swift new file mode 100644 index 0000000..21ee64f --- /dev/null +++ b/Sources/WatchSync/Subscriptions/SubscriptionToken.swift @@ -0,0 +1,16 @@ +// +// SubscriptionToken.swift +// WatchSync iOS +// +// Created by Nicholas Romano on 3/15/18. +// + +import Foundation + +/// Keep a strong reference to this when you want to continue receiving messages +public class SubscriptionToken { + private var object: Any? + init(object: Any) { + self.object = object + } +} diff --git a/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift b/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift new file mode 100644 index 0000000..ebf65d8 --- /dev/null +++ b/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift @@ -0,0 +1,34 @@ +// +// SyncableMessageSubscription.swift +// WatchSync iOS +// +// Created by Nicholas Romano on 3/15/18. +// + +import Foundation + +public typealias SyncableMessageListener = (T) -> Void + +protocol SubscriptionCallable: class { + func callCallback(_ message: SyncableMessage) +} + +class SyncableMessageSunscription: SubscriptionCallable { + private var callback: SyncableMessageListener? + private var dispatchQueue: DispatchQueue + + func callCallback(_ message: SyncableMessage) { + guard let message = message as? T else { + // Drop message of other types + return + } + dispatchQueue.async { [weak self] in + self?.callback?(message) + } + } + + init(callback: SyncableMessageListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } +} diff --git a/Sources/WatchSync/SyncableMessage.swift b/Sources/WatchSync/SyncableMessage.swift new file mode 100644 index 0000000..f4b53cc --- /dev/null +++ b/Sources/WatchSync/SyncableMessage.swift @@ -0,0 +1,28 @@ +// +// WatchMessages.swift +// WatchConnectivityExample +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import Foundation + +/** + Protocol for creating typed Watch/Phone messages + + To reduce the size of each message in transit, the object is JSON encoded. + This is to help prevent running into the `WCErrorCodePayloadTooLarge` error. + + Unless your message schema will never change, I recommend making all fields + optional. Otherwise Decodable will throw this error when new fields are + added: `"No value associated with key"` from old message data. + */ +public protocol SyncableMessage: Codable { +} + +extension SyncableMessage { + static var messageKey: String { + return String(describing: self) + } +} diff --git a/Sources/WatchSync/WatchSync.swift b/Sources/WatchSync/WatchSync.swift new file mode 100644 index 0000000..58529a9 --- /dev/null +++ b/Sources/WatchSync/WatchSync.swift @@ -0,0 +1,484 @@ +// +// WatchSync.swift +// WatchConnectivityExample +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import Foundation +import WatchConnectivity + +public struct CouldNotActivateError: Error { +} + +/// Singleton to manage phone and watch communication +open class WatchSync: NSObject { + public static let shared = WatchSync() + + public let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil + + private var activationCallback: ((Error?) -> Void)? + + /// Loop through these when processing an incoming message + /// + /// I would prefer to use a Set here but not sure not sure how + /// to implement the `Hashable` protocol on metatype `Type` + private var registeredMessageTypes: [SyncableMessage.Type] = [] + + /// Weak references to subscriptions for `SyncableMessage` messages + private var syncableMessageSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for `[String: Any]` messages + private var messageSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for applicationContext + private var applicationContextSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for file transfers + private var fileTransferSubscriptions = NSPointerArray.weakObjects() + + /// Store callbacks until we receive a response from didFinish userInfoTransfer + private var userInfoCallbacks: [WCSessionUserInfoTransfer: SendResultCallback?] = [:] + + /// Store callbacks until we receive a response from didFinish fileTransfer + private var fileTransferCallbacks: [WCSessionFileTransfer: FileTransferCallback?] = [:] + + /// Called when launching the app for the first time to setup Watch Connectivity + /// + /// - Parameter activationCallback: Closure called when activation has finished. + public func activateSession(activationCallback: @escaping (Error?) -> Void) { + self.activationCallback = activationCallback + session?.delegate = self + session?.activate() + } + + /// Observe messages of Type (Recommended) + /// + /// - Parameters: + /// - ofType: Message that conforms to `WatchSyncable` protocol + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving a message + /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages + public func subscribeToMessages(ofType: T.Type, on queue: DispatchQueue = DispatchQueue.main, callback: @escaping SyncableMessageListener) -> SubscriptionToken { + let subscription = SyncableMessageSunscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(subscription).toOpaque() + syncableMessageSubscriptions.addPointer(pointer) + + if !registeredMessageTypes.contains(where: { watchSyncableType -> Bool in + if watchSyncableType == T.self { + return true + } + return false + }) { + registeredMessageTypes.append(ofType) + } + + return SubscriptionToken(object: subscription) + } + + /// Observe messages for all data that is not a `WatchSyncable` message + /// + /// - Parameters: + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving a message + /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages + public func subscribeToMessages(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping MessageListener) -> SubscriptionToken { + let rawSubscription = MessageSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + messageSubscriptions.addPointer(pointer) + + return SubscriptionToken(object: rawSubscription) + } + + /// Observer application context, also called immediately with the most recently received context + /// + /// - Parameters: + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving an application context + /// - Returns: `SubscriptionToken` store this for as long as you would like to application contexts + public func subscribeToApplicationContext(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping ApplicationContextListener) -> SubscriptionToken { + let rawSubscription = ApplicationContextSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + applicationContextSubscriptions.addPointer(pointer) + + // Call immediately on the most recently received app context + if let session = session { + callback(session.receivedApplicationContext) + } + + return SubscriptionToken(object: rawSubscription) + } + + public func subscribeToFileTransfers(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping FileTransferListener) -> SubscriptionToken { + let rawSubscription = FileTransferSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + fileTransferSubscriptions.addPointer(pointer) + + return SubscriptionToken(object: rawSubscription) + } + + /// Send a `WatchSyncable` message to the paired device (Recommended) + /// + /// The data is JSON serialized to reduce the size of the payload. + /// If the device is reachable it will send it in realtime. + /// If the device is not reachable, it will store it in a queue to be received later. + /// + /// ``` + /// WatchSync.shared.sendMessage(myMessage) { result in + /// // switch on result + /// } + /// ``` + /// + /// - Parameters: + /// - message: object that conforms to `WatchSyncable` + /// - completion: Closure that provides a `SendResult` describing the status of the message + public func sendMessage(_ message: SyncableMessage, completion: SendResultCallback?) { + // Package message for sending + let messageJSON: String + do { + messageJSON = try message.toJSONString() + } catch let error { + completion?(.failure(.unableToSerializeMessageAsJSON(error))) + return + } + let data: [String: String] = [ + type(of: message).messageKey: messageJSON + ] + sendMessage(data, completion: completion) + } + + private func transferUserInfo(_ message: [String: Any], in session: WCSession, completion: SendResultCallback?) { + let transfer = session.transferUserInfo(message) + userInfoCallbacks[transfer] = completion + completion?(.sent) + } + + /// Send a dictionary message to the paired device + /// + /// If the device is reachable it will send it in realtime. + /// If the device is not reachable, it will store it in a queue to be received later. + /// + /// ``` + /// WatchSync.shared.sendMessage(["test": "hello"]) { result in + /// // switch on result + /// } + /// ``` + /// + /// - Parameters: + /// - message: object that conforms to `WatchSyncable` + /// - completion: Closure that provides a `SendResult` describing the status of the message + public func sendMessage(_ message: [String: Any], completion: SendResultCallback?) { + guard let session = session else { + logMessage("Watch connectivity not available") + completion?(.failure(.watchConnectivityNotAvailable)) + return + } + guard session.activationState == .activated else { + logMessage("Session not activated") + completion?(.failure(.sessionNotActivated)) + return + } + + #if os(iOS) + guard session.isPaired else { + logMessage("Watch app not paired") + completion?(.failure(.watchAppNotPaired)) + return + } + guard session.isWatchAppInstalled else { + logMessage("Watch app not installed") + completion?(.failure(.watchAppNotInstalled)) + return + } + #endif + + guard session.isReachable else { + logMessage("Watch app not reachable, transfer user info") + + transferUserInfo(message, in: session, completion: completion) + return + } + + logMessage("Reachable, trying to send message") + session.sendMessage(message, replyHandler: { _ in + self.logMessage("Delivered realtime message successfully") + completion?(.delivered) + }) { error in + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return + } + + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, + .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable: + // Shouldn't reach this state since we handle these above + completion?(.failure(.unhandledError(watchError))) + + case .fileAccessDenied, .insufficientSpace: + // Only relevant for file transfers + completion?(.failure(.unhandledError(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + + case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: + // Retry sending in the background + self.logMessage("Error sending message: \(error.localizedDescription), transfering user info") + self.transferUserInfo(message, in: session, completion: completion) + } + } + } + + /// Update the application context on the paired device. + /// + /// The paired device doesn't need to be reachable but the session must be activated + /// + /// - Parameters: + /// - applicationContext: Dictionary for the paired device + /// - completion: Closure that provides a `UpdateContextResult` describing the status of the update + public func update(applicationContext: [String: Any], completion: UpdateContextCallback?) { + guard let session = session else { + logMessage("Watch connectivity not available") + completion?(.failure(.watchConnectivityNotAvailable)) + return + } + guard session.activationState == .activated else { + logMessage("Session not activated") + completion?(.failure(.sessionNotActivated)) + return + } + + #if os(iOS) + guard session.isPaired else { + logMessage("Watch app not paired") + completion?(.failure(.watchAppNotPaired)) + return + } + guard session.isWatchAppInstalled else { + logMessage("Watch app not installed") + completion?(.failure(.watchAppNotInstalled)) + return + } + #endif + + do { + try session.updateApplicationContext(applicationContext) + } catch let error { + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return + } + + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, + .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable: + // Shouldn't reach this state since we handle these above + completion?(.failure(.unhandledError(watchError))) + + case .fileAccessDenied, .insufficientSpace: + // Only relevant for file transfers + completion?(.failure(.unhandledError(watchError))) + + case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: + // Only relevant for messages and transfers + completion?(.failure(.unhandledError(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + } + } + } + + public func transferFile(file: URL, metadata: [String: Any]?, completion: FileTransferCallback?) { + guard let session = session else { + logMessage("Watch connectivity not available") + completion?(.failure(.watchConnectivityNotAvailable)) + return + } + guard session.activationState == .activated else { + logMessage("Session not activated") + completion?(.failure(.sessionNotActivated)) + return + } + let transfer = session.transferFile(file, metadata: metadata) + fileTransferCallbacks[transfer] = completion + completion?(.sent) + } + + /// Entrypoint for all received messages from the paired device. + /// + /// - Parameter message: Paired device message + private func receivedMessage(_ message: [String: Any]) { + var foundMessage = false + // Call all observers based on message types + for messageType in registeredMessageTypes { + guard let messageJSONString = message[messageType.messageKey] as? String else { + continue + } + + let watchSyncableMessage: SyncableMessage + do { + watchSyncableMessage = try messageType.fromJSONString(messageJSONString) + foundMessage = true + } catch let error { + logMessage("Unable to parse JSON \(error.localizedDescription)") + continue + } + + // Cleanup the subscriptions from time to time + syncableMessageSubscriptions.compact() + + syncableMessageSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? SubscriptionCallable else { + return + } + subscription.callCallback(watchSyncableMessage) + } + } + // If there are no message types found, just give the raw payload back + if !foundMessage { + messageSubscriptions.compact() + messageSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? MessageSubscription else { + return + } + subscription.callCallback(message) + } + } + } + + /// Override this function to log messages using your own tooling + open func logMessage(_ message: String) { + print(message) + } +} + +extension WatchSync: WCSessionDelegate { + // MARK: Watch Activation, multiple devices can be paired and swapped with the phone + public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + logMessage("Session completed activation: \(activationState)") + var error = error + if error == nil && activationState != .activated { + // We should hopefully never end up in this state, if activationState + // isn't activated there should be an error with reason from Apple + error = CouldNotActivateError() + } + activationCallback?(error) + } + + #if os(iOS) + public func sessionDidBecomeInactive(_ session: WCSession) { + logMessage("Session became inactive") + } + + // Apple recommends trying to reactivate if the session has switched between devices + public func sessionDidDeactivate(_ session: WCSession) { + logMessage("Session deactivated, trying to setup for new device") + session.activate() + } + #endif + + // MARK: Reachability + public func sessionReachabilityDidChange(_ session: WCSession) { + logMessage("Reachability changed: \(session.isReachable)") + } + + // MARK: Realtime messaging (must be reachable) + public func session(_ session: WCSession, didReceiveMessage message: [String: Any]) { + // All session delegate methods are called on a background thread. + receivedMessage(message) + } + + public func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { + // All session delegate methods are called on a background thread. + receivedMessage(message) + // Reply handler is always called so the other device get's a confirmation the message was delivered in realtime + replyHandler([:]) + } + + // MARK: FIFO messaging (queue's with delivery guarantees) + public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) { + // All session delegate methods are called on a background thread. + receivedMessage(userInfo) + } + + public func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) { + if let completion = userInfoCallbacks[userInfoTransfer] { + if let error = error { + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return + } + + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, + .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, + .messageReplyTimedOut, .messageReplyFailed, .fileAccessDenied, .insufficientSpace: + // Not applicable for transfers + completion?(.failure(.unhandledError(watchError))) + + case .deliveryFailed, .transferTimedOut: + completion?(.failure(.failedToDeliver(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + } + } else { + completion?(.delivered) + } + } + } + + /// Entrypoint for application contexts, use `subscribeToApplicationContext` to receive this data + public func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) { + applicationContextSubscriptions.compact() + applicationContextSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? ApplicationContextSubscription else { + return + } + subscription.callCallback(applicationContext) + } + } + + /// Entrypoint for received file transfers, use `subscribeToFileTransfer` to receive these + public func session(_ session: WCSession, didReceive file: WCSessionFile) { + fileTransferSubscriptions.compact() + fileTransferSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? FileTransferSubscription else { + return + } + subscription.callCallback(file) + } + } + + public func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) { + if let callback = fileTransferCallbacks[fileTransfer] { + if let error = error { + callback?(.failure(.failedToSend(error))) + } else { + callback?(.delivered) + } + } + } +} diff --git a/WatchSync Example/WatchSync Example WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json b/WatchSync Example/WatchSync Example WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..dd221ba --- /dev/null +++ b/WatchSync Example/WatchSync Example WatchKit App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} diff --git a/WatchSync Example/WatchSync Example WatchKit App/Base.lproj/Interface.storyboard b/WatchSync Example/WatchSync Example WatchKit App/Base.lproj/Interface.storyboard new file mode 100644 index 0000000..48ca0b6 --- /dev/null +++ b/WatchSync Example/WatchSync Example WatchKit App/Base.lproj/Interface.storyboard @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WatchSync Example/WatchSync Example/Info.plist b/WatchSync Example/WatchSync Example/Info.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/WatchSync Example/WatchSync Example/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/WatchSync Example/WatchSync Example/MyMessage.swift b/WatchSync Example/WatchSync Example/MyMessage.swift new file mode 100644 index 0000000..7d1cb7f --- /dev/null +++ b/WatchSync Example/WatchSync Example/MyMessage.swift @@ -0,0 +1,18 @@ +// +// MyMessage.swift +// WatchSync Example +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import Foundation +import WatchSync + +/** + Example message + */ +struct MyMessage: SyncableMessage { + var myString: String? + var myDate: Date? +} diff --git a/WatchSync Example/WatchSync Example/ViewController.swift b/WatchSync Example/WatchSync Example/ViewController.swift new file mode 100644 index 0000000..2aea93d --- /dev/null +++ b/WatchSync Example/WatchSync Example/ViewController.swift @@ -0,0 +1,61 @@ +// +// ViewController.swift +// WatchSync Example +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import UIKit +import WatchSync + +class ViewController: UIViewController { + + var subscriptionToken: SubscriptionToken? + + override func viewDidLoad() { + super.viewDidLoad() + + subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in + print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) + } + } + + @IBAction func sendMessageButtonPressed(_ sender: Any) { + let myMessage = MyMessage(myString: "Test", myDate: Date()) + + WatchSync.shared.sendMessage(myMessage) { result in + switch result { + case .failure(let failure): + switch failure { + case .sessionNotActivated: + break + case .watchConnectivityNotAvailable: + break + case .unableToSerializeMessageAsJSON(let error): + break + case .watchAppNotPaired: + break + case .watchAppNotInstalled: + break + case .unhandledError(let error): + break + case .badPayloadError(let error): + break + case .failedToDeliver(let error): + let alertController = UIAlertController(title: "✅", message: "Failed to Deliver \(error.localizedDescription)", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + } + case .sent: + let alertController = UIAlertController(title: "✅", message: "Sent!", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + case .delivered: + let alertController = UIAlertController(title: "✅", message: "Delivery Confirmed", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + } + } + } +} diff --git a/WatchSync Example/WatchSync ExampleTests/Info.plist b/WatchSync Example/WatchSync ExampleTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/WatchSync Example/WatchSync ExampleTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift b/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift new file mode 100644 index 0000000..9fb876d --- /dev/null +++ b/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift @@ -0,0 +1,34 @@ +// +// WatchSync_ExampleTests.swift +// WatchSync ExampleTests +// +// Created by Nicholas Romano on 3/15/18. +// Copyright © 2018 Ten Minute Wait. All rights reserved. +// + +import XCTest +@testable import WatchSync_Example + +class WatchSyncExampleTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } +} diff --git a/WatchSync iOSTests/WatchSync_iOSTests.swift b/WatchSync iOSTests/WatchSync_iOSTests.swift new file mode 100644 index 0000000..288bb48 --- /dev/null +++ b/WatchSync iOSTests/WatchSync_iOSTests.swift @@ -0,0 +1,21 @@ +// +// WatchSync_iOSTests.swift +// WatchSync iOSTests +// +// Created by Nicholas Romano on 3/15/18. +// + +import XCTest +@testable import WatchSync + +class WatchSyncTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } +} diff --git a/WatchSync.podspec b/WatchSync.podspec new file mode 100644 index 0000000..2efeb70 --- /dev/null +++ b/WatchSync.podspec @@ -0,0 +1,25 @@ +Pod::Spec.new do |s| + s.name = "WatchSync" + s.version = "1.0.0" + s.summary = "WatchConnectivity wrapper with typed messages, better error handling, and simplified subscription APIs." + + s.description = <<-DESC + Use WatchSync as the WatchConnectivity delegate for your application. It allows you to send typed messages (using `Codable`), + receive messages using closures anywhere in your application, and provides better error handling when connectivity isn't working. + DESC + + s.homepage = "https://github.com/nickromano/WatchSync" + s.license = { :type => "MIT", :file => "LICENSE" } + + s.author = { "Nick Romano" => "nick.r.romano@gmail.com" } + + s.ios.deployment_target = '9.3' + s.watchos.deployment_target = '3.0' + + s.source = { :git => "https://github.com/nickromano/WatchSync.git", :tag => "#{s.version}" } + + s.source_files = "Sources/**/*.{h,swift}" + s.swift_version = "4.1" + + s.framework = "WatchConnectivity" +end diff --git a/WatchSync.xcodeproj/project.pbxproj b/WatchSync.xcodeproj/project.pbxproj new file mode 100644 index 0000000..567d26c --- /dev/null +++ b/WatchSync.xcodeproj/project.pbxproj @@ -0,0 +1,914 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 741E19C32068D95300D8C82B /* FileTransferSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741E19C22068D95300D8C82B /* FileTransferSubscription.swift */; }; + 741E19C42068D95800D8C82B /* FileTransferSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741E19C22068D95300D8C82B /* FileTransferSubscription.swift */; }; + 741E19C62068DA8300D8C82B /* FileTransferResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741E19C52068DA8300D8C82B /* FileTransferResult.swift */; }; + 741E19C72068DA9C00D8C82B /* FileTransferResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741E19C52068DA8300D8C82B /* FileTransferResult.swift */; }; + 742E93B3206374AF00419EBC /* ApplicationContextSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742E93B2206374AF00419EBC /* ApplicationContextSubscription.swift */; }; + 742E93B5206379B300419EBC /* UpdateContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742E93B4206379B300419EBC /* UpdateContextResult.swift */; }; + 742E93B62063843700419EBC /* UpdateContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742E93B4206379B300419EBC /* UpdateContextResult.swift */; }; + 742E93B72063845900419EBC /* ApplicationContextSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742E93B2206374AF00419EBC /* ApplicationContextSubscription.swift */; }; + CDC29652205B645400305A82 /* WatchSync.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDC29648205B645400305A82 /* WatchSync.framework */; }; + CDC29657205B645400305A82 /* WatchSync_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC29656205B645400305A82 /* WatchSync_iOSTests.swift */; }; + CDC29680205B656B00305A82 /* WatchSync.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC2967F205B656B00305A82 /* WatchSync.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CDC29681205B656B00305A82 /* WatchSync.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC2967F205B656B00305A82 /* WatchSync.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CDC29688205B679000305A82 /* WatchSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC29687205B678F00305A82 /* WatchSync.swift */; }; + CDC29689205B679000305A82 /* WatchSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC29687205B678F00305A82 /* WatchSync.swift */; }; + CDC2968B205B679600305A82 /* SyncableMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC2968A205B679500305A82 /* SyncableMessage.swift */; }; + CDC2968C205B679600305A82 /* SyncableMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC2968A205B679500305A82 /* SyncableMessage.swift */; }; + CDC2968E205B679C00305A82 /* Codable+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC2968D205B679C00305A82 /* Codable+JSON.swift */; }; + CDC2968F205B679C00305A82 /* Codable+JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC2968D205B679C00305A82 /* Codable+JSON.swift */; }; + CDC29691205B680B00305A82 /* SendResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC29690205B680B00305A82 /* SendResult.swift */; }; + CDC29692205B680B00305A82 /* SendResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC29690205B680B00305A82 /* SendResult.swift */; }; + CDE70257205B777800C40127 /* SyncableMessageSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE70256205B777800C40127 /* SyncableMessageSubscription.swift */; }; + CDE70259205B77A700C40127 /* MessageSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE70258205B77A700C40127 /* MessageSubscription.swift */; }; + CDE7025B205B77C200C40127 /* SubscriptionToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE7025A205B77C200C40127 /* SubscriptionToken.swift */; }; + CDE7025C205B78C800C40127 /* SubscriptionToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE7025A205B77C200C40127 /* SubscriptionToken.swift */; }; + CDE7025D205B78CA00C40127 /* SyncableMessageSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE70256205B777800C40127 /* SyncableMessageSubscription.swift */; }; + CDE7025E205B78CD00C40127 /* MessageSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE70258205B77A700C40127 /* MessageSubscription.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + CDC29653205B645400305A82 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CDC2963D205B644A00305A82 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CDC29647205B645400305A82; + remoteInfo = "WatchSync iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 741E19C22068D95300D8C82B /* FileTransferSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferSubscription.swift; sourceTree = ""; }; + 741E19C52068DA8300D8C82B /* FileTransferResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTransferResult.swift; sourceTree = ""; }; + 742E93B2206374AF00419EBC /* ApplicationContextSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationContextSubscription.swift; sourceTree = ""; }; + 742E93B4206379B300419EBC /* UpdateContextResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateContextResult.swift; sourceTree = ""; }; + CDC29648205B645400305A82 /* WatchSync.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WatchSync.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CDC29651205B645400305A82 /* WatchSync iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WatchSync iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + CDC29656205B645400305A82 /* WatchSync_iOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSync_iOSTests.swift; sourceTree = ""; }; + CDC29672205B647E00305A82 /* WatchSync.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WatchSync.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CDC2967B205B64CB00305A82 /* Info-watchOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = ""; }; + CDC2967C205B64CB00305A82 /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + CDC2967F205B656B00305A82 /* WatchSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WatchSync.h; sourceTree = ""; }; + CDC29682205B657C00305A82 /* Info-iOSTests.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOSTests.plist"; sourceTree = ""; }; + CDC29687205B678F00305A82 /* WatchSync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchSync.swift; sourceTree = ""; }; + CDC2968A205B679500305A82 /* SyncableMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncableMessage.swift; sourceTree = ""; }; + CDC2968D205B679C00305A82 /* Codable+JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Codable+JSON.swift"; sourceTree = ""; }; + CDC29690205B680B00305A82 /* SendResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendResult.swift; sourceTree = ""; }; + CDE70256205B777800C40127 /* SyncableMessageSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncableMessageSubscription.swift; sourceTree = ""; }; + CDE70258205B77A700C40127 /* MessageSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSubscription.swift; sourceTree = ""; }; + CDE7025A205B77C200C40127 /* SubscriptionToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionToken.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CDC29644205B645400305A82 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2964E205B645400305A82 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC29652205B645400305A82 /* WatchSync.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2966E205B647E00305A82 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 740915E82069767A00F14BE3 /* Results */ = { + isa = PBXGroup; + children = ( + 741E19C52068DA8300D8C82B /* FileTransferResult.swift */, + CDC29690205B680B00305A82 /* SendResult.swift */, + 742E93B4206379B300419EBC /* UpdateContextResult.swift */, + ); + path = Results; + sourceTree = ""; + }; + CDC2963C205B644A00305A82 = { + isa = PBXGroup; + children = ( + CDC2967A205B648A00305A82 /* Sources */, + CDC29655205B645400305A82 /* WatchSync iOSTests */, + CDC29649205B645400305A82 /* Products */, + ); + sourceTree = ""; + }; + CDC29649205B645400305A82 /* Products */ = { + isa = PBXGroup; + children = ( + CDC29648205B645400305A82 /* WatchSync.framework */, + CDC29651205B645400305A82 /* WatchSync iOSTests.xctest */, + CDC29672205B647E00305A82 /* WatchSync.framework */, + ); + name = Products; + sourceTree = ""; + }; + CDC29655205B645400305A82 /* WatchSync iOSTests */ = { + isa = PBXGroup; + children = ( + CDC29656205B645400305A82 /* WatchSync_iOSTests.swift */, + ); + path = "WatchSync iOSTests"; + sourceTree = ""; + }; + CDC2967A205B648A00305A82 /* Sources */ = { + isa = PBXGroup; + children = ( + CDC2967C205B64CB00305A82 /* Info-iOS.plist */, + CDC29682205B657C00305A82 /* Info-iOSTests.plist */, + CDC2967B205B64CB00305A82 /* Info-watchOS.plist */, + CDE70265205B8F3500C40127 /* WatchSync */, + CDC2967F205B656B00305A82 /* WatchSync.h */, + ); + path = Sources; + sourceTree = ""; + }; + CDE701EE205B6DED00C40127 /* Extensions */ = { + isa = PBXGroup; + children = ( + CDC2968D205B679C00305A82 /* Codable+JSON.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + CDE70253205B772300C40127 /* Subscriptions */ = { + isa = PBXGroup; + children = ( + CDE70258205B77A700C40127 /* MessageSubscription.swift */, + CDE7025A205B77C200C40127 /* SubscriptionToken.swift */, + CDE70256205B777800C40127 /* SyncableMessageSubscription.swift */, + 742E93B2206374AF00419EBC /* ApplicationContextSubscription.swift */, + 741E19C22068D95300D8C82B /* FileTransferSubscription.swift */, + ); + path = Subscriptions; + sourceTree = ""; + }; + CDE70265205B8F3500C40127 /* WatchSync */ = { + isa = PBXGroup; + children = ( + 740915E82069767A00F14BE3 /* Results */, + CDE701EE205B6DED00C40127 /* Extensions */, + CDE70253205B772300C40127 /* Subscriptions */, + CDC2968A205B679500305A82 /* SyncableMessage.swift */, + CDC29687205B678F00305A82 /* WatchSync.swift */, + ); + path = WatchSync; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + CDC29645205B645400305A82 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC29680205B656B00305A82 /* WatchSync.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2966F205B647E00305A82 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC29681205B656B00305A82 /* WatchSync.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + CDC29647205B645400305A82 /* WatchSync iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = CDC2965A205B645400305A82 /* Build configuration list for PBXNativeTarget "WatchSync iOS" */; + buildPhases = ( + CDC29643205B645400305A82 /* Sources */, + CDC29644205B645400305A82 /* Frameworks */, + CDC29645205B645400305A82 /* Headers */, + CDC29646205B645400305A82 /* Resources */, + CD8C9089205F1B2B0010649C /* swiftlint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "WatchSync iOS"; + productName = "WatchSync iOS"; + productReference = CDC29648205B645400305A82 /* WatchSync.framework */; + productType = "com.apple.product-type.framework"; + }; + CDC29650205B645400305A82 /* WatchSync iOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CDC2965D205B645400305A82 /* Build configuration list for PBXNativeTarget "WatchSync iOSTests" */; + buildPhases = ( + CDC2964D205B645400305A82 /* Sources */, + CDC2964E205B645400305A82 /* Frameworks */, + CDC2964F205B645400305A82 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + CDC29654205B645400305A82 /* PBXTargetDependency */, + ); + name = "WatchSync iOSTests"; + productName = "WatchSync iOSTests"; + productReference = CDC29651205B645400305A82 /* WatchSync iOSTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + CDC29671205B647E00305A82 /* WatchSync watchOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = CDC29677205B647E00305A82 /* Build configuration list for PBXNativeTarget "WatchSync watchOS" */; + buildPhases = ( + CDC2966D205B647E00305A82 /* Sources */, + CDC2966E205B647E00305A82 /* Frameworks */, + CDC2966F205B647E00305A82 /* Headers */, + CDC29670205B647E00305A82 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "WatchSync watchOS"; + productName = "WatchSync watchOS"; + productReference = CDC29672205B647E00305A82 /* WatchSync.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CDC2963D205B644A00305A82 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + TargetAttributes = { + CDC29647205B645400305A82 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 0920; + ProvisioningStyle = Manual; + }; + CDC29650205B645400305A82 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + CDC29671205B647E00305A82 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 0920; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = CDC29640205B644A00305A82 /* Build configuration list for PBXProject "WatchSync" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = CDC2963C205B644A00305A82; + productRefGroup = CDC29649205B645400305A82 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CDC29647205B645400305A82 /* WatchSync iOS */, + CDC29650205B645400305A82 /* WatchSync iOSTests */, + CDC29671205B647E00305A82 /* WatchSync watchOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CDC29646205B645400305A82 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2964F205B645400305A82 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC29670205B647E00305A82 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + CD8C9089205F1B2B0010649C /* swiftlint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = swiftlint; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CDC29643205B645400305A82 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CDE70259205B77A700C40127 /* MessageSubscription.swift in Sources */, + 741E19C62068DA8300D8C82B /* FileTransferResult.swift in Sources */, + CDC2968E205B679C00305A82 /* Codable+JSON.swift in Sources */, + CDC29688205B679000305A82 /* WatchSync.swift in Sources */, + CDC2968B205B679600305A82 /* SyncableMessage.swift in Sources */, + CDE7025B205B77C200C40127 /* SubscriptionToken.swift in Sources */, + 742E93B5206379B300419EBC /* UpdateContextResult.swift in Sources */, + 742E93B3206374AF00419EBC /* ApplicationContextSubscription.swift in Sources */, + 741E19C32068D95300D8C82B /* FileTransferSubscription.swift in Sources */, + CDE70257205B777800C40127 /* SyncableMessageSubscription.swift in Sources */, + CDC29691205B680B00305A82 /* SendResult.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2964D205B645400305A82 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC29657205B645400305A82 /* WatchSync_iOSTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CDC2966D205B647E00305A82 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CDC2968F205B679C00305A82 /* Codable+JSON.swift in Sources */, + 741E19C72068DA9C00D8C82B /* FileTransferResult.swift in Sources */, + CDC29689205B679000305A82 /* WatchSync.swift in Sources */, + 742E93B72063845900419EBC /* ApplicationContextSubscription.swift in Sources */, + CDE7025C205B78C800C40127 /* SubscriptionToken.swift in Sources */, + 742E93B62063843700419EBC /* UpdateContextResult.swift in Sources */, + CDC2968C205B679600305A82 /* SyncableMessage.swift in Sources */, + CDC29692205B680B00305A82 /* SendResult.swift in Sources */, + 741E19C42068D95800D8C82B /* FileTransferSubscription.swift in Sources */, + CDE7025E205B78CD00C40127 /* MessageSubscription.swift in Sources */, + CDE7025D205B78CA00C40127 /* SyncableMessageSubscription.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + CDC29654205B645400305A82 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CDC29647205B645400305A82 /* WatchSync iOS */; + targetProxy = CDC29653205B645400305A82 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + CDC29641205B644A00305A82 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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_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_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; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + ONLY_ACTIVE_ARCH = YES; + }; + name = Debug; + }; + CDC29642205B644A00305A82 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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_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_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; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + }; + name = Release; + }; + CDC2965B205B645400305A82 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-iOS.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-iOS"; + PRODUCT_NAME = WatchSync; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + CDC2965C205B645400305A82 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-iOS.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-iOS"; + PRODUCT_NAME = WatchSync; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CDC2965E205B645400305A82 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 4337ZFV4M9; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-iOSTests.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CDC2965F205B645400305A82 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 4337ZFV4M9; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-iOSTests.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-iOSTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CDC29678205B647E00305A82 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-watchOS.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-watchOS"; + PRODUCT_NAME = WatchSync; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 4; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; + }; + name = Debug; + }; + CDC29679205B647E00305A82 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = "Sources/Info-watchOS.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.tenminutewait.WatchSync-watchOS"; + PRODUCT_NAME = WatchSync; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CDC29640205B644A00305A82 /* Build configuration list for PBXProject "WatchSync" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CDC29641205B644A00305A82 /* Debug */, + CDC29642205B644A00305A82 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CDC2965A205B645400305A82 /* Build configuration list for PBXNativeTarget "WatchSync iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CDC2965B205B645400305A82 /* Debug */, + CDC2965C205B645400305A82 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CDC2965D205B645400305A82 /* Build configuration list for PBXNativeTarget "WatchSync iOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CDC2965E205B645400305A82 /* Debug */, + CDC2965F205B645400305A82 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CDC29677205B647E00305A82 /* Build configuration list for PBXNativeTarget "WatchSync watchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CDC29678205B647E00305A82 /* Debug */, + CDC29679205B647E00305A82 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = CDC2963D205B644A00305A82 /* Project object */; +} diff --git a/WatchSync.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/WatchSync.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..9340671 --- /dev/null +++ b/WatchSync.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync iOS.xcscheme b/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync iOS.xcscheme new file mode 100644 index 0000000..5f5636f --- /dev/null +++ b/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync iOS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync watchOS.xcscheme b/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync watchOS.xcscheme new file mode 100644 index 0000000..7edd746 --- /dev/null +++ b/WatchSync.xcodeproj/xcshareddata/xcschemes/WatchSync watchOS.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WatchSync.xcworkspace/contents.xcworkspacedata b/WatchSync.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c445b43 --- /dev/null +++ b/WatchSync.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/WatchSync.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/WatchSync.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/WatchSync.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + +