diff --git a/SwiftSimctlExample.xcodeproj/project.pbxproj b/SwiftSimctlExample.xcodeproj/project.pbxproj index 5dabe09..cb3c46a 100644 --- a/SwiftSimctlExample.xcodeproj/project.pbxproj +++ b/SwiftSimctlExample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3340A8692722117000C4CBA5 /* Example_03_OpenUrlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3340A8682722116F00C4CBA5 /* Example_03_OpenUrlTests.swift */; }; F830E0C4242BA5B900875D0A /* Example_02_ChangeAppearanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F830E0C3242BA5B900875D0A /* Example_02_ChangeAppearanceTests.swift */; }; F830E0C6242BA5E400875D0A /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = F830E0C5242BA5E400875D0A /* Shared.swift */; }; F851FF952423ADB20099A319 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F851FF942423ADB20099A319 /* AppDelegate.swift */; }; @@ -31,6 +32,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3340A8682722116F00C4CBA5 /* Example_03_OpenUrlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Example_03_OpenUrlTests.swift; sourceTree = ""; }; F830E0C3242BA5B900875D0A /* Example_02_ChangeAppearanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_02_ChangeAppearanceTests.swift; sourceTree = ""; }; F830E0C5242BA5E400875D0A /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; F851FF912423ADB20099A319 /* SwiftSimctlExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftSimctlExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -118,6 +120,7 @@ F830E0C5242BA5E400875D0A /* Shared.swift */, F851FFB62423ADB30099A319 /* Example_01_HandlePushNotificationsTests.swift */, F830E0C3242BA5B900875D0A /* Example_02_ChangeAppearanceTests.swift */, + 3340A8682722116F00C4CBA5 /* Example_03_OpenUrlTests.swift */, ); path = SwiftSimctlExampleUITests; sourceTree = ""; @@ -251,6 +254,7 @@ buildActionMask = 2147483647; files = ( F830E0C6242BA5E400875D0A /* Shared.swift in Sources */, + 3340A8692722117000C4CBA5 /* Example_03_OpenUrlTests.swift in Sources */, F851FFB72423ADB30099A319 /* Example_01_HandlePushNotificationsTests.swift in Sources */, F830E0C4242BA5B900875D0A /* Example_02_ChangeAppearanceTests.swift in Sources */, ); @@ -524,7 +528,7 @@ repositoryURL = "https://github.com/ctreffs/SwiftSimctl.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.3.0; + minimumVersion = 0.4.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/SwiftSimctlExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftSimctlExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8305dfe..e60453e 100644 --- a/SwiftSimctlExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftSimctlExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/ctreffs/SwiftSimctl.git", "state": { "branch": null, - "revision": "967131171e494aeeeb98976999e09b86b714be6f", - "version": "0.3.0" + "revision": "3b6542059bedd9f1e4eb0c722d68a99e623486f5", + "version": "0.4.0" } } ] diff --git a/SwiftSimctlExample/ContentView.swift b/SwiftSimctlExample/ContentView.swift index 486afa5..1fac145 100644 --- a/SwiftSimctlExample/ContentView.swift +++ b/SwiftSimctlExample/ContentView.swift @@ -10,6 +10,7 @@ import SwiftUI struct ContentView: View { @State private var authorizationStatus: UNAuthorizationStatus = .notDetermined + @State private var deepLinkPath: String = "none" var body: some View { VStack { @@ -25,8 +26,19 @@ struct ContentView: View { } else if authorizationStatus == .denied { Button(action: { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) }) { Text("Re-enable push authorization manually in settings") } } + + Text("Deep link path:") + .fontWeight(.bold) + .padding(4) + Text(deepLinkPath) + .foregroundColor(deepLinkPath == "none" ? .gray : .blue) + .padding(20) } .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in self.updateAuthorizationStatus() } + .onReceive(NotificationCenter.default.publisher(for: .deepLink)) { notification in + guard let deepLinkPath = notification.object as? String else { return } + self.deepLinkPath = deepLinkPath + } .onAppear(perform: { self.updateAuthorizationStatus() }) } @@ -73,12 +85,16 @@ extension UNAuthorizationStatus: CustomStringConvertible { switch self { case .authorized: return .green + case .notDetermined: return .gray + case .denied: return .red + case .ephemeral: return .blue + case .provisional: return .yellow @unknown default: diff --git a/SwiftSimctlExample/Info.plist b/SwiftSimctlExample/Info.plist index 937b1a0..b5a4d43 100644 --- a/SwiftSimctlExample/Info.plist +++ b/SwiftSimctlExample/Info.plist @@ -16,6 +16,19 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + None + CFBundleURLName + com.swiftsimctl.example + CFBundleURLSchemes + + swiftsimctlexample + + + CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/SwiftSimctlExample/SceneDelegate.swift b/SwiftSimctlExample/SceneDelegate.swift index 50e1073..2b9b284 100644 --- a/SwiftSimctlExample/SceneDelegate.swift +++ b/SwiftSimctlExample/SceneDelegate.swift @@ -22,4 +22,24 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window.makeKeyAndVisible() } } + + /// If the app is opened with a url using the registered scheme `swiftsimctlexample` + /// then this code will post a deep link notification whose object is url host + path. + /// + /// Example: + /// - Opening `swiftsimctlexample://foo/bar/baz` posts a Notification + /// named `"deepLink"` with String object `"foo/bar/baz"`. + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if + let url = URLContexts.first?.url, + url.scheme?.lowercased() == "swiftsimctlexample" + { + let path = "\(url.host ?? "")\(url.path)" + NotificationCenter.default.post(name: .deepLink, object: path) + } + } +} + +extension Notification.Name { + static let deepLink: Notification.Name = .init(rawValue: "deepLink") } diff --git a/SwiftSimctlExampleUITests/Example_01_HandlePushNotificationsTests.swift b/SwiftSimctlExampleUITests/Example_01_HandlePushNotificationsTests.swift index 39224d9..611a09f 100644 --- a/SwiftSimctlExampleUITests/Example_01_HandlePushNotificationsTests.swift +++ b/SwiftSimctlExampleUITests/Example_01_HandlePushNotificationsTests.swift @@ -10,9 +10,6 @@ import Simctl import XCTest class Example_01_HandlePushNotificationsTests: XCTestCase { - lazy var simctl = SimctlClient(SimulatorEnvironment(bundleIdentifier: exampleAppBundleId, - host: .localhost(port: 8080))!) - func testAllowPushNotifications() { // MARK: Launch app and enable push notifications let app = XCUIApplication.exampleApp diff --git a/SwiftSimctlExampleUITests/Example_02_ChangeAppearanceTests.swift b/SwiftSimctlExampleUITests/Example_02_ChangeAppearanceTests.swift index 3e8046d..e8e96e3 100644 --- a/SwiftSimctlExampleUITests/Example_02_ChangeAppearanceTests.swift +++ b/SwiftSimctlExampleUITests/Example_02_ChangeAppearanceTests.swift @@ -11,8 +11,6 @@ import SnapshotTesting import XCTest class Example_02_ChangeAppearanceTests: XCTestCase { - lazy var simctl = SimctlClient(SimulatorEnvironment(bundleIdentifier: exampleAppBundleId, - host: .localhost(port: 8080))!) // run on iPhone 11 Pro func testDifferentAppearances() throws { let app = XCUIApplication.exampleApp diff --git a/SwiftSimctlExampleUITests/Example_03_OpenUrlTests.swift b/SwiftSimctlExampleUITests/Example_03_OpenUrlTests.swift new file mode 100644 index 0000000..deb5f55 --- /dev/null +++ b/SwiftSimctlExampleUITests/Example_03_OpenUrlTests.swift @@ -0,0 +1,50 @@ +// +// Example_03_OpenUrlTests.swift +// SwiftSimctlExampleUITests +// +// Created by Robert Clark on 21.10.21. +// Copyright © 2021 Christian Treffs. All rights reserved. +// + +import Simctl +import XCTest + +class Example_03_OpenUrlTests: XCTestCase { + func testOpenUrlWithRegisteredScheme() throws { + let app = XCUIApplication.exampleApp + app.launch() + + let exp = expectation(description: "\(#function)") + + let deepLinkUrl = URL(string: "swiftsimctlexample://myDeepLink/display")! + + simctl.openUrl(deepLinkUrl) { result in + switch result { + case .success: + exp.fulfill() + + case let .failure(error): + XCTFail("\(error)") + } + } + + wait(for: [exp], timeout: 5.0) + + // First installs on fresh simulators will prompt to open registered urls. + // UIInterruptionMonitor doesn't intercept this dialog (as of iOS 15 + // & Xcode 13) so we use Springboard to tap on the potential positive dialog + // button instead. + let openButton = XCUIApplication.springboard.buttons.element(boundBy: 1) + if openButton.waitForExistence(timeout: 2) { + openButton.tap() + } + + let deepLinkPathLabel = app.staticTexts["myDeepLink/display"] + guard deepLinkPathLabel.waitForExistence(timeout: 5.0) else { + XCTFail("We did not find deep link path label") + return + } + + XCTAssertEqual(deepLinkPathLabel.label, "myDeepLink/display") + } +} diff --git a/SwiftSimctlExampleUITests/Shared.swift b/SwiftSimctlExampleUITests/Shared.swift index 2943e8e..fd1b3ef 100644 --- a/SwiftSimctlExampleUITests/Shared.swift +++ b/SwiftSimctlExampleUITests/Shared.swift @@ -6,10 +6,18 @@ // Copyright © 2020 Christian Treffs. All rights reserved. // +import Simctl import XCTest let exampleAppBundleId = "com.example.SwiftSimctlExample" +let simctl = SimctlClient( + SimulatorEnvironment( + bundleIdentifier: exampleAppBundleId, + host: .localhost(port: 8080) + )! +) + extension XCUIApplication { static let exampleApp = XCUIApplication(bundleIdentifier: exampleAppBundleId) static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") diff --git a/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.1.png b/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.1.png index 2b66630..63465c6 100644 Binary files a/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.1.png and b/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.1.png differ diff --git a/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.2.png b/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.2.png index e55f8ab..125add1 100644 Binary files a/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.2.png and b/SwiftSimctlExampleUITests/__Snapshots__/Example_02_ChangeAppearanceTests/testDifferentAppearancesFullscreen.2.png differ