├── .gitignore ├── Cassini ├── .gitignore ├── Cassini │ ├── Base.lproj │ │ └── Main.storyboard │ ├── CassiniViewController.swift │ ├── DemoURLs.swift │ ├── ImageViewController.swift │ ├── Info.plist │ ├── Supporting Files │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ └── Base.lproj │ │ │ └── LaunchScreen.storyboard │ └── oval.jpg ├── CassiniTests │ ├── CassiniTests.swift │ └── Info.plist └── CassiniUITests │ ├── CassiniUITests.swift │ └── Info.plist ├── Concentration ├── .gitignore ├── Concentration.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── ruben.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── ruben.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Concentration │ ├── Base.lproj │ └── Main.storyboard │ ├── Card.swift │ ├── Concentration.swift │ ├── ConcentrationThemeChooserViewController.swift │ ├── ConcentrationViewController.swift │ ├── Extensions │ ├── CollectionExtension.swift │ ├── IntExtension.swift │ └── MutableCollectionExtension.swift │ ├── Info.plist │ └── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ └── Base.lproj │ └── LaunchScreen.storyboard ├── EmojiArt ├── .gitignore ├── EmojiArt │ ├── Base.lproj │ │ └── Main.storyboard │ ├── EmojiArtDocumentTableViewController.swift │ ├── EmojiArtView.swift │ ├── EmojiArtViewController.swift │ ├── EmojiArtViewGestures.swift │ ├── EmojiCollectionViewCell.swift │ ├── Info.plist │ ├── TextFieldCollectionViewCell.swift │ └── Utilities.swift ├── EmojiArtTests │ ├── EmojiArtTests.swift │ └── Info.plist ├── EmojiArtUITests │ ├── EmojiArtUITests.swift │ └── Info.plist └── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ └── Base.lproj │ └── LaunchScreen.storyboard ├── EmojiArtL14 ├── EmojiArtL14.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── ruben.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── ruben.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── EmojiArtL14 │ ├── Base.lproj │ └── Main.storyboard │ ├── DocumentBrowserViewController.swift │ ├── EmojiArt.swift │ ├── EmojiArtDocument.swift │ ├── EmojiArtView.swift │ ├── EmojiArtViewController.swift │ ├── EmojiArtViewGestures.swift │ ├── EmojiCollectionViewCell.swift │ ├── Info.plist │ ├── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── TextFieldCollectionViewCell.swift │ └── Utilities.swift ├── ImageGallery ├── .gitignore └── ImageGallery │ ├── Base.lproj │ └── Main.storyboard │ ├── DocumentsTableViewController.swift │ ├── GalleryDao.swift │ ├── GalleryViewController │ ├── GalleryViewController+UICollectionViewDataSource.swift │ ├── GalleryViewController+UICollectionViewDelegateFlowLayout.swift │ ├── GalleryViewController+UICollectionViewDragDelegate.swift │ ├── GalleryViewController+UICollectionViewDropDelegate.swift │ └── GalleryViewController.swift │ ├── ImageCollectionViewCell.swift │ ├── ImageGallery.swift │ ├── Info.plist │ ├── ItemViewController │ ├── ItemViewController+UIScrollViewDelegate.swift │ └── ItemViewController.swift │ ├── Settings.swift │ ├── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── testImage.imageset │ │ │ ├── Contents.json │ │ │ └── testImage.png │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Utilities │ ├── UIViewControllerUtilities.swift │ └── URLUtilities.swift ├── ImageGallery_P6 ├── ImageGallery_P6.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── ruben.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── ruben.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── ImageGallery_P6 │ ├── Base.lproj │ └── Main.storyboard │ ├── DocumentBrowser │ ├── DocumentBrowserViewController.swift │ └── ImageGalleryDocument.swift │ ├── Gallery.imgallery │ ├── GalleryViewController │ ├── GalleryViewController+UICollectionViewDataSource.swift │ ├── GalleryViewController+UICollectionViewDelegateFlowLayout.swift │ ├── GalleryViewController+UICollectionViewDragDelegate.swift │ ├── GalleryViewController+UICollectionViewDropDelegate.swift │ └── GalleryViewController.swift │ ├── ImageCollectionViewCell.swift │ ├── ImageGallery.swift │ ├── Info.plist │ ├── ItemViewController+UIScrollViewDelegate.swift │ ├── ItemViewController.swift │ ├── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Utilities │ ├── Settings.swift │ ├── UIViewControllerUtilities.swift │ ├── UIViewUtilities.swift │ └── URLUtilities.swift ├── LICENSE ├── Lecture8_PlayingCard ├── .gitignore └── PlayingCard │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── J♠️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♣️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♥️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♦️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── K♠️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♣️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♥️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♦️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── Q♠️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♣️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♥️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♦️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ └── cardback.imageset │ │ ├── Contents.json │ │ ├── cardback.jpg │ │ └── cardback@2x.jpg │ ├── Base.lproj │ └── Main.storyboard │ ├── CardBehavior.swift │ ├── Extensions │ ├── CGFloatExtension.swift │ └── IntExtension.swift │ ├── Info.plist │ ├── PlayingCard.swift │ ├── PlayingCardDeck.swift │ ├── PlayingCardView.swift │ ├── Supporting Files │ ├── AppDelegate.swift │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ └── ViewController.swift ├── PlayingCard ├── PlayingCard.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── ruben.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── ruben.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── PlayingCard │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── J♠️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♣️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♥️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── J♦️.imageset │ │ ├── Contents.json │ │ ├── jack.jpg │ │ └── jack2x.jpg │ ├── K♠️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♣️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♥️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── K♦️.imageset │ │ ├── Contents.json │ │ ├── king.jpg │ │ └── king2x.jpg │ ├── Q♠️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♣️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♥️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ ├── Q♦️.imageset │ │ ├── Contents.json │ │ ├── qeen.jpg │ │ └── queen2x.jpg │ └── cardback.imageset │ │ ├── Contents.json │ │ ├── cardback.jpg │ │ └── cardback@2x.jpg │ ├── Base.lproj │ └── Main.storyboard │ ├── Extensions │ └── IntExtension.swift │ ├── Info.plist │ ├── PlayingCard.swift │ ├── PlayingCardDeck.swift │ ├── PlayingCardView.swift │ ├── Supporting Files │ ├── AppDelegate.swift │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ └── ViewController.swift ├── README.md ├── Set ├── .gitignore └── Set │ ├── Base.lproj │ └── Main.storyboard │ ├── Card.swift │ ├── CardView.swift │ ├── Grid.swift │ ├── Info.plist │ ├── SetGame.swift │ ├── SetViewController.swift │ └── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ └── Base.lproj │ └── LaunchScreen.storyboard └── artwork ├── assignment-1-preview.jpg ├── assignment-2-preview.jpg ├── assignment-3-preview.jpg ├── assignment-5.jpg ├── assignment-6-preview.jpg ├── course_logo.png ├── lecture-1.png ├── lecture-10.jpg ├── lecture-11.jpg ├── lecture-12.jpg ├── lecture-13.jpg ├── lecture-14.jpg ├── lecture-15.jpg ├── lecture-2.png ├── lecture-3.jpg ├── lecture-4.jpg ├── lecture-5.jpg ├── lecture-6.png ├── lecture-7.jpg ├── lecture-8.jpg └── lecture-9.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Cassini/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Cassini/Cassini/CassiniViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CassiniViewController.swift 3 | // Cassini 4 | // 5 | // Created by Ruben Baca on 1/7/18. 6 | // Copyright © 2018 Ruben Baca. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// Controller that allows user to select a Cassini image. 12 | /// 13 | class CassiniViewController: UIViewController { 14 | 15 | // MARK: - Navigation 16 | 17 | // In a storyboard-based application, you will often want to do a little 18 | // preparation before navigation 19 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 20 | 21 | // We prepare the image based on the segue's identifier 22 | guard let identifier = segue.identifier else { 23 | return // expected a valid identifier 24 | } 25 | 26 | // Destination must be an ImageViewController 27 | guard let imageViewController = segue.destination.contents as? ImageViewController else { 28 | return // expected a segue to ImageViewController class 29 | } 30 | 31 | // Setup destination's model (URL) and title 32 | imageViewController.imageURL = DemoURLs.NASA[identifier] 33 | imageViewController.title = (sender as? UIButton)?.currentTitle ?? "?" 34 | } 35 | 36 | } 37 | 38 | /// 39 | /// Simple but useful utilities for `UIViewController` 40 | /// 41 | extension UIViewController { 42 | 43 | /// 44 | /// The contents of the controller. It is `self` in all cases except for instances of 45 | /// `UINavigationController` where, if present, returns the controller's visible view 46 | /// controller (or self if none is set). 47 | /// 48 | /// This allows for great iphone/ipad compatibillity 49 | /// in just one UI. 50 | /// 51 | var contents: UIViewController { 52 | 53 | // Is this a navigation controller? 54 | if let navigationVC = self as? UINavigationController { 55 | return navigationVC.visibleViewController ?? self 56 | } 57 | else { 58 | return self 59 | } 60 | 61 | // TODO: this could work great with UITabViewController 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Cassini/Cassini/DemoURLs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoURLs.swift 3 | // Cassini 4 | // 5 | // Created by Ruben on 1/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// URL's containing big images. 12 | /// 13 | struct DemoURLs { 14 | 15 | // Stanford's Oval image 16 | static let stanford = Bundle.main.url(forResource: "oval", withExtension: "jpg") 17 | 18 | static var NASA: Dictionary = { 19 | let NASAURLStrings = [ 20 | // (This Tesla image takes less time to download) 21 | "Cassini" : "https://insideevs.com/wp-content/uploads/2017/08/3840x2160-White-MS-Snowy-Mountain.jpg", 22 | // "Cassini" : "https://www.jpl.nasa.gov/images/cassini/20090202/pia03883-full.jpg", 23 | "Earth" : "https://www.nasa.gov/sites/default/files/wave_earth_mosaic_3.jpg", 24 | "Saturn" : "https://www.nasa.gov/sites/default/files/saturn_collage.jpg" 25 | ] 26 | var urls = Dictionary() 27 | for (key, value) in NASAURLStrings { 28 | urls[key] = URL(string: value) 29 | } 30 | return urls 31 | }() 32 | } 33 | -------------------------------------------------------------------------------- /Cassini/Cassini/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Cassini/Cassini/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Cassini 4 | // 5 | // Created by Ruben on 1/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Cassini/Cassini/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Cassini/Cassini/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Cassini/Cassini/oval.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Cassini/Cassini/oval.jpg -------------------------------------------------------------------------------- /Cassini/CassiniTests/CassiniTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CassiniTests.swift 3 | // CassiniTests 4 | // 5 | // Created by Ruben on 1/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Cassini 11 | 12 | class CassiniTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Cassini/CassiniTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Cassini/CassiniUITests/CassiniUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CassiniUITests.swift 3 | // CassiniUITests 4 | // 5 | // Created by Ruben on 1/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class CassiniUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Cassini/CassiniUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Concentration/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Concentration/Concentration.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Concentration/Concentration.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Concentration/Concentration.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Concentration/Concentration.xcodeproj/xcuserdata/ruben.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Concentration.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Concentration/Concentration/Card.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Card.swift 3 | // Concentration 4 | // 5 | // Created by Ruben on 11/30/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a "Card" that is used in the "Concentration" game 12 | /// 13 | struct Card: Hashable { 14 | 15 | /// To conform to `Hashable` protocol 16 | var hashValue: Int { return identifier } 17 | 18 | /// To conform to `Equatable` protocol inherited form `Hashable` 19 | static func ==(lhs: Card, rhs: Card) -> Bool { 20 | return lhs.identifier == rhs.identifier 21 | } 22 | 23 | /// 24 | /// Is the current card facing up? 25 | /// 26 | var isFaceUp = false 27 | 28 | /// 29 | /// Is the current card already matched? 30 | /// 31 | /// A matched card means that the user playing the game already 32 | /// found both "matching" cards. 33 | /// 34 | var isMatched = false 35 | 36 | /// 37 | /// A unique identifier for the card. 38 | /// (The pair of matching cards have the same identifier) 39 | /// 40 | private var identifier: Int 41 | 42 | /// 43 | /// Create a card with the given identifier 44 | /// 45 | init() { 46 | self.identifier = Card.getUniqueIdentifier() 47 | } 48 | 49 | /// 50 | /// Static identifier that is increased every time a new one is 51 | /// requested by getUniqueIdentifier() 52 | /// 53 | private static var identifierFactory = 0 54 | 55 | /// 56 | /// Returns a unique id to be used as a card identifier 57 | /// 58 | private static func getUniqueIdentifier() -> Int { 59 | Card.identifierFactory += 1 60 | return Card.identifierFactory 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Concentration/Concentration/Extensions/CollectionExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionExtension.swift 3 | // Concentration 4 | // 5 | // Created by Ruben on 12/2/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | // Extension for simple but useful uitilities 10 | extension Collection { 11 | /// 12 | /// If the collection contains only one element, return it. Otherwise, 13 | /// return nil. 14 | /// 15 | var oneAndOnly: Element? { 16 | return count == 1 ? first : nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Concentration/Concentration/Extensions/IntExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntExtension.swift 3 | // Concentration 4 | // 5 | // Created by Ruben on 12/2/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | // Extension for simple but useful uitilities 11 | extension Int { 12 | 13 | /// 14 | /// Get a random number between `self` and 0. If `self` is zero, 15 | /// zero will be returned. 16 | /// 17 | var arc4random: Int { 18 | if self > 0 { 19 | return Int(arc4random_uniform(UInt32(self))) 20 | } 21 | else if self < 0 { 22 | return -Int(arc4random_uniform(UInt32(abs(self)))) 23 | } 24 | else { 25 | return 0 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Concentration/Concentration/Extensions/MutableCollectionExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MutableCollectionExtension.swift 3 | // Concentration 4 | // 5 | // Created by Ruben on 12/2/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | // Extension for simple but useful uitilities 11 | extension MutableCollection { 12 | 13 | /// Shuffle the elements of `self` in-place. 14 | mutating func shuffle() { 15 | 16 | // Shuffle logic retrieved from: 17 | // https://stackoverflow.com/questions/37843647/shuffle-array-swift-3/37843901 18 | 19 | // Empty and single-element collections don't shuffle 20 | if count < 2 { return } 21 | 22 | // Shuffle them 23 | for i in indices.dropLast() { 24 | let diff = distance(from: i, to: endIndex) 25 | let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(diff)))) 26 | swapAt(i, j) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Concentration/Concentration/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationPortraitUpsideDown 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Concentration/Concentration/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Concentration 4 | // 5 | // Created by Ruben on 11/29/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Concentration/Concentration/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Concentration/Concentration/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /EmojiArt/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArt/EmojiArtDocumentTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtDocumentTableViewController.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 1/12/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// Controller for handling emoji-art documents 13 | /// 14 | class EmojiArtDocumentTableViewController: UITableViewController { 15 | 16 | // MARK: Model - List of documents represented as strings 17 | var emojiArtDocuments = ["One", "Two", "Three"] 18 | 19 | // MARK: - Table view data source 20 | 21 | /// 22 | /// Number of sections in the table view 23 | /// 24 | override func numberOfSections(in tableView: UITableView) -> Int { 25 | return 1 26 | } 27 | 28 | /// 29 | /// Number of rows in the table view 30 | /// 31 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 32 | // One row for each element in the emojiArtDocuments array 33 | return emojiArtDocuments.count 34 | } 35 | 36 | /// 37 | /// Get the tableView cell for the given indexPath 38 | /// 39 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 40 | // Reusable cell to populate 41 | let cell = tableView.dequeueReusableCell(withIdentifier: "DocumentCell", for: indexPath) 42 | 43 | // Setup the cell's content 44 | cell.textLabel?.text = emojiArtDocuments[indexPath.row] 45 | 46 | // Return the cell 47 | return cell 48 | } 49 | 50 | 51 | /// 52 | /// Override to support conditional editing of the table view. (i.e. swipe to delete) 53 | /// 54 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 55 | // Return false if you do not want the specified item to be editable. 56 | return true 57 | } 58 | 59 | /// 60 | /// Override to support editing the table view. 61 | /// 62 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 63 | // Delete 64 | if editingStyle == .delete { 65 | // Update model (delete record) 66 | emojiArtDocuments.remove(at: indexPath.row) 67 | 68 | // Delete the row from the data source 69 | tableView.deleteRows(at: [indexPath], with: .fade) 70 | } 71 | } 72 | 73 | /// 74 | /// Add a new document with an auto-generated name 75 | /// 76 | @IBAction func newEmojiArt(_ sender: UIBarButtonItem) { 77 | // Add a new document to the model (with auto-generated name) 78 | emojiArtDocuments.append("Document".madeUnique(withRespectTo: emojiArtDocuments)) 79 | 80 | // Reload tableView 81 | tableView.reloadData() 82 | } 83 | 84 | // 85 | // Called to notify the view controller that its view is about to layout its subviews. 86 | // 87 | override func viewWillLayoutSubviews() { 88 | super.viewWillLayoutSubviews() 89 | 90 | // Make sure the split-view controller's diplay mode is the one we want. 91 | if splitViewController?.preferredDisplayMode != .primaryOverlay { 92 | // The primary view controller is layered on top of the secondary view controller, 93 | // leaving the secondary view controller partially visible. 94 | splitViewController?.preferredDisplayMode = .primaryOverlay 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArt/EmojiArtView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtView.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 1/12/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// View for EmojiArt. Contains/shows the given image. 13 | /// 14 | class EmojiArtView: UIView { 15 | 16 | /// 17 | /// The background image 18 | /// 19 | var backgroundImage: UIImage? { 20 | didSet { 21 | // When image is set, we need to re-draw ourselves 22 | setNeedsDisplay() 23 | } 24 | } 25 | 26 | // Only override draw() if you perform custom drawing. 27 | // An empty implementation adversely affects performance during animation. 28 | override func draw(_ rect: CGRect) { 29 | // Set backgroundImage as the background 30 | backgroundImage?.draw(in: bounds) 31 | } 32 | 33 | // Init with frame 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | setup() 37 | } 38 | 39 | // Init with coder 40 | required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | setup() 43 | } 44 | 45 | /// 46 | /// Do initialization setup. Called after init(frame:) and init(coder:) 47 | /// 48 | private func setup() { 49 | // Register `self` to accept drop interactions 50 | addInteraction(UIDropInteraction(delegate: self)) 51 | } 52 | 53 | /// 54 | /// Add UILabel with the given `attributedString` and centered at `centerPoint` 55 | /// 56 | private func addLabel(with attributedString: NSAttributedString, centeredAt centerPoint: CGPoint) { 57 | let label = UILabel() 58 | 59 | // Setup label's color, text, size, etc. 60 | label.backgroundColor = .clear 61 | label.attributedText = attributedString 62 | label.sizeToFit() 63 | label.center = centerPoint 64 | 65 | // Add gesture recognizers to the label (i.e. pan to move, pinch to zoom, etc.) 66 | addEmojiArtGestureRecognizers(to: label) 67 | 68 | // Add subview 69 | self.addSubview(label) 70 | } 71 | } 72 | 73 | // 74 | // Make `EmojiArtView` conform to `UIDropInteractionDelegate` 75 | // 76 | extension EmojiArtView: UIDropInteractionDelegate { 77 | 78 | // 79 | // What to do when performing a drop 80 | // 81 | func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { 82 | // Copy item(s) 83 | return UIDropProposal(operation: .copy) 84 | } 85 | 86 | // 87 | // Determine if we can accept the given drop/session 88 | // 89 | func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { 90 | return session.canLoadObjects(ofClass: NSAttributedString.self) 91 | } 92 | 93 | // 94 | // Perform the given drop 95 | // 96 | func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { 97 | 98 | // Process any dropped attributed strings 99 | session.loadObjects(ofClass: NSAttributedString.self) { providers in 100 | 101 | // The location where the drop happens. Used to drop label in correct place 102 | let dropPoint = session.location(in: self) 103 | 104 | // Process each dropped attributed string 105 | for attributedString in providers as? [NSAttributedString] ?? [] { 106 | // Add a label with the dropped string 107 | self.addLabel(with: attributedString, centeredAt: dropPoint) 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArt/EmojiCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiCollectionViewCell.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 1/13/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// Collection view cell containing an emoji label 13 | /// 14 | class EmojiCollectionViewCell: UICollectionViewCell { 15 | /// 16 | /// The label shown in the collection view cell 17 | /// 18 | @IBOutlet weak var label: UILabel! 19 | } 20 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArt/TextFieldCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCollectionViewCell.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 3/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// CollectionViewCell with an input textField. 12 | /// 13 | class TextFieldCollectionViewCell: UICollectionViewCell, UITextFieldDelegate { 14 | 15 | /// 16 | /// This is called inside `textFieldDidEndEditing`. 17 | /// 18 | /// You may set this to do custom things when the text field ends editing. 19 | /// 20 | var resignationHandler: (()->Void)? 21 | 22 | /// 23 | /// The textField used to get input from the user 24 | /// 25 | @IBOutlet weak var textField: UITextField! { 26 | didSet { 27 | textField.delegate = self 28 | } 29 | } 30 | 31 | /// 32 | /// Text field ended editing 33 | /// 34 | func textFieldDidEndEditing(_ textField: UITextField) { 35 | // If user provided a resignationHandler, call it 36 | resignationHandler?() 37 | } 38 | 39 | /// 40 | /// Text field should return 41 | /// 42 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 43 | textField.resignFirstResponder() 44 | return true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArtTests/EmojiArtTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtTests.swift 3 | // EmojiArtTests 4 | // 5 | // Created by Ruben on 1/12/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import EmojiArt 11 | 12 | class EmojiArtTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArtTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArtUITests/EmojiArtUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtUITests.swift 3 | // EmojiArtUITests 4 | // 5 | // Created by Ruben on 1/12/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class EmojiArtUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /EmojiArt/EmojiArtUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /EmojiArt/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 1/12/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /EmojiArt/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /EmojiArt/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/EmojiArtL14/EmojiArtL14.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14.xcodeproj/xcuserdata/ruben.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | EmojiArtL14.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/DocumentBrowserViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentBrowserViewController.swift 3 | // EmojiArtL14 4 | // 5 | // Created by Ruben on 3/9/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// A view controller for browsing and performing actions on documents stored locally and in the cloud. 13 | /// 14 | class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate { 15 | 16 | // 17 | // View did load 18 | // 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | // UIDocumentBrowserViewControllerDelegate 23 | delegate = self 24 | 25 | // This will change to true if we're on iPad 26 | allowsDocumentCreation = false 27 | 28 | // We can only open one document at a time 29 | allowsPickingMultipleItems = false 30 | 31 | // If necessary, setup a "template" document used for creating new documents 32 | setupTemplate() 33 | 34 | // Only allow document creation if we got a valid template 35 | if template != nil { 36 | // Allow creation only if we have a valid template file 37 | allowsDocumentCreation = FileManager.default.createFile(atPath: template!.path, contents: Data()) 38 | } 39 | } 40 | 41 | /// 42 | /// If necessary, setup a "template" document used for creating new documents. 43 | /// 44 | private func setupTemplate() { 45 | // Templates are only setup on iPad (we only create documents on iPad because iPhones cannot drag n' drop from outside 46 | // our app) 47 | if UIDevice.current.userInterfaceIdiom == .pad { 48 | template = try? FileManager.default.url( 49 | for: .applicationSupportDirectory, 50 | in: .userDomainMask, 51 | appropriateFor: nil, 52 | create: true) 53 | .appendingPathComponent("Untitled.json") 54 | } 55 | } 56 | 57 | /// 58 | /// A URL where the "template/blank" document is stored. Used for creating new documents. 59 | /// 60 | private var template: URL? 61 | 62 | // MARK: UIDocumentBrowserViewControllerDelegate 63 | 64 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) { 65 | importHandler(template, .copy) 66 | } 67 | 68 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) { 69 | guard let sourceURL = documentURLs.first else { return } 70 | 71 | // Present the Document View Controller for the first document that was picked. 72 | // If you support picking multiple items, make sure you handle them all. 73 | presentDocument(at: sourceURL) 74 | } 75 | 76 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) { 77 | // Present the Document View Controller for the new newly created document 78 | presentDocument(at: destinationURL) 79 | } 80 | 81 | func documentBrowser(_ controller: UIDocumentBrowserViewController, failedToImportDocumentAt documentURL: URL, error: Error?) { 82 | // Make sure to handle the failed import appropriately, e.g., by presenting an error message to the user. 83 | } 84 | 85 | // MARK: Document Presentation 86 | 87 | func presentDocument(at documentURL: URL) { 88 | 89 | // Used to instansiate a new MVC 90 | let storyBoard = UIStoryboard(name: "Main", bundle: nil) 91 | 92 | // Note: this "DocumentMVC" identifier is setup in Interface Builder 93 | // (click MVC -> Identity inspector -> Storyboard ID) 94 | let documentVC = storyBoard.instantiateViewController(withIdentifier: "DocumentMVC") 95 | 96 | // Setup destination's view controller model 97 | if let emojiArtVC = documentVC.contents as? EmojiArtViewController { 98 | emojiArtVC.document = EmojiArtDocument(fileURL: documentURL) 99 | // Present the document view controller 100 | present(documentVC, animated: true) 101 | } 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/EmojiArt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArt.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 3/9/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 12 | /// Model an EmojiArt. Composed of an URL image for the background and a bunch of emojis/label 13 | /// added into it with a respective size and location. 14 | /// 15 | struct EmojiArt: Codable { 16 | 17 | /// 18 | /// URL for the background image 19 | /// 20 | var url: URL 21 | 22 | /// 23 | /// The emojis that are dropped into the docuemnt 24 | /// 25 | var emojis: [EmojiInfo] = [] 26 | 27 | /// 28 | /// Represents a dropped emoji: location, text and size 29 | /// 30 | struct EmojiInfo: Codable { 31 | let x: Int 32 | let y: Int 33 | let text: String 34 | let size: Int 35 | } 36 | 37 | /// 38 | /// Create new EmojiArt with given URL (should be an image URL), and a bunch of emojis 39 | /// 40 | init(url: URL, emojis: [EmojiInfo]) { 41 | self.url = url 42 | self.emojis = emojis 43 | } 44 | 45 | /// 46 | /// JSON Data representing the current object. 47 | /// 48 | var json: Data? { 49 | return try? JSONEncoder().encode(self) 50 | } 51 | 52 | /// 53 | /// Create object from given JSON Data. 54 | /// 55 | init?(json: Data) { 56 | if let newValue = try? JSONDecoder().decode(EmojiArt.self, from: json) { 57 | self = newValue 58 | } 59 | else { 60 | return nil 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/EmojiArtDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtDocument.swift 3 | // EmojiArtL14 4 | // 5 | // Created by Ruben on 3/9/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// A `UIDocument` representing an EmojiArt document. 13 | /// 14 | class EmojiArtDocument: UIDocument { 15 | 16 | // Model: EmojiArt 17 | var emojiArt: EmojiArt? 18 | 19 | // Thumbnail image representing the document (i.e. icon that shows in the Files app) 20 | var thumbnail: UIImage? 21 | 22 | // 23 | // Override this method to return the document data to be saved. 24 | // Note: this method returns `Any` in case you want to return a `FileWrapper` instead of `Data` 25 | // 26 | override func contents(forType typeName: String) throws -> Any { 27 | return emojiArt?.json ?? Data() 28 | } 29 | 30 | // 31 | // Override this method to load the document data into the app’s data model. 32 | // 33 | override func load(fromContents contents: Any, ofType typeName: String?) throws { 34 | // Check if the contents passed to us are actually of type Data 35 | if let json = contents as? Data { 36 | emojiArt = EmojiArt(json: json) 37 | } 38 | else { 39 | print("Error trying to load EmojiArtDocument. Content is not JSON data") 40 | } 41 | } 42 | 43 | // 44 | // Returns a dictionary of file attributes to associate with the document file when writing 45 | // or updating it. 46 | // 47 | // We're overriding this to add a custom thumbnail image that represents our document. 48 | // 49 | override func fileAttributesToWrite(to url: URL, for saveOperation: UIDocumentSaveOperation) throws -> [AnyHashable : Any] { 50 | var attributes = try super.fileAttributesToWrite(to: url, for: saveOperation) 51 | if let thumbnail = self.thumbnail { 52 | // Add our ustom thumbnail image 53 | attributes[URLResourceKey.thumbnailDictionaryKey] = [URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey:thumbnail] 54 | } 55 | return attributes 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/EmojiCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiCollectionViewCell.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 1/13/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 12 | /// Collection view cell containing an emoji label 13 | /// 14 | class EmojiCollectionViewCell: UICollectionViewCell { 15 | /// 16 | /// The label shown in the collection view cell 17 | /// 18 | @IBOutlet weak var label: UILabel! 19 | } 20 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeIconFiles 11 | 12 | CFBundleTypeName 13 | JSON 14 | CFBundleTypeRole 15 | Viewer 16 | LSHandlerRank 17 | Alternate 18 | LSItemContentTypes 19 | 20 | public.json 21 | 22 | 23 | 24 | CFBundleExecutable 25 | $(EXECUTABLE_NAME) 26 | CFBundleIdentifier 27 | $(PRODUCT_BUNDLE_IDENTIFIER) 28 | CFBundleInfoDictionaryVersion 29 | 6.0 30 | CFBundleName 31 | $(PRODUCT_NAME) 32 | CFBundlePackageType 33 | APPL 34 | CFBundleShortVersionString 35 | 1.0 36 | CFBundleVersion 37 | 1 38 | LSRequiresIPhoneOS 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIMainStoryboardFile 43 | Main 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | UISupportsDocumentBrowser 62 | 63 | UTImportedTypeDeclarations 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EmojiArtL14 4 | // 5 | // Created by Ruben on 3/9/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | func application(_ app: UIApplication, open inputURL: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 45 | // Ensure the URL is a file URL 46 | guard inputURL.isFileURL else { return false } 47 | 48 | // Reveal / import the document at the URL 49 | guard let documentBrowserViewController = window?.rootViewController as? DocumentBrowserViewController else { return false } 50 | 51 | documentBrowserViewController.revealDocument(at: inputURL, importIfNeeded: true) { (revealedDocumentURL, error) in 52 | if let error = error { 53 | // Handle the error appropriately 54 | print("Failed to reveal the document at URL \(inputURL) with error: '\(error)'") 55 | return 56 | } 57 | 58 | // Present the Document View Controller for the revealed URL 59 | documentBrowserViewController.presentDocument(at: revealedDocumentURL!) 60 | } 61 | 62 | return true 63 | } 64 | 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /EmojiArtL14/EmojiArtL14/TextFieldCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCollectionViewCell.swift 3 | // EmojiArt 4 | // 5 | // Created by Ruben on 3/6/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// CollectionViewCell with an input textField. 12 | /// 13 | class TextFieldCollectionViewCell: UICollectionViewCell, UITextFieldDelegate { 14 | 15 | /// 16 | /// This is called inside `textFieldDidEndEditing`. 17 | /// 18 | /// You may set this to do custom things when the text field ends editing. 19 | /// 20 | var resignationHandler: (()->Void)? 21 | 22 | /// 23 | /// The textField used to get input from the user 24 | /// 25 | @IBOutlet weak var textField: UITextField! { 26 | didSet { 27 | textField.delegate = self 28 | } 29 | } 30 | 31 | /// 32 | /// Text field ended editing 33 | /// 34 | func textFieldDidEndEditing(_ textField: UITextField) { 35 | // If user provided a resignationHandler, call it 36 | resignationHandler?() 37 | } 38 | 39 | /// 40 | /// Text field should return 41 | /// 42 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 43 | textField.resignFirstResponder() 44 | return true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ImageGallery/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/GalleryViewController/GalleryViewController+UICollectionViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDataSource.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/19/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Conform to `UICollectionViewDataSource` 13 | // 14 | extension GalleryViewController: UICollectionViewDataSource { 15 | 16 | /// 17 | /// Number of items in section 18 | /// 19 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 20 | // How many items does the gallery have? 21 | return gallery.items.count 22 | } 23 | 24 | /// 25 | /// Cell for row at indexPath 26 | /// 27 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 28 | 29 | // Deque reusable cell for an item's cell 30 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Settings.StoryboardIdentifiers.ImageCell, for: indexPath) as! ImageCollectionViewCell 31 | 32 | // Setup the cell 33 | cell.imageURL = gallery.items[indexPath.item].imageURL 34 | 35 | // Return it 36 | return cell 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/GalleryViewController/GalleryViewController+UICollectionViewDelegateFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDelegateFlowLayout.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/19/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Adpot protocol UICollectionViewDelegateFlowLayout: 13 | // 14 | extension GalleryViewController: UICollectionViewDelegateFlowLayout { 15 | 16 | // 17 | // Asks the delegate for the size of the specified item’s cell. 18 | // 19 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 20 | 21 | // Get aspect ratio of the image to display 22 | let ratio = gallery.items[indexPath.item].ratio 23 | 24 | // Height is calculated based on the width and aspect ratio 25 | let height: CGFloat = (cellWidth * CGFloat(ratio.height)) / CGFloat(ratio.width) 26 | 27 | // The cell's size 28 | return CGSize(width: cellWidth, height: height) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/GalleryViewController/GalleryViewController+UICollectionViewDragDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDragDelegate.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Adopt `UICollectionViewDragDelegate`: 13 | // 14 | // "The interface for initiating drags from a collection view." 15 | // 16 | extension GalleryViewController: UICollectionViewDragDelegate { 17 | 18 | // 19 | // Provide items to begin a drag associated with a given indexPath. 20 | // If an empty array is returned a drag session will not begin. 21 | // 22 | func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { 23 | 24 | // Set local context to the collectionView 25 | session.localContext = collectionView 26 | 27 | // The item being dragged 28 | let item = gallery.items[indexPath.item] 29 | 30 | // For drags outside our app, the URL of the image will be provided 31 | let url = UIDragItem(itemProvider: NSItemProvider(object: item.imageURL as NSURL)) 32 | 33 | // For local drags, the full `ImageGallery.Item` will be provided 34 | url.localObject = item 35 | 36 | return [url] 37 | } 38 | 39 | func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { 40 | // Set local context to the collectionView 41 | session.localContext = collectionView 42 | 43 | // The item being dragged 44 | let item = gallery.items[indexPath.item] 45 | 46 | // For drags outside our app, the URL of the image will be provided 47 | let url = UIDragItem(itemProvider: NSItemProvider(object: item.imageURL as NSURL)) 48 | 49 | // For local drags, the full `ImageGallery.Item` will be provided 50 | url.localObject = item 51 | 52 | return [url] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/ImageGallery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageGallery.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a gallery of "images". It does not store images per se, 12 | /// just an URL where the image is retrieved from, as well as the aspect 13 | /// ratio of such image. 14 | /// 15 | class ImageGallery { 16 | 17 | /// 18 | /// Create new gallery with the given `name` and `items` 19 | /// 20 | init(name: String, items: [Item]) { 21 | self.name = name 22 | self.items = items 23 | } 24 | 25 | /// 26 | /// Create empty gallery with default name and no items 27 | /// 28 | init() {} 29 | 30 | /// 31 | /// Represents an item in the gallery. 32 | /// 33 | struct Item { 34 | 35 | /// 36 | /// URL where the image is to be retrieved 37 | /// 38 | var imageURL: URL 39 | 40 | /// 41 | /// The aspect ratio of the image 42 | /// 43 | var ratio: AspectRatio 44 | } 45 | 46 | /// 47 | /// Provides width and height aspect ratio. (i.e. 16:9, 4:3, etc.) 48 | /// 49 | struct AspectRatio { 50 | /// 51 | /// Width 52 | /// 53 | var width: Int 54 | 55 | /// 56 | /// Height 57 | /// 58 | var height: Int 59 | } 60 | 61 | /// 62 | /// The name of the gallery 63 | /// 64 | var name: String = "Untitled" 65 | 66 | /// 67 | /// List of items in the gallery 68 | /// 69 | var items = [Item]() 70 | } 71 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/ItemViewController/ItemViewController+UIScrollViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewController+UIScrollViewDelegate.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/21/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | // 11 | // Confrom to `UIScrollViewDelegate` 12 | // 13 | extension ItemViewController: UIScrollViewDelegate { 14 | 15 | /// 16 | /// The view for zooming in the scrollView 17 | /// 18 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 19 | // If scrollView contains an imageView, return it 20 | return scrollView.subviews.first as? UIImageView 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/ItemViewController/ItemViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewController.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// View controller that displays a given `ImageGallery.Item` 12 | /// 13 | class ItemViewController: UIViewController { 14 | 15 | // TODO: Should properly reflect "fetching" and "failed" states as in ImageCollectionViewCell 16 | 17 | // MARK: Public 18 | 19 | /// 20 | /// The image to display 21 | /// 22 | var galleryItem: ImageGallery.Item? { 23 | didSet { 24 | if galleryItem != nil { 25 | fetchImage(galleryItem!.imageURL) 26 | } 27 | } 28 | } 29 | 30 | // MARK: Private 31 | 32 | /// 33 | /// Fetch image using given `url` 34 | /// 35 | private func fetchImage(_ url: URL) { 36 | 37 | // TODO: should show "loading" animation 38 | 39 | DispatchQueue.global(qos: .userInitiated).async { 40 | guard let data = try? Data(contentsOf: url.imageURL) else { 41 | // TODO: should reflect "failed" state 42 | return 43 | } 44 | guard let image = UIImage(data: data) else { 45 | // TODO: should reflect "failed" state 46 | return 47 | } 48 | 49 | DispatchQueue.main.async { 50 | self.imageView = UIImageView(image: image) 51 | } 52 | } 53 | } 54 | 55 | /// 56 | /// The imageView to display 57 | /// 58 | private var imageView: UIImageView? { 59 | didSet { 60 | if imageView != nil { 61 | // Adapt view to fit image 62 | imageView!.sizeToFit() 63 | 64 | // Add imageView to scrollView 65 | scrollView.addSubview(imageView!) 66 | 67 | // Update scrollView accordingly 68 | updateScrollView() 69 | } 70 | else { 71 | // Show a blank screen 72 | scrollView.subviews.forEach { $0.removeFromSuperview() } 73 | } 74 | } 75 | } 76 | 77 | /// 78 | /// Update scrollView to properly fit the image 79 | /// 80 | private func updateScrollView() { 81 | 82 | if imageView != nil { 83 | 84 | // The size of the image 85 | let imageSize = imageView?.image?.size ?? CGSize.zero 86 | 87 | // Size of the display 88 | let displaySize = scrollView.bounds.size 89 | 90 | // Scrollview content-size must fit the image 91 | scrollView.contentSize = imageSize 92 | 93 | // A scale that will fit the whole image on screen 94 | let zoomScaleThatFitsWholeImage = min(displaySize.width/imageSize.width, 95 | displaySize.height/imageSize.height) 96 | 97 | scrollView.minimumZoomScale = zoomScaleThatFitsWholeImage 98 | scrollView.zoomScale = zoomScaleThatFitsWholeImage 99 | scrollView.maximumZoomScale = 3.0 100 | } 101 | 102 | } 103 | 104 | /// 105 | /// ScrollView showing the image 106 | /// 107 | @IBOutlet private weak var scrollView: UIScrollView! { 108 | didSet { 109 | setupScrollView() 110 | } 111 | } 112 | 113 | /// 114 | /// Setup scrollView. Called right after the scrollView is set. 115 | /// 116 | private func setupScrollView() { 117 | scrollView.delegate = self 118 | } 119 | 120 | /// 121 | /// View did layout subviews 122 | /// 123 | override func viewDidLayoutSubviews() { 124 | super.viewDidLayoutSubviews() 125 | 126 | // Keep scrollView updated to fit the new bounds 127 | updateScrollView() 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/21/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 12 | /// Settings for the app 13 | /// 14 | struct Settings { 15 | 16 | /// 17 | /// Do not allow new instances 18 | /// 19 | private init() {} 20 | 21 | /// 22 | /// Contains storyboard segue identifiers 23 | /// 24 | struct StoryboardSegues { 25 | 26 | /// 27 | /// Segue that goes from a gallery item cell, to a item detail controller 28 | /// 29 | static let ShowItem = "ShowItem" 30 | 31 | /// 32 | /// Segue that shows the selected image gallery 33 | /// 34 | static let ShowGallery = "ShowGallery" 35 | } 36 | 37 | /// 38 | /// Contains storyboard identifiers, such as tableView cell identifiers 39 | /// 40 | struct StoryboardIdentifiers { 41 | /// 42 | /// CollectionViewCell that shows a gallery image 43 | /// 44 | static let ImageCell = "ImageCell" 45 | 46 | /// 47 | /// TableViewCell that shows a gallery document 48 | /// 49 | static let DocumentCell = "DocumentCell" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/17/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/Assets.xcassets/testImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "testImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/Assets.xcassets/testImage.imageset/testImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/ImageGallery/ImageGallery/Supporting Files/Assets.xcassets/testImage.imageset/testImage.png -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Utilities/UIViewControllerUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewControllerUtilities.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/26/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// Simple but useful utilities for `UIViewController` 12 | /// 13 | extension UIViewController { 14 | 15 | /// 16 | /// The contents of the controller. It is `self` in all cases except for instances of 17 | /// `UINavigationController` where, if present, returns the controller's visible view 18 | /// controller (or self if none is set). 19 | /// 20 | /// This allows for great iphone/ipad compatibillity 21 | /// in just one UI. 22 | /// 23 | var contents: UIViewController { 24 | 25 | // Is this a navigation controller? 26 | if let navigationVC = self as? UINavigationController { 27 | return navigationVC.visibleViewController ?? self 28 | } 29 | else { 30 | return self 31 | } 32 | 33 | // TODO: this could work great with UITabViewController 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ImageGallery/ImageGallery/Utilities/URLUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLUtilities.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Simple but useful utilities for `URL` 12 | /// 13 | extension URL { 14 | var imageURL: URL { 15 | // check to see if there is an embedded imgurl reference 16 | for query in query?.components(separatedBy: "&") ?? [] { 17 | let queryComponents = query.components(separatedBy: "=") 18 | if queryComponents.count == 2 { 19 | if queryComponents[0] == "imgurl", let url = URL(string: queryComponents[1].removingPercentEncoding ?? "") { 20 | return url 21 | } 22 | } 23 | } 24 | return self.baseURL ?? self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/ImageGallery_P6/ImageGallery_P6.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6.xcodeproj/xcuserdata/ruben.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6.xcodeproj/xcuserdata/ruben.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageGallery_P6.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/DocumentBrowser/DocumentBrowserViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentBrowserViewController.swift 3 | // ImageGallery_P6 4 | // 5 | // Created by Ruben on 3/16/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate { 12 | 13 | // 14 | // View did load 15 | // 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // Setup view controller 20 | delegate = self 21 | allowsDocumentCreation = true 22 | allowsPickingMultipleItems = false 23 | } 24 | 25 | 26 | // MARK: UIDocumentBrowserViewControllerDelegate 27 | 28 | // 29 | // Asks the delegate to create a new document. 30 | // 31 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didRequestDocumentCreationWithHandler importHandler: @escaping (URL?, UIDocumentBrowserViewController.ImportMode) -> Void) { 32 | 33 | // To create a new document, we'll use the "Gallery.imgallery" template that ships with the app 34 | let newDocumentURL: URL = Bundle.main.url(forResource: "Gallery", withExtension: "imgallery")! 35 | 36 | // Call handler that imports the document with the given URL 37 | importHandler(newDocumentURL, .copy) 38 | } 39 | 40 | // 41 | // Tells the delegate that the user has selected one or more documents. 42 | // 43 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentURLs documentURLs: [URL]) { 44 | 45 | // Make sure we have a valid URL (we only support picking one document at a time) 46 | guard let sourceURL = documentURLs.first else { return } 47 | 48 | // Present the Document View Controller for the document that was picked. 49 | presentDocument(at: sourceURL) 50 | } 51 | 52 | // 53 | // Tells the delegate that a document has been successfully imported. 54 | // 55 | func documentBrowser(_ controller: UIDocumentBrowserViewController, didImportDocumentAt sourceURL: URL, toDestinationURL destinationURL: URL) { 56 | // Present the Document View Controller for the new newly created document 57 | presentDocument(at: destinationURL) 58 | } 59 | 60 | // 61 | // Tells the delegate that the document browser failed to import the specified document. 62 | // 63 | func documentBrowser(_ controller: UIDocumentBrowserViewController, failedToImportDocumentAt documentURL: URL, error: Error?) { 64 | // TODO: we should display a message to the user here 65 | print("==== FAILED") 66 | } 67 | 68 | // MARK: Document Presentation 69 | 70 | // 71 | // Present document for the given url 72 | // 73 | func presentDocument(at documentURL: URL) { 74 | 75 | // We'll get a view controller from the `Main` storyboard 76 | let storyBoard = UIStoryboard(name: "Main", bundle: nil) 77 | 78 | // Our gallery view controller is embedded in a navigation controller 79 | let navigationVC = storyBoard.instantiateViewController(withIdentifier: "GalleryControllerEntry") as! UINavigationController 80 | let documentViewController = navigationVC.contents as! GalleryViewController 81 | 82 | // Setup/prepare VC before presenting it 83 | documentViewController.document = ImageGalleryDocument(fileURL: documentURL) 84 | 85 | // Present the navigation VC to the user 86 | present(navigationVC, animated: true, completion: nil) 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/DocumentBrowser/ImageGalleryDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // ImageGallery_P6 4 | // 5 | // Created by Ruben on 3/16/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// UIDocument that represents an ImageGallery 12 | /// 13 | class ImageGalleryDocument: UIDocument { 14 | 15 | /// 16 | /// The image gallery for the document 17 | /// 18 | var gallery: ImageGallery? 19 | 20 | /// 21 | /// A thumbnail image representing the document. I.e. shown in Finder, as the document's icon 22 | /// 23 | var thumbnail: UIImage? 24 | 25 | // 26 | // Override this method to return the document data to be saved. 27 | // 28 | override func contents(forType typeName: String) throws -> Any { 29 | // Note: this method returns `Any` in case you need to return a FileWrapper. For regular 30 | // documents, returning `Data` is the way to go. Here, we'll return a JSON/Data representation 31 | // of the `ImageGallery` 32 | return try! JSONEncoder().encode(gallery) 33 | } 34 | 35 | // 36 | // Override this method to load the document data into the app’s data model. 37 | // 38 | override func load(fromContents contents: Any, ofType typeName: String?) throws { 39 | // We only support loading `Data`, which should be a JSON representation of an `ImageGallery` 40 | guard let data = contents as? Data else { 41 | // The thing we want to load is not valid Data 42 | throw ImageGalleryDocumentError.dataProblem 43 | } 44 | 45 | // Try to decode the Data as JSON, otherwise, error will be thrown to the caller 46 | gallery = try JSONDecoder().decode(ImageGallery.self, from: data) 47 | } 48 | 49 | // 50 | // Called or overridden to handle an error that occurs during an attempt to read, save, or 51 | // revert a document. 52 | // 53 | override func handleError(_ error: Error, userInteractionPermitted: Bool) { 54 | // TODO: here we would handle any errors thrown 55 | print("--- ERROR: \(error)") 56 | } 57 | 58 | // 59 | // Returns a dictionary of file attributes to associate with the document file when writing 60 | // or updating it. 61 | // 62 | // We're overriding this to add a custom thumbnail image that represents our document. 63 | // 64 | override func fileAttributesToWrite(to url: URL, for saveOperation: UIDocumentSaveOperation) throws -> [AnyHashable : Any] { 65 | 66 | // Get all attributes for the document, so we can add a thumbnail image 67 | var attributes = try super.fileAttributesToWrite(to: url, for: saveOperation) 68 | 69 | // If we have a vaild thumbnail set, use it 70 | if let thumbnail = self.thumbnail { 71 | // Add our ustom thumbnail image 72 | attributes[URLResourceKey.thumbnailDictionaryKey] = [URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey:thumbnail] 73 | } 74 | return attributes 75 | } 76 | } 77 | 78 | /// 79 | /// Possible errors for ImageGalleryDocument operations 80 | /// 81 | enum ImageGalleryDocumentError: Error { 82 | /// 83 | /// Data problem. (i.e. we were asked to open a document with invalid Data) 84 | /// 85 | case dataProblem 86 | 87 | /// 88 | /// Problem decoding the data. (i.e. we got a corrupt/invalid JSON representation) 89 | /// 90 | case decodingProblem(String) 91 | } 92 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Gallery.imgallery: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Untitled", 3 | "items": [] 4 | } 5 | 6 | 7 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/GalleryViewController/GalleryViewController+UICollectionViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDataSource.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/19/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Conform to `UICollectionViewDataSource` 13 | // 14 | extension GalleryViewController: UICollectionViewDataSource { 15 | 16 | /// 17 | /// Number of items in section 18 | /// 19 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 20 | // How many items does the gallery have? 21 | return gallery.items.count 22 | } 23 | 24 | /// 25 | /// Cell for row at indexPath 26 | /// 27 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 28 | 29 | // Deque reusable cell for an item's cell 30 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Settings.StoryboardIdentifiers.ImageCell, for: indexPath) as! ImageCollectionViewCell 31 | 32 | // Setup the cell 33 | cell.imageURL = gallery.items[indexPath.item].imageURL 34 | 35 | // Return it 36 | return cell 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/GalleryViewController/GalleryViewController+UICollectionViewDelegateFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDelegateFlowLayout.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/19/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Adpot protocol UICollectionViewDelegateFlowLayout: 13 | // 14 | extension GalleryViewController: UICollectionViewDelegateFlowLayout { 15 | 16 | // 17 | // Asks the delegate for the size of the specified item’s cell. 18 | // 19 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 20 | 21 | // Get aspect ratio of the image to display 22 | let ratio = gallery.items[indexPath.item].ratio 23 | 24 | // Height is calculated based on the width and aspect ratio 25 | let height: CGFloat = (cellWidth * CGFloat(ratio.height)) / CGFloat(ratio.width) 26 | 27 | // The cell's size 28 | return CGSize(width: cellWidth, height: height) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/GalleryViewController/GalleryViewController+UICollectionViewDragDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GalleryViewController+UICollectionViewDragDelegate.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // 12 | // Adopt `UICollectionViewDragDelegate`: 13 | // 14 | // "The interface for initiating drags from a collection view." 15 | // 16 | extension GalleryViewController: UICollectionViewDragDelegate { 17 | 18 | // 19 | // Provide items to begin a drag associated with a given indexPath. 20 | // If an empty array is returned a drag session will not begin. 21 | // 22 | func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { 23 | 24 | // Set local context to the collectionView 25 | session.localContext = collectionView 26 | 27 | // The item being dragged 28 | let item = gallery.items[indexPath.item] 29 | 30 | // For drags outside our app, the URL of the image will be provided 31 | let url = UIDragItem(itemProvider: NSItemProvider(object: item.imageURL as NSURL)) 32 | 33 | // For local drags, the full `ImageGallery.Item` will be provided 34 | url.localObject = item 35 | 36 | return [url] 37 | } 38 | 39 | func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] { 40 | // Set local context to the collectionView 41 | session.localContext = collectionView 42 | 43 | // The item being dragged 44 | let item = gallery.items[indexPath.item] 45 | 46 | // For drags outside our app, the URL of the image will be provided 47 | let url = UIDragItem(itemProvider: NSItemProvider(object: item.imageURL as NSURL)) 48 | 49 | // For local drags, the full `ImageGallery.Item` will be provided 50 | url.localObject = item 51 | 52 | return [url] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/ImageGallery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageGallery.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a gallery of "images". It does not store images per se, 12 | /// just an URL where the image is retrieved from, as well as the aspect 13 | /// ratio of such image. 14 | /// 15 | class ImageGallery: Codable { 16 | 17 | /// 18 | /// Create new gallery with the given `name` and `items` 19 | /// 20 | init(name: String, items: [Item]) { 21 | self.name = name 22 | self.items = items 23 | } 24 | 25 | /// 26 | /// Create empty gallery with default name and no items 27 | /// 28 | init() {} 29 | 30 | /// 31 | /// Represents an item in the gallery. 32 | /// 33 | struct Item: Codable { 34 | 35 | /// 36 | /// URL where the image is to be retrieved 37 | /// 38 | var imageURL: URL 39 | 40 | /// 41 | /// The aspect ratio of the image 42 | /// 43 | var ratio: AspectRatio 44 | } 45 | 46 | /// 47 | /// Provides width and height aspect ratio. (i.e. 16:9, 4:3, etc.) 48 | /// 49 | struct AspectRatio: Codable { 50 | /// 51 | /// Width 52 | /// 53 | var width: Int 54 | 55 | /// 56 | /// Height 57 | /// 58 | var height: Int 59 | } 60 | 61 | /// 62 | /// The name of the gallery 63 | /// 64 | var name: String = "Untitled" 65 | 66 | /// 67 | /// List of items in the gallery 68 | /// 69 | var items = [Item]() 70 | } 71 | 72 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleDocumentTypes 13 | 14 | 15 | CFBundleTypeIconFiles 16 | 17 | CFBundleTypeName 18 | ImageGallery 19 | CFBundleTypeRole 20 | Editor 21 | LSHandlerRank 22 | Owner 23 | LSItemContentTypes 24 | 25 | com.rubenbaca.imgallery 26 | 27 | 28 | 29 | CFBundleExecutable 30 | $(EXECUTABLE_NAME) 31 | CFBundleIdentifier 32 | $(PRODUCT_BUNDLE_IDENTIFIER) 33 | CFBundleInfoDictionaryVersion 34 | 6.0 35 | CFBundleName 36 | $(PRODUCT_NAME) 37 | CFBundlePackageType 38 | APPL 39 | CFBundleShortVersionString 40 | 1.0 41 | CFBundleVersion 42 | 1 43 | LSRequiresIPhoneOS 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIMainStoryboardFile 48 | Main 49 | UIRequiredDeviceCapabilities 50 | 51 | armv7 52 | 53 | UISupportedInterfaceOrientations 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UISupportedInterfaceOrientations~ipad 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationPortraitUpsideDown 63 | UIInterfaceOrientationLandscapeLeft 64 | UIInterfaceOrientationLandscapeRight 65 | 66 | UISupportsDocumentBrowser 67 | 68 | UTExportedTypeDeclarations 69 | 70 | 71 | UTTypeConformsTo 72 | 73 | public.data 74 | public.content 75 | 76 | UTTypeDescription 77 | ImageGallery 78 | UTTypeIdentifier 79 | com.rubenbaca.imgallery 80 | UTTypeTagSpecification 81 | 82 | public.filename-extension 83 | 84 | imgallery 85 | 86 | 87 | 88 | 89 | UTImportedTypeDeclarations 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/ItemViewController+UIScrollViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewController+UIScrollViewDelegate.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/21/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | // 11 | // Confrom to `UIScrollViewDelegate` 12 | // 13 | extension ItemViewController: UIScrollViewDelegate { 14 | 15 | /// 16 | /// The view for zooming in the scrollView 17 | /// 18 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 19 | // If scrollView contains an imageView, return it 20 | return scrollView.subviews.first as? UIImageView 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/ItemViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewController.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// View controller that displays a given `ImageGallery.Item` 12 | /// 13 | class ItemViewController: UIViewController { 14 | 15 | // TODO: Should properly reflect "fetching" and "failed" states as in ImageCollectionViewCell 16 | 17 | // MARK: Public 18 | 19 | /// 20 | /// The image to display 21 | /// 22 | var galleryItem: ImageGallery.Item? { 23 | didSet { 24 | if galleryItem != nil { 25 | fetchImage(galleryItem!.imageURL) 26 | } 27 | } 28 | } 29 | 30 | // MARK: Private 31 | 32 | /// 33 | /// Fetch image using given `url` 34 | /// 35 | private func fetchImage(_ url: URL) { 36 | 37 | // TODO: should show "loading" animation 38 | // TODO: should also use cache 39 | 40 | DispatchQueue.global(qos: .userInitiated).async { 41 | guard let data = try? Data(contentsOf: url.imageURL) else { 42 | // TODO: should reflect "failed" state 43 | return 44 | } 45 | guard let image = UIImage(data: data) else { 46 | // TODO: should reflect "failed" state 47 | return 48 | } 49 | 50 | DispatchQueue.main.async { 51 | self.imageView = UIImageView(image: image) 52 | } 53 | } 54 | } 55 | 56 | /// 57 | /// The imageView to display 58 | /// 59 | private var imageView: UIImageView? { 60 | didSet { 61 | if imageView != nil { 62 | // Adapt view to fit image 63 | imageView!.sizeToFit() 64 | 65 | // Add imageView to scrollView 66 | scrollView.addSubview(imageView!) 67 | 68 | // Update scrollView accordingly 69 | updateScrollView() 70 | } 71 | else { 72 | // Show a blank screen 73 | scrollView.subviews.forEach { $0.removeFromSuperview() } 74 | } 75 | } 76 | } 77 | 78 | /// 79 | /// Update scrollView to properly fit the image 80 | /// 81 | private func updateScrollView() { 82 | 83 | if imageView != nil { 84 | 85 | // The size of the image 86 | let imageSize = imageView?.image?.size ?? CGSize.zero 87 | 88 | // Size of the display 89 | let displaySize = scrollView.bounds.size 90 | 91 | // Scrollview content-size must fit the image 92 | scrollView.contentSize = imageSize 93 | 94 | // A scale that will fit the whole image on screen 95 | let zoomScaleThatFitsWholeImage = min(displaySize.width/imageSize.width, 96 | displaySize.height/imageSize.height) 97 | 98 | scrollView.minimumZoomScale = zoomScaleThatFitsWholeImage 99 | scrollView.zoomScale = zoomScaleThatFitsWholeImage 100 | scrollView.maximumZoomScale = 3.0 101 | } 102 | 103 | } 104 | 105 | /// 106 | /// ScrollView showing the image 107 | /// 108 | @IBOutlet private weak var scrollView: UIScrollView! { 109 | didSet { 110 | setupScrollView() 111 | } 112 | } 113 | 114 | /// 115 | /// Setup scrollView. Called right after the scrollView is set. 116 | /// 117 | private func setupScrollView() { 118 | scrollView.delegate = self 119 | } 120 | 121 | /// 122 | /// View did layout subviews 123 | /// 124 | override func viewDidLayoutSubviews() { 125 | super.viewDidLayoutSubviews() 126 | 127 | // Keep scrollView updated to fit the new bounds 128 | updateScrollView() 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ImageGallery_P6 4 | // 5 | // Created by Ruben on 3/16/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | func application(_ app: UIApplication, open inputURL: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 45 | // Ensure the URL is a file URL 46 | guard inputURL.isFileURL else { return false } 47 | 48 | // Reveal / import the document at the URL 49 | guard let documentBrowserViewController = window?.rootViewController as? DocumentBrowserViewController else { return false } 50 | 51 | documentBrowserViewController.revealDocument(at: inputURL, importIfNeeded: true) { (revealedDocumentURL, error) in 52 | if let error = error { 53 | // Handle the error appropriately 54 | print("Failed to reveal the document at URL \(inputURL) with error: '\(error)'") 55 | return 56 | } 57 | 58 | // Present the Document View Controller for the revealed URL 59 | documentBrowserViewController.presentDocument(at: revealedDocumentURL!) 60 | } 61 | 62 | return true 63 | } 64 | 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Utilities/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/21/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 12 | /// Settings for the app 13 | /// 14 | struct Settings { 15 | 16 | /// 17 | /// Do not allow new instances 18 | /// 19 | private init() {} 20 | 21 | /// 22 | /// Contains storyboard segue identifiers 23 | /// 24 | struct StoryboardSegues { 25 | 26 | /// 27 | /// Segue that goes from a gallery item cell, to a item detail controller 28 | /// 29 | static let ShowItem = "ShowItem" 30 | 31 | /// 32 | /// Segue that shows the selected image gallery 33 | /// 34 | static let ShowGallery = "ShowGallery" 35 | } 36 | 37 | /// 38 | /// Contains storyboard identifiers, such as tableView cell identifiers 39 | /// 40 | struct StoryboardIdentifiers { 41 | /// 42 | /// CollectionViewCell that shows a gallery image 43 | /// 44 | static let ImageCell = "ImageCell" 45 | 46 | /// 47 | /// TableViewCell that shows a gallery document 48 | /// 49 | static let DocumentCell = "DocumentCell" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Utilities/UIViewControllerUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewControllerUtilities.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/26/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// Simple but useful utilities for `UIViewController` 12 | /// 13 | extension UIViewController { 14 | 15 | /// 16 | /// The contents of the controller. It is `self` in all cases except for instances of 17 | /// `UINavigationController` where, if present, returns the controller's visible view 18 | /// controller (or self if none is set). 19 | /// 20 | /// This allows for great iphone/ipad compatibillity 21 | /// in just one UI. 22 | /// 23 | var contents: UIViewController { 24 | 25 | // Is this a navigation controller? 26 | if let navigationVC = self as? UINavigationController { 27 | return navigationVC.visibleViewController ?? self 28 | } 29 | else { 30 | return self 31 | } 32 | 33 | // TODO: this could work great with UITabViewController 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Utilities/UIViewUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewUtilities.swift 3 | // ImageGallery_P6 4 | // 5 | // Created by Ruben on 3/17/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | var snapshot: UIImage? { 13 | UIGraphicsBeginImageContext(bounds.size) 14 | drawHierarchy(in: bounds, afterScreenUpdates: true) 15 | let image = UIGraphicsGetImageFromCurrentImageContext() 16 | UIGraphicsEndImageContext() 17 | return image 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ImageGallery_P6/ImageGallery_P6/Utilities/URLUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLUtilities.swift 3 | // ImageGallery 4 | // 5 | // Created by Ruben on 1/20/18. 6 | // Copyright © 2018 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Simple but useful utilities for `URL` 12 | /// 13 | extension URL { 14 | var imageURL: URL { 15 | // check to see if there is an embedded imgurl reference 16 | for query in query?.components(separatedBy: "&") ?? [] { 17 | let queryComponents = query.components(separatedBy: "=") 18 | if queryComponents.count == 2 { 19 | if queryComponents[0] == "imgurl", let url = URL(string: queryComponents[1].removingPercentEncoding ?? "") { 20 | return url 21 | } 22 | } 23 | } 24 | return self.baseURL ?? self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 rubenbaca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/qeen.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/qeen.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/qeen.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/qeen.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cardback.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cardback@2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/Lecture8_PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback@2x.jpg -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/CardBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardBehavior.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/16/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// Contains UIDynamicBehavior for a PlayingCard 12 | /// 13 | class CardBehavior: UIDynamicBehavior { 14 | 15 | /// 16 | /// Create a new CardBehavior for a PlayingCard 17 | /// 18 | override init() { 19 | super.init() 20 | addChildBehavior(collisionBehavior) 21 | addChildBehavior(itemBehavior) 22 | } 23 | 24 | /// 25 | /// Create a new CardBehavior for a PlayingCard in the given 26 | /// animator 27 | /// 28 | convenience init(in animator: UIDynamicAnimator) { 29 | self.init() 30 | animator.addBehavior(self) 31 | } 32 | 33 | /// 34 | /// Collision behavior 35 | /// 36 | private lazy var collisionBehavior: UICollisionBehavior = { 37 | let behavior = UICollisionBehavior() 38 | behavior.translatesReferenceBoundsIntoBoundary = true 39 | return behavior 40 | }() 41 | 42 | /// 43 | /// Item behavioor 44 | /// 45 | lazy var itemBehavior: UIDynamicItemBehavior = { 46 | let behavior = UIDynamicItemBehavior() 47 | //behavior.allowsRotation = false // I do want rotation 48 | behavior.elasticity = 1.0 49 | behavior.resistance = 0 50 | return behavior 51 | }() 52 | 53 | /// 54 | /// Make sure the given item is affected by all types of CardBehavior 55 | /// 56 | func addItem(_ item: UIDynamicItem) { 57 | collisionBehavior.addItem(item) 58 | itemBehavior.addItem(item) 59 | push(item) 60 | } 61 | 62 | /// 63 | /// Remove the give item from all types of CardBehavior 64 | /// 65 | func removeItem(_ item: UIDynamicItem) { 66 | collisionBehavior.removeItem(item) 67 | itemBehavior.removeItem(item) 68 | } 69 | 70 | /// 71 | /// Intantly push the item towards the center of the current bounds 72 | /// 73 | private func push(_ item: UIDynamicItem) { 74 | 75 | let push = UIPushBehavior(items: [item], mode: .instantaneous) 76 | 77 | // Push item towards the center 78 | if let referenceBounds = dynamicAnimator?.referenceView?.bounds { 79 | let center = CGPoint(x: referenceBounds.midX, y: referenceBounds.midY) 80 | switch (item.center.x, item.center.y) { 81 | case let (x, y) where x < center.x && y < center.y: 82 | push.angle = (CGFloat.pi/2).arc4random 83 | case let (x, y) where x > center.x && y < center.y: 84 | push.angle = CGFloat.pi-(CGFloat.pi/2).arc4random 85 | case let (x, y) where x < center.x && y > center.y: 86 | push.angle = (-CGFloat.pi/2).arc4random 87 | case let (x, y) where x > center.x && y > center.y: 88 | push.angle = CGFloat.pi+(CGFloat.pi/2).arc4random 89 | default: 90 | push.angle = (CGFloat.pi*2).arc4random 91 | } 92 | } 93 | 94 | push.magnitude = CGFloat(1) + CGFloat(2).arc4random 95 | 96 | // After item is pushed, we no longer need it 97 | push.action = { [unowned push, weak self] in 98 | self?.removeChildBehavior(push) 99 | } 100 | addChildBehavior(push) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Extensions/CGFloatExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGFloatExtension.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/16/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | // Extension for simple but useful uitilities 11 | extension CGFloat { 12 | var arc4random: CGFloat { 13 | return self * (CGFloat(arc4random_uniform(UInt32.max))/CGFloat(UInt32.max)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Extensions/IntExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntExtension.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Extension for simple but useful uitilities 12 | extension Int { 13 | 14 | /// 15 | /// Get a random number between `self` and 0. If `self` is zero, 16 | /// zero will be returned. 17 | /// 18 | var arc4random: Int { 19 | if self > 0 { 20 | return Int(arc4random_uniform(UInt32(self))) 21 | } 22 | else if self < 0 { 23 | return -Int(arc4random_uniform(UInt32(abs(self)))) 24 | } 25 | else { 26 | return 0 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/PlayingCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayingCard.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a playing card 12 | /// 13 | struct PlayingCard { 14 | 15 | /// Suit of the card 16 | var suit: Suit 17 | 18 | /// Rank of the card 19 | var rank: Rank 20 | 21 | /// 22 | /// Represents a Suit in a playing card 23 | /// 24 | enum Suit: String { 25 | case spades = "♠️" 26 | case hearts = "♥️" 27 | case clubs = "♣️" 28 | case diamonds = "♦️" 29 | 30 | /// 31 | /// Array containing all possible suits 32 | /// 33 | static var all: [Suit] = [.spades, .hearts, .clubs, .diamonds] 34 | } 35 | 36 | /// 37 | /// Represents the Rank in a playing card 38 | /// 39 | enum Rank { 40 | case ace 41 | case face(String) // ugly design 42 | case numeric(Int) 43 | 44 | /// 45 | /// The order of each Rank 46 | /// 47 | var order: Int { 48 | switch self { 49 | case .ace: return 1 50 | case .numeric(let pips): return pips 51 | case .face(let kind) where kind == "J": return 11 52 | case .face(let kind) where kind == "Q": return 12 53 | case .face(let kind) where kind == "K": return 13 54 | default: return 0 // ugly design 55 | } 56 | } 57 | 58 | /// 59 | /// Array containing all possible suits 60 | /// 61 | static var all: [Rank] { 62 | // Ace 63 | var allRanks: [Rank] = [.ace] 64 | 65 | // 2...10 66 | for pips in 2...10 { 67 | allRanks.append(.numeric(pips)) 68 | } 69 | 70 | // Jack, Queen, King 71 | allRanks += [.face("J"), .face("Q"), .face("K")] 72 | return allRanks 73 | } 74 | } 75 | } 76 | 77 | // Make `PlayingCard` confrom to `CustomStringConvertible` 78 | extension PlayingCard: CustomStringConvertible { 79 | /// 80 | /// String representation of a `PlayingCard` 81 | /// 82 | var description: String { 83 | return "\(rank)\(suit)" 84 | } 85 | } 86 | 87 | // Make `PlayingCard.Suit` confrom to `CustomStringConvertible` 88 | extension PlayingCard.Suit: CustomStringConvertible { 89 | /// 90 | /// String representation of a `PlayingCard.Suit` 91 | /// 92 | var description: String { 93 | return rawValue 94 | } 95 | } 96 | 97 | // Make `PlayingCard.Rank` confrom to `CustomStringConvertible` 98 | extension PlayingCard.Rank: CustomStringConvertible { 99 | /// 100 | /// String representation of a `PlayingCard.Rank` 101 | /// 102 | var description: String { 103 | switch self { 104 | case .ace: return "A" 105 | case .numeric(let pips): return String(pips) 106 | case .face(let kind): return kind 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/PlayingCardDeck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayingCardDeck.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a deck of playing cards 12 | /// 13 | struct PlayingCardDeck { 14 | 15 | /// 16 | /// Create a new deck of cards 17 | /// 18 | init() { 19 | for suit in PlayingCard.Suit.all { 20 | for rank in PlayingCard.Rank.all { 21 | cards.append(PlayingCard(suit: suit, rank: rank)) 22 | } 23 | } 24 | } 25 | 26 | /// 27 | /// The collection of cards 28 | /// 29 | private(set) var cards = [PlayingCard]() 30 | 31 | /// 32 | /// Draw a card from the deck 33 | /// 34 | mutating func draw() -> PlayingCard? { 35 | // If there are cards, return a random one 36 | if cards.count > 0 { 37 | return cards.remove(at: cards.count.arc4random) 38 | } 39 | // No more cards available 40 | return nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Lecture8_PlayingCard/PlayingCard/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard.xcodeproj/project.xcworkspace/xcuserdata/ruben.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PlayingCard/PlayingCard.xcodeproj/xcuserdata/ruben.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PlayingCard.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♠️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♣️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♥️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "jack.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "jack2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/J♦️.imageset/jack2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♠️.imageset/king2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♣️.imageset/king2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♥️.imageset/king2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "king.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "king2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/K♦️.imageset/king2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/qeen.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♠️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/qeen.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♣️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/qeen.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♥️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qeen.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "queen2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/qeen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/qeen.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/queen2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/Q♦️.imageset/queen2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cardback.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cardback@2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/PlayingCard/PlayingCard/Assets.xcassets/cardback.imageset/cardback@2x.jpg -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Extensions/IntExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntExtension.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Extension for simple but useful uitilities 12 | extension Int { 13 | 14 | /// 15 | /// Get a random number between `self` and 0. If `self` is zero, 16 | /// zero will be returned. 17 | /// 18 | var arc4random: Int { 19 | if self > 0 { 20 | return Int(arc4random_uniform(UInt32(self))) 21 | } 22 | else if self < 0 { 23 | return -Int(arc4random_uniform(UInt32(abs(self)))) 24 | } 25 | else { 26 | return 0 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/PlayingCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayingCard.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a playing card 12 | /// 13 | struct PlayingCard { 14 | 15 | /// Suit of the card 16 | var suit: Suit 17 | 18 | /// Rank of the card 19 | var rank: Rank 20 | 21 | /// 22 | /// Represents a Suit in a playing card 23 | /// 24 | enum Suit: String { 25 | case spades = "♠️" 26 | case hearts = "♥️" 27 | case clubs = "♣️" 28 | case diamonds = "♦️" 29 | 30 | /// 31 | /// Array containing all possible suits 32 | /// 33 | static var all: [Suit] = [.spades, .hearts, .clubs, .diamonds] 34 | } 35 | 36 | /// 37 | /// Represents the Rank in a playing card 38 | /// 39 | enum Rank { 40 | case ace 41 | case face(String) // ugly design 42 | case numeric(Int) 43 | 44 | /// 45 | /// The order of each Rank 46 | /// 47 | var order: Int { 48 | switch self { 49 | case .ace: return 1 50 | case .numeric(let pips): return pips 51 | case .face(let kind) where kind == "J": return 11 52 | case .face(let kind) where kind == "Q": return 12 53 | case .face(let kind) where kind == "K": return 13 54 | default: return 0 // ugly design 55 | } 56 | } 57 | 58 | /// 59 | /// Array containing all possible suits 60 | /// 61 | static var all: [Rank] { 62 | // Ace 63 | var allRanks: [Rank] = [.ace] 64 | 65 | // 2...10 66 | for pips in 2...10 { 67 | allRanks.append(.numeric(pips)) 68 | } 69 | 70 | // Jack, Queen, King 71 | allRanks += [.face("J"), .face("Q"), .face("K")] 72 | return allRanks 73 | } 74 | } 75 | } 76 | 77 | // Make `PlayingCard` confrom to `CustomStringConvertible` 78 | extension PlayingCard: CustomStringConvertible { 79 | /// 80 | /// String representation of a `PlayingCard` 81 | /// 82 | var description: String { 83 | return "\(rank)\(suit)" 84 | } 85 | } 86 | 87 | // Make `PlayingCard.Suit` confrom to `CustomStringConvertible` 88 | extension PlayingCard.Suit: CustomStringConvertible { 89 | /// 90 | /// String representation of a `PlayingCard.Suit` 91 | /// 92 | var description: String { 93 | return rawValue 94 | } 95 | } 96 | 97 | // Make `PlayingCard.Rank` confrom to `CustomStringConvertible` 98 | extension PlayingCard.Rank: CustomStringConvertible { 99 | /// 100 | /// String representation of a `PlayingCard.Rank` 101 | /// 102 | var description: String { 103 | switch self { 104 | case .ace: return "A" 105 | case .numeric(let pips): return String(pips) 106 | case .face(let kind): return kind 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/PlayingCardDeck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayingCardDeck.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a deck of playing cards 12 | /// 13 | struct PlayingCardDeck { 14 | 15 | /// 16 | /// Create a new deck of cards 17 | /// 18 | init() { 19 | for suit in PlayingCard.Suit.all { 20 | for rank in PlayingCard.Rank.all { 21 | cards.append(PlayingCard(suit: suit, rank: rank)) 22 | } 23 | } 24 | } 25 | 26 | /// 27 | /// The collection of cards 28 | /// 29 | private(set) var cards = [PlayingCard]() 30 | 31 | /// 32 | /// Draw a card from the deck 33 | /// 34 | mutating func draw() -> PlayingCard? { 35 | // If there are cards, return a random one 36 | if cards.count > 0 { 37 | return cards.remove(at: cards.count.arc4random) 38 | } 39 | // No more cards available 40 | return nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PlayingCard/PlayingCard/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PlayingCard 4 | // 5 | // Created by Ruben on 12/3/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import UIKit 9 | 10 | /// 11 | /// Main view controller 12 | /// 13 | class ViewController: UIViewController { 14 | 15 | /// 16 | /// The deck of cards (model) 17 | /// 18 | var deck = PlayingCardDeck() 19 | 20 | /// 21 | /// The playing card view (view) 22 | /// 23 | @IBOutlet weak var playingCardView: PlayingCardView! { 24 | didSet { setupGestureRecognizers() } 25 | } 26 | 27 | /// 28 | /// Flip the card 29 | /// 30 | @IBAction func flipCard(_ sender: UITapGestureRecognizer) { 31 | // Make sure tap was successful 32 | if sender.state == .ended { 33 | playingCardView.isFaceUp = !playingCardView.isFaceUp 34 | } 35 | } 36 | 37 | /// 38 | /// Get the next card on the deck 39 | /// 40 | @objc private func nextCard() { 41 | // Try to draw a card 42 | if let card = deck.draw() { 43 | // Configure the view to show the new card 44 | playingCardView.rank = card.rank.order 45 | playingCardView.suit = card.suit.rawValue 46 | } 47 | } 48 | 49 | /// 50 | /// Setup gesture recognizers on the playing card: 51 | /// - Swipe: Go to next card in the deck 52 | /// - Pinch: Zoom the card's face 53 | /// 54 | /// Note: Tap gesture recognizer (to flip the card) was added 55 | /// using interface builder. 56 | /// 57 | private func setupGestureRecognizers() { 58 | // Swipe gesture recognizer to go to next card 59 | let swipe = UISwipeGestureRecognizer(target: self, action: #selector(nextCard)) 60 | swipe.direction = [.left, .right] 61 | playingCardView.addGestureRecognizer(swipe) 62 | 63 | // Pinch gesture recognizer to zoom the card's face 64 | let pinch = UIPinchGestureRecognizer( 65 | target: playingCardView, 66 | action: #selector(playingCardView.adjustFaceCardScale(gestureRecognizer:)) 67 | ) 68 | playingCardView.addGestureRecognizer(pinch) 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Set/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /Set/Set/Card.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Card.swift 3 | // Set 4 | // 5 | // Created by Ruben on 12/7/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | /// 11 | /// Represents a Card used in the `SetGame` 12 | /// 13 | struct Card { 14 | 15 | /// 16 | /// There are 4 different features a card might have. Each feature's value might vary based 17 | /// on the `Variant` options. 18 | /// 19 | /// These features and variants are completely generic and not tied to any specific ones. For 20 | /// instance, a "classic" game would contain: 21 | /// - Feature1: color 22 | /// - Feature2: shape 23 | /// - Feature3: shade 24 | /// - Feature4: number 25 | /// 26 | init(_ feature1: Variant, _ feature2: Variant, _ feature3: Variant, _ feature4: Variant) { 27 | self.feature1 = feature1 28 | self.feature2 = feature2 29 | self.feature3 = feature3 30 | self.feature4 = feature4 31 | } 32 | 33 | // Assignment 2 (Task #13): "13.Use an enum as a meaningful part of your solution." 34 | 35 | /// 36 | /// Each feature might contain one of three different variants. 37 | /// 38 | /// These variants are completely generic and not tied to any specific ones. 39 | /// 40 | enum Variant: Int { 41 | case v1 = 1 42 | case v2 = 2 43 | case v3 = 3 44 | } 45 | 46 | let feature1: Variant 47 | let feature2: Variant 48 | let feature3: Variant 49 | let feature4: Variant 50 | } 51 | 52 | // Conform to `CustomStringConvertible` 53 | extension Card: CustomStringConvertible { 54 | var description: String { 55 | return "[\(feature1.rawValue), \(feature2.rawValue), \(feature3.rawValue), \(feature4.rawValue)]" 56 | } 57 | } 58 | 59 | // Conform to `Equatable` 60 | extension Card: Equatable { 61 | static func ==(lhs: Card, rhs: Card) -> Bool { 62 | return ( 63 | (lhs.feature1 == rhs.feature1) && 64 | (lhs.feature2 == rhs.feature2) && 65 | (lhs.feature3 == rhs.feature3) && 66 | (lhs.feature4 == rhs.feature4) 67 | ) 68 | } 69 | } 70 | 71 | // Conform to `Hashable` 72 | extension Card: Hashable { 73 | var hashValue: Int { 74 | return feature1.rawValue ^ feature2.rawValue ^ feature3.rawValue ^ feature4.rawValue 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Set/Set/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Set/Set/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Set 4 | // 5 | // Created by Ruben on 12/7/17. 6 | // Copyright © 2017 Ruben. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Set/Set/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Set/Set/Supporting Files/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /artwork/assignment-1-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/assignment-1-preview.jpg -------------------------------------------------------------------------------- /artwork/assignment-2-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/assignment-2-preview.jpg -------------------------------------------------------------------------------- /artwork/assignment-3-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/assignment-3-preview.jpg -------------------------------------------------------------------------------- /artwork/assignment-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/assignment-5.jpg -------------------------------------------------------------------------------- /artwork/assignment-6-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/assignment-6-preview.jpg -------------------------------------------------------------------------------- /artwork/course_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/course_logo.png -------------------------------------------------------------------------------- /artwork/lecture-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-1.png -------------------------------------------------------------------------------- /artwork/lecture-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-10.jpg -------------------------------------------------------------------------------- /artwork/lecture-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-11.jpg -------------------------------------------------------------------------------- /artwork/lecture-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-12.jpg -------------------------------------------------------------------------------- /artwork/lecture-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-13.jpg -------------------------------------------------------------------------------- /artwork/lecture-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-14.jpg -------------------------------------------------------------------------------- /artwork/lecture-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-15.jpg -------------------------------------------------------------------------------- /artwork/lecture-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-2.png -------------------------------------------------------------------------------- /artwork/lecture-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-3.jpg -------------------------------------------------------------------------------- /artwork/lecture-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-4.jpg -------------------------------------------------------------------------------- /artwork/lecture-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-5.jpg -------------------------------------------------------------------------------- /artwork/lecture-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-6.png -------------------------------------------------------------------------------- /artwork/lecture-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-7.jpg -------------------------------------------------------------------------------- /artwork/lecture-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-8.jpg -------------------------------------------------------------------------------- /artwork/lecture-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenbaca/cs193p_iOS11/ad372e55b777add91335d58c62e8c7e2032315c6/artwork/lecture-9.jpg --------------------------------------------------------------------------------