Slide 1

Slide 1 text

Working with web APIs in Swift Leah Culver Twilio Signal 2015

Slide 2

Slide 2 text

Dropbox Developer Platform

Slide 3

Slide 3 text

New! Dropbox API v2

Slide 4

Slide 4 text

API v2 preview • HTTP POST method • JSON in the request and response body • fewer HTTP status codes • more descriptive error messages • general cleanup ✨

Slide 5

Slide 5 text

API v2 preview endpoints User endpoints users/get_account users/get_current_account File endpoints files/get_metadata files/list_folder files/upload files/download files/search … and more!

Slide 6

Slide 6 text

New! SwiftyDropbox

Slide 7

Slide 7 text

SwiftyDropbox SDK (v0.1) methods User endpoints usersGetAccount usersGetCurrentAccount File endpoints filesGetMetadata filesListFolder filesUpload filesDownload filesSearch … and more!

Slide 8

Slide 8 text

+ + + …

Slide 9

Slide 9 text

+ + + ⌚

Slide 10

Slide 10 text

=

Slide 11

Slide 11 text

Photo Watch Photos from your Dropbox on your Apple Watch

Slide 12

Slide 12 text

(phone simulator)

Slide 13

Slide 13 text

github.com/dropbox/SwiftyDropbox blogs.dropbox.com/developers import SwiftyDropbox

Slide 14

Slide 14 text

Link account with Dropbox func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { DropboxAuthManager.sharedAuthManager = DropboxAuthManager(appKey: “APP_KEY”) return true } AppDelegate.swift

Slide 15

Slide 15 text

@IBAction func linkButtonPressed(sender: AnyObject) { // Present view to log in DropboxAuthManager.sharedAuthManager.authorizeFromController(self) } ViewController.swift

Slide 16

Slide 16 text

OAuth 2 on iOS • switch to Safari for log in and authorization

Slide 17

Slide 17 text

OAuth 2 on iOS • switch to Safari for log in and authorization

Slide 18

Slide 18 text

OAuth 2 on iOS • switch to Safari for log in and authorization • present web view controller

Slide 19

Slide 19 text

OAuth 2 on iOS • switch to Safari for log in and authorization • present web view controller

Slide 20

Slide 20 text

OAuth 2 on iOS • switch to Safari for log in and authorization • present web view controller • switch to Dropbox app (“DAuth”, not yet implemented)

Slide 21

Slide 21 text

func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool { if let authResult = DropboxAuthManager.sharedAuthManager.handleRedirectURL(url) { switch authResult { case .Success(let token): println("Success! User is logged into Dropbox.") case .Error(let error, let description): println("Error: \(description)") } } return false } AppDelegate.swift

Slide 22

Slide 22 text

Set up DropboxClient // Check if the user is logged in if let token = DropboxAuthManager.sharedAuthManager.getFirstAccessToken() { // Set the sharedClient for re-use DropboxClient.sharedClient = DropboxClient(accessToken: token) … } ViewController.swift

Slide 23

Slide 23 text

List user’s Dropbox files self.filenames = [] // List contents of app folder DropboxClient.sharedClient.filesListFolder(path: "").response { response, error in if let result = response { for entry in result.entries { // Check that file is a photo (by file extension) if fileURL.absoluteString!.hasSuffix(".jpg") || fileURL.absoluteString!.hasSuffix(".png") { // Add photo self.filenames?.append(entry.name) } } } } ViewController.swift

Slide 24

Slide 24 text

UIPageViewControllerDataSource func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int // Number of pages is number of photos return self.filenames!.count }

Slide 25

Slide 25 text

UIPageViewControllerDataSource func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { var nextIndex = … let photoViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoViewController") as! PhotoViewController photoViewController.filename = self.filenames![nextIndex] return photoViewController }

Slide 26

Slide 26 text

Download photo class PhotoViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! var filename: String? override func viewDidLoad() { … // Download the photo from Dropbox // A thumbnail would be better but there's no endpoint for that in API v2 yet! DropboxClient.sharedClient.filesDownload(path: “/\(self.filename)").response { response, error in if let (metadata, data) = response, image = UIImage(data: data) { // Resize image for watch (so it's not huge) let resizedImage = self.resizeImage(image) // Display image self.imageView.image = resizedImage } } } }

Slide 27

Slide 27 text

Cache files… share with watch…
 app group!

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Store photos in app group // Get app group shared by phone and watch let containerURL = NSFileManager.defaultManager() .containerURLForSecurityApplicationGroupIdentifier("group.com.Dropbox.DropboxPhotoWatch") if let fileURL = containerURL?.URLByAppendingPathComponent(filename) { if let data = NSData(contentsOfURL: fileURL) { // Display image self.imageView.image = UIImage(data: data) } else { // Download the photo from Dropbox, resize, then display … self.imageView.image = resizedImage // Save image to local filesystem app group - allows us to access in the watch let resizedImageData = UIImageJPEGRepresentation(resizedImage, 1.0) resizedImageData.writeToURL(fileURL, atomically: true) } }

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

InterfaceController.swift override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Get images from the shared app group if let images = self.getImagesFromAppGroup() { // Data for each photo view let controllerNames = [String](count: images.count, repeatedValue: "PhotoInterfaceController") let contexts = images.map({ image in ["image": image] }) // Update watch display WKInterfaceController.reloadRootControllersWithNames(controllerNames, contexts: contexts) } }

Slide 32

Slide 32 text

InterfaceController.swift private func getImagesFromAppGroup() -> Array? { var images = [UIImage]() // Get app group shared by phone and watch if let containerURL = NSFileManager.defaultManager() .containerURLForSecurityApplicationGroupIdentifier("group.com.Dropbox.DropboxPhotoWatch") { // Fetch all files in the app group for fileURL in NSFileManager.defaultManager().contentsOfDirectoryAtURL(…) as! Array { // Check that file is a photo (by file extension) if fileURL.absoluteString!.hasSuffix(".jpg") || fileURL.absoluteString!.hasSuffix(".png") { // Add image to array of images if let data = NSData(contentsOfURL: fileURL), image = UIImage(data: data) { images.append(image) } } } return images }

Slide 33

Slide 33 text

PhotoInterfaceController.swift class PhotoInterfaceController: WKInterfaceController { @IBOutlet weak var image: WKInterfaceImage! override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) if let data = context as? Dictionary { self.image.setImage(data["image"]) } } }

Slide 34

Slide 34 text

(watch simulator)

Slide 35

Slide 35 text

github.com/dropbox/PhotoWatch Photo Watch

Slide 36

Slide 36 text

Working with APIs in Swift

Slide 37

Slide 37 text

let maxSize: CGFloat = 200.0 var size: CGSize? Constants and Variables

Slide 38

Slide 38 text

Optionals if let (metadata, data) = response { // … } else { println("Error: \(error!)") }

Slide 39

Slide 39 text

Optional chaining (new!) if let (metadata, data) = response, image = UIImage(data: data) { // Display image self.imageView.image = image } else { println("Error downloading file from Dropbox: \(error!)") }

Slide 40

Slide 40 text

Emoji! let = DropboxClient.sharedClient

Slide 41

Slide 41 text

HTTP requests Recommended library: NSURLSession Check out: Alamofire

Slide 42

Slide 42 text

JSON Recommended library: NSJSONSerialization Check out: ??

Slide 43

Slide 43 text

Singleton for a default - shared instance

Slide 44

Slide 44 text

iOS app longer lifetime of in-memory code Web app request/response pattern API clients DropboxClient DropboxAuthManager

Slide 45

Slide 45 text

+ (id)sharedManager { static MyManager *sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedManager = [[self alloc] init]; }); return sharedManager; } class MyManager { static let sharedManager = MyManager() init() { //... } } Singletons Obj-C Swift

Slide 46

Slide 46 text

Enumerations if let authResult = DropboxAuthManager.sharedAuthManager.handleRedirectURL(url) { switch authResult { case .Success(let token): println("Success! User is logged into Dropbox.") case .Error(let error, let description): println("Error: \(description)") } }

Slide 47

Slide 47 text

public enum DropboxAuthResult { case Success(DropboxAccessToken) case Error(OAuth2Error, String) } public func handleRedirectURL(url: NSURL) -> DropboxAuthResult? { … } Enumerations

Slide 48

Slide 48 text

DropboxClient.sharedClient.filesListFolder(path: "").response { response, error in if response != nil { println(response) } else if error != nil { println("Error: \(error)") } } Completion handler

Slide 49

Slide 49 text

Feedback? [email protected] Session feedback? Text @T14 to 39242

Slide 50

Slide 50 text

Photo credits: https://www.facebook.com/media/set/?set=a.754673357914502.1073741837.669409179774254 Fox icon: http://www.funnyeve.com/ https://www.flickr.com/photos/geezaweezer/6096470743 http://www.stellamarrs.com/ https://www.flickr.com/photos/dinnerseries/14896417171 https://www.flickr.com/photos/ukgardenphotos/15826945730 https://www.flickr.com/photos/thomashawk/11708785 https://www.flickr.com/photos/kwl/7701369614