├── 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 |  
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 |  
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 |
--------------------------------------------------------------------------------