├── 2021 ├── DemoCode │ └── MemorizeL8 │ │ ├── Memorize.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── Memorize │ │ ├── AspectVGrid.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Cardify.swift │ │ ├── EmojiMemoryGameView.swift │ │ ├── Info.plist │ │ ├── MemoryGame.swift │ │ ├── Pie.swift │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── EmojiArt │ ├── EmojiArt.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── EmojiArt │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── EmojiArtApp.swift │ │ ├── Info.plist │ │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Materials │ ├── assignment_1.pdf │ ├── assignment_2.pdf │ ├── reading_1.pdf │ └── reading_2.pdf └── Memorize │ ├── Memorize.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── Memorize │ ├── AspectVGrid.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Cardify.swift │ ├── EmojiMemoryGame.swift │ ├── EmojiMemoryGameView.swift │ ├── Info.plist │ ├── MemorizeApp.swift │ ├── MemoryGame.swift │ ├── Pie.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── .gitignore ├── Asteroids ├── Asteroids.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── Asteroids │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── asteroid1.imageset │ │ ├── Contents.json │ │ └── asteroid1.png │ ├── asteroid2.imageset │ │ ├── Contents.json │ │ └── asteroid2.png │ ├── asteroid3.imageset │ │ ├── Contents.json │ │ └── asteroid3.png │ ├── asteroid4.imageset │ │ ├── Contents.json │ │ └── asteroid4.png │ ├── asteroid5.imageset │ │ ├── Contents.json │ │ └── asteroid5.png │ ├── asteroid6.imageset │ │ ├── Contents.json │ │ └── asteroid6.png │ ├── asteroid7.imageset │ │ ├── Contents.json │ │ └── asteroid7.png │ ├── asteroid8.imageset │ │ ├── Contents.json │ │ └── asteroid8.png │ ├── asteroid9.imageset │ │ ├── Contents.json │ │ └── asteroid9.png │ ├── explosion0.imageset │ │ ├── Contents.json │ │ └── explosion0.png │ ├── explosion1.imageset │ │ ├── Contents.json │ │ └── explosion1.png │ ├── explosion2.imageset │ │ ├── Contents.json │ │ └── explosion2.png │ ├── explosion3.imageset │ │ ├── Contents.json │ │ └── explosion3.png │ ├── explosion4.imageset │ │ ├── Contents.json │ │ └── explosion4.png │ ├── ship.imageset │ │ ├── Contents.json │ │ └── ship.png │ └── shipfiring.imageset │ │ ├── Contents.json │ │ └── shipfiring.png │ ├── AsteroidBehavior.swift │ ├── AsteroidFieldView.swift │ ├── AsteroidView.swift │ ├── AsteroidsViewController.swift │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CoreGraphicsExtensions.swift │ ├── Info.plist │ └── SpaceshipView.swift ├── Calculator ├── Calculator.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── Calculator │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CalculatorBrain.swift │ ├── Info.plist │ └── ViewController.swift ├── CalculatorPlayground.playground ├── Contents.swift └── contents.xcplayground ├── Cassini ├── Cassini.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── Cassini │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CassiniViewController.swift │ ├── DemoURL.swift │ ├── ImageViewController.swift │ └── Info.plist ├── CoreDataExample ├── CoreDataExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── CoreDataExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CoreDataExample.xcdatamodeld │ ├── .xccurrentversion │ └── CoreDataExample.xcdatamodel │ │ └── contents │ ├── Info.plist │ ├── Model.xcdatamodeld │ └── Model.xcdatamodel │ │ └── contents │ ├── Tweet.swift │ ├── TwitterUser.swift │ └── ViewController.swift ├── FaceIt ├── FaceIt.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── FaceIt │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── BlinkingFaceViewController.swift │ ├── EmotionsViewController.swift │ ├── ExpressionEditorViewController.swift │ ├── EyeView.swift │ ├── FaceView.swift │ ├── FaceViewController.swift │ ├── FacialExpression.swift │ ├── Info.plist │ └── VCLLoggingViewController.swift ├── LICENSE ├── Lecture03 ├── Lecture03Playground.playground │ ├── Contents.swift │ └── contents.xcplayground └── README.md ├── Lecture04 ├── README.md └── UIWindow-Demo │ ├── UIWindow-Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── UIWindow-Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── ValidationWindow.swift │ └── ViewController.swift ├── Lecture05 ├── Lecture05-Demo │ ├── Lecture05-Demo.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ └── Lecture05-Demo │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── ModallyViewController.swift │ │ ├── PopoverViewController.swift │ │ ├── ShowDetailViewController.swift │ │ └── ViewController.swift └── README.md ├── Lecture06 └── README.md ├── Lecture07 └── README.md ├── Lecture08 ├── README.md └── UITextField-Demo │ ├── UITextField-Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── UITextField-Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── MyTextField.swift │ └── ViewController.swift ├── Lecture10 └── README.md ├── Lecture13 └── README.md ├── Lecture16 └── README.md ├── Lecture17 └── README.md ├── README.md ├── README_CN.md └── Smashtag ├── L9.xcworkspace └── contents.xcworkspacedata ├── Smashtag.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Smashtag ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── FetchedResultsTableViewController.swift ├── Info.plist ├── Smash.xcdatamodeld │ └── Smash.xcdatamodel │ │ └── contents ├── SmashTweetTableViewController.swift ├── SmashTweetersTableViewController.swift ├── Tweet.swift ├── TweetTableViewCell.swift ├── TweetTableViewController.swift ├── TwitterUser.swift └── UITableViewDataSource+NSFetchedResultsController.swift └── Twitter ├── Twitter.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── Twitter ├── Info.plist ├── MediaItem.swift ├── NSDictionary+KeyPathConvenience.swift ├── Request.swift ├── Tweet.swift └── User.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/AspectVGrid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectVGrid.swift 3 | // Memorize 4 | // 5 | // Created by CS193p Instructor on 4/14/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AspectVGrid: View where ItemView: View, Item: Identifiable { 11 | var items: [Item] 12 | var aspectRatio: CGFloat 13 | var content: (Item) -> ItemView 14 | 15 | init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping (Item) -> ItemView) { 16 | self.items = items 17 | self.aspectRatio = aspectRatio 18 | self.content = content 19 | } 20 | 21 | var body: some View { 22 | GeometryReader { geometry in 23 | VStack { 24 | let width: CGFloat = widthThatFits(itemCount: items.count, in: geometry.size, itemAspectRatio: aspectRatio) 25 | LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) { 26 | ForEach(items) { item in 27 | content(item).aspectRatio(aspectRatio, contentMode: .fit) 28 | } 29 | } 30 | Spacer(minLength: 0) 31 | } 32 | } 33 | } 34 | 35 | private func adaptiveGridItem(width: CGFloat) -> GridItem { 36 | var gridItem = GridItem(.adaptive(minimum: width)) 37 | gridItem.spacing = 0 38 | return gridItem 39 | } 40 | 41 | private func widthThatFits(itemCount: Int, in size: CGSize, itemAspectRatio: CGFloat) -> CGFloat { 42 | var columnCount = 1 43 | var rowCount = itemCount 44 | repeat { 45 | let itemWidth = size.width / CGFloat(columnCount) 46 | let itemHeight = itemWidth / itemAspectRatio 47 | if CGFloat(rowCount) * itemHeight < size.height { 48 | break 49 | } 50 | columnCount += 1 51 | rowCount = (itemCount + (columnCount - 1)) / columnCount 52 | } while columnCount < itemCount 53 | if columnCount > itemCount { 54 | columnCount = itemCount 55 | } 56 | return floor(size.width / CGFloat(columnCount)) 57 | } 58 | 59 | } 60 | 61 | //struct AspectVGrid_Previews: PreviewProvider { 62 | // static var previews: some View { 63 | // AspectVGrid() 64 | // } 65 | //} 66 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Cardify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cardify.swift 3 | // Memorize 4 | // 5 | // Created by CS193p Instructor on 4/19/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // AnimatableModifier is just a combo of the Animatable and ViewModifier protocols 11 | struct Cardify: AnimatableModifier { 12 | // views that use us only think of isFaceUp-ness 13 | // but we think in terms of rotation 14 | // (since we can animate our rotation) 15 | // so this init is a convenience for views that use us 16 | // (we just turn isFaceUp to the appropriate rotation) 17 | init(isFaceUp: Bool) { 18 | rotation = isFaceUp ? 0 : 180 19 | } 20 | 21 | // this is the var in the Animatable protocol 22 | // (which the protocol AnimatableModifier inherits) 23 | // in our case, it is computed to just set/get the value of our rotation var 24 | // (since our rotation is the thing we animate) 25 | // this will be called repeatedly as the animation system breaks 26 | // any animation of our rotation into little pieces 27 | // (and our body will get invalidated and recalculated) 28 | var animatableData: Double { 29 | get { rotation } 30 | set { rotation = newValue } 31 | } 32 | 33 | // how far around we are (in degrees) 34 | // from 0 (fully face up) to 180 (fully face down) 35 | var rotation: Double // in degrees 36 | 37 | // our body 38 | // basically the same as our Memorize CardView's body was in previous lectures 39 | // the only difference is that what's on the card is the given content argument 40 | func body(content: Content) -> some View { 41 | ZStack { 42 | let shape = RoundedRectangle(cornerRadius: DrawingConstants.cornerRadius) 43 | if rotation < 90 { 44 | shape.fill().foregroundColor(.white) 45 | shape.strokeBorder(lineWidth: DrawingConstants.lineWidth) 46 | } else { 47 | shape.fill() 48 | } 49 | // we don't put this inside the "if rotation < 90" 50 | // because we don't want our content being removed and put back into the UI all the time 51 | // we want it to stay in the UI, but be hidden from the user when we're face down 52 | // (i.e. opacity = 0 when rotation >= 90) 53 | // we do this so that any animations on content can be started even while we're face down 54 | // (even though we're hiding them until the card goes back face up) 55 | // otherwise, only changes to the content while we're face up can be animated 56 | content 57 | .opacity(rotation < 90 ? 1 : 0) 58 | } 59 | // above we are showing or hiding the front/back as rotation passes 90 60 | // here we are doing the actual 3D rotation effect for however many degrees we've rotated 61 | .rotation3DEffect(Angle.degrees(rotation), axis: (0, 1, 0)) 62 | } 63 | 64 | private struct DrawingConstants { 65 | static let cornerRadius: CGFloat = 10 66 | static let lineWidth: CGFloat = 3 67 | } 68 | } 69 | 70 | // add the cardify(isFaceUp:) func to the View protocol 71 | // purely syntactic sugar for views that want to use our Cardify view modifier 72 | extension View { 73 | func cardify(isFaceUp: Bool) -> some View { 74 | self.modifier(Cardify(isFaceUp: isFaceUp)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/MemoryGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryGame.swift 3 | // Memorize 4 | // 5 | // Created by CS193p Instructor on 4/5/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MemoryGame where CardContent: Equatable { 11 | 12 | // add your implementation of your MemoryGame here 13 | 14 | struct Card: Identifiable { 15 | var isFaceUp = false { 16 | didSet { 17 | if isFaceUp { 18 | startUsingBonusTime() 19 | } else { 20 | stopUsingBonusTime() 21 | } 22 | } 23 | } 24 | var isMatched = false { 25 | didSet { 26 | stopUsingBonusTime() 27 | } 28 | } 29 | let content: CardContent 30 | let id: Int 31 | 32 | // MARK: - Bonus Time 33 | 34 | // this could give matching bonus points 35 | // if the user matches the card 36 | // before a certain amount of time passes during which the card is face up 37 | 38 | // can be zero which means "no bonus available" for this card 39 | var bonusTimeLimit: TimeInterval = 6 40 | 41 | // how long this card has ever been face up 42 | private var faceUpTime: TimeInterval { 43 | if let lastFaceUpDate = self.lastFaceUpDate { 44 | return pastFaceUpTime + Date().timeIntervalSince(lastFaceUpDate) 45 | } else { 46 | return pastFaceUpTime 47 | } 48 | } 49 | // the last time this card was turned face up (and is still face up) 50 | var lastFaceUpDate: Date? 51 | // the accumulated time this card has been face up in the past 52 | // (i.e. not including the current time it's been face up if it is currently so) 53 | var pastFaceUpTime: TimeInterval = 0 54 | 55 | // how much time left before the bonus opportunity runs out 56 | var bonusTimeRemaining: TimeInterval { 57 | max(0, bonusTimeLimit - faceUpTime) 58 | } 59 | // percentage of the bonus time remaining 60 | var bonusRemaining: Double { 61 | (bonusTimeLimit > 0 && bonusTimeRemaining > 0) ? bonusTimeRemaining/bonusTimeLimit : 0 62 | } 63 | // whether the card was matched during the bonus time period 64 | var hasEarnedBonus: Bool { 65 | isMatched && bonusTimeRemaining > 0 66 | } 67 | // whether we are currently face up, unmatched and have not yet used up the bonus window 68 | var isConsumingBonusTime: Bool { 69 | isFaceUp && !isMatched && bonusTimeRemaining > 0 70 | } 71 | 72 | // called when the card transitions to face up state 73 | private mutating func startUsingBonusTime() { 74 | if isConsumingBonusTime, lastFaceUpDate == nil { 75 | lastFaceUpDate = Date() 76 | } 77 | } 78 | // called when the card goes back face down (or gets matched) 79 | private mutating func stopUsingBonusTime() { 80 | pastFaceUpTime = faceUpTime 81 | self.lastFaceUpDate = nil 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Pie.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pie.swift 3 | // Memorize 4 | // 5 | // Created by CS193p Instructor on 4/14/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Pie: Shape { 11 | var startAngle: Angle 12 | var endAngle: Angle 13 | var clockwise: Bool = false 14 | 15 | // the Shape protocol inherits from Animatable 16 | // and this var is the only thing in Animatable 17 | // so by implementing it to get/set our pair of angles 18 | // we are thus animatable 19 | var animatableData: AnimatablePair { 20 | get { 21 | AnimatablePair(startAngle.radians, endAngle.radians) 22 | } 23 | set { 24 | startAngle = Angle.radians(newValue.first) 25 | endAngle = Angle.radians(newValue.second) 26 | } 27 | } 28 | 29 | func path(in rect: CGRect) -> Path { 30 | let center = CGPoint(x: rect.midX, y: rect.midY) 31 | let radius = min(rect.width, rect.height) / 2 32 | let start = CGPoint( 33 | x: center.x + radius * CGFloat(cos(startAngle.radians)), 34 | y: center.y + radius * CGFloat(sin(startAngle.radians)) 35 | ) 36 | 37 | // we did this by creating a path and returning it 38 | // but there is also a Path { } version we could have used 39 | var p = Path() 40 | p.move(to: center) 41 | p.addLine(to: start) 42 | p.addArc( 43 | center: center, 44 | radius: radius, 45 | startAngle: startAngle, 46 | endAngle: endAngle, 47 | clockwise: !clockwise 48 | ) 49 | p.addLine(to: center) 50 | return p 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /2021/DemoCode/MemorizeL8/Memorize/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // EmojiArt 4 | // 5 | // Created by kingcos on 2021/7/12. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | Text("Hello, world!") 13 | .padding() 14 | } 15 | } 16 | 17 | struct ContentView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | ContentView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/EmojiArtApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiArtApp.swift 3 | // EmojiArt 4 | // 5 | // Created by kingcos on 2021/7/12. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct EmojiArtApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /2021/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /2021/EmojiArt/EmojiArt/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /2021/Materials/assignment_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/2021/Materials/assignment_1.pdf -------------------------------------------------------------------------------- /2021/Materials/assignment_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/2021/Materials/assignment_2.pdf -------------------------------------------------------------------------------- /2021/Materials/reading_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/2021/Materials/reading_1.pdf -------------------------------------------------------------------------------- /2021/Materials/reading_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/2021/Materials/reading_2.pdf -------------------------------------------------------------------------------- /2021/Memorize/Memorize.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/AspectVGrid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AspectVGrid.swift 3 | // Memorize 4 | // 5 | // Created by kingcos on 2021/7/10. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AspectVGrid: View where Item: Identifiable, ItemView: View { 11 | typealias ContentFactory = (Item) -> ItemView 12 | 13 | var items: [Item] 14 | var aspectRatio: CGFloat 15 | var content: ContentFactory 16 | 17 | init(items: [Item], aspectRatio: CGFloat, @ViewBuilder content: @escaping ContentFactory) { 18 | self.items = items 19 | self.aspectRatio = aspectRatio 20 | self.content = content 21 | } 22 | 23 | var body: some View { 24 | GeometryReader { geometry in 25 | VStack { 26 | let width = widthThatFits(itemCount: items.count, in: geometry.size, itemAspectRatio: aspectRatio) 27 | LazyVGrid(columns: [adaptiveGridItem(width: width)], spacing: 0) { 28 | ForEach(items) { content($0).aspectRatio(aspectRatio, contentMode: .fit) } 29 | } 30 | Spacer(minLength: 0) 31 | } 32 | } 33 | } 34 | 35 | private func widthThatFits(itemCount: Int, in size: CGSize, itemAspectRatio: CGFloat) -> CGFloat { 36 | var columnCount = 1 37 | var rowCount = itemCount 38 | 39 | repeat { 40 | let itemWidth = size.width / CGFloat(columnCount) 41 | let itemHeight = itemWidth / itemAspectRatio 42 | 43 | if CGFloat(rowCount) * itemHeight < size.height { 44 | break 45 | } 46 | 47 | columnCount += 1 48 | rowCount = (itemCount + (columnCount - 1)) / columnCount 49 | } while itemCount > columnCount 50 | 51 | if columnCount > itemCount { 52 | columnCount = itemCount 53 | } 54 | 55 | return floor(size.width / CGFloat(columnCount)) 56 | } 57 | 58 | private func adaptiveGridItem(width: CGFloat) -> GridItem { 59 | var gridItem = GridItem(.adaptive(minimum: width)) 60 | gridItem.spacing = 0 61 | return gridItem 62 | } 63 | } 64 | 65 | //struct AspectVGrid_Previews: PreviewProvider { 66 | // static var previews: some View { 67 | // AspectVGrid() 68 | // } 69 | //} 70 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/Cardify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cardify.swift 3 | // Memorize 4 | // 5 | // Created by kingcos on 2021/7/11. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Cardify: AnimatableModifier { 11 | var rotation: Double // in degrees 12 | 13 | init(isFaceUp: Bool) { 14 | rotation = isFaceUp ? 0 : 180 15 | } 16 | 17 | var animatableData: Double { 18 | get { rotation } 19 | set { rotation = newValue } // rotate from 0 to 180 (0, 1, 2 ... 90, ... 180) 20 | } 21 | 22 | func body(content: Content) -> some View { 23 | ZStack { 24 | let shape = RoundedRectangle(cornerRadius: DrawingConstants.cornerRadius) 25 | if rotation < 90 { 26 | shape.fill().foregroundColor(.white) 27 | shape.strokeBorder(lineWidth: DrawingConstants.lineWidth) 28 | } else { 29 | shape.fill() 30 | } 31 | content 32 | .opacity(rotation < 90 ? 1 : 0) 33 | } 34 | .rotation3DEffect(.degrees(rotation), axis: (0, 1, 0)) 35 | } 36 | 37 | private struct DrawingConstants { 38 | static let cornerRadius: CGFloat = 10 39 | static let lineWidth: CGFloat = 3 40 | } 41 | } 42 | 43 | extension View { 44 | func cardify(isFaceUp: Bool) -> some View { 45 | modifier(Cardify(isFaceUp: isFaceUp)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/EmojiMemoryGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmojiMemoryGame.swift 3 | // Memorize 4 | // 5 | // Created by kingcos on 2021/7/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // ViewModel Layer 11 | 12 | class EmojiMemoryGame : ObservableObject { 13 | typealias Card = MemoryGame.Card 14 | 15 | private static let emojis = ["🚗", "✈️", "🛵", "🚢"] 16 | 17 | private static func createMemoryGame() -> MemoryGame { 18 | MemoryGame(numberOfPairsOfCards: 4) { pairIndex in emojis[pairIndex] } 19 | } 20 | 21 | // --- 22 | 23 | @Published private var model = createMemoryGame() 24 | 25 | var cards: [Card] { 26 | model.cards 27 | } 28 | 29 | func choose(_ card: Card) { 30 | model.choose(card) 31 | } 32 | 33 | func shuffle() { 34 | model.shuffle() 35 | } 36 | 37 | func restart() { 38 | model = EmojiMemoryGame.createMemoryGame() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/MemorizeApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemorizeApp.swift 3 | // Memorize 4 | // 5 | // Created by kingcos on 2021/7/4. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct MemorizeApp: App { 12 | private let game = EmojiMemoryGame() 13 | 14 | var body: some Scene { 15 | WindowGroup { 16 | EmojiMemoryGameView(game: game) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/MemoryGame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryGame.swift 3 | // Memorize 4 | // 5 | // Created by kingcos on 2021/7/9. 6 | // 7 | 8 | import Foundation 9 | 10 | // Model Layer 11 | 12 | struct MemoryGame where CardContent: Equatable { 13 | struct Card: Identifiable { 14 | let id: Int 15 | let content: CardContent 16 | 17 | var isFaceUp = false { 18 | didSet { 19 | if isFaceUp { 20 | startUsingBonusTime() 21 | } else { 22 | stopUsingBonusTime() 23 | } 24 | } 25 | } 26 | var isMatched = false { 27 | didSet { 28 | stopUsingBonusTime() 29 | } 30 | } 31 | 32 | // MARK: Bonus Time 33 | 34 | var bonusTimeLimit: TimeInterval = 6 35 | var lastFaceUpDate: Date? 36 | var pastFaceUpTime: TimeInterval = 0 37 | 38 | private var faceUpTime: TimeInterval { 39 | if let lastFaceUpDate = self.lastFaceUpDate { 40 | return pastFaceUpTime + Date().timeIntervalSince(lastFaceUpDate) 41 | } 42 | 43 | return pastFaceUpTime 44 | } 45 | 46 | var bonusTimeRemaining: TimeInterval { 47 | max(0, bonusTimeLimit - faceUpTime) 48 | } 49 | 50 | var bonusRemaining: Double { 51 | (bonusTimeLimit > 0 && bonusTimeRemaining > 0) ? bonusTimeRemaining / bonusTimeLimit : 0 52 | } 53 | 54 | var hasEarnedBonus: Bool { 55 | isMatched && bonusTimeRemaining > 0 56 | } 57 | 58 | var isConsumingBonusTime: Bool { 59 | isFaceUp && !isMatched && bonusTimeRemaining > 0 60 | } 61 | 62 | private mutating func startUsingBonusTime() { 63 | if isConsumingBonusTime, lastFaceUpDate == nil { 64 | lastFaceUpDate = Date() 65 | } 66 | } 67 | 68 | private mutating func stopUsingBonusTime() { 69 | pastFaceUpTime = faceUpTime 70 | lastFaceUpDate = nil 71 | } 72 | } 73 | 74 | private(set) var cards: [Card] = [] 75 | private var indexOfTheOneAndOnlyFaceUpCard: Int? { 76 | get { cards.indices.filter { cards[$0].isFaceUp }.oneAndOnly } 77 | set { cards.indices.forEach { cards[$0].isFaceUp = ($0 == newValue) } } 78 | } 79 | 80 | init(numberOfPairsOfCards: Int, _ createCardContent: (Int) -> CardContent) { 81 | for pairIndex in 0.. { 17 | get { 18 | AnimatablePair(startAngle.radians, endAngle.radians) 19 | } 20 | 21 | set { 22 | startAngle = .radians(newValue.first) 23 | endAngle = .radians(newValue.second) 24 | } 25 | } 26 | 27 | func path(in rect: CGRect) -> Path { 28 | let center = CGPoint(x: rect.midX, y: rect.midY) 29 | let radius = min(rect.width, rect.height) / 2 30 | let start = CGPoint( 31 | x: center.x + radius * CGFloat(cos(startAngle.radians)), 32 | y: center.y + radius * CGFloat(sin(startAngle.radians)) 33 | ) 34 | 35 | var p = Path() 36 | p.move(to: center) 37 | p.addLine(to: start) 38 | p.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: !isClockwise) 39 | p.addLine(to: center) 40 | 41 | return p 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /2021/Memorize/Memorize/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Asteroids/Asteroids.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Asteroids/Asteroids/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Asteroids 4 | // 5 | // Created by 买明 on 09/04/2017. 6 | // Copyright © 2017 买明. 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 | -------------------------------------------------------------------------------- /Asteroids/Asteroids/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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid1.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid1.imageset/asteroid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid1.imageset/asteroid1.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid2.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid2.imageset/asteroid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid2.imageset/asteroid2.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid3.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid3.imageset/asteroid3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid3.imageset/asteroid3.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid4.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid4.imageset/asteroid4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid4.imageset/asteroid4.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid5.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid5.imageset/asteroid5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid5.imageset/asteroid5.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid6.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid6.imageset/asteroid6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid6.imageset/asteroid6.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid7.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid7.imageset/asteroid7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid7.imageset/asteroid7.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid8.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid8.imageset/asteroid8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid8.imageset/asteroid8.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asteroid9.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/asteroid9.imageset/asteroid9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/asteroid9.imageset/asteroid9.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "explosion0.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion0.imageset/explosion0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/explosion0.imageset/explosion0.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "explosion1.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion1.imageset/explosion1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/explosion1.imageset/explosion1.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "explosion2.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion2.imageset/explosion2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/explosion2.imageset/explosion2.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "explosion3.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion3.imageset/explosion3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/explosion3.imageset/explosion3.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "explosion4.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/explosion4.imageset/explosion4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/explosion4.imageset/explosion4.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/ship.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ship.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/ship.imageset/ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/ship.imageset/ship.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/shipfiring.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shipfiring.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 | } -------------------------------------------------------------------------------- /Asteroids/Asteroids/Assets.xcassets/shipfiring.imageset/shipfiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingcos/CS193p/ea6215fb1b09c018d0a47ab2e4b8d3536c3d8af1/Asteroids/Asteroids/Assets.xcassets/shipfiring.imageset/shipfiring.png -------------------------------------------------------------------------------- /Asteroids/Asteroids/AsteroidFieldView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsteroidFieldView.swift 3 | // Asteroids 4 | // 5 | // Created by CS193p Instructor on 2/26/17. 6 | // Copyright © 2017 Stanford University. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AsteroidFieldView: UIView 12 | { 13 | var asteroidBehavior: AsteroidBehavior? { 14 | didSet { 15 | for asteroid in asteroids { 16 | oldValue?.removeAsteroid(asteroid) 17 | asteroidBehavior?.addAsteroid(asteroid) 18 | } 19 | } 20 | } 21 | 22 | private var asteroids: [AsteroidView] { 23 | return subviews.flatMap { $0 as? AsteroidView } 24 | } 25 | 26 | var scale: CGFloat = 0.002 // size of average asteroid (compared to bounds.size) 27 | var minAsteroidSize: CGFloat = 0.25 // compared to average 28 | var maxAsteroidSize: CGFloat = 2.00 // compared to average 29 | 30 | func addAsteroids(count: Int, exclusionZone: CGRect = CGRect.zero) { 31 | assert(!bounds.isEmpty, "can't add asteroids to an empty field") 32 | let averageAsteroidSize = bounds.size * scale 33 | for _ in 0.. 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 | 27 | 28 | -------------------------------------------------------------------------------- /Asteroids/Asteroids/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Asteroids/Asteroids/CoreGraphicsExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreGraphicsExtensions.swift 3 | // Asteroids 4 | // 5 | // Created by CS193p Instructor. 6 | // Copyright © 2017 Stanford University. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | extension CGFloat { 12 | static func random(in range: Range) -> CGFloat { 13 | return CGFloat(arc4random())/CGFloat(UInt32.max)*(range.upperBound-range.lowerBound)+range.lowerBound 14 | } 15 | static let up = -CGFloat.pi/2 16 | static let down = CGFloat.pi/2 17 | static let left = CGFloat.pi 18 | static let right: CGFloat = 0 19 | } 20 | 21 | extension CGSize { 22 | static func square(_ size: CGFloat) -> CGSize { 23 | return CGSize(width: size, height: size) 24 | } 25 | 26 | static func *(_ size: CGSize, by: CGFloat) -> CGSize { 27 | return CGSize(width: size.width * sqrt(by), height: size.height * sqrt(by)) 28 | } 29 | 30 | static func /(_ size: CGSize, by: CGFloat) -> CGSize { 31 | return CGSize(width: size.width / sqrt(by), height: size.height / sqrt(by)) 32 | } 33 | 34 | var minEdge: CGFloat { return min(width, height) } 35 | 36 | var area: CGFloat { return width * height } 37 | } 38 | 39 | extension CGRect 40 | { 41 | var mid: CGPoint { return CGPoint(x: midX, y: midY) } 42 | 43 | init(squareCenteredAt center: CGPoint, size: CGFloat) { 44 | let origin = CGPoint(x: center.x - size / 2, y: center.y - size / 2) 45 | self.init(origin: origin, size: CGSize.square(size)) 46 | } 47 | 48 | init(center: CGPoint, size: CGSize) { 49 | self.init(origin: CGPoint(x: center.x-size.width/2, y: center.y-size.height/2), size: size) 50 | } 51 | 52 | var randomPoint: CGPoint { 53 | return CGPoint(x: CGFloat.random(in: 0.. CGPoint { 59 | return CGPoint(x: rect.origin.x + x * rect.size.width, y: rect.origin.y + y * rect.size.height) 60 | } 61 | 62 | static func -(left: CGPoint, right: CGPoint) -> CGVector { 63 | return CGVector(dx: left.x-right.x, dy: left.y-right.y) 64 | } 65 | } 66 | 67 | extension CGVector 68 | { 69 | var angle: CGFloat { 70 | let angle = atan(abs(dy)/abs(dx)) 71 | if dx > 0 && dy < 0 { 72 | return -angle 73 | } else if dx < 0 && dy < 0 { 74 | return -CGFloat.pi + angle 75 | } else if dx > 0 && dy > 0 { 76 | return -CGFloat.pi*2 + angle 77 | } else if dx < 0 && dy > 0 { 78 | return -CGFloat.pi - angle 79 | } else if dx < 0 && dy == 0 { 80 | return -CGFloat.pi 81 | } else if dx == 0 && dy < 0 { 82 | return -CGFloat.pi/2 83 | } else if dx == 0 && dy > 0 { 84 | return -3*CGFloat.pi/2 85 | } else { 86 | return 0 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Asteroids/Asteroids/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /Calculator/Calculator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Calculator/Calculator/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Calculator 4 | // 5 | // Created by 买明 on 15/02/2017. 6 | // Copyright © 2017 买明. 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 | -------------------------------------------------------------------------------- /Calculator/Calculator/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Calculator/Calculator/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 | 27 | 28 | -------------------------------------------------------------------------------- /Calculator/Calculator/CalculatorBrain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalculatorBrain.swift 3 | // Calculator 4 | // 5 | // Created by 买明 on 16/02/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CalculatorBrain { 12 | 13 | // Lecture 06 Update: Escaping Closure: @escaping 14 | mutating func addUnaryOperation(named symbol: String, _ operation: @escaping (Double) -> Double) { 15 | operations[symbol] = Operation.unaryOperation(operation) 16 | } 17 | 18 | private var accumulator: Double? 19 | private var operations: Dictionary = [ 20 | "π" : Operation.constant(Double.pi), 21 | "e" : Operation.constant(M_E), 22 | "√" : Operation.unaryOperation(sqrt), 23 | "cos" : Operation.unaryOperation(cos), 24 | "±" : Operation.unaryOperation({ -$0 }), 25 | "+" : Operation.binaryOperation({ $0 + $1 }), 26 | "-" : Operation.binaryOperation({ $0 - $1 }), 27 | "×" : Operation.binaryOperation({ $0 * $1 }), 28 | "/" : Operation.binaryOperation({ $0 / $1 }), 29 | "=" : Operation.equals 30 | ] 31 | private var pendingBinaryOperation: PendingBinaryOperation? 32 | 33 | private enum Operation { 34 | case constant(Double) 35 | case unaryOperation((Double) -> Double) 36 | case binaryOperation((Double, Double) -> Double) 37 | case equals 38 | } 39 | 40 | private struct PendingBinaryOperation { 41 | let function: (Double, Double) -> Double 42 | let firstOperand: Double 43 | 44 | func perform(with secondOperand: Double) -> Double { 45 | return function(firstOperand, secondOperand) 46 | } 47 | } 48 | 49 | var result: Double? { 50 | get { 51 | return accumulator 52 | } 53 | } 54 | 55 | private mutating func performPendingBinaryOperation() { 56 | if pendingBinaryOperation != nil && accumulator != nil { 57 | accumulator = pendingBinaryOperation!.perform(with: accumulator!) 58 | pendingBinaryOperation = nil 59 | } 60 | } 61 | 62 | mutating func performOperation(_ symbol: String) { 63 | 64 | if let operation = operations[symbol] { 65 | switch operation { 66 | case .constant(let value): 67 | accumulator = value 68 | case .unaryOperation(let function): 69 | if accumulator != nil { 70 | accumulator = function(accumulator!) 71 | } 72 | case .binaryOperation(let function): 73 | if accumulator != nil { 74 | pendingBinaryOperation = PendingBinaryOperation(function: function, firstOperand: accumulator!) 75 | accumulator = nil 76 | } 77 | case .equals: 78 | performPendingBinaryOperation() 79 | } 80 | } 81 | 82 | } 83 | 84 | mutating func setOperand(_ operand: Double) { 85 | accumulator = operand 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Calculator/Calculator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /CalculatorPlayground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | 3 | import UIKit 4 | 5 | let i = 27 6 | 7 | // 函数在 Swift 中也是类型 8 | var f: (Double) -> Double 9 | 10 | f = sqrt 11 | let x = f(81) 12 | 13 | f = cos 14 | let y = f(Double.pi) 15 | 16 | func changeSign(operand: Double) -> Double { 17 | return -operand 18 | } 19 | 20 | f = changeSign 21 | let z = f(81) 22 | -------------------------------------------------------------------------------- /CalculatorPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Cassini/Cassini.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cassini/Cassini/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Cassini 4 | // 5 | // Created by 买明 on 07/03/2017. 6 | // Copyright © 2017 买明. 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/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Cassini/Cassini/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 | 27 | 28 | -------------------------------------------------------------------------------- /Cassini/Cassini/CassiniViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CassiniViewController.swift 3 | // Cassini 4 | // 5 | // Created by 买明 on 11/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | var contents: UIViewController { 13 | if let navcon = self as? UINavigationController { 14 | return navcon.visibleViewController ?? self 15 | } else { 16 | return self 17 | } 18 | } 19 | } 20 | 21 | class CassiniViewController: UIViewController, 22 | UISplitViewControllerDelegate { 23 | 24 | // 设置代理 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | self.splitViewController?.delegate = self 28 | } 29 | 30 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 31 | if let url = DemoURL.NASA[segue.identifier ?? ""] { 32 | if let imageVC = segue.destination.contents as? ImageViewController { 33 | imageVC.imgURL = url 34 | imageVC.title = (sender as? UIButton)?.currentTitle 35 | } 36 | } 37 | } 38 | 39 | func splitViewController(_ splitViewController: UISplitViewController, 40 | collapseSecondary secondaryViewController: UIViewController, 41 | onto primaryViewController: UIViewController) -> Bool { 42 | if primaryViewController.contents == self { 43 | if let ivc = secondaryViewController.contents as? ImageViewController, 44 | ivc.imgURL == nil { 45 | return true 46 | } 47 | } 48 | return false 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Cassini/Cassini/DemoURL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // 4 | // Created by CS193p Instructor. 5 | // Copyright (c) 2017 Stanford University. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | struct DemoURL 11 | { 12 | static let stanford = URL(string: "http://stanford.edu/about/images/intro_about.jpg") 13 | 14 | static var NASA: Dictionary = { 15 | let NASAURLStrings = [ 16 | "Cassini" : "http://www.jpl.nasa.gov/images/cassini/20090202/pia03883-full.jpg", 17 | "Earth" : "http://www.nasa.gov/sites/default/files/wave_earth_mosaic_3.jpg", 18 | "Saturn" : "http://www.nasa.gov/sites/default/files/saturn_collage.jpg" 19 | ] 20 | var urls = Dictionary() 21 | for (key, value) in NASAURLStrings { 22 | urls[key] = URL(string: value) 23 | } 24 | return urls 25 | }() 26 | } 27 | -------------------------------------------------------------------------------- /Cassini/Cassini/ImageViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ImageViewController.swift 4 | // Cassini 5 | // 6 | // Created by 买明 on 07/03/2017. 7 | // Copyright © 2017 买明. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | // 利用扩展代理 UIScrollViewDelegate 13 | extension ImageViewController: UIScrollViewDelegate { 14 | // 缩放 15 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 16 | return imageView 17 | } 18 | } 19 | 20 | class ImageViewController: UIViewController { 21 | 22 | @IBOutlet weak var spinner: UIActivityIndicatorView! 23 | @IBOutlet weak var scrollView: UIScrollView! { 24 | didSet { 25 | // 设置代理 26 | scrollView.delegate = self 27 | 28 | // 缩放控制 29 | scrollView.minimumZoomScale = 0.03 30 | scrollView.maximumZoomScale = 1.0 31 | 32 | // UIScrollView 内容大小 33 | scrollView.contentSize = imageView.frame.size 34 | scrollView.addSubview(imageView) 35 | } 36 | } 37 | 38 | var imgURL: URL? { 39 | didSet { 40 | image = nil 41 | 42 | if view.window != nil { 43 | fetchImage() 44 | } 45 | } 46 | } 47 | 48 | fileprivate var imageView = UIImageView() 49 | 50 | private var image: UIImage? { 51 | get { 52 | return imageView.image 53 | } 54 | 55 | set { 56 | imageView.image = newValue 57 | imageView.sizeToFit() 58 | 59 | scrollView?.contentSize = imageView.frame.size 60 | // 设置完停止加载动画 61 | spinner?.stopAnimating() 62 | } 63 | } 64 | 65 | // override func viewDidLoad() { 66 | // super.viewDidLoad() 67 | // 68 | // imgURL = DemoURL.stanford 69 | // } 70 | 71 | override func viewWillAppear(_ animated: Bool) { 72 | super.viewWillAppear(animated) 73 | 74 | // 视图将要显示时获取图片(耗时) 75 | if image == nil { 76 | fetchImage() 77 | } 78 | } 79 | 80 | private func fetchImage() { 81 | // 加载动画开始 82 | spinner.startAnimating() 83 | 84 | if let url = imgURL { 85 | // Global 队列 86 | DispatchQueue.global(qos: .userInitiated).async { [weak self] in 87 | // 捕获错误,返回可选 88 | let urlContents = try? Data(contentsOf: url) 89 | if let imageData = urlContents { 90 | // Main 队列 91 | DispatchQueue.main.async { 92 | self?.image = UIImage(data: imageData) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Cassini/Cassini/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/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 | 27 | 28 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/Base.lproj/Main.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 | 27 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/CoreDataExample.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/CoreDataExample.xcdatamodeld/CoreDataExample.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 38 | 39 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/Model.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/Tweet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tweet.swift 3 | // CoreDataExample 4 | // 5 | // Created by 买明 on 19/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class Tweet: NSManagedObject { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/TwitterUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterUser.swift 3 | // CoreDataExample 4 | // 5 | // Created by 买明 on 19/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class TwitterUser: NSManagedObject { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /CoreDataExample/CoreDataExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CoreDataExample 4 | // 5 | // Created by 买明 on 19/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /FaceIt/FaceIt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FaceIt 4 | // 5 | // Created by 买明 on 24/02/2017. 6 | // Copyright © 2017 买明. 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 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/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 | } -------------------------------------------------------------------------------- /FaceIt/FaceIt/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 | 27 | 28 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/BlinkingFaceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlinkingFaceViewController.swift 3 | // FaceIt 4 | // 5 | // Created by 买明 on 08/04/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BlinkingFaceViewController: FaceViewController { 12 | 13 | var blinking = false { 14 | didSet { 15 | blinkIfNeeded() 16 | } 17 | } 18 | 19 | private var canBlink = false 20 | private var inABlink = false 21 | 22 | private struct BlinkRate { 23 | static let closedDuration: TimeInterval = 0.4 24 | static let openDuration: TimeInterval = 2.5 25 | } 26 | 27 | private func blinkIfNeeded() { 28 | if blinking && canBlink && !inABlink { 29 | faceView.eyesOpen = false 30 | inABlink = true 31 | Timer.scheduledTimer(withTimeInterval: BlinkRate.closedDuration, 32 | repeats: false) { [weak self] timer in 33 | self?.faceView.eyesOpen = true 34 | Timer.scheduledTimer(withTimeInterval: BlinkRate.openDuration, 35 | repeats: false) { [weak self] timer in 36 | self?.inABlink = false 37 | self?.blinkIfNeeded() 38 | } 39 | } 40 | } 41 | } 42 | 43 | override func viewDidAppear(_ animated: Bool) { 44 | super.viewDidAppear(animated) 45 | 46 | // blinking = true 47 | canBlink = true 48 | blinkIfNeeded() 49 | } 50 | 51 | override func viewWillDisappear(_ animated: Bool) { 52 | super.viewDidDisappear(animated) 53 | 54 | // blinking = false 55 | canBlink = true 56 | } 57 | 58 | override func updateUI() { 59 | super.updateUI() 60 | blinking = expression.eyes == .squinting 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/EmotionsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmotionsViewController.swift 3 | // FaceIt 4 | // 5 | // Created by 买明 on 06/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EmotionsViewController: UITableViewController, UIPopoverPresentationControllerDelegate { 12 | 13 | // 表情字典:代替 switch-case 14 | // private let emotionalFaces: Dictionary = [ 15 | // "sad": FacialExpression(eyes: .closed, mouth: .frown), 16 | // "happy": FacialExpression(eyes: .open, mouth: .smile), 17 | // "worried": FacialExpression(eyes: .open, mouth: .smirk) 18 | // ] 19 | private var emotionalFaces: [(name: String, expression: FacialExpression)] = [ 20 | ("Sad", FacialExpression(eyes: .closed, mouth: .frown)), 21 | ("Happy", FacialExpression(eyes: .open, mouth: .smile)), 22 | ("Worried", FacialExpression(eyes: .open, mouth: .smirk)) 23 | ] 24 | 25 | @IBAction func addEmotionalFace(from segue: UIStoryboardSegue) { 26 | if let editor = segue.source as? ExpressionEditorViewController { 27 | emotionalFaces.append((editor.name, editor.expression)) 28 | tableView.reloadData() 29 | } 30 | } 31 | 32 | // 预备 segue 33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 34 | // 目标 segue 35 | var destinationViewController = segue.destination 36 | 37 | if let navigationController = destinationViewController as? UINavigationController { 38 | destinationViewController = navigationController.visibleViewController ?? destinationViewController 39 | } 40 | 41 | if let faceViewController = destinationViewController as? FaceViewController, 42 | let cell = sender as? UITableViewCell, 43 | let indexPath = tableView.indexPath(for: cell) { 44 | faceViewController.expression = emotionalFaces[indexPath.row].expression 45 | faceViewController.navigationItem.title = emotionalFaces[indexPath.row].name 46 | } else if destinationViewController is ExpressionEditorViewController { 47 | if let popoverPresentationController = segue.destination.popoverPresentationController { 48 | popoverPresentationController.delegate = self 49 | } 50 | } 51 | } 52 | 53 | func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { 54 | if traitCollection.verticalSizeClass == .compact { 55 | return .none 56 | } else if traitCollection.horizontalSizeClass == .compact { 57 | return .overFullScreen 58 | } else { 59 | return .none 60 | } 61 | } 62 | 63 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 64 | return emotionalFaces.count 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 | let cell = tableView.dequeueReusableCell(withIdentifier: "Emotion Cell", for: indexPath) 69 | cell.textLabel?.text = emotionalFaces[indexPath.row].name 70 | return cell 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/EyeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EyeView.swift 3 | // 4 | // Created by CS193p Instructor. 5 | // Copyright © 2017 Stanford University. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class EyeView: UIView 11 | { 12 | var lineWidth: CGFloat = 5.0 { didSet { setNeedsDisplay() } } 13 | var color: UIColor = UIColor.blue { didSet { setNeedsDisplay() } } 14 | 15 | var _eyesOpen: Bool = true { didSet { setNeedsDisplay() } } 16 | 17 | var eyesOpen: Bool { 18 | get { 19 | return _eyesOpen 20 | } 21 | set { 22 | if newValue != _eyesOpen { 23 | UIView.transition(with: self, 24 | duration: 0.4, 25 | options: [.transitionFlipFromTop], 26 | animations: { 27 | self._eyesOpen = newValue 28 | }) 29 | } 30 | } 31 | } 32 | 33 | override func draw(_ rect: CGRect) 34 | { 35 | var path: UIBezierPath 36 | 37 | if eyesOpen { 38 | path = UIBezierPath(ovalIn: bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2)) 39 | } else { 40 | path = UIBezierPath() 41 | path.move(to: CGPoint(x: bounds.minX, y: bounds.midY)) 42 | path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY)) 43 | } 44 | 45 | path.lineWidth = lineWidth 46 | color.setStroke() 47 | path.stroke() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/FacialExpression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FacialExpression.swift 3 | // FaceIt 4 | // 5 | // Created by 买明 on 25/02/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FacialExpression { 12 | 13 | let eyes: Eyes 14 | let mouth: Mouth 15 | 16 | enum Eyes: Int { 17 | case open 18 | case closed 19 | // 眯着眼 20 | case squinting 21 | } 22 | 23 | enum Mouth: Int { 24 | // ☹️ 0 25 | case frown 26 | // 😠 1 27 | case smirk 28 | // 😐 2 29 | case neutral 30 | // 😁 3 31 | case grin 32 | // 😊 4 33 | case smile 34 | 35 | var sadder: Mouth { 36 | return Mouth(rawValue: rawValue - 1) ?? .frown 37 | } 38 | 39 | var happier: Mouth { 40 | return Mouth(rawValue: rawValue + 1) ?? .smile 41 | } 42 | } 43 | 44 | var sadder: FacialExpression { 45 | return FacialExpression(eyes: self.eyes, mouth: self.mouth.sadder) 46 | } 47 | 48 | var happier: FacialExpression { 49 | return FacialExpression(eyes: self.eyes, mouth: self.mouth.happier) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /FaceIt/FaceIt/VCLLoggingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VCLLoggingViewController.swift 3 | // 4 | // Created by CS193p Instructor. 5 | // Copyright © 2015-17 Stanford University. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | class VCLLoggingViewController : UIViewController 11 | { 12 | private struct LogGlobals { 13 | var prefix = "" 14 | var instanceCounts = [String:Int]() 15 | var lastLogTime = Date() 16 | var indentationInterval: TimeInterval = 1 17 | var indentationString = "__" 18 | } 19 | 20 | private static var logGlobals = LogGlobals() 21 | 22 | private static func logPrefix(for className: String) -> String { 23 | if logGlobals.lastLogTime.timeIntervalSinceNow < -logGlobals.indentationInterval { 24 | logGlobals.prefix += logGlobals.indentationString 25 | print("") 26 | } 27 | logGlobals.lastLogTime = Date() 28 | return logGlobals.prefix + className 29 | } 30 | 31 | private static func bumpInstanceCount(for className: String) -> Int { 32 | logGlobals.instanceCounts[className] = (logGlobals.instanceCounts[className] ?? 0) + 1 33 | return logGlobals.instanceCounts[className]! 34 | } 35 | 36 | private var instanceCount: Int! 37 | 38 | private func logVCL(_ msg: String) { 39 | let className = String(describing: type(of: self)) 40 | if instanceCount == nil { 41 | instanceCount = VCLLoggingViewController.bumpInstanceCount(for: className) 42 | } 43 | print("\(VCLLoggingViewController.logPrefix(for: className))(\(instanceCount!)) \(msg)") 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | super.init(coder: aDecoder) 48 | logVCL("init(coder:) - created via InterfaceBuilder ") 49 | } 50 | 51 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 52 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 53 | logVCL("init(nibName:bundle:) - create in code") 54 | } 55 | 56 | deinit { 57 | logVCL("left the heap") 58 | } 59 | 60 | override func awakeFromNib() { 61 | logVCL("awakeFromNib()") 62 | } 63 | 64 | override func viewDidLoad() { 65 | super.viewDidLoad() 66 | logVCL("viewDidLoad()") 67 | } 68 | 69 | override func viewWillAppear(_ animated: Bool) { 70 | super.viewWillAppear(animated) 71 | logVCL("viewWillAppear(animated = \(animated))") 72 | } 73 | override func viewDidAppear(_ animated: Bool) { 74 | super.viewDidAppear(animated) 75 | logVCL("viewDidAppear(animated = \(animated))") 76 | } 77 | override func viewWillDisappear(_ animated: Bool) { 78 | super.viewWillDisappear(animated) 79 | logVCL("viewWillDisappear(animated = \(animated))") 80 | } 81 | override func viewDidDisappear(_ animated: Bool) { 82 | super.viewDidDisappear(animated) 83 | logVCL("viewDidDisappear(animated = \(animated))") 84 | } 85 | 86 | override func didReceiveMemoryWarning() { 87 | super.didReceiveMemoryWarning() 88 | logVCL("didReceiveMemoryWarning()") 89 | } 90 | 91 | override func viewWillLayoutSubviews() { 92 | super.viewWillLayoutSubviews() 93 | logVCL("viewWillLayoutSubviews() bounds.size = \(view.bounds.size)") 94 | } 95 | override func viewDidLayoutSubviews() { 96 | super.viewDidLayoutSubviews() 97 | logVCL("viewDidLayoutSubviews() bounds.size = \(view.bounds.size)") 98 | } 99 | 100 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 101 | super.viewWillTransition(to: size, with: coordinator) 102 | logVCL("viewWillTransition(to: \(size), with: coordinator)") 103 | coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext!) -> Void in 104 | self.logVCL("begin animate(alongsideTransition:completion:)") 105 | }, completion: { context -> Void in 106 | self.logVCL("end animate(alongsideTransition:completion:)") 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 萌面大道 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 | -------------------------------------------------------------------------------- /Lecture03/Lecture03Playground.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Playground - noun: a place where people can play 2 | //: Powered by http://maimieng.com from https://github.com/kingcos/CS193P_2017 3 | //: See all at: 4 | 5 | import UIKit 6 | 7 | // Optional 8 | enum Optional { 9 | case none 10 | case some(T) 11 | } 12 | 13 | var label: UILabel! = UILabel() 14 | label.text = "Optional Chain" 15 | 16 | if let text = label?.text?.hashValue { 17 | print(text) 18 | } 19 | 20 | // Tuple 21 | func getTemperature() -> (celsius: Double, fahrenheit: Double) { 22 | return (10.0, 50.0) 23 | } 24 | 25 | let currentTemperature = getTemperature() 26 | print(currentTemperature.celsius) 27 | print(currentTemperature.fahrenheit) 28 | 29 | // Range 30 | struct Range { 31 | var startIndex: T 32 | var endIndex: T 33 | } 34 | 35 | for i in 0..<3 { 36 | print(i) 37 | } 38 | 39 | // Class & Structure & Enum 40 | class ClassDemo : NSObject { 41 | var storedProperty = 0.0 42 | var computedProperty: Double { 43 | get { 44 | return 0.0 45 | } 46 | set { 47 | self.computedProperty = newValue 48 | } 49 | } 50 | 51 | init(prop: Double) { 52 | storedProperty = prop 53 | } 54 | 55 | func supportAllTheseThreeDataStructures() { 56 | } 57 | } 58 | 59 | struct StructDemo { 60 | var storedProperty = 0.0 61 | var computedProperty: Double { 62 | get { 63 | return 0.0 64 | } 65 | set { 66 | self.computedProperty = newValue 67 | } 68 | } 69 | 70 | init(prop: Double) { 71 | storedProperty = prop 72 | } 73 | 74 | func supportAllTheseThreeDataStructures() { 75 | } 76 | } 77 | 78 | enum EnumDemo { 79 | var computedProperty: Double { 80 | get { 81 | return 0.0 82 | } 83 | set { 84 | self.computedProperty = newValue 85 | } 86 | } 87 | 88 | func supportAllTheseThreeDataStructures() { 89 | } 90 | } 91 | 92 | // Type & Instance Methods/Properties 93 | struct TypeDemo { 94 | static func testTypeMethod() { 95 | print(#function) 96 | } 97 | 98 | static var TypeProperty = 0.0 99 | } 100 | 101 | TypeDemo.testTypeMethod() 102 | print(TypeDemo.TypeProperty) 103 | 104 | // Array Methods 105 | let arrA = [1, 2, 3, 4, 5] 106 | print(arrA.filter({ $0 > 3 })) 107 | 108 | print(arrA.map({ Int($0) })) 109 | 110 | print(arrA.reduce(0) { $0 + $1 }) 111 | 112 | // NSObject & NSNumber & Date & Data 113 | let num = NSNumber(value: 3.14) 114 | let numDoubleValue = num.doubleValue 115 | let numBoolValue = num.boolValue 116 | let numIntValue = num.intValue 117 | 118 | let date = Date() 119 | print(date) 120 | 121 | let data = Data() 122 | 123 | // UserDefaults 不适合 Playground 环境 124 | let PI_ID = "PI" 125 | let defaults = UserDefaults.standard 126 | defaults.set(3.14, forKey: PI_ID) 127 | 128 | if !defaults.synchronize() { 129 | print("Failed!") 130 | } 131 | 132 | defaults.set(nil, forKey: PI_ID) 133 | defaults.double(forKey: PI_ID) 134 | 135 | UIImage(named: "123") 136 | 137 | // Assert 138 | func validation() -> Int? { 139 | return nil 140 | } 141 | 142 | //assert(validation() != nil, "the validation function returned nil") 143 | -------------------------------------------------------------------------------- /Lecture03/Lecture03Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UIWindow-Demo 4 | // 5 | // Created by 买明 on 25/02/2017. 6 | // Copyright © 2017 买明. 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 | 19 | // UIWindowLevel: UIWindow 层级 0.0(default) 1000.0 2000.0 20 | print("UIWindowLevelNormal: \(UIWindowLevelNormal)\n" + 21 | "UIWindowLevelStatusBar: \(UIWindowLevelStatusBar)\n" + 22 | "UIWindowLevelAlert: \(UIWindowLevelAlert)") 23 | 24 | return true 25 | } 26 | 27 | func applicationWillResignActive(_ application: UIApplication) { 28 | // 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. 29 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 30 | } 31 | 32 | func applicationDidEnterBackground(_ application: UIApplication) { 33 | ValidationWindow.sharedInstance.show() 34 | } 35 | 36 | func applicationWillEnterForeground(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationDidBecomeActive(_ application: UIApplication) { 41 | // 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. 42 | } 43 | 44 | func applicationWillTerminate(_ application: UIApplication) { 45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 46 | } 47 | 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/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 | } -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/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 | 27 | 28 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/ValidationWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidationWindow.swift 3 | // UIWindow-Demo 4 | // 5 | // Created by 买明 on 26/02/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ValidationWindow: UIWindow { 12 | 13 | var textField: UITextField? 14 | static let sharedInstance = ValidationWindow(frame: UIScreen.main.bounds) 15 | 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | 19 | let label = UILabel(frame: CGRect(x: 10, y: 50, width: 200, height: 20)) 20 | label.text = "请输入密码" 21 | addSubview(label) 22 | 23 | let textField = UITextField(frame: CGRect(x: 10, y: 80, width: 200, height: 20)) 24 | textField.backgroundColor = UIColor.white 25 | textField.isSecureTextEntry = true 26 | self.textField = textField 27 | addSubview(textField) 28 | 29 | let button = UIButton(frame: CGRect(x: 10, y: 110, width: 200, height: 44)) 30 | button.backgroundColor = UIColor.blue 31 | button.titleLabel?.textColor = UIColor.black 32 | button.setTitle("确定", for: .normal) 33 | button.addTarget(self, action: #selector(completeButtonPressed), for: .touchUpInside) 34 | addSubview(button) 35 | 36 | backgroundColor = UIColor.yellow 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | func completeButtonPressed() { 44 | let textContent = textField?.text 45 | textField?.text?.removeAll() 46 | if textContent == "abcd" { 47 | textField?.resignFirstResponder() 48 | resignKey() 49 | isHidden = true 50 | } else { 51 | showErrorAlertView() 52 | } 53 | } 54 | 55 | func showErrorAlertView() { 56 | let alertView = UIAlertView(title: "密码错误", 57 | message: "正确密码是 abcd", 58 | delegate: self, 59 | cancelButtonTitle: "Ok") 60 | 61 | alertView.show() 62 | } 63 | 64 | func show() { 65 | makeKey() 66 | isHidden = false 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Lecture04/UIWindow-Demo/UIWindow-Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // UIWindow-Demo 4 | // 5 | // Created by 买明 on 25/02/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, 12 | UIAlertViewDelegate { 13 | 14 | var myWindow: UIWindow? 15 | 16 | @IBAction func clickAlertWindow(_ sender: UIButton) { 17 | // UIAlertView 在 iOS 9 中过时 18 | let alertView = UIAlertView(title: "AlertView", 19 | message: "Demo", 20 | delegate: self, 21 | cancelButtonTitle: "Ok") 22 | 23 | alertView.show() 24 | 25 | // let alertController = UIAlertController(title: "UIAlertController", 26 | // message: "Demo", 27 | // preferredStyle: .alert) 28 | // alertController.addAction(UIAlertAction(title: "Ok", 29 | // style: .cancel, 30 | // handler: nil)) 31 | // present(alertController, animated: true) 32 | } 33 | 34 | @IBAction func clickCustomWindow(_ sender: UIButton) { 35 | myWindow = UIWindow(frame: UIScreen.main.bounds) 36 | myWindow?.windowLevel = UIWindowLevelNormal 37 | myWindow?.backgroundColor = UIColor.red 38 | myWindow?.isHidden = false 39 | 40 | let gesture = UITapGestureRecognizer(target: self, action: #selector(hideWindow(with:))) 41 | myWindow?.addGestureRecognizer(gesture) 42 | } 43 | 44 | func hideWindow(with guesture: UIGestureRecognizer) { 45 | myWindow?.isHidden = true 46 | myWindow = nil 47 | } 48 | 49 | func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { 50 | let window = alertView.window 51 | print("alertView windowLevel: \(window?.windowLevel)\n") 52 | } 53 | 54 | override func viewDidLoad() { 55 | super.viewDidLoad() 56 | // Do any additional setup after loading the view, typically from a nib. 57 | } 58 | 59 | override func didReceiveMemoryWarning() { 60 | super.didReceiveMemoryWarning() 61 | // Dispose of any resources that can be recreated. 62 | } 63 | 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Lecture05-Demo 4 | // 5 | // Created by 买明 on 03/03/2017. 6 | // Copyright © 2017 买明. 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 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/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 | 27 | 28 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/ModallyViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModallyViewController.swift 3 | // Lecture05-Demo 4 | // 5 | // Created by 买明 on 03/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ModallyViewController: UIViewController { 12 | 13 | @IBAction func clickBack(_ sender: UIButton) { 14 | dismiss(animated: true) 15 | } 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | /* 30 | // MARK: - Navigation 31 | 32 | // In a storyboard-based application, you will often want to do a little preparation before navigation 33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 34 | // Get the new view controller using segue.destinationViewController. 35 | // Pass the selected object to the new view controller. 36 | } 37 | */ 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/PopoverViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverViewController.swift 3 | // Lecture05-Demo 4 | // 5 | // Created by 买明 on 03/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PopoverViewController: UIViewController { 12 | 13 | @IBAction func clickBack(_ sender: UIButton) { 14 | dismiss(animated: true) 15 | } 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | /* 30 | // MARK: - Navigation 31 | 32 | // In a storyboard-based application, you will often want to do a little preparation before navigation 33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 34 | // Get the new view controller using segue.destinationViewController. 35 | // Pass the selected object to the new view controller. 36 | } 37 | */ 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/ShowDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShowDetailViewController.swift 3 | // Lecture05-Demo 4 | // 5 | // Created by 买明 on 03/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ShowDetailViewController: UIViewController { 12 | 13 | @IBAction func clickBack(_ sender: UIButton) { 14 | dismiss(animated: true) 15 | } 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | /* 30 | // MARK: - Navigation 31 | 32 | // In a storyboard-based application, you will often want to do a little preparation before navigation 33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 34 | // Get the new view controller using segue.destinationViewController. 35 | // Pass the selected object to the new view controller. 36 | } 37 | */ 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Lecture05/Lecture05-Demo/Lecture05-Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Lecture05-Demo 4 | // 5 | // Created by 买明 on 03/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | // 在 Attributes Inspector 中勾选 User Interaction Enabled 14 | @IBOutlet weak var pannableLabel: UILabel! { 15 | didSet { 16 | let panGestureRecognizer = UIPanGestureRecognizer( 17 | target: self, action: #selector(ViewController.pan(recognizer:)) 18 | ) 19 | pannableLabel.addGestureRecognizer(panGestureRecognizer) 20 | } 21 | } 22 | 23 | @IBOutlet weak var tapLabel: UILabel! { 24 | didSet { 25 | let tapGestureRecognizer = UITapGestureRecognizer( 26 | target: self, action: #selector(ViewController.tap(recognizer:)) 27 | ) 28 | tapLabel.addGestureRecognizer(tapGestureRecognizer) 29 | } 30 | } 31 | 32 | func pan(recognizer: UIPanGestureRecognizer) { 33 | print("pan 手势状态枚举原始值:\(recognizer.state.rawValue)") 34 | 35 | switch recognizer.state { 36 | case .changed: fallthrough 37 | case .ended: 38 | let point = recognizer.translation(in: pannableLabel) 39 | let center = pannableLabel.center 40 | 41 | recognizer.view?.center = CGPoint(x: center.x + point.x, y: center.y) 42 | recognizer.setTranslation(CGPoint.zero, in: pannableLabel) 43 | default: 44 | break 45 | } 46 | } 47 | 48 | func tap(recognizer: UITapGestureRecognizer) { 49 | print("tap 手势状态枚举原始值:\(recognizer.state.rawValue)") 50 | } 51 | 52 | override func performSegue(withIdentifier identifier: String, sender: Any?) { 53 | 54 | } 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | // Do any additional setup after loading the view, typically from a nib. 59 | } 60 | 61 | override func didReceiveMemoryWarning() { 62 | super.didReceiveMemoryWarning() 63 | // Dispose of any resources that can be recreated. 64 | } 65 | 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /Lecture05/README.md: -------------------------------------------------------------------------------- 1 | # CS193p 查漏补缺(三)Lecture 05 2 | 3 | > Developing iOS 10 Apps with Swift - CS193p 4 | 5 | | Date | Notes | Swift | Xcode | 6 | |:-----:|:-----:|:-----:|:-----:| 7 | | 2017-03-03 | 首次提交 | 3.0 | 8.2.1 | 8 | 9 | ## Preface 10 | 11 | CS193p 是斯坦福大学的一门公开课,今年 iOS 10 & Swift 3.0 的版本如约而至,还是 Paul 老爷爷带课。之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。为了方便交流分享,我在 GitHub 建立了一个 Repo:[https://github.com/kingcos/CS193P_2017](https://github.com/kingcos/CS193P_2017),会将课上的代码 Commit,也会分享笔记、心得。 12 | 13 | 由于之前学过 Swift,也相信学习这门课的同学应当有一些 Swift 基础,所以定为查漏补缺,目标只将难点、重点、常用点总结。 14 | 15 | **本文对应的 Demo 可以在:[https://github.com/kingcos/CS193P_2017/tree/master/Lecture05](https://github.com/kingcos/CS193P_2017/tree/master/Lecture05) 查看、下载。** 16 | 17 | ## Gestures 18 | 19 | - 当 iOS 在运行时链接 `@IBOutlet` 后,属性观察器 `didSet` 即被调用。 20 | - 当拖动手势产生,`target` 得到通知,调用相应 `action` 方法。 21 | 22 | ```Swift 23 | // 在 Attributes Inspector 中勾选 User Interaction Enabled 24 | @IBOutlet weak var panLabel: UILabel! { 25 | didSet { 26 | let panGestureRecognizer = UIPanGestureRecognizer( 27 | target: self, action: #selector(ViewController.pan(recognizer:)) 28 | ) 29 | panLabel.addGestureRecognizer(panGestureRecognizer) 30 | } 31 | } 32 | ``` 33 | 34 | ### UIPanGestureRecognizer 35 | 36 | ```Swift 37 | // 返回手势积累量 38 | func translation(in view: UIView?) -> CGPoint 39 | // 重置 translation,为防止多次手势叠加,需最后重置为 CGPoint.zero 40 | func setTranslation(_ translation: CGPoint, in view: UIView?) 41 | // 手势拖动速度,单位 points/s 42 | func velocity(in view: UIView?) -> CGPoint 43 | ``` 44 | 45 | ### UIGestureRecognizerState 46 | 47 | - UIGestureRecognizerState 是手势状态的枚举。 48 | - 手势开始前:`.possible`。 49 | - 对于连续的手势:例如 pan(拖动),从 `.begin` 开始,重复 `.changed`,最后结束 `.ended`,状态有时会 `.failed` 或 `.cancelled`,需要特别注意。 50 | - 对于不连续的手势:例如 swipe(猛划)、tap(轻拍),没有中间过程,直接 `.ended` 或 `.recognized`。 51 | 52 | ```Swift 53 | func pan(recognizer: UIPanGestureRecognizer) { 54 | print("pan 手势状态枚举原始值:\(recognizer.state.rawValue)") 55 | 56 | switch recognizer.state { 57 | case .changed: fallthrough 58 | case .ended: 59 | let point = recognizer.translation(in: pannableLabel) 60 | let center = pannableLabel.center 61 | 62 | recognizer.view?.center = CGPoint(x: center.x + point.x, y: center.y) 63 | recognizer.setTranslation(CGPoint.zero, in: pannableLabel) 64 | default: 65 | break 66 | } 67 | } 68 | 69 | func tap(recognizer: UITapGestureRecognizer) { 70 | print("tap 手势状态枚举原始值:\(recognizer.state.rawValue)") 71 | } 72 | ``` 73 | 74 | ## MVC 75 | 76 | - 本节主要涉及了多 MVC 的场景,例如 UINavigationController,UITabbarController,以及 UISplitController。 77 | - UISplitController 主要为大屏设备使用,为适配小屏,需配合 UINavigationController 使用。 78 | - 本章内容使用文字表述过于复杂,未来可能整理至 Demo 中。 79 | 80 | ## Segue 81 | 82 | - Segue 即转场。 83 | 84 | ### Types 85 | 86 | - Show Segue: e.g. push 87 | - 从右向左进入,从左向右返回。 88 | - 嵌入 UINavigationController 时,头部自带返回按钮。 89 | - Show Detail Segue: e.g. replace 90 | - 从下向上进入,从上向下返回。 91 | - 嵌入 UISplitViewController 时,替换 DetailViewController,不带返回按钮。 92 | - Modally Segue 93 | - 由 Presentation 选项定义。 94 | - Popover Segue 95 | - Custom 96 | 97 | ### Identifier 98 | 99 | - Segue 总是创建一个新的 MVC 实例(不会重用)。 100 | - 使用代码调用 Segue `func performSegue(withIdentifier identifier: String, sender: Any?)`。 101 | 102 | ### Preparing 103 | 104 | - 以下方法的调用时机在 Outlet 链接前。 105 | 106 | ```Swift 107 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 108 | if let identifier = segue.identifier { 109 | switch identifier { 110 | case "Show Graph": 111 | if let vc = segue.destination as? GraphController { 112 | // Setup vc 113 | } 114 | default: 115 | break 116 | } 117 | } 118 | } 119 | ``` 120 | 121 | ### Preventing 122 | 123 | - 若要 UIViewController 阻止 Segue 发生,重写以下方法并返回 `false` 即可。 124 | 125 | ```Swift 126 | override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { 127 | return false 128 | } 129 | ``` 130 | 131 | ## Reference 132 | 133 | - [CS193P_2017](https://github.com/kingcos/CS193P_2017) 134 | - [does anyone can explain the difference between segues: show, show detail, present modally, present as popover](http://stackoverflow.com/questions/26287247/does-anyone-can-explain-the-difference-between-segues-show-show-detail-presen) 135 | -------------------------------------------------------------------------------- /Lecture06/README.md: -------------------------------------------------------------------------------- 1 | # CS193p 查漏补缺(四)Lecture 06 2 | 3 | > Developing iOS 10 Apps with Swift - CS193p 4 | 5 | | Date | Notes | Swift | Xcode | 6 | |:-----:|:-----:|:-----:|:-----:| 7 | | 2017-03-07 | 首次提交 | 3.0 | 8.2.1 | 8 | 9 | ## Preface 10 | 11 | CS193p 是斯坦福大学的一门公开课,今年 iOS 10 & Swift 3.0 的版本如约而至,还是 Paul 老爷爷带课。之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。为了方便交流分享,我在 GitHub 建立了一个 Repo:[https://github.com/kingcos/CS193P_2017](https://github.com/kingcos/CS193P_2017),会将课上的代码 Commit,也会分享笔记、心得。 12 | 13 | 由于之前学过 Swift,也相信学习这门课的同学应当有一些 Swift 基础,所以定为查漏补缺,目标只将难点、重点、常用点总结。 14 | 15 | ## Life Cycle 16 | 17 | > 对象的生命周期一直是我们所需要关心的,老师在这一节也详细的讲述了 UIViewController 的生命周期。为了搞清楚其生命周期,特将该部分单独行文:[探究 UIViewController 生命周期](http://www.jianshu.com/p/9d3d95e1ef5a)。同时由于之前写了[初探 iOS 中自定义 UIView 的初始化过程](http://www.jianshu.com/p/bfea8efee664),也更新了相应 Demo。 18 | 19 | ## Memory Management 20 | 21 | > 内存管理也是 iOS 中不可回避的问题,但由于我个人能力有限,这里只记录了老师所讲的点,未来可能会再进行总结。 22 | 23 | - ARC: Automatic Reference Count 自动引用计数(!= Garbage Collection 垃圾回收) 24 | - 引用类型(例如类)存储在堆(Heap)中。 25 | 26 | ### strong 27 | 28 | - 「强」引用: 29 | - 默认的引用指针,可省略 30 | - 只要有强引用指针指向,对象将一直保存在堆中。 31 | 32 | ### weak 33 | 34 | - 「弱」引用: 35 | - 当对象没有被使用时,即被销毁(nil)。 36 | - 弱引用用于指向引用类型的可选类型指针。 37 | - 弱引用指针将不会把对象保存在堆中。 38 | - 例子: 39 | - outlets(其被视图层次强力持有,所以可为 weak)。 40 | 41 | ### unowned 42 | 43 | - 「不」持有: 44 | - 需确保指针指向的对象没有被销毁(离开堆),否则程序会崩溃。 45 | - 常只用于打破循环引用。 46 | 47 | ### Closures 48 | 49 | > 此处代码已更新至 [Calculator](https://github.com/kingcos/CS193P_2017/tree/master/Calculator)。 50 | 51 | - 闭包是引用类型,同样存储在堆区。 52 | - 闭包可以放在数组,字典等,是 Swift 的一等(first-class)类型。 53 | - 当作为参数的闭包,执行的时机超出其自身时,需声明为逃逸闭包,即在闭包参数前加 `@escaping`: 54 | 55 | ```Swift 56 | mutating func addUnaryOperation(named symbol: String, _ operation: @escaping (Double) -> Double) { 57 | operations[symbol] = Operation.unaryOperation(operation) 58 | } 59 | ``` 60 | 61 | - 逃逸尾随闭包的使用: 62 | 63 | ```Swift 64 | // 常规写法 65 | brain.addUnaryOperation(named: "✅") { (value) -> Double in 66 | return sqrt(value) 67 | } 68 | 69 | // 简写 70 | brain.addUnaryOperation(named: "✅") { 71 | return sqrt($0) 72 | } 73 | ``` 74 | 75 | - 只要闭包仍保留在堆中,那么其捕获的引用也仍在堆中。 76 | - 闭包中的循环引用: 77 | 78 | ```Swift 79 | // 此时模型和控制器在闭包中相互引用,构成循环引用 80 | brain.addUnaryOperation(named: "✅") { 81 | self.display.textColor = UIColor.green 82 | return sqrt($0) 83 | } 84 | ``` 85 | 86 | - 解决闭包中的循环引用的方法: 87 | 88 | - *weak* 89 | 90 | ```Swift 91 | brain.addUnaryOperation(named: "✅") { [weak self] in 92 | // self 为 Optional 93 | self?.display.textColor = UIColor.green 94 | return sqrt($0) 95 | } 96 | 97 | brain.addUnaryOperation(named: "✅") { [weak weakSelf = self] in 98 | weakSelf?.display.textColor = UIColor.green 99 | return sqrt($0) 100 | } 101 | ``` 102 | 103 | - *unowned* 104 | 105 | ```Swift 106 | brain.addUnaryOperation(named: "✅") { [me = self] in 107 | me.display.textColor = UIColor.green 108 | return sqrt($0) 109 | } 110 | 111 | brain.addUnaryOperation(named: "✅") { [unowned me = self] in 112 | me.display.textColor = UIColor.green 113 | return sqrt($0) 114 | } 115 | 116 | brain.addUnaryOperation(named: "✅") { [unowned self = self] in 117 | self.display.textColor = UIColor.green 118 | return sqrt($0) 119 | } 120 | 121 | brain.addUnaryOperation(named: "✅") { [unowned self] in 122 | self.display.textColor = UIColor.green 123 | return sqrt($0) 124 | } 125 | ``` 126 | 127 | ## Reference 128 | 129 | - [CS193P_2017](https://github.com/kingcos/CS193P_2017) 130 | - [探究 UIView 生命周期](http://www.jianshu.com/p/bfea8efee664) 131 | - [探究 UIViewController 生命周期](http://www.jianshu.com/p/9d3d95e1ef5a) 132 | -------------------------------------------------------------------------------- /Lecture08/README.md: -------------------------------------------------------------------------------- 1 | # CS193p 查漏补缺(六)Lecture 08 2 | 3 | > Developing iOS 10 Apps with Swift - CS193p 4 | 5 | | Date | Notes | Swift | Xcode | 6 | |:-----:|:-----:|:-----:|:-----:| 7 | | 2017-03-14 | 首次提交 | 3.0 | 8.3 beta | 8 | 9 | ## Preface 10 | 11 | CS193p 是斯坦福大学的一门公开课,今年 iOS 10 & Swift 3.0 的版本如约而至,还是 Paul 老爷爷带课。之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。为了方便交流分享,我在 GitHub 建立了一个 Repo:[https://github.com/kingcos/CS193P_2017](https://github.com/kingcos/CS193P_2017),会将课上的代码 Commit,也会分享笔记、心得。 12 | 13 | 由于之前学过 Swift,也相信学习这门课的同学应当有一些 Swift 基础,所以定为查漏补缺,目标只将难点、重点、常用点总结。 14 | 15 | **本文对应的 Demo 可以在:[https://github.com/kingcos/CS193P_2017/tree/master/Lecture08](https://github.com/kingcos/CS193P_2017/tree/master/Lecture08) 查看、下载。模拟器中打开软键盘:Simulator-Hardware-Keyboard-Toggle Software Keyboard** 16 | 17 | ## Multithreading 18 | 19 | > 由于多线程部分是 iOS 中的一个难点、重点,因此正在总结一篇更加全面、且适用于 Swift 3.0 的文章,之后会在此更新相应链接。 20 | 21 | ## UITextField 22 | 23 | - UITextField 是 iOS 中的文本输入控件,部分属性类似于 UILabel。 24 | - 当用户点击 UITextField 或发送 `becomeFirstResponder` 消息,即成为第一响应者(First Responder),软键盘随即显示;发送 `resignFirstResponder` 消息,则注销第一响应者,键盘随即消失。 25 | - 控制器若要代理 UITextField,需遵从 UITextFieldDelegate 协议,并通过 Storyboard 或代码设置代理,即可实现相应代理方法。 26 | - UITextField 也实现了 UIControl 协议,即遵守了 [Target-Action 设计模式](http://www.jianshu.com/p/b00056fac0a8)。 27 | 28 | ### Keyboard 29 | 30 | - 软键盘的属性定义在 UITextInputTraits 协议中,UITextField 实现了该协议,因此可以通过 UITextField 更改软件的属性。 31 | - 软键盘上方可以自定义视图,设置为 UITextField 的 `inputAccessoryView` 属性即可。 32 | - 软键盘弹出时,会覆盖在其他控件之上,为了良好的用户体验,UITextField 要保持可见(不能被覆盖)。 33 | - UITableViewController 会监听 UITextField,并当键盘弹出时,自动调整到该 UITextField 的一行,即 UITextField 不会被覆盖。 34 | - 可以通过 UIWindow 发出的 Notification(通知)来响应,当相应的事件发生时,注册的方法将会被调用。如下:`Notification.Name.UIKeyboardDidShow` 事件发生,selector 的方法即被调用。 35 | 36 | ```Swift 37 | // 通知 38 | NotificationCenter.default.addObserver(self, 39 | selector: #selector(keyboardAppeared(_:)), 40 | name: Notification.Name.UIKeyboardDidShow, 41 | object: view.window) 42 | 43 | func keyboardAppeared(_ notification: Notification) { 44 | // Do something... 45 | print("\(#function) - \(notification.userInfo ?? [:])") 46 | } 47 | ``` 48 | 49 | ## Reference 50 | 51 | - [CS193P_2017](https://github.com/kingcos/CS193P_2017) 52 | - [小窥 iOS 中的 Target-Action 设计模式](http://www.jianshu.com/p/b00056fac0a8) 53 | -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UITextField-Demo 4 | // 5 | // Created by 买明 on 14/03/2017. 6 | // Copyright © 2017 买明. 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 | -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo/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 | 27 | 28 | -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | -------------------------------------------------------------------------------- /Lecture08/UITextField-Demo/UITextField-Demo/MyTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyTextField.swift 3 | // UITextField-Demo 4 | // 5 | // Created by 买明 on 14/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MyTextField: UITextField { 12 | 13 | override func becomeFirstResponder() -> Bool { 14 | print(#function) 15 | return super.becomeFirstResponder() 16 | } 17 | 18 | override func resignFirstResponder() -> Bool { 19 | print(#function) 20 | return super.resignFirstResponder() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Lecture13/README.md: -------------------------------------------------------------------------------- 1 | > Developing iOS 10 Apps with Swift - CS193p 2 | 3 | | Date | Notes | Swift | Xcode | 4 | |:-----:|:-----:|:-----:|:-----:| 5 | | 2017-05-25 | 首次提交 | 3.1 | 8.3.2 | 6 | 7 | ## Preface 8 | 9 | CS193p 是斯坦福大学的一门公开课,今年 iOS 10 & Swift 3.0 的版本如约而至,还是 Paul 老爷爷带课。之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。为了方便交流分享,我在 GitHub 建立了一个 Repo:[https://github.com/kingcos/CS193P_2017](https://github.com/kingcos/CS193P_2017),会将课上的代码 Commit,也会分享笔记、心得。 10 | 11 | 由于之前学过 Swift,也相信学习这门课的同学应当有一些 Swift 基础,所以定为查漏补缺,目标只将难点、重点、常用点总结。 12 | 13 | **本文 Dynamic Animation 对应的 Demo 可以在:[https://github.com/kingcos/CS193P_2017/tree/master/Asteroids](https://github.com/kingcos/CS193P_2017/tree/master/Asteroids) 查看、下载。** 14 | 15 | ## Timer 16 | 17 | - Timer,即定时器。 18 | - Timer 的类方法: 19 | 20 | ```Swift 21 | @available(iOS 10.0, *) 22 | open class func scheduledTimer(withTimeInterval interval: TimeInterval, 23 | repeats: Bool, 24 | block: @escaping (Timer) -> Swift.Void) -> Timer 25 | ``` 26 | 27 | - Timer 的使用: 28 | 29 | ```Swift 30 | // weak 31 | private weak var timer: Timer? 32 | 33 | // 开启 Timer(Run Loop 会保持强指针指向) 34 | timer = Timer.scheduledTimer(withTimeInterval: 2.0, 35 | repeats: true) { (timer) in 36 | // do something... 37 | } 38 | 39 | // 停止 Timer(Run Loop 不再保持强指针指向,timer 将被设置为 nil) 40 | timer.invalidate() 41 | 42 | // 设置公差,单位秒(可能会提升系统性能) 43 | timer.tolerance = 10 44 | ``` 45 | 46 | ## Animation 47 | 48 | - 动画分类: 49 | - UIView 动画 50 | - 控制器过渡 51 | - Core Animation 52 | - OpenGL & Metal 53 | - SpriteKit 54 | - Dynamic Animation 55 | 56 | ### UIView 动画 57 | 58 | - UIView 可动画的属性: 59 | - frame/center 60 | - transform(变换,旋转,比例) 61 | - alpha(透明度) 62 | - backgroundColor 63 | - UIView 动画的类方法: 64 | 65 | ```Swift 66 | // 动画 67 | @available(iOS 4.0, *) 68 | open class func animate(withDuration duration: TimeInterval, 69 | delay: TimeInterval, 70 | options: UIViewAnimationOptions = [], 71 | animations: @escaping () -> Swift.Void, 72 | completion: ((Bool) -> Swift.Void)? = nil) 73 | 74 | // 变换 75 | @available(iOS 4.0, *) 76 | open class func transition(with view: UIView, duration: TimeInterval, 77 | options: UIViewAnimationOptions = [], 78 | animations: (() -> Swift.Void)?, 79 | completion: ((Bool) -> Swift.Void)? = nil) 80 | ``` 81 | 82 | - 方法中 animations 闭包内部的改变是立即生效的,尽管需要一段时间才显现。 83 | 84 | ### Dynamic Animation 85 | 86 | - Dynamic Animation:物理特性相关的动画。 87 | 88 | ```Swift 89 | // 创建 UIDynamicAnimator 90 | var animator = UIDynamicAnimator(referenceView: UIView) 91 | 92 | // 添加重力行为 93 | let gravity = UIGravityBehavior() 94 | animator.addBehavior(gravity) 95 | 96 | // 添加碰撞行为 97 | collider = UICollisionBehavior() 98 | animator.addBehavior(collider) 99 | 100 | // UIDynamicBehavior 添加 UIDynamicItem 101 | let item1: UIDynamicItem = ... // usually a UIView 102 | let item2: UIDynamicItem = ... // usually a UIView 103 | gravity.addItem(item1) 104 | collider.addItem(item1) 105 | gravity.addItem(item2) 106 | ``` 107 | 108 | - UIDynamicItem 协议:任何动态项必须实现该协议(UIView 遵守该协议)。 109 | 110 | ```Swift 111 | protocol UIDynamicItem { 112 | var bounds: CGRect { get } // bounds 不可动 113 | var center: CGPoint { get set } // 中心位置可变 114 | var transform: CGAffineTransform { get set } // 旋转也可 115 | } 116 | ``` 117 | 118 | - 若在动画进行时调整中心或变形,必须调用 `func updateItemUsingCurrentState(item: UIDynamicItem)` 方法。 119 | - Behaviors 行为: 120 | - UIGravityBehavior 121 | - UIAttachmentBehavior 122 | - UICollisionBehavior 123 | - UISnapBehavior 124 | - UIPushBehavior 125 | - UIDynamicItemBehavior 126 | 127 | ## Reference 128 | 129 | - [CS193P_2017](https://github.com/kingcos/CS193P_2017) 130 | -------------------------------------------------------------------------------- /Lecture17/README.md: -------------------------------------------------------------------------------- 1 | > Developing iOS 10 Apps with Swift - CS193p 2 | 3 | | Date | Notes | Swift | Xcode | 4 | |:-----:|:-----:|:-----:|:-----:| 5 | | 2017-05-26 | 首次提交 | 3.1 | 8.3.2 | 6 | 7 | ## Preface 8 | 9 | CS193p 是斯坦福大学的一门公开课,今年 iOS 10 & Swift 3.0 的版本如约而至,还是 Paul 老爷爷带课。之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。为了方便交流分享,我在 GitHub 建立了一个 Repo:[https://github.com/kingcos/CS193P_2017](https://github.com/kingcos/CS193P_2017),会将课上的代码 Commit,也会分享笔记、心得。 10 | 11 | 由于之前学过 Swift,也相信学习这门课的同学应当有一些 Swift 基础,所以定为查漏补缺,目标只将难点、重点、常用点总结。 12 | 13 | ## Accessibility 14 | 15 | > 辅助功能是 iOS 中相当重要的一个功能,使得这些最先进的科技可以为每个人所用,即使是那些身患残疾的人们。虽然本笔记只会给出一些官方的文档链接,但我想这是一个很重要的内容,因此在此列出。希望未来开发 App 时,也可以及时适配。 16 | 17 | - Accessibility 即辅助功能。 18 | - [Accessibility - Apple Inc.](https://www.apple.com/accessibility/) 19 | - [UIAccessibility](https://developer.apple.com/reference/uikit/uiaccessibility) 20 | - [UIKit Functions](https://developer.apple.com/reference/uikit/uikit_functions) 21 | - [Typography](https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography) 22 | - [What's New in Accessibility - WWDC 16 - 202](https://developer.apple.com/videos/play/wwdc2016/202) 23 | - [Inclusive App Design - WWDC 16 - 801](https://developer.apple.com/videos/play/wwdc2016/801) 24 | - [Auditing Your Apps for Accessibility - WWDC 16 - 407](https://developer.apple.com/videos/play/wwdc2016/407) 25 | 26 | ## Reference 27 | 28 | - [CS193P_2017](https://github.com/kingcos/CS193P_2017) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS193P_2017 & 2021 2 | 3 | Notes & code for CS193p - Developing Apps for iOS 4 | 5 | [中文版 README](README_CN.md) 6 | 7 | ![](https://img.shields.io/badge/language-swift-orange.svg) ![](https://img.shields.io/badge/license-MIT-000000.svg) 8 | 9 | ## 2021 10 | 11 | ### Materials 12 | 13 | - All materials below are from [https://cs193p.sites.stanford.edu](https://cs193p.sites.stanford.edu). 14 | 15 | | Lecture | Reference | 16 | | - | - | 17 | | 1 - Getting Started with SwiftUI | [Video - YouTuBe](https://youtu.be/bqu6BquVi2M)| 18 | | 2 - Learning More about SwiftUI | [Video - YouTuBe](https://youtu.be/3lahkdHEhW8), [PDF 1](https://github.com/kingcos/CS193p/blob/master/2021/Materials/reading_1.pdf), [PDF 2](https://github.com/kingcos/CS193p/blob/master/2021/Materials/assignment_1.pdf) | 19 | | 3 - MVVM | [Video - YouTuBe](https://youtu.be/--qKOhdgJAs) | 20 | | 4 - More MVVM enum Optionals | [Video - YouTuBe](https://youtu.be/oWZOFSYS5GE), [PDF 1](https://github.com/kingcos/CS193p/blob/master/2021/Materials/reading_2.pdf), [PDF 2](https://github.com/kingcos/CS193p/blob/master/2021/Materials/assignment_2.pdf) | 21 | | 5 - Properties Layout @ViewBuilder | [Video - YouTuBe](https://www.youtube.com/watch?v=ayQl_F_uMS4) | 22 | 23 |
24 | -> Click here for more about CS193p of 2017 <- 25 | 26 | ## 2017 27 | 28 | ### Info 29 | 30 | - Xcode 8.0+ 31 | - Swift 3.0+ 32 | 33 | ### Preface 34 | 35 | CS193P is an iOS lesson from Stanford University in Spring, 2017. You can learn it by using iTunes U. 36 | 37 | I will share my own notes & code here. If you find some bugs, please issue me! 38 | 39 | **Updated completely.** 40 | 41 | ### Content 42 | 43 | #### Video 44 | 45 | - Code 46 | - [Calculator](/Calculator/) 47 | - [CalculatorPlayground](/CalculatorPlayground.playground/) 48 | - [FaceIt](/FaceIt/) 49 | - [Cassini](/Cassini/) 50 | - [Smashtag](/Smashtag/) 51 | - [CoreDataExample](/CoreDataExample/) 52 | - [Asteroids](/Asteroids/) 53 | 54 | #### Document 55 | 56 | - Notes 57 | 58 | - [CS193p Lecture 03 Notes (zh-CN)](/Lecture03/) 59 | - [CS193p Lecture 04 Notes (zh-CN)](/Lecture04/) 60 | - [CS193p Lecture 05 Notes (zh-CN)](/Lecture05/) 61 | - [CS193p Lecture 06 Notes (zh-CN)](/Lecture06/) 62 | - [CS193p Lecture 07 Notes (zh-CN)](/Lecture07/) 63 | - [CS193p Lecture 08 Notes (zh-CN)](/Lecture08/) 64 | - [CS193p Lecture 10 Notes (zh-CN)](/Lecture10/) 65 | - [CS193p Lecture 13 Notes (zh-CN)](/Lecture13/) 66 | - [CS193p Lecture 16 Notes (zh-CN)](/Lecture16/) 67 | - [CS193p Lecture 17 Notes (zh-CN)](/Lecture17/) 68 | 69 | - Code 70 | - [Lecture 03 Playground](/Lecture03/) 71 | - [Lecture 04 Demo](/Lecture04/) 72 | - [Lecture 05 Demo](/Lecture05/) 73 | - [Lecture 08 Demo](/Lecture08/) 74 | 75 |
76 | 77 | ## Contact 78 | 79 | - [Twitter](https://twitter.com/kingcos_v) 80 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # CS193P_2017 & 2021 2 | 3 | Notes & code for CS193p - Developing Apps for iOS 4 | 5 | [English Version README](README.md) 6 | 7 | ![](https://img.shields.io/badge/language-swift-orange.svg) ![](https://img.shields.io/badge/license-MIT-000000.svg) 8 | 9 | ## 2021 10 | 11 | ### 课程材料 12 | 13 | - 关于此次课程的所有材料均来源自 [https://cs193p.sites.stanford.edu](https://cs193p.sites.stanford.edu)。 14 | 15 | | 课程 | 地址 | 16 | | - | - | 17 | | 1 - Getting Started with SwiftUI(从 SwiftUI 开始) | [视频 - YouTuBe](https://youtu.be/bqu6BquVi2M) | 18 | | 2 - Learning More about SwiftUI(学习更多关于 SwiftUI)| [视频 - YouTuBe](https://youtu.be/3lahkdHEhW8)、[PDF 1](https://github.com/kingcos/CS193p/blob/master/2021/Materials/reading_1.pdf)、[PDF 2](https://github.com/kingcos/CS193p/blob/master/2021/Materials/assignment_1.pdf) | 19 | | 3 - MVVM | [视频 - YouTuBe](https://youtu.be/--qKOhdgJAs) | 20 | | 4 - More MVVM enum Optionals(更多 MMVM、枚举、可选)| [视频 - YouTuBe](https://youtu.be/oWZOFSYS5GE)、[PDF 1](https://github.com/kingcos/CS193p/blob/master/2021/Materials/reading_2.pdf)、[PDF 2](https://github.com/kingcos/CS193p/blob/master/2021/Materials/assignment_2.pdf) | 21 | | 5 - Properties Layout @ViewBuilder(属性布局 @ViewBuilder)| [视频 - YouTuBe](https://www.youtube.com/watch?v=ayQl_F_uMS4) | 22 | 23 |
24 | -> 点击此处查看 2017 年度 CS193p 更多内容 <- 25 | 26 | ## 2017 27 | 28 | ### 简介 29 | 30 | - Xcode 8.0+ 31 | - Swift 3.0+ 32 | 33 | ### 前言 34 | 35 | 美国斯坦福大学的 iOS 课程又在 iTunes U 开课啦。搜索全名「Developing iOS 10 Apps with Swift」即可找到。 36 | 37 | 之前虽然也有听过他的课,但没有坚持下来,也没有做相应的笔记。这次希望可以坚持看完,并且与大家分享笔记、心得。 38 | 39 | **更新已完结。** 40 | 41 | ### 目录 42 | 43 | #### 视频 44 | 45 | - 代码 46 | - [Calculator](/Calculator/) 47 | - [CalculatorPlayground](/CalculatorPlayground.playground/) 48 | - [FaceIt](/FaceIt) 49 | - [Cassini](/Cassini) 50 | - [Smashtag](/Smashtag) 51 | - [CoreDataExample](/CoreDataExample/) 52 | - [Asteroids](/Asteroids/) 53 | 54 | #### 文档 55 | 56 | - 笔记 57 | 58 | - [CS193p 查漏补缺(一)Lecture 03](/Lecture03/) 59 | - [CS193p 查漏补缺(二)Lecture 04](/Lecture04/) 60 | - [CS193p 查漏补缺(三)Lecture 05](/Lecture05/) 61 | - [CS193p 查漏补缺(四)Lecture 06](/Lecture06/) 62 | - [CS193p 查漏补缺(五)Lecture 07](/Lecture07/) 63 | - [CS193p 查漏补缺(六)Lecture 08](/Lecture08/) 64 | - [CS193p 查漏补缺(七)Lecture 10](/Lecture10/) 65 | - [CS193p 查漏补缺(八)Lecture 13](/Lecture13/) 66 | - [CS193p 查漏补缺(九)Lecture 16](/Lecture16/) 67 | - [CS193p 查漏补缺(十)Lecture 17](/Lecture17/) 68 | 69 | - 代码 70 | - [Lecture 03 Playground](/Lecture03/) 71 | - [Lecture 04 Demo](/Lecture04/) 72 | - [Lecture 05 Demo](/Lecture05/) 73 | - [Lecture 08 Demo](/Lecture08/) 74 | 75 | ### 参考 76 | 77 | - [Swift 中的值类型与引用类型](http://www.jianshu.com/p/ba12b64f6350) 78 | - [浅谈 Swift 中的属性(Property)](http://www.jianshu.com/p/fe60f5bafab3) 79 | - [Swift 中的字符串截取](http://www.jianshu.com/p/94310202ba1b) 80 | - [初探 iOS 中自定义 UIView 的初始化过程](http://www.jianshu.com/p/bfea8efee664) 81 | - [iOS 中的 bounds & frame](http://www.jianshu.com/p/edb2ae03115c) 82 | - [tangqiaoboy/iOS-Pro](https://github.com/tangqiaoboy/iOS-Pro) 83 | - [isOpaque - Apple Inc.](https://developer.apple.com/reference/uikit/uiview/1622622-isopaque) 84 | - [探究 UIViewController 生命周期](http://www.jianshu.com/p/9d3d95e1ef5a) 85 | - [Swift 中的错误处理](http://www.jianshu.com/p/16bfad50c39a) 86 | - [小窥 iOS 中的 Target-Action 设计模式](http://www.jianshu.com/p/b00056fac0a8) 87 | - [浅谈 iOS 应用启动过程](http://www.jianshu.com/p/ec4a9b3d2576) 88 | 89 |
90 | 91 | ## 个人 92 | 93 | - [微博](http://weibo.com/u/1798410923) 94 | -------------------------------------------------------------------------------- /Smashtag/L9.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Smashtag/Smashtag.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Smashtag/Smashtag/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 | 27 | 28 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/FetchedResultsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchedResultsTableViewController.swift 3 | // SmashtagA5 4 | // 5 | // Created by Paul Hegarty on 2/3/17. 6 | // Copyright © 2017 Stanford University. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class FetchedResultsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 13 | { 14 | public func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 15 | tableView.beginUpdates() 16 | } 17 | 18 | public func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { 19 | switch type { 20 | case .insert: tableView.insertSections([sectionIndex], with: .fade) 21 | case .delete: tableView.deleteSections([sectionIndex], with: .fade) 22 | default: break 23 | } 24 | } 25 | 26 | public func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 27 | switch type { 28 | case .insert: 29 | tableView.insertRows(at: [newIndexPath!], with: .fade) 30 | case .delete: 31 | tableView.deleteRows(at: [indexPath!], with: .fade) 32 | case .update: 33 | tableView.reloadRows(at: [indexPath!], with: .fade) 34 | case .move: 35 | tableView.deleteRows(at: [indexPath!], with: .fade) 36 | tableView.insertRows(at: [newIndexPath!], with: .fade) 37 | } 38 | } 39 | 40 | public func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 41 | tableView.endUpdates() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/Smash.xcdatamodeld/Smash.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/SmashTweetTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmashTweetTableViewController.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 08/04/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Twitter 11 | import CoreData 12 | 13 | class SmashTweetTableViewController: TweetTableViewController { 14 | 15 | var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer 16 | 17 | override func insertTweets(_ newTweets: [Twitter.Tweet]) { 18 | super.insertTweets(newTweets) 19 | 20 | updateDatabase(with: newTweets) 21 | } 22 | 23 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 24 | if segue.identifier == "Tweeters Mentioning Search Term" { 25 | if let tweetersTVC = segue.destination as? SmashTweetersTableViewController { 26 | tweetersTVC.mention = searchText 27 | tweetersTVC.container = container 28 | } 29 | } 30 | } 31 | 32 | private func updateDatabase(with tweets: [Twitter.Tweet]) { 33 | print("Starting database load") 34 | container?.performBackgroundTask { [weak self] context in 35 | for twitterInfo in tweets { 36 | _ = try? Tweet.findOrCreateTweet(matching: twitterInfo, in: context) 37 | } 38 | try? context.save() 39 | print("Done loading database") 40 | self?.printDatabaseStatistics() 41 | } 42 | } 43 | 44 | private func printDatabaseStatistics() { 45 | if let context = container?.viewContext { 46 | context.perform { 47 | if Thread.isMainThread { 48 | print("isMainThread") 49 | } else { 50 | print("is NOT MainThread") 51 | } 52 | let request: NSFetchRequest = Tweet.fetchRequest() 53 | if let tweetCount = (try? context.fetch(request))?.count { 54 | print("\(tweetCount) tweet(s)") 55 | } 56 | if let twitterCount = try? context.count(for: TwitterUser.fetchRequest()) { 57 | print("\(twitterCount) Twitter user(s)") 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/SmashTweetersTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmashTweetersTableViewController.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 08/04/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class SmashTweetersTableViewController: FetchedResultsTableViewController { 13 | 14 | var mention: String? { 15 | didSet { 16 | updateUI() 17 | } 18 | } 19 | 20 | var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { 21 | didSet { 22 | updateUI() 23 | } 24 | } 25 | 26 | var fetchedResultsController: NSFetchedResultsController? 27 | 28 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 29 | let cell = tableView.dequeueReusableCell(withIdentifier: "TweeterUser Cell", for: indexPath) 30 | 31 | if let twitterUser = fetchedResultsController?.object(at: indexPath) { 32 | cell.textLabel?.text = twitterUser.handle 33 | let tweetCount = tweetCountWithMentionBy(twitterUser) 34 | cell.detailTextLabel?.text = "\(tweetCount) tweet(s)" 35 | } 36 | 37 | return cell 38 | } 39 | 40 | private func updateUI() { 41 | if let context = container?.viewContext, mention != nil { 42 | let request: NSFetchRequest = TwitterUser.fetchRequest() 43 | request.sortDescriptors = [NSSortDescriptor(key: "handle", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))] 44 | request.predicate = NSPredicate(format: "any tweets.text contains[c] %@ AND !handle beginswith[c] %@", mention!, "a") 45 | fetchedResultsController = NSFetchedResultsController(fetchRequest: request, 46 | managedObjectContext: context, 47 | sectionNameKeyPath: nil, 48 | cacheName: nil) 49 | fetchedResultsController?.delegate = self 50 | try? fetchedResultsController?.performFetch() 51 | tableView.reloadData() 52 | } 53 | } 54 | 55 | private func tweetCountWithMentionBy(_ twitterUser: TwitterUser) -> Int { 56 | let request: NSFetchRequest = Tweet.fetchRequest() 57 | request.predicate = NSPredicate(format: "text contains[c] %@ and tweeter = %@", mention!, twitterUser) 58 | return (try? twitterUser.managedObjectContext!.count(for: request)) ?? 0 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/Tweet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tweet.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 08/04/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Twitter 11 | import CoreData 12 | 13 | class Tweet: NSManagedObject { 14 | class func findOrCreateTweet(matching twitterInfo: Twitter.Tweet, in context: NSManagedObjectContext) throws -> Tweet { 15 | let request: NSFetchRequest = Tweet.fetchRequest() 16 | request.predicate = NSPredicate(format: "unique = %@", twitterInfo.identifier) 17 | 18 | do { 19 | let matches = try context.fetch(request) 20 | if matches.count > 0 { 21 | assert(matches.count == 1, "Tweet.\(#function) - Database inconsistency.") 22 | return matches[0] 23 | } 24 | } catch { 25 | throw error 26 | } 27 | 28 | let tweet = Tweet(context: context) 29 | tweet.unique = twitterInfo.identifier 30 | tweet.text = twitterInfo.text 31 | tweet.created = twitterInfo.created as NSDate 32 | tweet.tweeter = try? TwitterUser.findOrCreateTwitterUser(matching: twitterInfo.user, in: context) 33 | 34 | return tweet 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/TweetTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetTableViewCell.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 19/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Twitter 11 | 12 | class TweetTableViewCell: UITableViewCell { 13 | 14 | 15 | @IBOutlet weak var tweetProfileImageView: UIImageView! 16 | @IBOutlet weak var tweetCreatedLabel: UILabel! 17 | @IBOutlet weak var tweetUserLabel: UILabel! 18 | @IBOutlet weak var tweetTextLabel: UILabel! 19 | 20 | var tweet: Twitter.Tweet? { 21 | didSet { 22 | updateUI() 23 | } 24 | } 25 | 26 | private func updateUI() { 27 | tweetTextLabel?.text = tweet?.text 28 | tweetUserLabel?.text = tweet?.user.description 29 | 30 | if let profileImageURL = tweet?.user.profileImageURL { 31 | // Global 队列 32 | // DispatchQueue.global(qos: .userInitiated).async { [weak self] in 33 | // if let imageData = try? Data(contentsOf: profileImageURL) { 34 | // self?.tweetProfileImageView?.image = UIImage(data: imageData) 35 | // } 36 | // } 37 | if let imageData = try? Data(contentsOf: profileImageURL) { 38 | tweetProfileImageView?.image = UIImage(data: imageData) 39 | } 40 | } else { 41 | tweetProfileImageView?.image = nil 42 | } 43 | 44 | if let created = tweet?.created { 45 | let formatter = DateFormatter() 46 | if Date().timeIntervalSince(created) > 24*60*60 { 47 | formatter.dateStyle = .short 48 | } else { 49 | formatter.dateStyle = .short 50 | } 51 | tweetCreatedLabel?.text = formatter.string(from: created) 52 | } else { 53 | tweetCreatedLabel?.text = nil 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/TweetTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetTableViewController.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 14/03/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Twitter 11 | 12 | class TweetTableViewController: UITableViewController, 13 | UITextFieldDelegate { 14 | 15 | @IBOutlet weak var searchTextField: UITextField! { 16 | didSet { 17 | searchTextField.delegate = self 18 | } 19 | } 20 | private var tweets = Array>() { 21 | didSet { 22 | print(tweets) 23 | } 24 | } 25 | private var lastTwitterRequest: Twitter.Request? 26 | 27 | var searchText: String? { 28 | didSet { 29 | searchTextField?.text = searchText 30 | searchTextField?.resignFirstResponder() 31 | lastTwitterRequest = nil 32 | 33 | tweets.removeAll() 34 | tableView.reloadData() 35 | searchForTweets() 36 | title = searchText 37 | } 38 | } 39 | 40 | private func twitterRequest() -> Twitter.Request? { 41 | if let query = searchText, !query.isEmpty { 42 | // return Twitter.Request(search: query, count: 100) 43 | return Twitter.Request(search: "\(query) -filter:safe -filter:retweets", count: 100) 44 | } 45 | return nil 46 | } 47 | 48 | private func searchForTweets() { 49 | if let request = lastTwitterRequest?.newer ?? twitterRequest() { 50 | lastTwitterRequest = request 51 | request.fetchTweets({ [weak self] newTweets in 52 | DispatchQueue.main.async { 53 | if request == self?.lastTwitterRequest { 54 | self?.insertTweets(newTweets) 55 | } 56 | } 57 | self?.refreshControl?.endRefreshing() 58 | }) 59 | } else { 60 | refreshControl?.endRefreshing() 61 | } 62 | } 63 | 64 | @IBAction func refresh(_ sender: UIRefreshControl) { 65 | searchForTweets() 66 | } 67 | 68 | func insertTweets(_ newTweets: [Twitter.Tweet]) { 69 | tweets.insert(newTweets, at: 0) 70 | tableView.insertSections([0], with: .fade) 71 | } 72 | 73 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 74 | if textField == searchTextField { 75 | searchText = searchTextField.text 76 | } 77 | return true 78 | } 79 | 80 | override func viewDidLoad() { 81 | super.viewDidLoad() 82 | 83 | tableView.estimatedRowHeight = tableView.rowHeight 84 | tableView.rowHeight = UITableViewAutomaticDimension 85 | // searchText = "#stanford" 86 | } 87 | 88 | // MARK: - Table view data source 89 | 90 | override func numberOfSections(in tableView: UITableView) -> Int { 91 | // #warning Incomplete implementation, return the number of sections 92 | return tweets.count 93 | } 94 | 95 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 96 | // #warning Incomplete implementation, return the number of rows 97 | return tweets[section].count 98 | } 99 | 100 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 101 | let cell = tableView.dequeueReusableCell(withIdentifier: "Tweet", for: indexPath) 102 | 103 | let tweet: Twitter.Tweet = tweets[indexPath.section][indexPath.row] 104 | // cell.textLabel?.text = tweet.text 105 | // cell.detailTextLabel?.text = tweet.user.name 106 | if let tweetCell = cell as? TweetTableViewCell { 107 | tweetCell.tweet = tweet 108 | } 109 | 110 | return cell 111 | } 112 | 113 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 114 | return "\(tweets.count - section)" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/TwitterUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterUser.swift 3 | // Smashtag 4 | // 5 | // Created by 买明 on 08/04/2017. 6 | // Copyright © 2017 买明. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Twitter 11 | import CoreData 12 | 13 | class TwitterUser: NSManagedObject { 14 | class func findOrCreateTwitterUser(matching twitterInfo: Twitter.User, in context: NSManagedObjectContext) throws -> TwitterUser { 15 | let request: NSFetchRequest = TwitterUser.fetchRequest() 16 | request.predicate = NSPredicate(format: "handle = %@", twitterInfo.screenName) 17 | 18 | do { 19 | let matches = try context.fetch(request) 20 | if matches.count > 0 { 21 | assert(matches.count == 1, "TwitterUser.\(#function) - Database inconsistency.") 22 | return matches[0] 23 | } 24 | } catch { 25 | throw error 26 | } 27 | 28 | let twitterUser = TwitterUser(context: context) 29 | twitterUser.handle = twitterInfo.screenName 30 | twitterUser.name = twitterInfo.name 31 | 32 | return twitterUser 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Smashtag/Smashtag/UITableViewDataSource+NSFetchedResultsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewController extension for use with NSFetchedResultsController 3 | // 4 | // Created by CS193p Instructor. 5 | // Copyright © 2017 Stanford University. All rights reserved. 6 | // 7 | // This implements the UITableViewDataSources 8 | // assuming a var called fetchedResultsController exists 9 | 10 | import UIKit 11 | import CoreData 12 | 13 | extension SmashTweetersTableViewController 14 | { 15 | // MARK: UITableViewDataSource 16 | 17 | override func numberOfSections(in tableView: UITableView) -> Int { 18 | return fetchedResultsController?.sections?.count ?? 1 19 | } 20 | 21 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 22 | if let sections = fetchedResultsController?.sections, sections.count > 0 { 23 | return sections[section].numberOfObjects 24 | } else { 25 | return 0 26 | } 27 | } 28 | 29 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 30 | if let sections = fetchedResultsController?.sections, sections.count > 0 { 31 | return sections[section].name 32 | } else { 33 | return nil 34 | } 35 | } 36 | 37 | override func sectionIndexTitles(for tableView: UITableView) -> [String]? { 38 | return fetchedResultsController?.sectionIndexTitles 39 | } 40 | 41 | override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { 42 | return fetchedResultsController?.section(forSectionIndexTitle: title, at: index) ?? 0 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Smashtag/Twitter/Twitter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Smashtag/Twitter/Twitter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Smashtag/Twitter/Twitter/MediaItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaItem.swift 3 | // Twitter 4 | // 5 | // Created by CS193p Instructor. 6 | // Copyright (c) 2015-17 Stanford University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // holds the network url and aspectRatio of an image attached to a Tweet 12 | // created automatically when a Tweet object is created 13 | 14 | public struct MediaItem: CustomStringConvertible 15 | { 16 | public let url: URL 17 | public let aspectRatio: Double 18 | 19 | public var description: String { return "\(url.absoluteString) (aspect ratio = \(aspectRatio))" } 20 | 21 | // MARK: - Internal Implementation 22 | 23 | init?(data: NSDictionary?) { 24 | guard 25 | let height = data?.double(forKeyPath: TwitterKey.height), height > 0, 26 | let width = data?.double(forKeyPath: TwitterKey.width), width > 0, 27 | let url = data?.url(forKeyPath: TwitterKey.mediaURL) 28 | else { 29 | return nil 30 | } 31 | self.url = url 32 | self.aspectRatio = width/height 33 | } 34 | 35 | struct TwitterKey { 36 | static let mediaURL = "media_url_https" 37 | static let width = "sizes.small.w" 38 | static let height = "sizes.small.h" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Smashtag/Twitter/Twitter/NSDictionary+KeyPathConvenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+KeyPathConvenience.swift 3 | // Twitter 4 | // 5 | // Created by CS193p Instructor. 6 | // Copyright (c) 2015-17 Stanford University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSDictionary { 12 | func double(forKeyPath keyPath: String) -> Double? { 13 | return value(forKeyPath: keyPath) as? Double 14 | } 15 | func int(forKeyPath keyPath: String) -> Int? { 16 | return value(forKeyPath: keyPath) as? Int 17 | } 18 | func string(forKeyPath keyPath: String) -> String? { 19 | return value(forKeyPath: keyPath) as? String 20 | } 21 | func bool(forKeyPath keyPath: String) -> Bool? { 22 | return (value(forKeyPath: keyPath) as? NSNumber)?.boolValue 23 | } 24 | func url(forKeyPath keyPath: String) -> URL? { 25 | if let urlString = string(forKeyPath: keyPath), urlString.characters.count > 0, let url = URL(string: urlString) { 26 | return url 27 | } else { 28 | return nil 29 | } 30 | } 31 | func dictionary(forKeyPath keyPath: String) -> NSDictionary? { 32 | return value(forKeyPath: keyPath) as? NSDictionary 33 | } 34 | func array(forKeyPath keyPath: String) -> NSArray? { 35 | return value(forKeyPath: keyPath) as? NSArray 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Smashtag/Twitter/Twitter/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Twitter 4 | // 5 | // Created by CS193p Instructor. 6 | // Copyright (c) 2015-17 Stanford University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // container to hold data about a Twitter user 12 | 13 | public struct User: CustomStringConvertible 14 | { 15 | public let screenName: String 16 | public let name: String 17 | public let id: String 18 | public let verified: Bool 19 | public let profileImageURL: URL? 20 | 21 | public var description: String { return "@\(screenName) (\(name))\(verified ? " ✅" : "")" } 22 | 23 | // MARK: - Internal Implementation 24 | 25 | init?(data: NSDictionary?) { 26 | guard 27 | let screenName = data?.string(forKeyPath: TwitterKey.screenName), 28 | let name = data?.string(forKeyPath: TwitterKey.name), 29 | let id = data?.string(forKeyPath: TwitterKey.identifier) 30 | else { 31 | return nil 32 | } 33 | 34 | self.screenName = screenName 35 | self.name = name 36 | self.id = id 37 | self.verified = data?.bool(forKeyPath: TwitterKey.verified) ?? false 38 | self.profileImageURL = data?.url(forKeyPath: TwitterKey.profileImageURL) 39 | } 40 | 41 | var asPropertyList: [String:String] { 42 | return [ 43 | TwitterKey.name : name, 44 | TwitterKey.screenName : screenName, 45 | TwitterKey.identifier : id, 46 | TwitterKey.verified : verified ? "YES" : "NO", 47 | TwitterKey.profileImageURL : profileImageURL?.absoluteString ?? "" 48 | ] 49 | } 50 | 51 | struct TwitterKey { 52 | static let name = "name" 53 | static let screenName = "screen_name" 54 | static let identifier = "id_str" 55 | static let verified = "verified" 56 | static let profileImageURL = "profile_image_url" 57 | } 58 | } 59 | --------------------------------------------------------------------------------