diff --git a/OSX/DeckRocket/AppDelegate.swift b/OSX/DeckRocket/AppDelegate.swift
index 040c395..e19883d 100644
--- a/OSX/DeckRocket/AppDelegate.swift
+++ b/OSX/DeckRocket/AppDelegate.swift
@@ -9,18 +9,18 @@
import Cocoa
import MultipeerConnectivity
-class AppDelegate: NSObject, NSApplicationDelegate {
+final class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: Properties
let multipeerClient = MultipeerClient()
- let menuView = MenuView()
+ private let menuView = MenuView()
// MARK: App
- func applicationDidFinishLaunching(aNotification: NSNotification?) {
- multipeerClient.onStateChange = {(state: MCSessionState) -> () in
- var stateString = ""
+ func applicationDidFinishLaunching(aNotification: NSNotification) {
+ multipeerClient.onStateChange = { state in
+ let stateString: String
switch state {
case .NotConnected:
stateString = "Not Connected"
@@ -30,7 +30,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
stateString = "Connected"
}
dispatch_async(dispatch_get_main_queue()) {
- self.menuView.menu!.itemAtIndex(0)!.title = stateString
+ self.menuView.menu?.itemAtIndex(0)?.title = stateString
}
}
}
diff --git a/OSX/DeckRocket/HUDView.swift b/OSX/DeckRocket/HUDView.swift
index 69ee0b3..22a5707 100644
--- a/OSX/DeckRocket/HUDView.swift
+++ b/OSX/DeckRocket/HUDView.swift
@@ -8,14 +8,14 @@
import Cocoa
-let hudWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 300, height: 300),
+private let hudWindow = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 300, height: 300),
styleMask: NSBorderlessWindowMask,
backing: .Buffered,
defer: false)
-class HUDView: NSView {
+final class HUDView: NSView {
- override class func initialize() {
+ override static func initialize() {
hudWindow.backgroundColor = NSColor.clearColor()
hudWindow.opaque = false
hudWindow.makeKeyAndOrderFront(NSApp)
@@ -25,8 +25,10 @@ class HUDView: NSView {
DJProgressHUD.setBackgroundAlpha(0, disableActions: false)
}
- class func show(string: String) {
- DJProgressHUD.showProgress(1, withStatus: string, fromView: hudWindow.contentView as NSView)
+ static func show(string: String) {
+ if let windowView = hudWindow.contentView as? NSView {
+ DJProgressHUD.showProgress(1, withStatus: string, fromView: windowView)
+ }
let delay = 2 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
@@ -34,15 +36,19 @@ class HUDView: NSView {
}
}
- class func showProgress(progress: CGFloat, string: String) {
- DJProgressHUD.showProgress(progress, withStatus: string, fromView: hudWindow.contentView as NSView)
+ static func showProgress(progress: CGFloat, string: String) {
+ if let windowView = hudWindow.contentView as? NSView {
+ DJProgressHUD.showProgress(progress, withStatus: string, fromView: windowView)
+ }
}
- class func showWithActivity(string: String) {
- DJProgressHUD.showStatus(string, fromView: hudWindow.contentView as NSView)
+ static func showWithActivity(string: String) {
+ if let windowView = hudWindow.contentView as? NSView {
+ DJProgressHUD.showStatus(string, fromView: windowView)
+ }
}
- class func dismiss() {
+ static func dismiss() {
DJProgressHUD.dismiss()
}
}
diff --git a/OSX/DeckRocket/Info.plist b/OSX/DeckRocket/Info.plist
index 0155588..c091d20 100644
--- a/OSX/DeckRocket/Info.plist
+++ b/OSX/DeckRocket/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.0.3
+ 0.0.4
CFBundleSignature
????
CFBundleVersion
diff --git a/OSX/DeckRocket/MenuView.swift b/OSX/DeckRocket/MenuView.swift
index b999183..8c15679 100644
--- a/OSX/DeckRocket/MenuView.swift
+++ b/OSX/DeckRocket/MenuView.swift
@@ -8,12 +8,13 @@
import Cocoa
-class MenuView: NSView, NSMenuDelegate {
- var highlight = false
+// FIXME: Use system-defined constant once accessible from Swift.
+let NSVariableStatusItemLength: CGFloat = -1
- // NSVariableStatusItemLength == -1
- // Not using symbol because it doesn't link properly in Swift
- let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
+final class MenuView: NSView, NSMenuDelegate {
+ private var highlight = false
+
+ private let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
// MARK: Initializers
@@ -30,26 +31,28 @@ class MenuView: NSView, NSMenuDelegate {
// MARK: Menu
- func setupMenu() {
+ private func setupMenu() {
let menu = NSMenu()
menu.addItemWithTitle("Not Connected", action: nil, keyEquivalent: "")
- menu.itemAtIndex(0)!.enabled = false
+ menu.itemAtIndex(0)?.enabled = false
menu.addItemWithTitle("Quit DeckRocket", action: "quit", keyEquivalent: "")
self.menu = menu
- self.menu!.delegate = self
+ self.menu?.delegate = self
}
override func mouseDown(theEvent: NSEvent) {
super.mouseDown(theEvent)
- statusItem.popUpStatusItemMenu(menu!)
+ if let menu = menu {
+ statusItem.popUpStatusItemMenu(menu)
+ }
}
- func menuWillOpen(menu: NSMenu!) {
+ func menuWillOpen(menu: NSMenu) {
highlight = true
needsDisplay = true
}
- func menuDidClose(menu: NSMenu!) {
+ func menuDidClose(menu: NSMenu) {
highlight = false
needsDisplay = true
}
@@ -63,32 +66,33 @@ class MenuView: NSView, NSMenuDelegate {
// MARK: Dragging
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
- return NSDragOperation.Copy
+ return .Copy
}
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
let pboard = sender.draggingPasteboard()
- if contains(pboard.types as [NSString], NSFilenamesPboardType) {
- let files = pboard.propertyListForType(NSFilenamesPboardType) as [String]
- let file = files[0]
- if validateFile(file) {
- let appDelegate = NSApplication.sharedApplication().delegate as AppDelegate
- appDelegate.multipeerClient.sendFile(file)
+ if contains((pboard.types as? [String]) ?? [], NSFilenamesPboardType) {
+ if let file = (pboard.propertyListForType(NSFilenamesPboardType) as? [String])?.first {
+ if validateFile(file) {
+ let appDelegate = NSApplication.sharedApplication().delegate as? AppDelegate
+ appDelegate?.multipeerClient.sendFile(file)
+ } else {
+ HUDView.show("Error!\nOnly PDF and Markdown files can be sent")
+ }
} else {
- HUDView.show("Error!\nOnly PDF and Markdown files can be sent")
+ HUDView.show("Error!\nFile not found")
}
}
return true
}
- func validateFile(filePath: NSString) -> Bool {
- var allowedExtensions = [
+ private func validateFile(filePath: NSString) -> Bool {
+ let allowedExtensions = [
// Markdown
"markdown", "mdown", "mkdn", "md", "mkd", "mdwn", "mdtxt", "mdtext", "text",
// PDF
"pdf"
]
-
return contains(allowedExtensions, filePath.pathExtension.lowercaseString)
}
}
diff --git a/OSX/DeckRocket/MultipeerClient.swift b/OSX/DeckRocket/MultipeerClient.swift
index 93fe175..1a12a15 100644
--- a/OSX/DeckRocket/MultipeerClient.swift
+++ b/OSX/DeckRocket/MultipeerClient.swift
@@ -10,26 +10,27 @@ import Foundation
import MultipeerConnectivity
typealias stateChange = ((state: MCSessionState) -> ())?
-typealias KVOContext = UInt8
-var ProgressContext = KVOContext()
+private typealias KVOContext = UInt8
+private var progressContext = KVOContext()
+private var lastDisplayTime = NSDate()
-class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSessionDelegate {
+final class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSessionDelegate {
// MARK: Properties
- let localPeerID = MCPeerID(displayName: NSHost.currentHost().localizedName)
- let advertiser: MCNearbyServiceAdvertiser?
- var session: MCSession?
+ private let localPeerID = MCPeerID(displayName: NSHost.currentHost().localizedName)
+ private let advertiser: MCNearbyServiceAdvertiser?
+ private var session: MCSession?
+ private var pdfProgress: NSProgress?
var onStateChange: stateChange?
- var pdfProgress: NSProgress?
// MARK: Lifecycle
override init() {
- super.init()
advertiser = MCNearbyServiceAdvertiser(peer: localPeerID, discoveryInfo: nil, serviceType: "deckrocket")
- advertiser!.delegate = self
- advertiser!.startAdvertisingPeer()
+ super.init()
+ advertiser?.delegate = self
+ advertiser?.startAdvertisingPeer()
}
// MARK: Send File
@@ -37,46 +38,48 @@ class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSessionDel
func sendFile(filePath: String) {
let url = NSURL(fileURLWithPath: filePath)
- if session == nil || session!.connectedPeers.count == 0 {
+ if session == nil || session!.connectedPeers.count == 0 { // Safe to force unwrap
HUDView.show("Error!\niPhone not connected")
return
}
- 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)
- if error != nil {
- HUDView.show("Error!\n\(error.localizedDescription)")
- } else {
- HUDView.show("Success!")
+ 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)
+ if let errorDescription = error?.localizedDescription {
+ HUDView.show("Error!\n\(errorDescription)")
+ } else {
+ HUDView.show("Success!")
+ }
}
}
+ pdfProgress?.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &progressContext)
}
- pdfProgress!.addObserver(self, forKeyPath: "fractionCompleted", options: .New, context: &ProgressContext)
}
// MARK: MCNearbyServiceAdvertiserDelegate
func advertiser(advertiser: MCNearbyServiceAdvertiser!, didReceiveInvitationFromPeer peerID: MCPeerID!, withContext context: NSData!, invitationHandler: ((Bool, MCSession!) -> Void)!) {
session = MCSession(peer: localPeerID, securityIdentity: nil, encryptionPreference: .None)
- session!.delegate = self
- invitationHandler(true, session!)
+ session?.delegate = self
+ invitationHandler(true, session)
}
// MARK: MCSessionDelegate
func session(session: MCSession!, peer peerID: MCPeerID!, didChangeState state: MCSessionState) {
- if let block = onStateChange! {
- block(state: state)
- }
+ onStateChange??(state: state)
}
func session(session: MCSession!, didReceiveData data: NSData!, fromPeer peerID: MCPeerID!) {
- let task = NSTask()
- task.launchPath = NSBundle.mainBundle().pathForResource("deckrocket", ofType: "scpt")!
- task.arguments = [NSString(data: data, encoding: NSUTF8StringEncoding)!]
- task.launch()
+ if let launchPath = NSBundle.mainBundle().pathForResource("deckrocket", ofType: "scpt"),
+ argument = NSString(data: data, encoding: NSUTF8StringEncoding) {
+ let task = NSTask()
+ task.launchPath = launchPath
+ task.arguments = [argument]
+ task.launch()
+ }
}
func session(session: MCSession!, didReceiveStream stream: NSInputStream!, withName streamName: String!, fromPeer peerID: MCPeerID!) {
@@ -94,13 +97,15 @@ class MultipeerClient: NSObject, MCNearbyServiceAdvertiserDelegate, MCSessionDel
// MARK: KVO
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<()>) {
- if context == &ProgressContext {
- dispatch_async(dispatch_get_main_queue()) {
- let progress = change[NSKeyValueChangeNewKey]! as CGFloat
- HUDView.showProgress(progress, string: "Sending File to iPhone")
- }
- } else {
+ 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()
+ }
+ }
}
}
}
diff --git a/OSX/DeckRocket/main.swift b/OSX/DeckRocket/main.swift
index 02f1b46..ce79e71 100644
--- a/OSX/DeckRocket/main.swift
+++ b/OSX/DeckRocket/main.swift
@@ -8,4 +8,4 @@
import Cocoa
-NSApplicationMain(C_ARGC, C_ARGV)
+NSApplicationMain(Process.argc, Process.unsafeArgv)
diff --git a/README.md b/README.md
index 2bbdb63..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. Therefore Xcode 6.1, 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/AppDelegate.swift b/iOS/DeckRocket/AppDelegate.swift
index 5b55504..b9c3fd0 100644
--- a/iOS/DeckRocket/AppDelegate.swift
+++ b/iOS/DeckRocket/AppDelegate.swift
@@ -9,15 +9,13 @@
import UIKit
@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
+final class AppDelegate: UIResponder, UIApplicationDelegate {
- var window: UIWindow?
+ var window: UIWindow? = UIWindow(frame: UIScreen.mainScreen().bounds)
- func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
- self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
-
- self.window!.rootViewController = ViewController()
- self.window!.makeKeyAndVisible()
+ func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
+ window?.rootViewController = ViewController()
+ window?.makeKeyAndVisible()
return true
}
}
diff --git a/iOS/DeckRocket/Cell.swift b/iOS/DeckRocket/Cell.swift
index 3423fe5..e9a589b 100644
--- a/iOS/DeckRocket/Cell.swift
+++ b/iOS/DeckRocket/Cell.swift
@@ -8,7 +8,7 @@
import UIKit
-class Cell: UICollectionViewCell {
+final class Cell: UICollectionViewCell {
let imageView = UIImageView()
override init(frame: CGRect) {
@@ -22,6 +22,6 @@ class Cell: UICollectionViewCell {
}
override func layoutSubviews() {
- imageView.frame = self.bounds
+ imageView.frame = bounds
}
}
diff --git a/iOS/DeckRocket/CollectionViewLayout.swift b/iOS/DeckRocket/CollectionViewLayout.swift
index 443b4d0..5ff40e6 100644
--- a/iOS/DeckRocket/CollectionViewLayout.swift
+++ b/iOS/DeckRocket/CollectionViewLayout.swift
@@ -8,11 +8,13 @@
import UIKit
-class CollectionViewLayout: UICollectionViewFlowLayout {
+final class CollectionViewLayout: UICollectionViewFlowLayout {
override init() {
super.init()
- itemSize = UIApplication.sharedApplication().delegate!.window!!.bounds.size
+ if let windowSize = UIApplication.sharedApplication().delegate?.window??.bounds.size {
+ itemSize = windowSize
+ }
scrollDirection = .Horizontal
minimumInteritemSpacing = 0
minimumLineSpacing = 0
diff --git a/iOS/DeckRocket/FileType.swift b/iOS/DeckRocket/FileType.swift
index cef74b6..c6178d7 100644
--- a/iOS/DeckRocket/FileType.swift
+++ b/iOS/DeckRocket/FileType.swift
@@ -9,27 +9,26 @@
import Foundation
enum FileType {
- case PDF, Markdown, Unknown
+ case PDF, Markdown
- init(fileExtension: String) {
- var ext = fileExtension.lowercaseString
+ init?(fileExtension: String) {
+ let ext = fileExtension.lowercaseString
if contains(FileType.extensionsForType(PDF), ext) {
self = PDF
+ return
} else if contains(FileType.extensionsForType(Markdown), ext) {
self = Markdown
- } else {
- self = Unknown
+ return
}
+ return nil
}
static func extensionsForType(fileType: FileType) -> [String] {
switch fileType {
- case PDF:
- return ["pdf"]
- case Markdown:
- return ["markdown", "mdown", "mkdn", "md", "mkd", "mdwn", "mdtxt", "mdtext", "text"]
- case Unknown:
- return [String]()
+ case PDF:
+ return ["pdf"]
+ case Markdown:
+ return ["markdown", "mdown", "mkdn", "md", "mkd", "mdwn", "mdtxt", "mdtext", "text"]
}
}
}
diff --git a/iOS/DeckRocket/Info.plist b/iOS/DeckRocket/Info.plist
index a6971c2..1284c58 100644
--- a/iOS/DeckRocket/Info.plist
+++ b/iOS/DeckRocket/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.0.3
+ 0.0.4
CFBundleSignature
????
CFBundleVersion
diff --git a/iOS/DeckRocket/MultipeerClient.swift b/iOS/DeckRocket/MultipeerClient.swift
index 4d60627..bcfe398 100644
--- a/iOS/DeckRocket/MultipeerClient.swift
+++ b/iOS/DeckRocket/MultipeerClient.swift
@@ -11,33 +11,35 @@ import MultipeerConnectivity
typealias stateChange = ((state: MCSessionState, peerID: MCPeerID) -> ())?
-class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelegate {
+final class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelegate {
// MARK: Properties
- let localPeerID = MCPeerID(displayName: UIDevice.currentDevice().name)
- var browser: MCNearbyServiceBrowser?
- var session: MCSession?
- var state = MCSessionState.NotConnected
+ private let localPeerID = MCPeerID(displayName: UIDevice.currentDevice().name)
+ let browser: MCNearbyServiceBrowser?
+ private(set) var session: MCSession?
+ private(set) var state = MCSessionState.NotConnected
var onStateChange: stateChange?
// MARK: Init
override init() {
- super.init()
browser = MCNearbyServiceBrowser(peer: localPeerID, serviceType: "deckrocket")
- browser!.delegate = self
- browser!.startBrowsingForPeers()
+ super.init()
+ browser?.delegate = self
+ browser?.startBrowsingForPeers()
}
// MARK: Send
func send(data: NSData) {
- 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) {
- send(string.dataUsingEncoding(NSUTF8StringEncoding)!)
+ if let stringData = string.dataUsingEncoding(NSUTF8StringEncoding) {
+ send(stringData)
+ }
}
// MARK: MCNearbyServiceBrowserDelegate
@@ -45,7 +47,7 @@ class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelega
func browser(browser: MCNearbyServiceBrowser!, foundPeer peerID: MCPeerID!, withDiscoveryInfo info: [NSObject : AnyObject]!) {
if session == nil {
session = MCSession(peer: localPeerID)
- session!.delegate = self
+ session?.delegate = self
}
browser.invitePeer(peerID, toSession: session, withContext: nil, timeout: 30)
}
@@ -58,9 +60,7 @@ class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelega
func session(session: MCSession!, peer peerID: MCPeerID!, didChangeState state: MCSessionState) {
self.state = state
- if let block = onStateChange! {
- block(state: state, peerID: peerID)
- }
+ onStateChange??(state: state, peerID: peerID)
}
func session(session: MCSession!, didReceiveData data: NSData!, fromPeer peerID: MCPeerID!) {
@@ -77,15 +77,12 @@ class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelega
func session(session: MCSession!, didFinishReceivingResourceWithName resourceName: String!, fromPeer peerID: MCPeerID!, atURL localURL: NSURL!, withError error: NSError!) {
if error == nil {
- dispatch_async(dispatch_get_main_queue()) {
- let fileType = FileType(fileExtension: resourceName.pathExtension)
+ if let fileType = FileType(fileExtension: resourceName.pathExtension) {
switch fileType {
- case .PDF:
- self.handlePDF(resourceName, atURL: localURL)
- case .Markdown:
- self.handleMarkdown(resourceName, atURL: localURL)
- case .Unknown:
- println("file type unknown")
+ case .PDF:
+ handlePDF(resourceName, atURL: localURL)
+ case .Markdown:
+ handleMarkdown(resourceName, atURL: localURL)
}
}
}
@@ -93,38 +90,39 @@ class MultipeerClient: NSObject, MCNearbyServiceBrowserDelegate, MCSessionDelega
// MARK: Handle Resources
- func handlePDF(resourceName: String!, atURL localURL: NSURL!) {
- promptToLoadResource("New Presentation File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "pdfPath")
+ private func handlePDF(resourceName: String!, atURL localURL: NSURL!) {
+ promptToLoadResource("New Presentation File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "pdfName")
}
- func handleMarkdown(resourceName: String!, atURL localURL: NSURL!) {
- promptToLoadResource("New Markdown File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "mdPath")
+ private func handleMarkdown(resourceName: String!, atURL localURL: NSURL!) {
+ promptToLoadResource("New Markdown File", resourceName: resourceName, atURL: localURL, userDefaultsKey: "mdName")
}
- func promptToLoadResource(title: String, resourceName: String, atURL localURL: NSURL, userDefaultsKey: String) {
- let rootVC = UIApplication.sharedApplication().delegate!.window!!.rootViewController as ViewController
+ private func promptToLoadResource(title: String, resourceName: String, atURL localURL: NSURL, userDefaultsKey: String) {
+ let rootVC = UIApplication.sharedApplication().delegate?.window??.rootViewController as? ViewController
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
- var error: NSError? = nil
- let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let filePath = documentsPath.stringByAppendingPathComponent(resourceName)
+ let fileManager = NSFileManager.defaultManager()
- if NSFileManager.defaultManager().fileExistsAtPath(filePath) {
- NSFileManager.defaultManager().removeItemAtPath(filePath, error: nil)
+ var error: NSError? = nil
+ if fileManager.fileExistsAtPath(filePath) {
+ fileManager.removeItemAtPath(filePath, error: &error)
}
- let url = NSURL(fileURLWithPath: filePath)
-
- NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: url!, error: &error)
- if error == nil {
- NSUserDefaults.standardUserDefaults().setObject(filePath, forKey: userDefaultsKey)
+ 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()
+ rootVC?.updatePresentation()
+ } else {
+ let message = error?.localizedDescription ?? "move file failed with no error"
+ fatalError(message)
}
})
-
- rootVC.presentViewController(alert, animated: true, completion: nil)
+ dispatch_async(dispatch_get_main_queue()) {
+ rootVC?.presentViewController(alert, animated: true, completion: nil)
+ }
}
}
diff --git a/iOS/DeckRocket/PDFImages.swift b/iOS/DeckRocket/PDFImages.swift
index bd10b5d..82019b2 100644
--- a/iOS/DeckRocket/PDFImages.swift
+++ b/iOS/DeckRocket/PDFImages.swift
@@ -11,36 +11,38 @@ import UIKit
import CoreGraphics
extension UIImage {
- class func imagesFromPDFPath(pdfPath: String) -> [UIImage] {
- let pdfURL = NSURL(fileURLWithPath: pdfPath)
- let pdf = CGPDFDocumentCreateWithURL(pdfURL)
- let numberOfPages = CGPDFDocumentGetNumberOfPages(pdf)
- var images = [UIImage]()
-
- if numberOfPages == 0 {
+ static func imagesFromPDFPath(pdfPath: String) -> [UIImage] {
+ if let pdfURL = NSURL(fileURLWithPath: pdfPath),
+ let pdf = CGPDFDocumentCreateWithURL(pdfURL) {
+ let numberOfPages = CGPDFDocumentGetNumberOfPages(pdf)
+
+ if numberOfPages == 0 {
+ return []
+ }
+
+ var images = [UIImage]()
+ if let screenSize = UIApplication.sharedApplication().delegate?.window??.bounds.size {
+ let largestDimension = max(screenSize.width, screenSize.height)
+ let largestSize = CGSize(width: largestDimension, height: largestDimension)
+
+ for pageNumber in 1...Int(numberOfPages) {
+ if let image = UIImage(pdfURL: pdfURL, page: pageNumber, fitSize: largestSize) {
+ images.append(image)
+ }
+ }
+ }
return images
}
-
- let screenSize = UIApplication.sharedApplication().delegate!.window!!.bounds.size
- let largestDimension = max(screenSize.width, screenSize.height)
- let largestSize = CGSize(width: largestDimension, height: largestDimension)
-
- for pageNumber in 1...numberOfPages {
- images.append(UIImage.imageWithPDFURL(pdfURL!, page: pageNumber, fitSize: largestSize))
- }
- return images
+ return []
}
- class func pdfRectForURL(url: NSURL, page: UInt) -> CGRect {
- let pdf = CGPDFDocumentCreateWithURL(url);
- let pageRef = CGPDFDocumentGetPage(pdf, page);
-
- let rect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox)
-
- return rect
+ private static func pdfRectForURL(url: NSURL, page: Int) -> CGRect {
+ let pdf = CGPDFDocumentCreateWithURL(url)
+ let pageRef = CGPDFDocumentGetPage(pdf, page)
+ return CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox)
}
- class func imageWithPDFURL(url: NSURL, page: UInt, size: CGSize) -> UIImage {
+ convenience init?(pdfURL: NSURL, page: Int, size: CGSize) {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale)
let ctx = UIGraphicsGetCurrentContext()
@@ -50,28 +52,26 @@ extension UIImage {
CGContextScaleCTM(ctx, 1, -1)
CGContextTranslateCTM(ctx, 0, -size.height)
- let pdf = CGPDFDocumentCreateWithURL(url)
-
+ let pdf = CGPDFDocumentCreateWithURL(pdfURL)
let pageRef = CGPDFDocumentGetPage(pdf, page)
-
let rect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox)
- CGContextScaleCTM(ctx, size.width/rect.size.width, size.height/rect.size.height)
+ CGContextScaleCTM(ctx, size.width / rect.size.width, size.height / rect.size.height)
CGContextTranslateCTM(ctx, -rect.origin.x, -rect.origin.y)
CGContextDrawPDFPage(ctx, pageRef)
- let pdfImage = UIGraphicsGetImageFromCurrentImageContext();
+ let pdfImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
- return pdfImage
+ self.init(CGImage: pdfImage.CGImage)
}
- class func imageWithPDFURL(url: NSURL, page: UInt, fitSize size: CGSize) -> UIImage {
- let rect = pdfRectForURL(url, page: page)
- let scaleFactor = max(rect.size.width/size.width, rect.size.height/size.height)
- let newWidth = ceil(rect.size.width/scaleFactor)
- let newHeight = ceil(rect.size.height/scaleFactor)
+ 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)
+ let newHeight = ceil(rect.size.height / scaleFactor)
let newSize = CGSize(width: newWidth, height: newHeight)
- return UIImage.imageWithPDFURL(url, page: page, size: newSize)
+ self.init(pdfURL: pdfURL, page: page, size: newSize)
}
}
diff --git a/iOS/DeckRocket/Presentation.swift b/iOS/DeckRocket/Presentation.swift
index 9abbd26..905750b 100644
--- a/iOS/DeckRocket/Presentation.swift
+++ b/iOS/DeckRocket/Presentation.swift
@@ -9,73 +9,48 @@
import Foundation
import UIKit
-class Presentation {
+struct Presentation {
// MARK: Properties
- var markdown = ""
- var slides = [Slide]()
+ let markdown: String
+ let slides: [Slide]
// MARK: Initializers
init(pdfPath: String, markdown: String?) {
let slideImages = UIImage.imagesFromPDFPath(pdfPath)
+ let pages = (markdown != nil) ? Presentation.pages(markdown!) : []
- var pages = [String]()
-
- if markdown != nil {
- self.markdown = markdown!
- pages = self.pages()
- }
-
- for (index, image) in enumerate(slideImages) {
- var page: String?
- if pages.count > index {
- page = pages[index]
- }
- slides.append(Slide(image: image, markdown: page?))
+ self.markdown = markdown ?? ""
+ slides = map(enumerate(slideImages)) { index, image in
+ let page: String? = (pages.count > index) ? pages[index] : nil
+ return Slide(image: image, markdown: page)
}
}
// MARK: Markdown Parsing
- func pages() -> [String] {
- let locations = pageLocations()
-
- var pages = [String]()
-
- for (index, end) in enumerate(locations) {
- var start = 0
- if index > 0 {
- start = locations[index - 1]
- }
- var substring = (markdown as NSString).substringWithRange(NSRange(location: start, length: end-start))
- substring = substring.stringByReplacingOccurrencesOfString("---\n", withString: "")
- pages.append(substring)
+ private static func pages(markdown: NSString) -> [String] {
+ let locations = pageLocations(markdown)
+ return map(enumerate(locations)) { index, end in
+ let start = (index > 0) ? locations[index - 1] : 0
+ return markdown.substringWithRange(NSRange(location: start, length: end - start))
+ .stringByReplacingOccurrencesOfString("---\n", withString: "")
}
-
- return pages
}
- func pageLocations() -> [Int] {
+ private static func pageLocations(markdown: NSString) -> [Int] {
// Pattern must match http://www.decksetapp.com/support/#i-separated-my-content-by-----but-deckset-shows-it-on-one-slide-whats-wrong
let pattern = "^\\-\\-\\-" // ^\-\-\-
let pagesExpression = NSRegularExpression(pattern: pattern,
- options: NSRegularExpressionOptions.AnchorsMatchLines,
+ options: .AnchorsMatchLines,
error: nil)
- var pageDelimiters = [Int]()
-
- let range = NSRange(location: 0, length: (markdown as NSString).length)
- if let matches = pagesExpression?.matchesInString(markdown, options: NSMatchingOptions(0), range: range) {
- for match in matches as [NSTextCheckingResult] {
- pageDelimiters.append(match.range.location)
- }
- }
-
- // EOF is an implicit page delimiter
- pageDelimiters.append(range.length)
-
- return pageDelimiters
+ let range = NSRange(location: 0, length: markdown.length)
+ return (pagesExpression?
+ .matchesInString(markdown as! String, options: nil, range: range)
+ .map {$0.range.location} ?? [])
+ + [range.length] // EOF is an implicit page delimiter
}
}
diff --git a/iOS/DeckRocket/Slide.swift b/iOS/DeckRocket/Slide.swift
index 08e24e4..3e10c2c 100644
--- a/iOS/DeckRocket/Slide.swift
+++ b/iOS/DeckRocket/Slide.swift
@@ -9,7 +9,7 @@
import Foundation
import UIKit
-class Slide {
+struct Slide {
// MARK: Properties
@@ -22,49 +22,43 @@ class Slide {
init(image: UIImage, markdown: String?) {
self.image = image
- if markdown != nil {
- self.markdown = markdown
- body = bodyFromMarkdown()
- notes = notesFromMarkdown()
- }
+ self.markdown = markdown
+ body = Slide.bodyFromMarkdown(markdown)
+ notes = Slide.notesFromMarkdown(markdown)
}
// MARK: String Parsing
- func bodyFromMarkdown() -> String {
+ private static func bodyFromMarkdown(markdown: NSString?) -> String? {
// Skip the trailing \n
- let bodyRange = NSRange(location: 0, length: notesStart() - 1)
- return (markdown! as NSString).substringWithRange(bodyRange)
+ return markdown?.substringWithRange(NSRange(location: 0, length: notesStart(markdown!) - 1)) // Safe to force unwrap
}
- func notesFromMarkdown() -> String? {
- let nsMarkdown = markdown! as NSString
-
- var start = notesStart()
- if start == nsMarkdown.length {
- // No notes
- return nil
+ private static func notesFromMarkdown(markdown: NSString?) -> String? {
+ if let markdown = markdown {
+ let start = notesStart(markdown)
+ if start == markdown.length {
+ return nil // No notes
+ }
+ // Skip the leading ^
+ let startWithOutLeadingCaret = start + 1
+ let length = markdown.length - startWithOutLeadingCaret
+ let notesRange = NSRange(location: startWithOutLeadingCaret, length: length)
+ return markdown.substringWithRange(notesRange)
}
-
- // Skip the leading ^
- start++
- let length = nsMarkdown.length - start
-
- let notesRange = NSRange(location: start, length: length)
- return nsMarkdown.substringWithRange(notesRange)
+ return nil
}
- func notesStart() -> Int {
+ private static func notesStart(markdown: NSString) -> Int {
// Pattern must match http://www.decksetapp.com/support/#how-do-i-add-presenter-notes
let pattern = "^\\^" // ^\^
let notesExpression = NSRegularExpression(pattern: pattern,
- options: NSRegularExpressionOptions.AnchorsMatchLines,
+ options: .AnchorsMatchLines,
error: nil)
- let fullRange = NSRange(location: 0, length: (markdown! as NSString).length)
- if let notesMatch = notesExpression?.firstMatchInString(markdown!, options: NSMatchingOptions(0), range: fullRange) {
- return notesMatch.range.location
- }
- return fullRange.length
+ let fullRange = NSRange(location: 0, length: markdown.length)
+ return notesExpression?
+ .firstMatchInString(markdown as! String, options: nil, range: fullRange)?.range.location // Safe to force unwrap
+ ?? fullRange.length
}
}
diff --git a/iOS/DeckRocket/ViewController.swift b/iOS/DeckRocket/ViewController.swift
index ea32556..36dd7ac 100644
--- a/iOS/DeckRocket/ViewController.swift
+++ b/iOS/DeckRocket/ViewController.swift
@@ -9,16 +9,31 @@
import UIKit
import MultipeerConnectivity
-class ViewController: UICollectionViewController, UIScrollViewDelegate {
+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
- var presentation: Presentation?
- let multipeerClient = MultipeerClient()
- let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
- let notesView = UITextView()
- let nextSlideView = UIImageView()
- let infoLabel = UILabel()
+ 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()
+ private let infoLabel = UILabel()
// MARK: View Lifecycle
@@ -40,31 +55,31 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
// MARK: Connectivity Updates
- func setupConnectivityObserver() {
- multipeerClient.onStateChange = {(state: MCSessionState, peerID: MCPeerID) -> () in
+ private func setupConnectivityObserver() {
+ multipeerClient.onStateChange = { state, peerID in
dispatch_async(dispatch_get_main_queue(), {
self.notesView.alpha = 0
self.nextSlideView.alpha = 0
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 {
- self.effectView.alpha = 1
+ } else if self.multipeerClient.session!.connectedPeers.count == 0 { // Safe to force unwrap
+ self.effectParentView.alpha = 1
self.infoLabel.text = "Not Connected"
- self.multipeerClient.browser!.invitePeer(peerID, toSession: self.multipeerClient.session, withContext: nil, timeout: 30)
+ 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..."
}
})
@@ -74,22 +89,24 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
// MARK: Presentation Updates
func updatePresentation() {
- if let pdfPath = NSUserDefaults.standardUserDefaults().objectForKey("pdfPath") as? NSString {
- var markdown: String?
- if let mdPath = NSUserDefaults.standardUserDefaults().objectForKey("mdPath") as? NSString {
+ if let pdfPath = userDefaultsPathIfFileExists("pdfName") {
+ let markdown: String?
+ if let mdPath = userDefaultsPathIfFileExists("mdName") {
markdown = String(contentsOfFile: mdPath, encoding: NSUTF8StringEncoding)
+ } else {
+ markdown = nil
}
- presentation = Presentation(pdfPath: pdfPath, markdown: markdown?)
- collectionView!.contentOffset.x = 0
- collectionView!.reloadData()
+ presentation = Presentation(pdfPath: pdfPath, markdown: markdown)
+ collectionView?.contentOffset.x = 0
+ collectionView?.reloadData()
}
- // Force state change block
- multipeerClient.onStateChange!!(state: multipeerClient.state, peerID: MCPeerID())
+ // Trigger state change block
+ multipeerClient.onStateChange??(state: multipeerClient.state, peerID: MCPeerID())
}
// MARK: UI
- func setupUI() {
+ private func setupUI() {
setupCollectionView()
setupEffectView()
setupInfoLabel()
@@ -97,24 +114,32 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
setupNextSlideView()
}
- func setupCollectionView() {
- collectionView!.registerClass(Cell.self, forCellWithReuseIdentifier: "cell")
- collectionView!.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: "longPress:"))
- collectionView!.pagingEnabled = true
- collectionView!.showsHorizontalScrollIndicator = false
+ private func setupCollectionView() {
+ collectionView?.registerClass(Cell.self, forCellWithReuseIdentifier: "cell")
+ collectionView?.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: "longPress:"))
+ collectionView?.pagingEnabled = true
+ collectionView?.showsHorizontalScrollIndicator = false
}
- func setupEffectView() {
- effectView.setTranslatesAutoresizingMaskIntoConstraints(false)
- view.addSubview(effectView)
+ private func setupEffectView() {
+ effectParentView.setTranslatesAutoresizingMaskIntoConstraints(false)
+ view.addSubview(effectParentView)
- let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[effectView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["effectView": effectView])
- let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|[effectView]|", options: NSLayoutFormatOptions(0), 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)
}
- func setupInfoLabel() {
+ private func setupInfoLabel() {
infoLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
infoLabel.text = "Not Connected"
infoLabel.textColor = UIColor.whiteColor()
@@ -138,7 +163,7 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
effectView.addConstraints([centerX, centerY])
}
- func setupNotesView() {
+ private func setupNotesView() {
notesView.setTranslatesAutoresizingMaskIntoConstraints(false)
notesView.font = UIFont.systemFontOfSize(30)
notesView.backgroundColor = UIColor.clearColor()
@@ -147,13 +172,13 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
notesView.alpha = 0
effectView.addSubview(notesView)
- let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[notesView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["notesView": notesView])
- let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[notesView]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["notesView": notesView])
+ let horizontal = NSLayoutConstraint.constraintsWithVisualFormat("|[notesView]|", options: nil, metrics: nil, views: ["notesView": notesView])
+ let vertical = NSLayoutConstraint.constraintsWithVisualFormat("V:|-20-[notesView]|", options: nil, metrics: nil, views: ["notesView": notesView])
effectView.addConstraints(horizontal)
effectView.addConstraints(vertical)
}
- func setupNextSlideView() {
+ private func setupNextSlideView() {
nextSlideView.setTranslatesAutoresizingMaskIntoConstraints(false)
nextSlideView.contentMode = UIViewContentMode.ScaleAspectFit
effectView.addSubview(nextSlideView)
@@ -207,51 +232,54 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
break
default:
// Don't do anything if the effect view is now being used to show a connectivity message
- if multipeerClient.session!.connectedPeers.count > 0 {
+ if let session = multipeerClient.session where session.connectedPeers.count > 0 {
showNotes(false)
}
}
}
- func showNotes(show: Bool) {
- let currentSlideIndex = Int(currentSlide())
- notesView.text = presentation!.slides[currentSlideIndex].notes
- notesView.alpha = 1
- nextSlideView.alpha = 1
-
- if currentSlideIndex < presentation!.slides.count - 1 {
- nextSlideView.image = presentation!.slides[currentSlideIndex+1].image
- } else {
- nextSlideView.image = nil
- }
- UIView.animateWithDuration(0.25, animations: {
- self.effectView.alpha = CGFloat(show)
- }) { finished in
- self.notesView.alpha = CGFloat(show)
- self.nextSlideView.alpha = CGFloat(show)
+ private func showNotes(show: Bool) {
+ if let presentation = presentation {
+ let currentSlideIndex = Int(currentSlide())
+ notesView.text = presentation.slides[currentSlideIndex].notes
+ notesView.alpha = 1
+ nextSlideView.alpha = 1
+
+ if currentSlideIndex < presentation.slides.count - 1 {
+ nextSlideView.image = presentation.slides[currentSlideIndex + 1].image
+ } else {
+ nextSlideView.image = nil
+ }
+ let alpha = CGFloat(show)
+ UIView.animateWithDuration(0.25, animations: {
+ self.effectParentView.alpha = alpha
+ }) { finished in
+ self.notesView.alpha = alpha
+ self.nextSlideView.alpha = alpha
+ }
}
}
// MARK: Collection View
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- if let presentation = self.presentation {
- return presentation.slides.count
- }
- return 0
+ return presentation?.slides.count ?? 0
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as Cell
- let slide = presentation!.slides[indexPath.item]
- cell.imageView.image = slide.image
+ let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! Cell
+ let slide = presentation?.slides[indexPath.item]
+ cell.imageView.image = slide?.image
return cell
}
// MARK: UIScrollViewDelegate
- func currentSlide() -> UInt {
- return UInt(round(collectionView!.contentOffset.x / collectionView!.frame.size.width))
+ private func currentSlide() -> UInt {
+ if let collectionView = collectionView {
+ return UInt(round(collectionView.contentOffset.x / collectionView.frame.size.width))
+ }
+ return 0
}
override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
@@ -263,18 +291,18 @@ class ViewController: UICollectionViewController, UIScrollViewDelegate {
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
// Update Layout
- let layout = collectionView!.collectionViewLayout as CollectionViewLayout
- layout.invalidateLayout()
- layout.itemSize = CGSize(width: view.bounds.size.height, height: view.bounds.size.width)
+ let layout = collectionView?.collectionViewLayout as? CollectionViewLayout
+ layout?.invalidateLayout()
+ layout?.itemSize = CGSize(width: view.bounds.size.height, height: view.bounds.size.width)
// Update Offset
- let targetOffset = CGFloat(self.currentSlide()) * layout.itemSize.width
+ let targetOffset = CGFloat(currentSlide()) * (layout?.itemSize.width ?? 0)
// We do this half-way through the animation
let delay = (duration / 2) * NSTimeInterval(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
- self.collectionView!.contentOffset.x = targetOffset
+ self.collectionView?.contentOffset.x = targetOffset
}
}
}