From 71a67c648e0007ba39e8e7707bca45bf4465628a Mon Sep 17 00:00:00 2001 From: Jeff Sawatzky Date: Fri, 4 Jun 2021 13:50:34 -0400 Subject: [PATCH] allow multiple devices. refs #82 --- .../LocationSpoofer/LocationSpoofer.swift | 28 ++++++++-- LocationSimulator/Storyboard/Main.storyboard | 2 +- ...ewController+LocationSpooferDelegate.swift | 2 - .../MapViewController/MapViewController.swift | 32 ++++++------ ...idebarDataSource+DeviceNotifications.swift | 43 ++++++++------- .../SidebarDataSource/SidebarDataSource.swift | 52 ++++++++++++------- .../SidebarViewController.swift | 6 +-- 7 files changed, 101 insertions(+), 64 deletions(-) diff --git a/LocationSimulator/LocationSpoofer/LocationSpoofer.swift b/LocationSimulator/LocationSpoofer/LocationSpoofer.swift index c33f06f..d5b1072 100644 --- a/LocationSimulator/LocationSpoofer/LocationSpoofer.swift +++ b/LocationSimulator/LocationSpoofer/LocationSpoofer.swift @@ -102,7 +102,7 @@ class LocationSpoofer { } /// The connected iOS Device. - public let device: Device + public let devices: [Device] /// Total moved distance in m public var totalDistance: Double = 0.0 @@ -118,9 +118,9 @@ class LocationSpoofer { // MARK: - Constructor - init(_ device: Device) { + init(_ devices: [Device]) { self.route = [] - self.device = device + self.devices = devices self.currentLocation = nil self.dispatchQueue = DispatchQueue(label: "locationUpdates", qos: .userInteractive) } @@ -146,7 +146,16 @@ class LocationSpoofer { dispatchQueue.async { [weak self] in // try to reset the location - let success: Bool = self?.device.disableSimulation() ?? false + var success = false + if let devices = self?.devices { + success = true + for device in devices { + if !device.disableSimulation() { + success = false + } + } + } + if success { self?.totalDistance = 0.0 self?.currentLocation = nil @@ -200,7 +209,16 @@ class LocationSpoofer { dispatchQueue.async { [weak self] in // try to simulate the location on the device - let success: Bool = self?.device.simulateLocation(coordinate) ?? false + var success = false + if let devices = self?.devices { + success = true + for device in devices { + if !device.simulateLocation(coordinate) { + success = false + } + } + } + if success { self?.totalDistance += self?.currentLocation?.distanceTo(coordinate: coordinate) ?? 0 self?.currentLocation = coordinate diff --git a/LocationSimulator/Storyboard/Main.storyboard b/LocationSimulator/Storyboard/Main.storyboard index 3bddf04..79d7d86 100755 --- a/LocationSimulator/Storyboard/Main.storyboard +++ b/LocationSimulator/Storyboard/Main.storyboard @@ -1464,7 +1464,7 @@ - + diff --git a/LocationSimulator/ViewController/MapViewController/MapViewController+LocationSpooferDelegate.swift b/LocationSimulator/ViewController/MapViewController/MapViewController+LocationSpooferDelegate.swift index 38ff4d5..244835c 100644 --- a/LocationSimulator/ViewController/MapViewController/MapViewController+LocationSpooferDelegate.swift +++ b/LocationSimulator/ViewController/MapViewController/MapViewController+LocationSpooferDelegate.swift @@ -90,7 +90,6 @@ extension MapViewController: LocationSpooferDelegate { // Post the update for the current app status. NotificationCenter.default.post(name: .StatusChanged, object: self, userInfo: [ - "device": spoofer.device, "status": status ]) } @@ -119,7 +118,6 @@ extension MapViewController: LocationSpooferDelegate { // Update the current application status. NotificationCenter.default.post(name: .StatusChanged, object: self, userInfo: [ - "device": spoofer.device, "status": status ]) } diff --git a/LocationSimulator/ViewController/MapViewController/MapViewController.swift b/LocationSimulator/ViewController/MapViewController/MapViewController.swift index 238e261..7ec5ed1 100755 --- a/LocationSimulator/ViewController/MapViewController/MapViewController.swift +++ b/LocationSimulator/ViewController/MapViewController/MapViewController.swift @@ -33,15 +33,15 @@ class MapViewController: NSViewController { /// The current device managed by this viewController. Assigning this property will NOT automatically try to connect /// the device. You still need to call `connectDevice`. That beeing said, changing a device assigned to this /// viewController is not officially supported. - public var device: Device? { - get { return self.spoofer?.device } + public var devices: [Device]? { + get { return self.spoofer?.devices } set { // Either we removed the device or the new device is not connected yet. - self.deviceIsConnectd = false + self.deviceIsConnected = false // Check that the device exists. - guard let device = newValue else { return } + guard let devices = newValue else { return } // Create a spoofer instance for this device. - self.spoofer = LocationSpoofer(device) + self.spoofer = LocationSpoofer(devices) self.spoofer?.delegate = self } } @@ -73,15 +73,11 @@ class MapViewController: NSViewController { var isShowingAlert: Bool = false /// True if the current device is connected, false otherwise. - public private(set) var deviceIsConnectd: Bool = false { + public private(set) var deviceIsConnected: Bool = false { didSet { - var userInfo: [String: Any] = [ - "status": self.deviceIsConnectd ? DeviceStatus.connected : DeviceStatus.disconnected + let userInfo: [String: Any] = [ + "status": self.deviceIsConnected ? DeviceStatus.connected : DeviceStatus.disconnected ] - // Add the device to the info if available. - if let device = self.device { - userInfo["device"] = device - } // Post a notifcation about the new device status. NotificationCenter.default.post(name: .StatusChanged, object: self, userInfo: userInfo) } @@ -260,20 +256,24 @@ class MapViewController: NSViewController { @discardableResult func connectDevice() -> Bool { // Make sure we have a device to connect, which is not already connected. We need a window to show errors. - guard !self.deviceIsConnectd, let device = self.device, let window = self.view.window else { return false } + guard !self.deviceIsConnected, let devices = self.devices, let window = self.view.window else { return false } do { // Show the error indicator and a progress spinner. self.contentView?.showErrorInidcator() // If the pairing and uploading of the developer disk image is successfull create a spoofer instance. - try device.pair() + if self.devices != nil { + for device in self.devices! { + try device.pair() + } + } // Hide the error indicator if the device was connected sucessfully. self.contentView?.hideErrorInidcator() // We successfully connected the device. - self.deviceIsConnectd = true + self.deviceIsConnected = true } catch let error { // Stop the spinner even if an error occured. self.contentView?.stopSpinner() - self.deviceIsConnectd = false + self.deviceIsConnected = false // Handle the error message switch error { case DeviceError.pair: diff --git a/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource+DeviceNotifications.swift b/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource+DeviceNotifications.swift index 78354d0..6e118cd 100644 --- a/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource+DeviceNotifications.swift +++ b/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource+DeviceNotifications.swift @@ -89,36 +89,41 @@ extension SidebarDataSource { /// Callback when a device gets disconnected. /// - Parameter notification: notification with device information (UDID) @objc func deviceDisconnected(_ notification: Notification) { + let selectedDevices = self.selectedDevices + if let device: IOSDevice = notification.userInfo?["device"] as? IOSDevice { // remove the device from the list and the list popup if let index: Int = self.realDevices.firstIndex(of: device) { print("[INFO]: Disconnect device: \(device.name) with UDID: \(device.udid)") - - // True if the currently selected device was removed. - let removeCurrent = (self.selectedDevice as? IOSDevice == self.realDevices.remove(at: index)) - - // If the current device was removed, we need to change the status to disconnect. - // We do this by removing the device instance. - if removeCurrent { - let windowController = self.sidebarView?.window?.windowController as? WindowController - windowController?.mapViewController?.device = nil + self.realDevices.remove(at: index) + + // True if a currently selected device was removed. + for selectedDevice in selectedDevices { + // If a currently selected device was removed, we may need to change the status to disconnect. + // We do this by removing the device instance. + if selectedDevice as? IOSDevice == device { + let windowController = self.sidebarView?.window?.windowController as? WindowController + windowController?.mapViewController?.devices = selectedDevices + break + } } - // index +1 for the HeaderCell self.sidebarView?.removeItems(at: [index+1], inParent: nil, withAnimation: .effectGap) } } else if let device: SimulatorDevice = notification.userInfo?["device"] as? SimulatorDevice { if let index: Int = self.simDevices.firstIndex(of: device) { print("[INFO]: Disconnect simulator device: \(device.name) with UDID: \(device.udid)") - - // True if the currently selected device was removed. - let removeCurrent = (self.selectedDevice as? SimulatorDevice == self.simDevices.remove(at: index)) - - // If the current device was removed, we need to change the status to disconnect. - // We do this by removing the device instance. - if removeCurrent { - let windowController = self.sidebarView?.window?.windowController as? WindowController - windowController?.mapViewController?.device = nil + self.simDevices.remove(at: index) + + // True if a currently selected device was removed. + for selectedDevice in selectedDevices { + // If a currently selected device was removed, we may need to change the status to disconnect. + // We do this by removing the device instance. + if selectedDevice as? SimulatorDevice == device { + let windowController = self.sidebarView?.window?.windowController as? WindowController + windowController?.mapViewController?.devices = selectedDevices + break + } } // index +2 for the HeaderCells + the real device count diff --git a/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource.swift b/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource.swift index 682d602..1d51dc8 100644 --- a/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource.swift +++ b/LocationSimulator/ViewController/SidebarViewController/SidebarDataSource/SidebarDataSource.swift @@ -18,19 +18,29 @@ class SidebarDataSource: NSObject { /// List with all currently detected simulator devices. public var simDevices: [SimulatorDevice] = [] - /// The currently selected device. - public var selectedDevice: Device? { + /// The currently selected devices. + public var selectedDevices: [Device] { + var selectedDevices = [Device]() + let numIOSDevices = self.realDevices.count let numSimDevices = self.simDevices.count - let row = (self.sidebarView?.selectedRow ?? 0) - if row <= numIOSDevices { - // A real iOS Device was selected - return row >= 1 ? self.realDevices[row-1] : nil - } else if row > numIOSDevices && row < numIOSDevices + numSimDevices + 2 { - // A simulator device was selected - return row > numIOSDevices+1 ? self.simDevices[row-numIOSDevices-2] : nil + + if self.sidebarView != nil { + for (_, row) in self.sidebarView!.selectedRowIndexes.enumerated() { + if row <= numIOSDevices { + // A real iOS Device was selected + if row >= 1 { + selectedDevices.append(self.realDevices[row-1]) + } + } else if row > numIOSDevices && row < numIOSDevices + numSimDevices + 2 { + // A simulator device was selected + if row > numIOSDevices+1 { + selectedDevices.append(self.simDevices[row-numIOSDevices-2]) + } + } + } } - return nil + return selectedDevices } // MARK: - Constructor @@ -104,16 +114,22 @@ extension SidebarDataSource: NSOutlineViewDelegate { } // Allow selecting a device, if it is not already selected. - if let device = (item as AnyObject) as? IOSDevice { - return ((self.selectedDevice as? IOSDevice) != device) + var deviceAlreadySelected = false + for selectedDevice in self.selectedDevices { + if let device = (item as AnyObject) as? IOSDevice { + deviceAlreadySelected = ((selectedDevice as? IOSDevice) == device) + } + + if let simDevice = (item as AnyObject) as? SimulatorDevice { + deviceAlreadySelected = ((selectedDevice as? SimulatorDevice) == simDevice) + } + + if deviceAlreadySelected { + break + } } - - if let simDevice = (item as AnyObject) as? SimulatorDevice { - return ((self.selectedDevice as? SimulatorDevice) != simDevice) - } - // Default, should never be the case. - return false + return !deviceAlreadySelected } func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { diff --git a/LocationSimulator/ViewController/SidebarViewController/SidebarViewController.swift b/LocationSimulator/ViewController/SidebarViewController/SidebarViewController.swift index 5f48130..80b8e92 100644 --- a/LocationSimulator/ViewController/SidebarViewController/SidebarViewController.swift +++ b/LocationSimulator/ViewController/SidebarViewController/SidebarViewController.swift @@ -67,12 +67,12 @@ class SidebarViewController: NSViewController { // On macOS 11 use the line toolbar separator style for the MapViewController. Otherwise use None. var drawSeparator: Bool = false var viewController: Any? - if let device = self.dataSource?.selectedDevice { + if let devices = self.dataSource?.selectedDevices { drawSeparator = true // A device was connected => create and show the corresponding MapViewController. viewController = self.storyboard?.instantiateController(withIdentifier: "MapViewController") if let mapViewController = viewController as? MapViewController { - mapViewController.device = device + mapViewController.devices = devices // Set the currently selected move type. let windowController = self.view.window?.windowController as? WindowController mapViewController.moveType = windowController?.moveType @@ -80,7 +80,7 @@ class SidebarViewController: NSViewController { } else { drawSeparator = false // The last device was removed => create and show a NoDeviceViewController. - viewController = self.storyboard?.instantiateController(withIdentifier: "NoDeviceViewControlelr") + viewController = self.storyboard?.instantiateController(withIdentifier: "NoDeviceViewController") // If the sidebar is currently hidden, show it. The user might not know where to select a device. if splitViewController.isSidebarCollapsed { splitViewController.toggleSidebar()