diff --git a/OSX/DeckRocket/MultipeerClient.swift b/OSX/DeckRocket/MultipeerClient.swift index 7bab1e0..1a12a15 100644 --- a/OSX/DeckRocket/MultipeerClient.swift +++ b/OSX/DeckRocket/MultipeerClient.swift @@ -11,7 +11,8 @@ import MultipeerConnectivity typealias stateChange = ((state: MCSessionState) -> ())? private typealias KVOContext = UInt8 -private var ProgressContext = KVOContext() +private var progressContext = KVOContext() +private var lastDisplayTime = NSDate() final class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSessionDelegate { @@ -45,7 +46,7 @@ final class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSess if let peer = session?.connectedPeers[0] as? MCPeerID { pdfProgress = session?.sendResourceAtURL(url, withName: filePath.lastPathComponent, toPeer: peer) { error in dispatch_async(dispatch_get_main_queue()) { - self.pdfProgress?.removeObserver(self, forKeyPath: "fractionCompleted", context: &ProgressContext) + self.pdfProgress?.removeObserver(self, forKeyPath: "fractionCompleted", context: &progressContext) if let errorDescription = error?.localizedDescription { HUDView.show("Error!\n\(errorDescription)") } else { @@ -53,7 +54,7 @@ final class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSess } } } - pdfProgress?.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &ProgressContext) + pdfProgress?.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &progressContext) } } @@ -96,14 +97,15 @@ final class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSess // MARK: KVO override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<()>) { - if context == &ProgressContext { - dispatch_async(dispatch_get_main_queue()) { + if context != &progressContext { + super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) + } else if abs(lastDisplayTime.timeIntervalSinceNow) > 1/60 { // Update HUD at no more than 60fps + dispatch_sync(dispatch_get_main_queue()) { if let progress = change[NSKeyValueChangeNewKey] as? CGFloat { HUDView.showProgress(progress, string: "Sending File to iPhone") + lastDisplayTime = NSDate() } } - } else { - super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) } } } diff --git a/README.md b/README.md index 9850d19..18be833 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Requirements -DeckRocket is built in Swift and relies on Multipeer Connectivity on both OSX and iOS. Xcode 6.3b, OS X 10.10 and iOS 8 are all required to build, install and use DeckRocket. +DeckRocket is built in Swift and relies on Multipeer Connectivity on both OSX and iOS. Xcode 6.3b2, OS X 10.10 and iOS 8 are all required to build, install and use DeckRocket. ## Usage diff --git a/iOS/DeckRocket/MultipeerClient.swift b/iOS/DeckRocket/MultipeerClient.swift index 7462533..bcfe398 100644 --- a/iOS/DeckRocket/MultipeerClient.swift +++ b/iOS/DeckRocket/MultipeerClient.swift @@ -33,9 +33,7 @@ final class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSession // MARK: Send func send(data: NSData) { - if let session = session { - session.sendData(data, toPeers: session.connectedPeers, withMode: .Reliable, error: nil) - } + session?.sendData(data, toPeers: session!.connectedPeers, withMode: .Reliable, error: nil) // Safe to force unwrap } func sendString(string: NSString) { @@ -79,32 +77,25 @@ final class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSession func session(session: MCSession!, didFinishReceivingResourceWithName resourceName: String!, fromPeer peerID: MCPeerID!, atURL localURL: NSURL!, withError error: NSError!) { if error == nil { - // FIXME: Switch on FileType once it works - // TODO: File radar for this - if contains(FileType.extensionsForType(.PDF), resourceName.pathExtension) { - handlePDF(resourceName, atURL: localURL) - } else if contains(FileType.extensionsForType(.Markdown), resourceName.pathExtension) { - handleMarkdown(resourceName, atURL: localURL) + if let fileType = FileType(fileExtension: resourceName.pathExtension) { + switch fileType { + case .PDF: + handlePDF(resourceName, atURL: localURL) + case .Markdown: + handleMarkdown(resourceName, atURL: localURL) + } } -// if let fileType = FileType(fileExtension: resourceName.pathExtension) { -// switch fileType { -// case .PDF: -// handlePDF(resourceName, atURL: localURL) -// case .Markdown: -// handleMarkdown(resourceName, atURL: localURL) -// } -// } } } // MARK: Handle Resources private func handlePDF(resourceName: String!, atURL localURL: NSURL!) { - promptToLoadResource("New Presentation File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "pdfPath") + promptToLoadResource("New Presentation File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "pdfName") } private func handleMarkdown(resourceName: String!, atURL localURL: NSURL!) { - promptToLoadResource("New Markdown File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "mdPath") + promptToLoadResource("New Markdown File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "mdName") } private func promptToLoadResource(title: String, resourceName: String, atURL localURL: NSURL, userDefaultsKey: String) { @@ -113,22 +104,21 @@ final class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSession let alert = UIAlertController(title: title, message: "Would you like to load \"\(resourceName)\"?", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)) alert.addAction(UIAlertAction(title: "Load", style: .Default) { action in - if let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as? NSString { - let filePath = documentsPath.stringByAppendingPathComponent(resourceName) + let filePath = documentsPath.stringByAppendingPathComponent(resourceName) + let fileManager = NSFileManager.defaultManager() - var error: NSError? = nil - if NSFileManager.defaultManager().fileExistsAtPath(filePath) { - NSFileManager.defaultManager().removeItemAtPath(filePath, error: &error) - } + var error: NSError? = nil + if fileManager.fileExistsAtPath(filePath) { + fileManager.removeItemAtPath(filePath, error: &error) + } - if let url = NSURL(fileURLWithPath: filePath) { - NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: url, error: &error) - if error == nil { - NSUserDefaults.standardUserDefaults().setObject(filePath, forKey: userDefaultsKey) - NSUserDefaults.standardUserDefaults().synchronize() - rootVC?.updatePresentation() - } - } + if let url = NSURL(fileURLWithPath: filePath) where fileManager.moveItemAtURL(localURL, toURL: url, error: &error) { + NSUserDefaults.standardUserDefaults().setObject(resourceName, forKey: userDefaultsKey) + NSUserDefaults.standardUserDefaults().synchronize() + rootVC?.updatePresentation() + } else { + let message = error?.localizedDescription ?? "move file failed with no error" + fatalError(message) } }) dispatch_async(dispatch_get_main_queue()) { diff --git a/iOS/DeckRocket/PDFImages.swift b/iOS/DeckRocket/PDFImages.swift index 7939db9..82019b2 100644 --- a/iOS/DeckRocket/PDFImages.swift +++ b/iOS/DeckRocket/PDFImages.swift @@ -12,8 +12,8 @@ import CoreGraphics extension UIImage { static func imagesFromPDFPath(pdfPath: String) -> [UIImage] { - if let pdfURL = NSURL(fileURLWithPath: pdfPath) { - let pdf = CGPDFDocumentCreateWithURL(pdfURL) + if let pdfURL = NSURL(fileURLWithPath: pdfPath), + let pdf = CGPDFDocumentCreateWithURL(pdfURL) { let numberOfPages = CGPDFDocumentGetNumberOfPages(pdf) if numberOfPages == 0 { @@ -25,7 +25,7 @@ extension UIImage { let largestDimension = max(screenSize.width, screenSize.height) let largestSize = CGSize(width: largestDimension, height: largestDimension) - for pageNumber in 1...numberOfPages { + for pageNumber in 1...Int(numberOfPages) { if let image = UIImage(pdfURL: pdfURL, page: pageNumber, fitSize: largestSize) { images.append(image) } @@ -36,13 +36,13 @@ extension UIImage { return [] } - private static func pdfRectForURL(url: NSURL, page: UInt) -> CGRect { + private static func pdfRectForURL(url: NSURL, page: Int) -> CGRect { let pdf = CGPDFDocumentCreateWithURL(url) let pageRef = CGPDFDocumentGetPage(pdf, page) return CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox) } - convenience init?(pdfURL: NSURL, page: UInt, size: CGSize) { + convenience init?(pdfURL: NSURL, page: Int, size: CGSize) { UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale) let ctx = UIGraphicsGetCurrentContext() @@ -66,7 +66,7 @@ extension UIImage { self.init(CGImage: pdfImage.CGImage) } - convenience init?(pdfURL: NSURL, page: UInt, fitSize size: CGSize) { + convenience init?(pdfURL: NSURL, page: Int, fitSize size: CGSize) { let rect = UIImage.pdfRectForURL(pdfURL, page: page) let scaleFactor = max(rect.size.width / size.width, rect.size.height / size.height) let newWidth = ceil(rect.size.width / scaleFactor) diff --git a/iOS/DeckRocket/ViewController.swift b/iOS/DeckRocket/ViewController.swift index 5bce9fa..36dd7ac 100644 --- a/iOS/DeckRocket/ViewController.swift +++ b/iOS/DeckRocket/ViewController.swift @@ -9,16 +9,27 @@ import UIKit import MultipeerConnectivity +let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! NSString + private func userDefaultsString(key: String) -> String? { return NSUserDefaults.standardUserDefaults().objectForKey(key) as? NSString as? String } +private func userDefaultsPathIfFileExists(key: String) -> String? { + if let name = userDefaultsString(key), let path = Optional(documentsPath.stringByAppendingPathComponent(name)) where NSFileManager.defaultManager().fileExistsAtPath(path) { + return path + } + return nil +} + final class ViewController: UICollectionViewController, UIScrollViewDelegate { // MARK: Properties private var presentation: Presentation? private let multipeerClient = MultipeerClient() + // UIVisualEffectView's alpha can't be animated, so we nest it in a parent view + private let effectParentView = UIView() private let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark)) private let notesView = UITextView() private let nextSlideView = UIImageView() @@ -52,23 +63,23 @@ final class ViewController: UICollectionViewController, UIScrollViewDelegate { switch state { case .NotConnected: if self.multipeerClient.session == nil { - self.effectView.alpha = 1 + self.effectParentView.alpha = 1 self.infoLabel.text = "Not Connected" } else if self.multipeerClient.session!.connectedPeers.count == 0 { // Safe to force unwrap - self.effectView.alpha = 1 + self.effectParentView.alpha = 1 self.infoLabel.text = "Not Connected" self.multipeerClient.browser?.invitePeer(peerID, toSession: self.multipeerClient.session, withContext: nil, timeout: 30) } case .Connected: if let presentation = self.presentation { - self.effectView.alpha = 0 + self.effectParentView.alpha = 0 self.infoLabel.text = "" } else { - self.effectView.alpha = 1 + self.effectParentView.alpha = 1 self.infoLabel.text = "No Presentation Loaded" } case .Connecting: - self.effectView.alpha = 1 + self.effectParentView.alpha = 1 self.infoLabel.text = "Connecting..." } }) @@ -78,9 +89,9 @@ final class ViewController: UICollectionViewController, UIScrollViewDelegate { // MARK: Presentation Updates func updatePresentation() { - if let pdfPath = userDefaultsString("pdfPath") { + if let pdfPath = userDefaultsPathIfFileExists("pdfName") { let markdown: String? - if let mdPath = userDefaultsString("mdPath") { + if let mdPath = userDefaultsPathIfFileExists("mdName") { markdown = String(contentsOfFile: mdPath, encoding: NSUTF8StringEncoding) } else { markdown = nil @@ -111,13 +122,21 @@ final class ViewController: UICollectionViewController, UIScrollViewDelegate { } private func setupEffectView() { - effectView.setTranslatesAutoresizingMaskIntoConstraints(false) - view.addSubview(effectView) + effectParentView.setTranslatesAutoresizingMaskIntoConstraints(false) + view.addSubview(effectParentView) - let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[effectView]|", options: nil, metrics: nil, views: ["effectView": effectView]) - let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[effectView]|", options: nil, metrics: nil, views: ["effectView": effectView]) + let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[effectParentView]|", options: nil, metrics: nil, views: ["effectParentView": effectParentView]) + let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[effectParentView]|", options: nil, metrics: nil, views: ["effectParentView": effectParentView]) view.addConstraints(horizontal) view.addConstraints(vertical) + + effectView.setTranslatesAutoresizingMaskIntoConstraints(false) + effectParentView.addSubview(effectView) + + let horizontal2 = NSLayoutConstraint.constraintsWithVisualFormat("|[effectView]|", options: nil, metrics: nil, views: ["effectView": effectView]) + let vertical2 = NSLayoutConstraint.constraintsWithVisualFormat("V:|[effectView]|", options: nil, metrics: nil, views: ["effectView": effectView]) + effectParentView.addConstraints(horizontal2) + effectParentView.addConstraints(vertical2) } private func setupInfoLabel() { @@ -213,10 +232,8 @@ final class ViewController: UICollectionViewController, UIScrollViewDelegate { break default: // Don't do anything if the effect view is now being used to show a connectivity message - if let session = multipeerClient.session { - if session.connectedPeers.count > 0 { - showNotes(false) - } + if let session = multipeerClient.session where session.connectedPeers.count > 0 { + showNotes(false) } } } @@ -233,11 +250,12 @@ final class ViewController: UICollectionViewController, UIScrollViewDelegate { } else { nextSlideView.image = nil } + let alpha = CGFloat(show) UIView.animateWithDuration(0.25, animations: { - self.effectView.alpha = CGFloat(show) + self.effectParentView.alpha = alpha }) { finished in - self.notesView.alpha = CGFloat(show) - self.nextSlideView.alpha = CGFloat(show) + self.notesView.alpha = alpha + self.nextSlideView.alpha = alpha } } }