Conversion to Swift3
This commit is contained in:
@@ -10,15 +10,15 @@ import UIKit
|
||||
|
||||
class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
{
|
||||
private var _server: WemoServer
|
||||
private var _visualizationController: VisualizationViewController = VisualizationViewController()
|
||||
private var _switchesController: SwitchesViewController = SwitchesViewController()
|
||||
private var _headerView: HeaderView = HeaderView()
|
||||
private var _updateDevices: Bool = false
|
||||
fileprivate var _server: WemoServer
|
||||
fileprivate var _visualizationController: VisualizationViewController = VisualizationViewController()
|
||||
fileprivate var _switchesController: SwitchesViewController = SwitchesViewController()
|
||||
fileprivate var _headerView: HeaderView = HeaderView()
|
||||
fileprivate var _updateDevices: Bool = false
|
||||
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
|
||||
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
|
||||
{
|
||||
let url = NSURL(string: "http://midna.xionsf.com:5000")
|
||||
let url = URL(string: "http://midna.xionsf.com:5000")
|
||||
_server = WemoServer(url!)
|
||||
|
||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||
@@ -35,7 +35,7 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = UIColor.blackColor()
|
||||
self.view.backgroundColor = UIColor.black
|
||||
|
||||
self.addChildViewController(_visualizationController)
|
||||
self.view.addSubview(_visualizationController.view)
|
||||
@@ -46,7 +46,7 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
|
||||
self.view.addSubview(_headerView)
|
||||
|
||||
_updateConnectivityStatus(.Disconnected)
|
||||
_updateConnectivityStatus(.disconnected)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
@@ -64,7 +64,7 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
)
|
||||
let bodyBounds = CGRect(
|
||||
x: 0.0,
|
||||
y: CGRectGetMaxY(headerBounds),
|
||||
y: headerBounds.maxY,
|
||||
width: bounds.size.width,
|
||||
height: bounds.size.height - headerBounds.size.height
|
||||
)
|
||||
@@ -81,8 +81,8 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
|
||||
var switchesOriginX: CGFloat = 0.0
|
||||
var switchesWidth: CGFloat = 0.0
|
||||
if (horizontalSizeClass == .Regular) {
|
||||
switchesOriginX = CGRectGetMaxX(visualizationFrame)
|
||||
if (horizontalSizeClass == .regular) {
|
||||
switchesOriginX = visualizationFrame.maxX
|
||||
switchesWidth = bodyBounds.size.width - visualizationFrame.size.width
|
||||
} else {
|
||||
switchesOriginX = 0.0
|
||||
@@ -98,32 +98,32 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
_switchesController.view.frame = switchesControllerFrame
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool)
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
_headerView.xionLogoView.beginAnimating()
|
||||
|
||||
if (!_server.connected) {
|
||||
_updateConnectivityStatus(.Connecting)
|
||||
_server.connect { (error: NSError?) -> Void in
|
||||
_updateConnectivityStatus(.connecting)
|
||||
_server.connect { (error: Error?) -> Void in
|
||||
if (error == nil) {
|
||||
self._reloadDevices()
|
||||
self._startUpdatingDevices()
|
||||
} else {
|
||||
self._updateConnectivityStatus(.Error)
|
||||
self._updateConnectivityStatus(.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
|
||||
{
|
||||
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
_updateSizeClassPresentation()
|
||||
}
|
||||
|
||||
override func prefersStatusBarHidden() -> Bool
|
||||
override var prefersStatusBarHidden : Bool
|
||||
{
|
||||
return true
|
||||
}
|
||||
@@ -141,12 +141,12 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
|
||||
// MARK: SwitchesViewControllerDelegate
|
||||
|
||||
func switchesViewControllerDidToggleDevices(controller: SwitchesViewController, devices: [WemoDevice])
|
||||
func switchesViewControllerDidToggleDevices(_ controller: SwitchesViewController, devices: [WemoDevice])
|
||||
{
|
||||
_updateVisualization(true)
|
||||
|
||||
for device in devices {
|
||||
_server.toggleDevice(device, state: device.state, completion: { (error: NSError?) -> Void in })
|
||||
_server.toggleDevice(device, state: device.state, completion: { (error: Error?) -> Void in })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,18 +155,18 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
internal func _updateSizeClassPresentation()
|
||||
{
|
||||
let horizontalSizeClass = self.traitCollection.horizontalSizeClass
|
||||
if (horizontalSizeClass == .Regular) {
|
||||
_visualizationController.view.hidden = false
|
||||
if (horizontalSizeClass == .regular) {
|
||||
_visualizationController.view.isHidden = false
|
||||
} else {
|
||||
_visualizationController.view.hidden = true
|
||||
_visualizationController.view.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
internal func _updateVisualization(animated: Bool)
|
||||
internal func _updateVisualization(_ animated: Bool)
|
||||
{
|
||||
var activatedDevicesCount = 0
|
||||
for device in self.devices {
|
||||
if (device.state == .On) {
|
||||
if (device.state == .on) {
|
||||
activatedDevicesCount += 1
|
||||
}
|
||||
}
|
||||
@@ -177,9 +177,9 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
}
|
||||
}
|
||||
|
||||
internal func _updateConnectivityStatus(status: ConnectionStatus)
|
||||
internal func _updateConnectivityStatus(_ status: ConnectionStatus)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue()) { () -> Void in
|
||||
DispatchQueue.main.async { () -> Void in
|
||||
self._headerView.connectionStatusView.connectivityStatus = status
|
||||
self._headerView.setNeedsLayout()
|
||||
self._visualizationController.connectionStatus = status
|
||||
@@ -188,14 +188,14 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
|
||||
internal func _reloadDevices()
|
||||
{
|
||||
_server.fetchDevices({ (devices: [WemoDevice], error: NSError?) -> Void in
|
||||
dispatch_async(dispatch_get_main_queue()) { () -> Void in
|
||||
_server.fetchDevices({ (devices: [WemoDevice], error: Error?) -> Void in
|
||||
DispatchQueue.main.async { () -> Void in
|
||||
if (error == nil) {
|
||||
self.devices = devices
|
||||
self._updateConnectivityStatus(.Connected)
|
||||
self._updateConnectivityStatus(.connected)
|
||||
} else {
|
||||
self.devices = []
|
||||
self._updateConnectivityStatus(.Error)
|
||||
self._updateConnectivityStatus(.error)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -205,8 +205,8 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate
|
||||
{
|
||||
_updateDevices = true
|
||||
|
||||
let interval = dispatch_time(DISPATCH_TIME_NOW, Int64(10 * Double(NSEC_PER_SEC)))
|
||||
dispatch_after(interval, dispatch_get_main_queue()) { () -> Void in
|
||||
let interval = DispatchTime.now() + Double(Int64(10 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
|
||||
DispatchQueue.main.asyncAfter(deadline: interval) { () -> Void in
|
||||
if (self._updateDevices) {
|
||||
self._reloadDevices()
|
||||
self._startUpdatingDevices()
|
||||
|
||||
@@ -12,12 +12,12 @@ import UIKit
|
||||
|
||||
protocol SwitchesViewControllerDelegate: class
|
||||
{
|
||||
func switchesViewControllerDidToggleDevices(controller: SwitchesViewController, devices: [WemoDevice])
|
||||
func switchesViewControllerDidToggleDevices(_ controller: SwitchesViewController, devices: [WemoDevice])
|
||||
}
|
||||
|
||||
extension SwitchesViewControllerDelegate
|
||||
{
|
||||
func switchesViewControllerDidToggleDevices(controller: SwitchesViewController, devices: [WemoDevice]) {}
|
||||
func switchesViewControllerDidToggleDevices(_ controller: SwitchesViewController, devices: [WemoDevice]) {}
|
||||
}
|
||||
|
||||
class SwitchesViewController: UIViewController,
|
||||
@@ -25,25 +25,25 @@ class SwitchesViewController: UIViewController,
|
||||
UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
weak var delegate: SwitchesViewControllerDelegate?
|
||||
private var _collectionView: UICollectionView = UICollectionView(frame: CGRectZero,
|
||||
fileprivate var _collectionView: UICollectionView = UICollectionView(frame: CGRect.zero,
|
||||
collectionViewLayout: UICollectionViewFlowLayout())
|
||||
private var _currentDevicesHash: Int = 0
|
||||
fileprivate var _currentDevicesHash: Int = 0
|
||||
|
||||
static private let collectionViewDeviceSwitchCellReuseIdentifier = "DeviceSwitchReuseID"
|
||||
static private let collectionViewActionCellReuseIdentifier = "ActionCellReuseID"
|
||||
static private let collectionViewCellsSpacing: CGFloat = 5.0
|
||||
static fileprivate let collectionViewDeviceSwitchCellReuseIdentifier = "DeviceSwitchReuseID"
|
||||
static fileprivate let collectionViewActionCellReuseIdentifier = "ActionCellReuseID"
|
||||
static fileprivate let collectionViewCellsSpacing: CGFloat = 5.0
|
||||
|
||||
private enum ActionCell: Int
|
||||
fileprivate enum ActionCell: Int
|
||||
{
|
||||
case AllOn
|
||||
case AllOff
|
||||
case allOn
|
||||
case allOff
|
||||
|
||||
func name() -> String
|
||||
{
|
||||
switch self {
|
||||
case .AllOn:
|
||||
case .allOn:
|
||||
return "All On"
|
||||
case .AllOff:
|
||||
case .allOff:
|
||||
return "All Off"
|
||||
}
|
||||
}
|
||||
@@ -62,15 +62,15 @@ class SwitchesViewController: UIViewController,
|
||||
let deviceCellReuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
|
||||
let actionCellReuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
|
||||
let layout = _collectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
||||
layout.scrollDirection = .Vertical
|
||||
layout.scrollDirection = .vertical
|
||||
layout.minimumInteritemSpacing = SwitchesViewController.collectionViewCellsSpacing
|
||||
layout.minimumLineSpacing = SwitchesViewController.collectionViewCellsSpacing
|
||||
|
||||
_collectionView.backgroundColor = UIColor.blackColor()
|
||||
_collectionView.backgroundColor = UIColor.black
|
||||
_collectionView.delegate = self
|
||||
_collectionView.dataSource = self
|
||||
_collectionView.registerClass(WemoDeviceCellView.self, forCellWithReuseIdentifier: deviceCellReuseID)
|
||||
_collectionView.registerClass(WemoActionCellView.self, forCellWithReuseIdentifier: actionCellReuseID)
|
||||
_collectionView.register(WemoDeviceCellView.self, forCellWithReuseIdentifier: deviceCellReuseID)
|
||||
_collectionView.register(WemoActionCellView.self, forCellWithReuseIdentifier: actionCellReuseID)
|
||||
self.view.addSubview(_collectionView)
|
||||
|
||||
self.devices = []
|
||||
@@ -91,36 +91,36 @@ class SwitchesViewController: UIViewController,
|
||||
didSet
|
||||
{
|
||||
// sort devices by name
|
||||
self.devices.sortInPlace({ (d1: WemoDevice, d2: WemoDevice) -> Bool in
|
||||
return (d1.name.compare(d2.name) == .OrderedAscending)
|
||||
self.devices.sort(by: { (d1: WemoDevice, d2: WemoDevice) -> Bool in
|
||||
return (d1.name.compare(d2.name) == .orderedAscending)
|
||||
})
|
||||
|
||||
let hash = self.devices.reduce(0, combine: {$0 ^ $1.hashValue})
|
||||
let hash = self.devices.reduce(0, {$0 ^ $1.hashValue})
|
||||
if (hash != _currentDevicesHash) {
|
||||
let previousSet = NSOrderedSet(array: oldValue)
|
||||
let newSet = NSOrderedSet(array: self.devices)
|
||||
var insertedIndexPaths: [NSIndexPath] = []
|
||||
var updatedIndexPaths: [NSIndexPath] = []
|
||||
var deletedIndexPaths: [NSIndexPath] = []
|
||||
var insertedIndexPaths: [IndexPath] = []
|
||||
var updatedIndexPaths: [IndexPath] = []
|
||||
var deletedIndexPaths: [IndexPath] = []
|
||||
|
||||
// if we have devices now and we didn't before, or vice versa,
|
||||
// we need to update the action cells
|
||||
if ((oldValue.count == 0 && self.devices.count != 0) || (self.devices.count == 0 && oldValue.count != 0)) {
|
||||
for actionCellIdx in 0 ..< ActionCell.count {
|
||||
let actionCellIndexPath = NSIndexPath(forItem: actionCellIdx, inSection: 0)
|
||||
let actionCellIndexPath = IndexPath(item: actionCellIdx, section: 0)
|
||||
updatedIndexPaths.append(actionCellIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
// find deletes and updates
|
||||
for (idx, device) in previousSet.enumerate() {
|
||||
for (idx, device) in previousSet.enumerated() {
|
||||
let itemIndex = idx + ActionCell.count
|
||||
let curIndexPath = NSIndexPath(forItem: itemIndex, inSection: 0)
|
||||
let curIndexPath = IndexPath(item: itemIndex, section: 0)
|
||||
|
||||
if (!newSet.containsObject(device)) {
|
||||
if (!newSet.contains(device)) {
|
||||
deletedIndexPaths.append(curIndexPath)
|
||||
} else if (idx < newSet.count) {
|
||||
let deviceInNewSet = newSet.objectAtIndex(idx) as! WemoDevice
|
||||
let deviceInNewSet = newSet.object(at: idx) as! WemoDevice
|
||||
if (deviceInNewSet != (device as! WemoDevice)) {
|
||||
updatedIndexPaths.append(curIndexPath)
|
||||
}
|
||||
@@ -128,19 +128,19 @@ class SwitchesViewController: UIViewController,
|
||||
}
|
||||
|
||||
// find insertions
|
||||
for (idx, device) in newSet.enumerate() {
|
||||
if (!previousSet.containsObject(device)) {
|
||||
for (idx, device) in newSet.enumerated() {
|
||||
if (!previousSet.contains(device)) {
|
||||
let itemIndex = idx + ActionCell.count
|
||||
let insertedIndexPath = NSIndexPath(forItem: itemIndex, inSection: 0)
|
||||
let insertedIndexPath = IndexPath(item: itemIndex, section: 0)
|
||||
insertedIndexPaths.append(insertedIndexPath)
|
||||
}
|
||||
}
|
||||
|
||||
UIView.performWithoutAnimation { () -> Void in
|
||||
self._collectionView.performBatchUpdates({ () -> Void in
|
||||
self._collectionView.deleteItemsAtIndexPaths(deletedIndexPaths)
|
||||
self._collectionView.reloadItemsAtIndexPaths(updatedIndexPaths)
|
||||
self._collectionView.insertItemsAtIndexPaths(insertedIndexPaths)
|
||||
self._collectionView.deleteItems(at: deletedIndexPaths)
|
||||
self._collectionView.reloadItems(at: updatedIndexPaths)
|
||||
self._collectionView.insertItems(at: insertedIndexPaths)
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
@@ -151,46 +151,47 @@ class SwitchesViewController: UIViewController,
|
||||
|
||||
// MARK: UICollectionView
|
||||
|
||||
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
{
|
||||
return self.devices.count + ActionCell.count
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
|
||||
{
|
||||
if (indexPath.item < ActionCell.count) {
|
||||
let reuseID = SwitchesViewController.collectionViewActionCellReuseIdentifier
|
||||
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseID, forIndexPath: indexPath) as! WemoActionCellView
|
||||
cell.textLabel.text = ActionCell(rawValue: indexPath.item)?.name().uppercaseString
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoActionCellView
|
||||
cell.textLabel.text = ActionCell(rawValue: indexPath.item)?.name().uppercased()
|
||||
cell.enabled = (self.devices.count > 0)
|
||||
|
||||
return cell
|
||||
} else {
|
||||
let reuseID = SwitchesViewController.collectionViewDeviceSwitchCellReuseIdentifier
|
||||
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseID, forIndexPath: indexPath) as! WemoDeviceCellView
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) as! WemoDeviceCellView
|
||||
|
||||
let device = _deviceAtIndexPath(indexPath)
|
||||
cell.deviceName = device.name
|
||||
cell.toggled = (device.state == .On)
|
||||
cell.toggled = (device.state == .on)
|
||||
cell.ordinal = indexPath.item - ActionCell.count + 1
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView,
|
||||
func collectionView(_ collectionView: UICollectionView,
|
||||
layout collectionViewLayout: UICollectionViewLayout,
|
||||
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
|
||||
sizeForItemAt indexPath: IndexPath) -> CGSize
|
||||
{
|
||||
let spacing = SwitchesViewController.collectionViewCellsSpacing
|
||||
let bounds = collectionView.bounds
|
||||
var cellsPerRow: CGFloat = 0.0
|
||||
|
||||
switch (self.traitCollection.horizontalSizeClass) {
|
||||
case .Regular, .Compact where (bounds.size.width >= 400.0):
|
||||
case .regular where (bounds.size.width >= 400.0),
|
||||
.compact where (bounds.size.width >= 400.0):
|
||||
cellsPerRow = 3.0
|
||||
break
|
||||
case .Compact:
|
||||
case .compact:
|
||||
cellsPerRow = 2.0
|
||||
default:
|
||||
cellsPerRow = 2.0
|
||||
@@ -204,18 +205,18 @@ class SwitchesViewController: UIViewController,
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
if (indexPath.item < ActionCell.count) {
|
||||
let tappedActionCell = ActionCell(rawValue: indexPath.item)
|
||||
var currentDelay: NSTimeInterval = 0.0
|
||||
var currentDelay: TimeInterval = 0.0
|
||||
|
||||
for i in ActionCell.count ..< collectionView.numberOfItemsInSection(indexPath.section) {
|
||||
if let cell = collectionView.cellForItemAtIndexPath(NSIndexPath(forItem: i, inSection: indexPath.section)) as? WemoDeviceCellView {
|
||||
let animOptions = UIViewAnimationOptions([.AllowUserInteraction])
|
||||
for i in ActionCell.count ..< collectionView.numberOfItems(inSection: indexPath.section) {
|
||||
if let cell = collectionView.cellForItem(at: IndexPath(item: i, section: indexPath.section)) as? WemoDeviceCellView {
|
||||
let animOptions = UIViewAnimationOptions([.allowUserInteraction])
|
||||
|
||||
UIView.animateWithDuration(0.3, delay: currentDelay, options: animOptions, animations: {
|
||||
cell.toggled = (tappedActionCell == .AllOn)
|
||||
UIView.animate(withDuration: 0.3, delay: currentDelay, options: animOptions, animations: {
|
||||
cell.toggled = (tappedActionCell == .allOn)
|
||||
}, completion: nil)
|
||||
|
||||
currentDelay += 0.05
|
||||
@@ -223,22 +224,22 @@ class SwitchesViewController: UIViewController,
|
||||
}
|
||||
|
||||
for device in self.devices {
|
||||
device.state = (tappedActionCell == .AllOn ? .On : .Off)
|
||||
device.state = (tappedActionCell == .allOn ? .on : .off)
|
||||
}
|
||||
|
||||
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: self.devices)
|
||||
} else {
|
||||
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! WemoDeviceCellView
|
||||
let cell = collectionView.cellForItem(at: indexPath) as! WemoDeviceCellView
|
||||
cell.toggled = !cell.toggled
|
||||
|
||||
let device = _deviceAtIndexPath(indexPath)
|
||||
device.state = (cell.toggled ? .On : .Off)
|
||||
device.state = (cell.toggled ? .on : .off)
|
||||
|
||||
self.delegate?.switchesViewControllerDidToggleDevices(self, devices: [device])
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool
|
||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
|
||||
{
|
||||
if (indexPath.item < ActionCell.count) {
|
||||
return (self.devices.count > 0)
|
||||
@@ -247,7 +248,7 @@ class SwitchesViewController: UIViewController,
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool
|
||||
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
|
||||
{
|
||||
if (indexPath.item < ActionCell.count) {
|
||||
return (self.devices.count > 0)
|
||||
@@ -258,7 +259,7 @@ class SwitchesViewController: UIViewController,
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func _deviceAtIndexPath(indexPath: NSIndexPath) -> WemoDevice
|
||||
internal func _deviceAtIndexPath(_ indexPath: IndexPath) -> WemoDevice
|
||||
{
|
||||
let deviceIdx = indexPath.item - ActionCell.count
|
||||
let device = self.devices[deviceIdx]
|
||||
|
||||
@@ -11,31 +11,32 @@ import Foundation
|
||||
import GLKit
|
||||
import SceneKit
|
||||
import UIKit
|
||||
import SceneKit
|
||||
|
||||
let π = CGFloat(M_PI)
|
||||
let π = CGFloat(Double.pi)
|
||||
|
||||
class VisualizationViewController: UIViewController
|
||||
{
|
||||
private var _scene: SCNScene = SCNScene()
|
||||
private var _sceneView: SCNView?
|
||||
private var _cameraNode: SCNNode = SCNNode()
|
||||
private var _lightNode: SCNNode = SCNNode()
|
||||
private var _cubletsNode: SCNNode = SCNNode()
|
||||
private var _cublets: [SCNNode] = []
|
||||
private var _percentActivated: Float = 0.0
|
||||
fileprivate var _scene: SCNScene = SCNScene()
|
||||
fileprivate var _sceneView: SCNView?
|
||||
fileprivate var _cameraNode: SCNNode = SCNNode()
|
||||
fileprivate var _lightNode: SCNNode = SCNNode()
|
||||
fileprivate var _cubletsNode: SCNNode = SCNNode()
|
||||
fileprivate var _cublets: [SCNNode] = []
|
||||
fileprivate var _percentActivated: Float = 0.0
|
||||
|
||||
static private let cubletsDimensions = 5
|
||||
static private let cubletsSize = 1.0
|
||||
static private let cubletsSpacing = 2.0
|
||||
static private let rotationAnimationKey = "RotationAnimation"
|
||||
static fileprivate let cubletsDimensions = 5
|
||||
static fileprivate let cubletsSize = 1.0
|
||||
static fileprivate let cubletsSpacing = 2.0
|
||||
static fileprivate let rotationAnimationKey = "RotationAnimation"
|
||||
|
||||
// MARK: Overrides
|
||||
|
||||
override func loadView()
|
||||
{
|
||||
let opts = [SCNPreferredRenderingAPIKey : SCNRenderingAPI.OpenGLES2.rawValue]
|
||||
let view = SCNView(frame: UIScreen.mainScreen().bounds, options: opts)
|
||||
view.backgroundColor = UIColor.blackColor()
|
||||
let opts = [SCNView.Option.preferredRenderingAPI.rawValue : SCNRenderingAPI.openGLES2]
|
||||
let view = SCNView(frame: UIScreen.main.bounds, options: opts)
|
||||
view.backgroundColor = UIColor.black
|
||||
view.scene = _scene
|
||||
view.allowsCameraControl = false
|
||||
|
||||
@@ -55,13 +56,13 @@ class VisualizationViewController: UIViewController
|
||||
_beginModelResetTimer()
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool)
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
_sceneView?.play(nil)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(animated: Bool)
|
||||
override func viewDidDisappear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidDisappear(animated)
|
||||
_sceneView?.stop(nil)
|
||||
@@ -69,17 +70,17 @@ class VisualizationViewController: UIViewController
|
||||
|
||||
// MARK: API
|
||||
|
||||
var connectionStatus: ConnectionStatus = .Disconnected
|
||||
var connectionStatus: ConnectionStatus = .disconnected
|
||||
{
|
||||
didSet
|
||||
{
|
||||
switch (self.connectionStatus) {
|
||||
case .Disconnected, .Connecting, .Error:
|
||||
_cubletsNode.paused = true
|
||||
case .disconnected, .connecting, .error:
|
||||
_cubletsNode.isPaused = true
|
||||
_lightNode.light?.color = UIColor(white: 0.3, alpha: 1.0)
|
||||
case .Connected:
|
||||
_cubletsNode.paused = false
|
||||
_lightNode.light?.color = UIColor.whiteColor()
|
||||
case .connected:
|
||||
_cubletsNode.isPaused = false
|
||||
_lightNode.light?.color = UIColor.white
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +98,7 @@ class VisualizationViewController: UIViewController
|
||||
}
|
||||
}
|
||||
|
||||
func setPercentActivated(percentage: Float, animated: Bool)
|
||||
func setPercentActivated(_ percentage: Float, animated: Bool)
|
||||
{
|
||||
let cubletsCount = _cublets.count
|
||||
let cubletsToActivate = UInt(percentage * Float(cubletsCount))
|
||||
@@ -124,8 +125,8 @@ class VisualizationViewController: UIViewController
|
||||
let rotCoeff = CGFloat(arc4random() % 2 == 0 ? -1.0 : 1.0)
|
||||
let rotAngle = CGFloat(rotCoeff * π / 4.0)
|
||||
|
||||
let rotAction = SCNAction.rotateByAngle(rotAngle, aroundAxis: rotAxis, duration: 0.8)
|
||||
rotAction.timingMode = .Linear
|
||||
let rotAction = SCNAction.rotate(by: rotAngle, around: rotAxis, duration: 0.8)
|
||||
rotAction.timingMode = .linear
|
||||
rotAction.timingFunction = { (t: Float) -> Float in
|
||||
return min(((log10(4.0 * (t + 0.03)) + 1.0) / 1.5), 1.0)
|
||||
}
|
||||
@@ -134,7 +135,7 @@ class VisualizationViewController: UIViewController
|
||||
// smooth transition
|
||||
let longTermAnimKey = VisualizationViewController.rotationAnimationKey
|
||||
let newLongTermRotAction = _createLongTermRotationAnimation((rotCoeff * 2.0 * π), rotAxis)
|
||||
_cubletsNode.removeActionForKey(longTermAnimKey)
|
||||
_cubletsNode.removeAction(forKey: longTermAnimKey)
|
||||
|
||||
let actionSeq = SCNAction.sequence([rotAction, newLongTermRotAction])
|
||||
_cubletsNode.runAction(actionSeq, forKey: longTermAnimKey)
|
||||
@@ -166,8 +167,8 @@ class VisualizationViewController: UIViewController
|
||||
internal func _setupLights()
|
||||
{
|
||||
let light = SCNLight()
|
||||
light.type = SCNLightTypeOmni
|
||||
light.color = UIColor.whiteColor()
|
||||
light.type = SCNLight.LightType.omni
|
||||
light.color = UIColor.white
|
||||
|
||||
_lightNode = SCNNode()
|
||||
_lightNode.light = light
|
||||
@@ -178,7 +179,7 @@ class VisualizationViewController: UIViewController
|
||||
|
||||
internal func _setupModel()
|
||||
{
|
||||
_cubletsNode.enumerateChildNodesUsingBlock { $0.0.removeFromParentNode() }
|
||||
_cubletsNode.enumerateChildNodes { $0.0.removeFromParentNode() }
|
||||
_cublets.removeAll()
|
||||
|
||||
let sz = Float(VisualizationViewController.cubletsSize)
|
||||
@@ -191,7 +192,7 @@ class VisualizationViewController: UIViewController
|
||||
// setup material and geometry. each node needs its own material for the activation effect.
|
||||
let geom = SCNBox(width: CGFloat(sz), height: CGFloat(sz), length: CGFloat(sz), chamferRadius: 0.0)
|
||||
let material = SCNMaterial()
|
||||
material.diffuse.contents = UIColor.whiteColor()
|
||||
material.diffuse.contents = UIColor.white
|
||||
material.transparency = 0.75
|
||||
|
||||
// generate nodes for each cublet
|
||||
@@ -216,7 +217,7 @@ class VisualizationViewController: UIViewController
|
||||
}
|
||||
|
||||
_cubletsNode.position = SCNVector3Zero
|
||||
if (_cubletsNode.parentNode == nil) {
|
||||
if (_cubletsNode.parent == nil) {
|
||||
_scene.rootNode.addChildNode(_cubletsNode)
|
||||
}
|
||||
}
|
||||
@@ -232,8 +233,8 @@ class VisualizationViewController: UIViewController
|
||||
|
||||
internal func _setupEffects()
|
||||
{
|
||||
let techniqueURL = NSBundle.mainBundle().URLForResource("CubletsTechnique", withExtension: "plist")
|
||||
let techniqueDict = NSDictionary(contentsOfURL: techniqueURL!) as! [String : AnyObject]
|
||||
let techniqueURL = Bundle.main.url(forResource: "CubletsTechnique", withExtension: "plist")
|
||||
let techniqueDict = NSDictionary(contentsOf: techniqueURL!) as! [String : AnyObject]
|
||||
let technique = SCNTechnique(dictionary: techniqueDict)
|
||||
|
||||
_sceneView?.technique = technique
|
||||
@@ -244,8 +245,8 @@ class VisualizationViewController: UIViewController
|
||||
/* since this visualization is running all the time, trigonometric functions begin
|
||||
malfunctioning at very large numbers. just reload the model every 24 hours so we
|
||||
don't have to see it */
|
||||
let reloadModelTime = dispatch_time(DISPATCH_TIME_NOW, Int64(60 * 60 * 24 * NSEC_PER_SEC))
|
||||
dispatch_after(reloadModelTime, dispatch_get_main_queue()) { [weak self] in
|
||||
let reloadModelTime = DispatchTime.now() + Double(Int64(60 * 60 * 24 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)
|
||||
DispatchQueue.main.asyncAfter(deadline: reloadModelTime) { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf._cubletsNode.removeFromParentNode()
|
||||
strongSelf._cubletsNode = SCNNode()
|
||||
@@ -263,25 +264,25 @@ class VisualizationViewController: UIViewController
|
||||
}
|
||||
}
|
||||
|
||||
internal func _setCubletActivated(cublet: SCNNode, activated: Bool)
|
||||
internal func _setCubletActivated(_ cublet: SCNNode, activated: Bool)
|
||||
{
|
||||
let material = cublet.geometry?.firstMaterial
|
||||
material?.diffuse.contents = (activated ? UIColor.redColor() : UIColor.whiteColor())
|
||||
material?.diffuse.contents = (activated ? UIColor.red : UIColor.white)
|
||||
}
|
||||
|
||||
internal func _setAnimationSpeed(speed: CGFloat)
|
||||
internal func _setAnimationSpeed(_ speed: CGFloat)
|
||||
{
|
||||
let key = VisualizationViewController.rotationAnimationKey
|
||||
if let action = _cubletsNode.actionForKey(key) {
|
||||
_cubletsNode.removeActionForKey(key)
|
||||
if let action = _cubletsNode.action(forKey: key) {
|
||||
_cubletsNode.removeAction(forKey: key)
|
||||
|
||||
action.speed = speed
|
||||
_cubletsNode.runAction(action, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
internal func _createLongTermRotationAnimation(rotAngle: CGFloat, _ rotAxis: SCNVector3) -> SCNAction
|
||||
internal func _createLongTermRotationAnimation(_ rotAngle: CGFloat, _ rotAxis: SCNVector3) -> SCNAction
|
||||
{
|
||||
return SCNAction.repeatActionForever(SCNAction.rotateByAngle(rotAngle, aroundAxis: rotAxis, duration: 40.0))
|
||||
return SCNAction.repeatForever(SCNAction.rotate(by: rotAngle, around: rotAxis, duration: 40.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,40 +10,46 @@ import Foundation
|
||||
|
||||
enum ConnectionStatus
|
||||
{
|
||||
case Disconnected
|
||||
case Connecting
|
||||
case Connected
|
||||
case Error
|
||||
case disconnected
|
||||
case connecting
|
||||
case connected
|
||||
case error
|
||||
}
|
||||
|
||||
enum ConnectionError : Error
|
||||
{
|
||||
case unknown
|
||||
case serverUnavailable
|
||||
}
|
||||
|
||||
class WemoServer
|
||||
{
|
||||
private(set) var baseURL: NSURL
|
||||
private(set) var connected: Bool = false
|
||||
fileprivate(set) var baseURL: URL
|
||||
fileprivate(set) var connected: Bool = false
|
||||
|
||||
private var _urlSession: NSURLSession
|
||||
private var _errorStream: StandardErrorOutputStream = StandardErrorOutputStream()
|
||||
private var _operationQueue: NSOperationQueue = NSOperationQueue()
|
||||
fileprivate var _urlSession: URLSession
|
||||
fileprivate var _errorStream: StandardErrorOutputStream = StandardErrorOutputStream()
|
||||
fileprivate var _operationQueue: OperationQueue = OperationQueue()
|
||||
|
||||
init(_ url: NSURL)
|
||||
init(_ url: URL)
|
||||
{
|
||||
self.baseURL = url
|
||||
|
||||
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
|
||||
_urlSession = NSURLSession(configuration: config)
|
||||
let config = URLSessionConfiguration.default
|
||||
_urlSession = URLSession(configuration: config)
|
||||
|
||||
_operationQueue.maxConcurrentOperationCount = 1
|
||||
}
|
||||
|
||||
func connect(completion: (NSError?) -> Void)
|
||||
func connect(_ completion: @escaping (Error?) -> Void)
|
||||
{
|
||||
if (!self.connected) {
|
||||
let op = ConnectOperation(baseURL: self.baseURL, session: _urlSession)
|
||||
weak var weakOp = op
|
||||
op.completionBlock = {
|
||||
guard let strongOp = weakOp else { completion(nil) ; return }
|
||||
if (strongOp.error != nil) {
|
||||
self._logError("Error connecting to server", error: strongOp.error!)
|
||||
if let error = strongOp.error {
|
||||
self._logError("Error connecting to server", error: error)
|
||||
} else {
|
||||
self.connected = true
|
||||
}
|
||||
@@ -56,68 +62,68 @@ class WemoServer
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect(completion: (NSError?) -> Void)
|
||||
func disconnect(_ completion: (Error?) -> Void)
|
||||
{
|
||||
self.connected = false
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
func fetchDevices(completion: ([WemoDevice], NSError?) -> Void)
|
||||
func fetchDevices(_ completion: @escaping ([WemoDevice], Error?) -> Void)
|
||||
{
|
||||
if (self.connected) {
|
||||
let op = FetchDevicesOperation(baseURL: self.baseURL, session: _urlSession)
|
||||
weak var weakOp = op
|
||||
op.completionBlock = {
|
||||
guard let strongOp = weakOp else { completion([], nil) ; return }
|
||||
if (strongOp.error != nil) {
|
||||
self._logError("Error fetching devices", error: strongOp.error!)
|
||||
if let error = strongOp.error {
|
||||
self._logError("Error fetching devices", error: error)
|
||||
}
|
||||
|
||||
completion(strongOp.devices, strongOp.error)
|
||||
}
|
||||
_operationQueue.addOperation(op)
|
||||
} else {
|
||||
let err = NSError.xionError(.ConnectionError)
|
||||
let err = ConnectionError.serverUnavailable
|
||||
completion([], err)
|
||||
}
|
||||
}
|
||||
|
||||
func toggleDevice(device: WemoDevice, state: WemoDevice.State, completion: (NSError?) -> Void)
|
||||
func toggleDevice(_ device: WemoDevice, state: WemoDevice.State, completion: @escaping (Error?) -> Void)
|
||||
{
|
||||
if (self.connected) {
|
||||
let op = ToggleDeviceOperation(baseURL: self.baseURL, session: _urlSession, device: device, state: state)
|
||||
weak var weakOp = op
|
||||
op.completionBlock = {
|
||||
guard let strongOp = weakOp else { completion(nil) ; return }
|
||||
if (strongOp.error != nil) {
|
||||
self._logError("Error toggling device", error: strongOp.error!)
|
||||
if let error = strongOp.error {
|
||||
self._logError("Error toggling device", error: error)
|
||||
}
|
||||
|
||||
completion(strongOp.error)
|
||||
}
|
||||
_operationQueue.addOperation(op)
|
||||
} else {
|
||||
let err = NSError.xionError(.ConnectionError)
|
||||
let err = ConnectionError.serverUnavailable
|
||||
completion(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func _logError(description: String, error: NSError)
|
||||
internal func _logError(_ description: String, error: Error)
|
||||
{
|
||||
print("ERROR: \(description) \(error)", toStream: &_errorStream)
|
||||
print("ERROR: \(description) \(error)", to: &_errorStream)
|
||||
}
|
||||
}
|
||||
|
||||
internal class WemoOperation : NSOperation
|
||||
internal class WemoOperation : Operation
|
||||
{
|
||||
var baseURL: NSURL
|
||||
var session: NSURLSession
|
||||
var baseURL: URL
|
||||
var session: URLSession
|
||||
|
||||
internal(set) var error: NSError?
|
||||
internal(set) var error: Error?
|
||||
|
||||
init(baseURL: NSURL, session: NSURLSession)
|
||||
init(baseURL: URL, session: URLSession)
|
||||
{
|
||||
self.baseURL = baseURL
|
||||
self.session = session
|
||||
@@ -129,16 +135,14 @@ internal class ConnectOperation : WemoOperation
|
||||
override func main()
|
||||
{
|
||||
let semaphore = Semaphore(value: 0)
|
||||
let url = self.baseURL.URLByAppendingPathComponent("api/environment")
|
||||
let request = NSMutableURLRequest(URL: url)
|
||||
request.HTTPMethod = "POST"
|
||||
let url = self.baseURL.appendingPathComponent("api/environment")
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let task = self.session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
|
||||
if (error != nil) {
|
||||
self.error = NSError.xionError(.ConnectionError, underlying: error!)
|
||||
}
|
||||
let task = self.session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
|
||||
self.error = error
|
||||
semaphore.signal()
|
||||
}
|
||||
})
|
||||
task.resume()
|
||||
semaphore.wait()
|
||||
}
|
||||
@@ -151,24 +155,24 @@ internal class FetchDevicesOperation : WemoOperation
|
||||
override func main()
|
||||
{
|
||||
let semaphore = Semaphore(value: 0)
|
||||
let url = self.baseURL.URLByAppendingPathComponent("api/environment")
|
||||
let task = self.session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
|
||||
let url = self.baseURL.appendingPathComponent("api/environment")
|
||||
let task = self.session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in
|
||||
if (data != nil) {
|
||||
self.devices = self._parseDevices(data!)
|
||||
} else {
|
||||
self.error = NSError.xionError(.ConnectionError, underlying: error)
|
||||
self.error = NSError.xionError(.connectionError, underlying: error)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
} as! (Data?, URLResponse?, Error?) -> Void)
|
||||
task.resume()
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
internal func _parseDevices(data: NSData) -> [WemoDevice]
|
||||
internal func _parseDevices(_ data: Data) -> [WemoDevice]
|
||||
{
|
||||
var devices: [WemoDevice] = []
|
||||
|
||||
if let responseDict = (try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? NSDictionary) {
|
||||
if let responseDict = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? NSDictionary) {
|
||||
for responseObj in (responseDict?.allValues)! {
|
||||
if let responseDict = responseObj as? NSDictionary {
|
||||
let device = WemoDevice(responseDict)
|
||||
@@ -186,7 +190,7 @@ internal class ToggleDeviceOperation : WemoOperation
|
||||
var device: WemoDevice
|
||||
var state: WemoDevice.State
|
||||
|
||||
init(baseURL: NSURL, session: NSURLSession, device: WemoDevice, state: WemoDevice.State)
|
||||
init(baseURL: URL, session: URLSession, device: WemoDevice, state: WemoDevice.State)
|
||||
{
|
||||
self.device = device
|
||||
self.state = state
|
||||
@@ -196,15 +200,13 @@ internal class ToggleDeviceOperation : WemoOperation
|
||||
override func main()
|
||||
{
|
||||
let semaphore = Semaphore(value: 0)
|
||||
let stateArg = (self.state == .On ? "on" : "off")
|
||||
let url = self.baseURL.URLByAppendingPathComponent("api/device/\(self.device.name)").URLByAppendingRequestParameters(["state" : stateArg])
|
||||
let request = NSMutableURLRequest(URL: url!)
|
||||
request.HTTPMethod = "POST"
|
||||
let stateArg = (self.state == .on ? "on" : "off")
|
||||
let url = self.baseURL.appendingPathComponent("api/device/\(self.device.name)").URLByAppendingRequestParameters(["state" : stateArg])
|
||||
var request = URLRequest(url: url!)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let task = self.session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
|
||||
if (error != nil) {
|
||||
self.error = NSError.xionError(.ConnectionError, underlying: error)
|
||||
}
|
||||
let task = self.session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
|
||||
self.error = error
|
||||
semaphore.signal()
|
||||
}
|
||||
task.resume()
|
||||
|
||||
Reference in New Issue
Block a user