Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Multiple Devices at Once #83

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions LocationSimulator/LocationSpoofer/LocationSpoofer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion LocationSimulator/Storyboard/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,7 @@
<rect key="frame" x="0.0" y="0.0" width="86" height="302"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="medium" viewBased="YES" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="MzZ-ov-wur" id="7U9-5A-zYV">
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" autosaveColumns="NO" rowHeight="28" rowSizeStyle="medium" viewBased="YES" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="MzZ-ov-wur" id="7U9-5A-zYV">
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to allow multiple selection. I've tried shift+click and cmd+click and neither work.

<rect key="frame" x="0.0" y="0.0" width="86" height="302"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userInfo["device"] wasn't used in the observer, so to simplify things I just removed this.

"status": status
])
}
Expand Down Expand Up @@ -119,7 +118,6 @@ extension MapViewController: LocationSpooferDelegate {

// Update the current application status.
NotificationCenter.default.post(name: .StatusChanged, object: self, userInfo: [
"device": spoofer.device,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userInfo["device"] wasn't used in the observer, so to simplify things I just removed this.

"status": status
])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we should try to disconnect any successfully connected devices here?

// Handle the error message
switch error {
case DeviceError.pair:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,20 @@ 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
}
} 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()
Expand Down