├── .DS_Store
├── .gitignore
├── Practice-JH
├── Podfile
├── Podfile.lock
├── Pods
│ └── JJFloatingActionButton
│ │ └── Sources
│ │ └── JJFloatingActionButton+Animation.swift
├── Practice-JH.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── junholee.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ │ └── junholee.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── Practice-JH.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Practice-JH
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── cell_four.imageset
│ │ ├── Contents.json
│ │ ├── Group 293.png
│ │ ├── Group 293@2x.png
│ │ └── Group 293@3x.png
│ ├── cell_one.imageset
│ │ ├── Contents.json
│ │ ├── Group 58.png
│ │ ├── Group 58@2x.png
│ │ └── Group 58@3x.png
│ ├── cell_three.imageset
│ │ ├── Contents.json
│ │ ├── Group 61.png
│ │ ├── Group 61@2x.png
│ │ └── Group 61@3x.png
│ ├── cell_two.imageset
│ │ ├── Contents.json
│ │ ├── Group 293.png
│ │ ├── Group 293@2x.png
│ │ └── Group 293@3x.png
│ └── chatButton.imageset
│ │ ├── Contents.json
│ │ ├── chatButton.png
│ │ ├── chatButton@2x.png
│ │ └── chatButton@3x.png
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── LogoutViewController.swift
│ ├── SceneDelegate.swift
│ ├── ViewController.swift
│ └── carousel
│ ├── Carousel.storyboard
│ ├── CarouselCVC
│ ├── firstCVC.swift
│ ├── secondCVC.swift
│ └── thirdCVC.swift
│ ├── CarouselContainerView.swift
│ ├── CarouselLayout.swift
│ └── CarouselVC.swift
├── Practice-YSB
├── FloatingButton
│ ├── FloatingButton.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── FloatingButton.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── FloatingButton
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── BaseViewController.swift
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
│ ├── Podfile
│ └── Podfile.lock
├── Photo
│ ├── Photo.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Photo
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
├── StickyHeader
│ ├── Podfile
│ ├── Podfile.lock
│ ├── StickyHeader.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── StickyHeader.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── StickyHeader
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── CustomCVC.swift
│ │ ├── CustomHeaderView.swift
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
├── Stopwatch
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Stopwatch.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Stopwatch.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Stopwatch
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ ├── Stopwatch.swift
│ │ └── ViewController.swift
└── socialLogin
│ ├── Podfile
│ ├── Podfile.lock
│ ├── socialLogin.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── socialLogin.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── socialLogin
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ ├── ViewController.swift
│ └── socialLogin.entitlements
└── README.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/macos,xcode,cocoapods,swift
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,xcode,cocoapods,swift
4 |
5 | ### CocoaPods ###
6 | ## CocoaPods GitIgnore Template
7 |
8 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing
9 | # - Also handy if you have a large number of dependant pods
10 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE
11 | Pods/
12 |
13 | ### macOS ###
14 | # General
15 | .DS_Store
16 | .AppleDouble
17 | .LSOverride
18 |
19 | # Icon must end with two \r
20 | Icon
21 |
22 |
23 | # Thumbnails
24 | ._*
25 |
26 | # Files that might appear in the root of a volume
27 | .DocumentRevisions-V100
28 | .fseventsd
29 | .Spotlight-V100
30 | .TemporaryItems
31 | .Trashes
32 | .VolumeIcon.icns
33 | .com.apple.timemachine.donotpresent
34 |
35 | # Directories potentially created on remote AFP share
36 | .AppleDB
37 | .AppleDesktop
38 | Network Trash Folder
39 | Temporary Items
40 | .apdisk
41 |
42 | ### Swift ###
43 | # Xcode
44 | #
45 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
46 |
47 | ## User settings
48 | xcuserdata/
49 |
50 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
51 | *.xcscmblueprint
52 | *.xccheckout
53 |
54 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
55 | build/
56 | DerivedData/
57 | *.moved-aside
58 | *.pbxuser
59 | !default.pbxuser
60 | *.mode1v3
61 | !default.mode1v3
62 | *.mode2v3
63 | !default.mode2v3
64 | *.perspectivev3
65 | !default.perspectivev3
66 |
67 | ## Obj-C/Swift specific
68 | *.hmap
69 |
70 | ## App packaging
71 | *.ipa
72 | *.dSYM.zip
73 | *.dSYM
74 |
75 | ## Playgrounds
76 | timeline.xctimeline
77 | playground.xcworkspace
78 |
79 | # Swift Package Manager
80 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
81 | # Packages/
82 | # Package.pins
83 | # Package.resolved
84 | # *.xcodeproj
85 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
86 | # hence it is not needed unless you have added a package configuration file to your project
87 | # .swiftpm
88 |
89 | .build/
90 |
91 | # CocoaPods
92 | # We recommend against adding the Pods directory to your .gitignore. However
93 | # you should judge for yourself, the pros and cons are mentioned at:
94 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
95 | # Pods/
96 | # Add this line if you want to avoid checking in source code from the Xcode workspace
97 | # *.xcworkspace
98 |
99 | # Carthage
100 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
101 | # Carthage/Checkouts
102 |
103 | Carthage/Build/
104 |
105 | # Accio dependency management
106 | Dependencies/
107 | .accio/
108 |
109 | # fastlane
110 | # It is recommended to not store the screenshots in the git repo.
111 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
112 | # For more information about the recommended setup visit:
113 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
114 |
115 | fastlane/report.xml
116 | fastlane/Preview.html
117 | fastlane/screenshots/**/*.png
118 | fastlane/test_output
119 |
120 | # Code Injection
121 | # After new code Injection tools there's a generated folder /iOSInjectionProject
122 | # https://github.com/johnno1962/injectionforxcode
123 |
124 | iOSInjectionProject/
125 |
126 | ### Xcode ###
127 | # Xcode
128 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
129 |
130 |
131 |
132 |
133 | ## Gcc Patch
134 | /*.gcno
135 |
136 | ### Xcode Patch ###
137 | *.xcodeproj/*
138 | !*.xcodeproj/project.pbxproj
139 | !*.xcodeproj/xcshareddata/
140 | !*.xcworkspace/contents.xcworkspacedata
141 | **/xcshareddata/WorkspaceSettings.xcsettings
142 |
143 | # End of https://www.toptal.com/developers/gitignore/api/macos,xcode,cocoapods,swift
--------------------------------------------------------------------------------
/Practice-JH/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'Practice-JH' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for Practice-JH
9 | pod 'KakaoSDKUser' # 카카오 로그인
10 | pod 'KakaoSDKCommon' # 카카오 일반
11 | pod 'JJFloatingActionButton' # 플로팅버튼
12 | pod 'KakaoSDKAuth' # 사용자 관리
13 | pod 'SnapKit', '~> 5.0.0'
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/Practice-JH/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (5.4.4)
3 | - JJFloatingActionButton (2.5.0)
4 | - KakaoSDKAuth (2.8.4):
5 | - KakaoSDKCommon (= 2.8.4)
6 | - KakaoSDKCommon (2.8.4):
7 | - KakaoSDKCommon/Common (= 2.8.4)
8 | - KakaoSDKCommon/Network (= 2.8.4)
9 | - KakaoSDKCommon/Common (2.8.4)
10 | - KakaoSDKCommon/Network (2.8.4):
11 | - Alamofire (~> 5.1)
12 | - KakaoSDKCommon/Common (= 2.8.4)
13 | - KakaoSDKUser (2.8.4):
14 | - KakaoSDKAuth (= 2.8.4)
15 | - SnapKit (5.0.1)
16 |
17 | DEPENDENCIES:
18 | - KakaoSDKAuth
19 | - JJFloatingActionButton
20 | - KakaoSDKCommon
21 | - KakaoSDKUser
22 | - SnapKit (~> 5.0.0)
23 |
24 | SPEC REPOS:
25 | trunk:
26 | - Alamofire
27 | - JJFloatingActionButton
28 | - KakaoSDKAuth
29 | - KakaoSDKCommon
30 | - KakaoSDKUser
31 | - SnapKit
32 |
33 | SPEC CHECKSUMS:
34 | Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
35 | JJFloatingActionButton: fdc3aefb0f28f04d0f988fa25d5fac97028a9ea7
36 | KakaoSDKAuth: b9b693995f98b1821ccd22b39d383e1dede5ca45
37 | KakaoSDKCommon: 33c9902ee9ce13e9d1cac3549896406229836ff5
38 | KakaoSDKUser: d878977e0ba6b365cfeac3b6319cdf4231713bea
39 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
40 |
41 | PODFILE CHECKSUM: 95ee9ecf986dd9f5352c067a2cbba904fba53cd9
42 |
43 | COCOAPODS: 1.11.2
44 |
--------------------------------------------------------------------------------
/Practice-JH/Pods/JJFloatingActionButton/Sources/JJFloatingActionButton+Animation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJFloatingActionButton+Animation.swift
3 | //
4 | // Copyright (c) 2017-Present Jochen Pfeiffer
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import UIKit
26 |
27 | @objc public extension JJFloatingActionButton {
28 | /// Open the floating action button and show all action items.
29 | ///
30 | /// - Parameter animated: When true, button will be opened with an animation. Default is `true`.
31 | /// - Parameter completion: Will be handled upon completion. Default is `nil`.
32 | ///
33 | /// - Remark: Hidden items and items that have user interaction disabled are omitted.
34 | ///
35 | /// - SeeAlso: `buttonAnimationConfiguration`
36 | /// - SeeAlso: `itemAnimationConfiguration`
37 | ///
38 | func open(animated: Bool = true, completion: (() -> Void)? = nil) {
39 | guard let superview = superview, buttonState == .closed, !enabledItems.isEmpty, !isSingleActionButton else {
40 | return
41 | }
42 |
43 | buttonState = .opening
44 | delegate?.floatingActionButtonWillOpen?(self)
45 |
46 | storeAnimationState()
47 |
48 | superview.bringSubviewToFront(self)
49 | addOverlayView(to: superview)
50 | addItems(to: superview)
51 | itemContainerView.setNeedsLayout()
52 | itemContainerView.layoutIfNeeded()
53 |
54 | let animationGroup = DispatchGroup()
55 |
56 | showOverlay(animated: animated, group: animationGroup)
57 | openButton(withConfiguration: currentButtonAnimationConfiguration!,
58 | animated: animated,
59 | group: animationGroup)
60 | openItems(animated: animated, group: animationGroup)
61 |
62 | let groupCompletion: () -> Void = {
63 | guard self.buttonState == .opening else {
64 | return
65 | }
66 | self.buttonState = .open
67 | self.delegate?.floatingActionButtonDidOpen?(self)
68 | completion?()
69 | }
70 | if animated {
71 | animationGroup.notify(queue: .main, execute: groupCompletion)
72 | } else {
73 | groupCompletion()
74 | }
75 | }
76 |
77 | /// Close the floating action button and hide all action items.
78 | ///
79 | /// - Parameter animated: When true, button will be close with an animation. Default is `true`.
80 | /// - Parameter completion: Will be handled upon completion. Default is `nil`.
81 | ///
82 | /// - SeeAlso: `buttonAnimationConfiguration`
83 | /// - SeeAlso: `itemAnimationConfiguration`
84 | ///
85 | func close(animated: Bool = true, completion: (() -> Void)? = nil) {
86 | guard buttonState == .open || buttonState == .opening else {
87 | return
88 | }
89 | buttonState = .closing
90 | delegate?.floatingActionButtonWillClose?(self)
91 | overlayView.isEnabled = false
92 |
93 | let animationGroup = DispatchGroup()
94 |
95 | hideOverlay(animated: animated, group: animationGroup)
96 | closeButton(withConfiguration: currentButtonAnimationConfiguration!,
97 | animated: animated,
98 | group: animationGroup)
99 | closeItems(animated: animated, group: animationGroup)
100 |
101 | let groupCompletion: () -> Void = {
102 | self.openItems.forEach { item in
103 | item.removeFromSuperview()
104 | }
105 | self.resetAnimationState()
106 | self.itemContainerView.removeFromSuperview()
107 | self.buttonState = .closed
108 | self.delegate?.floatingActionButtonDidClose?(self)
109 | completion?()
110 | }
111 | if animated {
112 | animationGroup.notify(queue: .main, execute: groupCompletion)
113 | } else {
114 | groupCompletion()
115 | }
116 | }
117 | }
118 |
119 | internal extension JJFloatingActionButton {
120 | func removeRelatedViewsFromSuperview() {
121 | if overlayView.superview != nil {
122 | overlayView.removeFromSuperview()
123 | }
124 |
125 | if itemContainerView.superview != nil {
126 | itemContainerView.removeFromSuperview()
127 | }
128 | }
129 | }
130 |
131 | // MARK: - Animation State
132 |
133 | fileprivate extension JJFloatingActionButton {
134 | func storeAnimationState() {
135 | openItems = enabledItems
136 | currentItemAnimationConfiguration = itemAnimationConfiguration
137 | currentButtonAnimationConfiguration = buttonAnimationConfiguration
138 | }
139 |
140 | func resetAnimationState() {
141 | openItems.removeAll()
142 | currentButtonAnimationConfiguration = nil
143 | currentItemAnimationConfiguration = nil
144 | }
145 | }
146 |
147 | // MARK: - Overlay Animation
148 |
149 | fileprivate extension JJFloatingActionButton {
150 | func addOverlayView(to superview: UIView) {
151 | overlayView.isEnabled = true
152 | superview.insertSubview(overlayView, belowSubview: self)
153 | overlayView.translatesAutoresizingMaskIntoConstraints = false
154 | overlayView.topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
155 | overlayView.leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
156 | overlayView.trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
157 | overlayView.bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
158 | }
159 |
160 | func showOverlay(animated: Bool, group: DispatchGroup) {
161 | let buttonAnimation: () -> Void = {
162 | self.overlayView.alpha = 1
163 | }
164 | UIView.animate(duration: 0.3,
165 | usingSpringWithDamping: 1,
166 | initialSpringVelocity: 0.3,
167 | animations: buttonAnimation,
168 | group: group,
169 | animated: animated)
170 | }
171 |
172 | func hideOverlay(animated: Bool, group: DispatchGroup) {
173 | let animations: () -> Void = {
174 | self.overlayView.alpha = 0
175 | }
176 | let completion: (Bool) -> Void = { _ in
177 | self.overlayView.removeFromSuperview()
178 | }
179 | UIView.animate(duration: 0.3,
180 | usingSpringWithDamping: 1,
181 | initialSpringVelocity: 0.8,
182 | animations: animations,
183 | completion: completion,
184 | group: group,
185 | animated: animated)
186 | }
187 | }
188 |
189 | // MARK: - Button Animation
190 |
191 | fileprivate extension JJFloatingActionButton {
192 | func openButton(withConfiguration configuration: JJButtonAnimationConfiguration,
193 | animated: Bool,
194 | group: DispatchGroup) {
195 | switch configuration.style {
196 | case .rotation:
197 | rotateButton(toAngle: configuration.angle,
198 | settings: configuration.opening,
199 | group: group,
200 | animated: animated,
201 | status: 0)
202 | case .transition:
203 | transition(toImage: configuration.image,
204 | settings: configuration.opening,
205 | animated: animated,
206 | group: group)
207 | }
208 | }
209 |
210 | func closeButton(withConfiguration configuration: JJButtonAnimationConfiguration,
211 | animated: Bool,
212 | group: DispatchGroup) {
213 | switch configuration.style {
214 | case .rotation:
215 | rotateButton(toAngle: 0,
216 | settings: configuration.closing,
217 | group: group,
218 | animated: animated,
219 | status: 1)
220 | case .transition:
221 | transition(toImage: currentButtonImage,
222 | settings: configuration.closing,
223 | animated: animated,
224 | group: group)
225 | }
226 | }
227 |
228 | // MARK: 버튼 색상 전환을 위한 커스텀
229 | func rotateButton(toAngle angle: CGFloat,
230 | settings: JJAnimationSettings,
231 | group: DispatchGroup,
232 | animated: Bool,
233 | status: Int) {
234 | let spark_red_color: UIColor = UIColor(red: 1, green: 0, blue: 0.24, alpha: 1)
235 |
236 | let animation: () -> Void = {
237 | self.circleView.transform = CGAffineTransform(rotationAngle: angle)
238 | switch status {
239 | case 0:
240 | self.circleView.color = .white
241 | self.imageView.tintColor = spark_red_color
242 | default:
243 | self.circleView.color = spark_red_color
244 | self.imageView.tintColor = .white
245 | }
246 |
247 | }
248 |
249 | UIView.animate(duration: settings.duration,
250 | usingSpringWithDamping: settings.dampingRatio,
251 | initialSpringVelocity: settings.initialVelocity,
252 | animations: animation,
253 | group: group,
254 | animated: animated)
255 | }
256 |
257 | func transition(toImage image: UIImage?,
258 | settings: JJAnimationSettings,
259 | animated: Bool,
260 | group: DispatchGroup) {
261 | let transition: () -> Void = {
262 | self.imageView.image = image
263 | }
264 | UIView.transition(with: imageView,
265 | duration: settings.duration,
266 | animations: transition,
267 | group: group,
268 | animated: animated)
269 | }
270 | }
271 |
272 | // MARK: - Items Animation
273 |
274 | fileprivate extension JJFloatingActionButton {
275 | func addItems(to superview: UIView) {
276 | precondition(currentItemAnimationConfiguration != nil)
277 | let configuration = currentItemAnimationConfiguration!
278 |
279 | superview.insertSubview(itemContainerView, belowSubview: self)
280 |
281 | openItems.forEach { item in
282 | item.alpha = 0
283 | item.transform = .identity
284 | itemContainerView.addSubview(item)
285 |
286 | item.translatesAutoresizingMaskIntoConstraints = false
287 |
288 | item.circleView.heightAnchor.constraint(equalTo: circleView.heightAnchor,
289 | multiplier: itemSizeRatio).isActive = true
290 |
291 | item.topAnchor.constraint(greaterThanOrEqualTo: itemContainerView.topAnchor).isActive = true
292 | item.leadingAnchor.constraint(greaterThanOrEqualTo: itemContainerView.leadingAnchor).isActive = true
293 | item.trailingAnchor.constraint(lessThanOrEqualTo: itemContainerView.trailingAnchor).isActive = true
294 | item.bottomAnchor.constraint(lessThanOrEqualTo: itemContainerView.bottomAnchor).isActive = true
295 | }
296 |
297 | configuration.itemLayout.layout(openItems, self)
298 | }
299 |
300 | func openItems(animated: Bool, group: DispatchGroup) {
301 | precondition(currentItemAnimationConfiguration != nil)
302 | let configuration = currentItemAnimationConfiguration!
303 |
304 | let numberOfItems = openItems.count
305 | var delay: TimeInterval = 0.0
306 | var index = 0
307 | for item in openItems {
308 | configuration.closedState.prepare(item, index, numberOfItems, self)
309 | let animation: () -> Void = {
310 | configuration.openState.prepare(item, index, numberOfItems, self)
311 | }
312 | UIView.animate(duration: configuration.opening.duration,
313 | delay: delay,
314 | usingSpringWithDamping: configuration.opening.dampingRatio,
315 | initialSpringVelocity: configuration.opening.initialVelocity,
316 | animations: animation,
317 | group: group,
318 | animated: animated)
319 |
320 | delay += configuration.opening.interItemDelay
321 | index += 1
322 | }
323 | }
324 |
325 | func closeItems(animated: Bool, group: DispatchGroup) {
326 | precondition(currentItemAnimationConfiguration != nil)
327 | let configuration = currentItemAnimationConfiguration!
328 |
329 | let numberOfItems = openItems.count
330 | var delay: TimeInterval = 0.0
331 | var index = numberOfItems - 1
332 | for item in openItems.reversed() {
333 | let animation: () -> Void = {
334 | configuration.closedState.prepare(item, index, numberOfItems, self)
335 | }
336 | UIView.animate(duration: configuration.closing.duration,
337 | delay: delay,
338 | usingSpringWithDamping: configuration.closing.dampingRatio,
339 | initialSpringVelocity: configuration.closing.initialVelocity,
340 | animations: animation,
341 | group: group,
342 | animated: animated)
343 |
344 | delay += configuration.closing.interItemDelay
345 | index -= 1
346 | }
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcodeproj/project.xcworkspace/xcuserdata/junholee.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH.xcodeproj/project.xcworkspace/xcuserdata/junholee.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcodeproj/xcuserdata/junholee.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Practice-JH.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 6
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 | import KakaoSDKCommon
10 |
11 | @main
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | KakaoSDK.initSDK(appKey: "d462a0525fdd474a8bd51c58fe4081cb")
19 |
20 | return true
21 | }
22 |
23 | // MARK: UISceneSession Lifecycle
24 |
25 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
26 | // Called when a new scene session is being created.
27 | // Use this method to select a configuration to create the new scene with.
28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
29 | }
30 |
31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
32 | // Called when the user discards a scene session.
33 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
34 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
35 | }
36 |
37 |
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/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 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/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 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 293.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 293@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 293@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293@2x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_four.imageset/Group 293@3x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 58.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 58@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 58@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58@2x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_one.imageset/Group 58@3x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 61.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 61@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 61@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61@2x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_three.imageset/Group 61@3x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 293.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Group 293@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Group 293@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293@2x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/cell_two.imageset/Group 293@3x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "chatButton.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "chatButton@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "chatButton@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton@2x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamSparker/Spark-Practice-iOS/35168167b2fb6ea035dbd24b4d1669e60450ecb8/Practice-JH/Practice-JH/Assets.xcassets/chatButton.imageset/chatButton@3x.png
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/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 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
37 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
75 |
82 |
91 |
100 |
107 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleTypeRole
9 | Editor
10 | CFBundleURLSchemes
11 |
12 | kakaod462a0525fdd474a8bd51c58fe4081cb
13 |
14 |
15 |
16 | LSApplicationQueriesSchemes
17 |
18 | kakaolink
19 | kakaokompassauth
20 |
21 | UIApplicationSceneManifest
22 |
23 | UIApplicationSupportsMultipleScenes
24 |
25 | UISceneConfigurations
26 |
27 | UIWindowSceneSessionRoleApplication
28 |
29 |
30 | UISceneConfigurationName
31 | Default Configuration
32 | UISceneDelegateClassName
33 | $(PRODUCT_MODULE_NAME).SceneDelegate
34 | UISceneStoryboardFile
35 | Main
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/LogoutViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogoutViewController.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/06.
6 | //
7 |
8 | import UIKit
9 | import KakaoSDKUser
10 | import JJFloatingActionButton
11 |
12 | class LogoutViewController: UIViewController {
13 | var nickname: String?
14 | var email: String?
15 | var userId: Int64?
16 | var birthday: String?
17 | var urlString = "https://archijude.tistory.com/183"
18 | var profile: URL?
19 |
20 | @IBOutlet weak var nicknameLabel: UILabel!
21 | @IBOutlet weak var emailLabel: UILabel!
22 | @IBOutlet weak var userIdLabel: UILabel!
23 | @IBOutlet weak var birthdayLabel: UILabel!
24 | @IBOutlet weak var profileImageView: UIImageView!
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 |
29 | setText()
30 | }
31 | @IBAction func logoutClicked(_ sender: Any) {
32 | // ✅ 로그아웃 : 로그아웃은 API 요청의 성공 여부와 관계없이 토큰을 삭제 처리한다는 점에 유의합니다.
33 | UserApi.shared.logout {(error) in
34 | if let error = error {
35 | print(error)
36 | }
37 | else {
38 | print("logout() success.")
39 |
40 | // ✅ 로그아웃 시 메인으로 보냄
41 | self.navigationController?.popViewController(animated: true)
42 | }
43 | }
44 | }
45 |
46 | @IBAction func unlinkClicked(_ sender: Any) {
47 | // ✅ 연결 끊기 : 연결이 끊어지면 기존의 토큰은 더 이상 사용할 수 없으므로, 연결 끊기 API 요청 성공 시 로그아웃 처리가 함께 이뤄져 토큰이 삭제됩니다.
48 | UserApi.shared.unlink {(error) in
49 | if let error = error {
50 | print(error)
51 | }
52 | else {
53 | print("unlink() success.")
54 |
55 | // ✅ 연결끊기 시 메인으로 보냄
56 | self.navigationController?.popViewController(animated: true)
57 | }
58 | }
59 | }
60 | }
61 |
62 | extension LogoutViewController {
63 | func setText(){
64 | nicknameLabel.text = nickname
65 | emailLabel.text = email
66 | userIdLabel.text = "\(userId)"
67 | birthdayLabel.text = birthday
68 |
69 | let url = profile!
70 | DispatchQueue.global().async {
71 | let data = try? Data(contentsOf: url)
72 | DispatchQueue.main.async {
73 | self.profileImageView.image = UIImage(data: data!)
74 | }
75 | }
76 |
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 | import KakaoSDKAuth
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
24 | if let url = URLContexts.first?.url {
25 | if (AuthApi.isKakaoTalkLoginUrl(url)) {
26 | _ = AuthController.handleOpenUrl(url: url)
27 | }
28 | }
29 | }
30 |
31 | func sceneDidDisconnect(_ scene: UIScene) {
32 | // Called as the scene is being released by the system.
33 | // This occurs shortly after the scene enters the background, or when its session is discarded.
34 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
35 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
36 | }
37 |
38 | func sceneDidBecomeActive(_ scene: UIScene) {
39 | // Called when the scene has moved from an inactive state to an active state.
40 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
41 | }
42 |
43 | func sceneWillResignActive(_ scene: UIScene) {
44 | // Called when the scene will move from an active state to an inactive state.
45 | // This may occur due to temporary interruptions (ex. an incoming phone call).
46 | }
47 |
48 | func sceneWillEnterForeground(_ scene: UIScene) {
49 | // Called as the scene transitions from the background to the foreground.
50 | // Use this method to undo the changes made on entering the background.
51 | }
52 |
53 | func sceneDidEnterBackground(_ scene: UIScene) {
54 | // Called as the scene transitions from the foreground to the background.
55 | // Use this method to save data, release shared resources, and store enough scene-specific state information
56 | // to restore the scene back to its current state.
57 | }
58 |
59 |
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 | import KakaoSDKUser
10 | import KakaoSDKAuth
11 | import KakaoSDKCommon
12 | import JJFloatingActionButton
13 |
14 | class ViewController: UIViewController {
15 |
16 | @IBOutlet weak var talkLoginButton: UIButton!
17 | @IBOutlet weak var accountLoginButton: UIButton!
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | // 앱 실행시마다 연결 끊기
23 | UserApi.shared.unlink {(error) in
24 | if let error = error {
25 | print(error)
26 | }
27 | else {
28 | print("unlink() success.")
29 | }
30 | }
31 |
32 | setFloatingButton()
33 | }
34 |
35 | override func viewWillAppear(_ animated: Bool) {
36 | super.viewWillAppear(animated)
37 |
38 | // 토큰을 가지고 있는지 검사하여 logoutVC에서 pop되었을 때 상태에 따라 다시 돌아가게 만듬
39 | // if (AuthApi.hasToken()) {
40 | // UserApi.shared.accessTokenInfo { (_, error) in
41 | // if let error = error {
42 | // if let sdkError = error as? SdkError, sdkError.isInvalidTokenError() == true {
43 | // //로그인 필요
44 | // }
45 | // else {
46 | // //기타 에러
47 | // }
48 | // }
49 | // else {
50 | // // 토큰 유효성 체크 성공(필요 시 토큰 갱신됨)
51 | // print("토큰 유효성 체크 성공")
52 | //
53 | // // ✅ 사용자 정보를 가져오고 화면전환을 하는 커스텀 메서드
54 | // self.getUserInfo()
55 | // }
56 | // }
57 | // }
58 | // else {
59 | // //로그인 필요
60 | // }
61 | }
62 |
63 | @IBAction func touchTalkLogin(_ sender: Any) {
64 | loginKakao()
65 | }
66 |
67 | @IBAction func touchAccountLogin(_ sender: Any) {
68 | loginKakaoAccount()
69 | }
70 | @IBAction func touchPresentCarousel(_ sender: Any) {
71 | }
72 |
73 | extension ViewController {
74 | func loginKakao() {
75 | print("loginKakao() called.")
76 |
77 | // ✅ 카카오톡 설치 여부 확인
78 | if (UserApi.isKakaoTalkLoginAvailable()) {
79 | UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in
80 | if let error = error {
81 | print(error)
82 | }
83 | else {
84 | print("loginWithKakaoTalk() success.")
85 |
86 | // ✅ 회원가입 성공 시 oauthToken 저장가능하다
87 | // _ = oauthToken
88 |
89 | // ✅ 사용자정보를 성공적으로 가져오면 화면전환 한다.
90 | self.getUserInfo()
91 | }
92 | }
93 | }
94 | // ✅ 카카오톡 미설치
95 | else {
96 | print("카카오톡 미설치")
97 | }
98 | }
99 |
100 | private func getUserInfo() {
101 |
102 | // ✅ 사용자 정보 가져오기
103 | UserApi.shared.me() {(user, error) in
104 | if let error = error {
105 | print(error)
106 | }
107 | else {
108 | print("me() success.")
109 |
110 | // ✅ 닉네임, 이메일 정보
111 | let nickname = user?.kakaoAccount?.profile?.nickname
112 | let email = user?.kakaoAccount?.email
113 | let userId = user?.id
114 | let birthday = user?.kakaoAccount?.birthday
115 | let profile = user?.kakaoAccount?.profile?.profileImageUrl
116 |
117 | guard let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "LogoutViewController") as? LogoutViewController else { return }
118 |
119 | // ✅ 사용자 정보 넘기기
120 | nextVC.nickname = nickname
121 | nextVC.email = email
122 | nextVC.userId = userId
123 | nextVC.birthday = birthday
124 | nextVC.profile = profile!
125 |
126 | // ✅ 화면전환
127 | self.navigationController?.pushViewController(nextVC, animated: true)
128 | }
129 | }
130 | }
131 |
132 | func loginKakaoAccount() {
133 | print("loginKakaoAccount() called.")
134 |
135 | // ✅ 기본 웹 브라우저를 사용하여 로그인 진행.
136 | UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in
137 | if let error = error {
138 | print(error)
139 | }
140 | else {
141 | print("loginWithKakaoAccount() success.")
142 |
143 | // ✅ 회원가입 성공 시 oauthToken 저장
144 | // _ = oauthToken
145 |
146 | // ✅ 사용자정보를 성공적으로 가져오면 화면전환 한다.
147 | self.getUserInfo()
148 | }
149 | }
150 | }
151 |
152 | func setFloatingButton(){
153 | let actionButton = JJFloatingActionButton()
154 | let spark_red_color: UIColor = UIColor(red: 1, green: 0, blue: 0.24, alpha: 1)
155 |
156 | actionButton.addItem(title: "방 만들기", image: UIImage(named: "floating_room_icon")?.withRenderingMode(.alwaysTemplate)) { item in
157 | // 클릭 action 부분
158 | }
159 |
160 | actionButton.addItem(title: "코드로 참여", image: UIImage(named: "floating_code_icon")?.withRenderingMode(.alwaysTemplate)) { item in
161 | // 클릭 action 부분
162 | }
163 |
164 | actionButton.configureDefaultItem { item in
165 | item.buttonColor = spark_red_color
166 | item.buttonImageColor = .white
167 | }
168 |
169 | self.view.addSubview(actionButton)
170 | actionButton.buttonColor = spark_red_color
171 | actionButton.translatesAutoresizingMaskIntoConstraints = false
172 | actionButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16).isActive = true
173 | actionButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16).isActive = true
174 | }
175 | }
176 |
177 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/Carousel.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselCVC/firstCVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // firstCVC.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/12.
6 | //
7 |
8 | import UIKit
9 |
10 | class firstCVC: UICollectionViewCell {
11 | let customView: UIImageView = {
12 | let view = UIImageView()
13 | view.translatesAutoresizingMaskIntoConstraints = false
14 | view.layer.cornerRadius = 2
15 | view.clipsToBounds = true
16 |
17 | return view
18 | }()
19 |
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | self.addSubview(self.customView)
24 |
25 | self.customView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
26 | self.customView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
27 | self.customView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1).isActive = true
28 | self.customView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true
29 | }
30 |
31 | required init?(coder aDecoder: NSCoder) {
32 | fatalError("init(coder:) has not been implemented")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselCVC/secondCVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // secondCVC.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/12.
6 | //
7 |
8 | import UIKit
9 |
10 | class secondCVC: UICollectionViewCell {
11 | let customView: UIImageView = {
12 | let view = UIImageView()
13 | view.translatesAutoresizingMaskIntoConstraints = false
14 | view.layer.cornerRadius = 2
15 | view.clipsToBounds = true
16 |
17 | return view
18 | }()
19 |
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | self.addSubview(self.customView)
24 |
25 | self.customView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
26 | self.customView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
27 | self.customView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1).isActive = true
28 | self.customView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true
29 | }
30 |
31 | required init?(coder aDecoder: NSCoder) {
32 | fatalError("init(coder:) has not been implemented")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselCVC/thirdCVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // thirdCVC.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/12.
6 | //
7 |
8 | import UIKit
9 |
10 | class thirdCVC: UICollectionViewCell {
11 | let customView: UIImageView = {
12 | let view = UIImageView()
13 | view.translatesAutoresizingMaskIntoConstraints = false
14 | view.layer.cornerRadius = 2
15 | view.clipsToBounds = true
16 |
17 | return view
18 | }()
19 |
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | self.addSubview(self.customView)
24 |
25 | self.customView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
26 | self.customView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
27 | self.customView.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1).isActive = true
28 | self.customView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1).isActive = true
29 | }
30 |
31 | required init?(coder aDecoder: NSCoder) {
32 | fatalError("init(coder:) has not been implemented")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CarouselContainerView.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/11.
6 | //
7 |
8 | import UIKit
9 | import SnapKit
10 |
11 | class CarouselContainerView: UIView {
12 |
13 | var collectionView: UICollectionView = {
14 | let layout = UICollectionViewFlowLayout()
15 | layout.minimumLineSpacing = 10
16 |
17 | layout.scrollDirection = .horizontal
18 | layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
19 |
20 | let cv = UICollectionView(frame: CGRect(x: 0,y: 0,width: 0,height: 0), collectionViewLayout: layout)
21 |
22 | var cvIdentifier = "CVC"
23 |
24 | return cv
25 | }()
26 |
27 | public var identifier = "View"
28 |
29 | override init(frame: CGRect) {
30 | super.init(frame: .zero)
31 | }
32 |
33 | init(putIdentifier: String) {
34 | super.init(frame: .zero)
35 | self.backgroundColor = .red
36 | self.layer.name = "firstView"
37 |
38 | addSubview(collectionView)
39 | // collectionView.iden
40 |
41 | self.collectionView.snp.makeConstraints { make in
42 | make.edges.equalTo(self).inset(10)
43 | }
44 | identifier = putIdentifier
45 | }
46 |
47 | required init?(coder: NSCoder) {
48 | fatalError("init(coder:) has not been implemented")
49 | }
50 | //
51 | // override init(frame: CGRect) {
52 | // super.init(frame: frame)
53 | // self.backgroundColor = .red
54 | //
55 | // addSubview(collectionView)
56 | //
57 | // self.collectionView.snp.makeConstraints { make in
58 | // make.width.height.equalTo(self)
59 | // }
60 | // }
61 | //
62 | // required init?(coder aDecoder: NSCoder) {
63 | // fatalError("init(coder:) has not been implemented")
64 | // }
65 | }
66 |
67 | class MyButton: UIButton {
68 | public var statusCV: Int?
69 | }
70 |
71 | //extension UIView {
72 | // let cellCase :carouselCase = [.firstView, .secondView, .thirdView]
73 | //}
74 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CarouselLayout.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | class CarouselLayout: UICollectionViewFlowLayout {
11 |
12 | public var sideItemScale: CGFloat = 0.5
13 | public var sideItemAlpha: CGFloat = 0.5
14 | public var spacing: CGFloat = 10
15 |
16 | // animaition 전환에 사용하는 변수
17 | public var animationStatus: Bool = true
18 |
19 | public var isPagingEnabled: Bool = false
20 |
21 | private var isSetup: Bool = false
22 |
23 | // MARK: prepare는 사용자가 스크롤 시 매번 호출된다고 한다. setupLayout()은 초기에 한 번만 호출되도록 한 것이다.
24 | override public func prepare() {
25 | super.prepare()
26 | if isSetup == false {
27 | setupLayout()
28 | isSetup = true
29 | }
30 | }
31 |
32 | private func setupLayout() {
33 | guard let collectionView = self.collectionView else {return}
34 |
35 | let collectionViewSize = collectionView.bounds.size
36 |
37 | let xInset = (collectionViewSize.width - self.itemSize.width) / 2
38 | let yInset = (collectionViewSize.height - self.itemSize.height) / 2
39 |
40 | self.sectionInset = UIEdgeInsets(top: yInset, left: xInset, bottom: yInset, right: xInset)
41 |
42 | let itemWidth = self.itemSize.width
43 |
44 | let scaledItemOffset = (itemWidth - itemWidth*self.sideItemScale) / 2
45 | self.minimumLineSpacing = spacing - scaledItemOffset
46 |
47 | self.scrollDirection = .horizontal
48 | }
49 |
50 | public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
51 | return true
52 | }
53 |
54 | /// 모든 셀과 뷰에 대한 속성을 UICollectionViewLayoutAttributes의 배열로 반환해준다고 한다.
55 | /// 이 속성들을 사용하기 위해 map 함수를 통해 리턴해주었다.
56 | public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
57 | guard let superAttributes = super.layoutAttributesForElements(in: rect),
58 | let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes]
59 | else { return nil }
60 |
61 | /// transformLayoutAttributes() 함수를 이용해 기존 attributes 속성들을 원하는 대로 변환하고
62 | /// 이를 attributes에 다시 매핑하여 UICollectionViewLayoutAttributes로 반환한다
63 | return attributes.map({ self.transformLayoutAttributes(attributes: $0) })
64 | }
65 |
66 | private func transformLayoutAttributes(attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
67 |
68 | guard let collectionView = self.collectionView else {return attributes}
69 |
70 | // 순서대로 컬렉션뷰 크기의 절반, 컬렉션뷰의 컨텐트의 x좌표:예를 들어 한 셀이 지나면 셀의 크기만큼 더해진다, 현재 컨텐트의 오프셋과 각 셀의 중심 좌표의 차이다 : 이는 컬렉션뷰의 오리진 x좌표와 셀의 중심 사이의 거리라고 볼 수 있다.
71 | // 즉, center는 컬렉션뷰 좌표계를 기준으로 각 셀의 중심의 x표이다.
72 | let collectionCenter = collectionView.frame.size.width / 2
73 | let contentOffset = collectionView.contentOffset.x
74 | let center = attributes.center.x - contentOffset
75 |
76 | // 멀리 있는 경우, ratio가 0이 되며 sideItem으로 판별하여 alpha와 scale이 된다.
77 | // maxDistance보다 가까운 경우, ratio가 1에 가까워지면서 alpha와 scale 값이 1로 온전해진다.
78 | // distance의 경우, 컬렉션뷰 중심과 각 셀의 중심 사이의 거리이다. 컬렉션뷰 중심으로 셀이 가까이 올수록 거리가 줄어든다. maxdistance를 넣은 이유는 ratio가 음수가 되는 것을 방지하기 위해서이다.
79 | let maxDistance = self.itemSize.width + self.minimumLineSpacing
80 | let distance = min(abs(collectionCenter - center), maxDistance)
81 |
82 | let ratio = (maxDistance - distance)/maxDistance
83 |
84 | // 아래에 있는 1을 바꿔서 중심으로 올수록 흐려지거나, 작게 만들 수 있다.
85 | let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha
86 | let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale
87 |
88 | attributes.alpha = alpha
89 |
90 | let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
91 | let dist = attributes.frame.midX - visibleRect.midX
92 | var transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
93 |
94 | // fakescale은 중심에서 -1, 멀어졌을 때 1 / realscale은 중심에서 0, 멀어졌을 때 -1
95 | // CA로테이션 함수에서 1이 한 바퀴 회전이다. 360도임.
96 | // CATransform은 한 번에 하나씩만 사용 가능하다.
97 | switch animationStatus{
98 | // 회전시키는 경우
99 | case true:
100 | let fakescale = (492/520-scale)*(520/28)
101 | let realscale = (-1-fakescale)*0.5
102 | transform = CATransform3DMakeRotation(realscale*8, 0, 1, 0)
103 | // 크기를 조절하는 경우
104 | default:
105 | transform = CATransform3DTranslate(transform, 0, 0, -abs(dist/1000))
106 | }
107 | attributes.transform3D = transform
108 |
109 | return attributes
110 | }
111 |
112 | // MARK: 페이징 가능하게 해주는 코드
113 | // 이 함수는 현재 스크롤의 방향과 속력을 고려하여 스크롤을 멈출 ContentOffset을 지정해준다. 따라서, 멈추길 원하는 목적하는 CGPoint를 함수 내에서 반환해주면 된다.
114 | // 우리는 각 셀의 center와, 컬렉션뷰의 center를 일치시키고자 한다.
115 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
116 |
117 | // 여기서 collectionView = self.collectionVIew의 의미를 모르겠어요. 컬렉션뷰는 항상 있는건데 왜 else로 빠지는걸까요?
118 | guard let collectionView = self.collectionView else {
119 | let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
120 | print(latestOffset)
121 | return latestOffset
122 | }
123 |
124 | // print(targetRect)를 통해 targertRect가 하나만 생성된다는 것을 확인했다. 컨텐츠가 멈추기 원하는 목표지점에 rect를 생성해주는 코드이다. 우리는, 이 rect에 포함되는 셀들 중에 컬렉션뷰의 중심에 가장 가까이 있는 셀의 중심에 멈춰야 한다.
125 | let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
126 |
127 | // layoutAttrbutesForeElemets는 parameter로 취하는 targetRect의 범위 안에 있는 셀들의 UICollectionViewLayoutAttributes를 반환해준다. 따라서, Rect의 위치에 따라 2개 셀의 레이아웃 애트리뷰트들이 반환되거나, 3개 셀의 레이아웃 애트리뷰트들이 반환된다.
128 | // targetRect의 width를 2000으로 늘린다면? -> 2000의 범위 안에 있는 셀들의 속성이 반환된다.
129 | guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero }
130 |
131 | // finite number란, 무한보다 작은 셀 수 있는 숫자이다. 여기에 greatest가 붙어서, 가장 큰 양의 숫자를 반환한다. 시스템 상에서 가장 큰 소수의 값이다. _MAX를 생각하면 된다.
132 | var offsetAdjustment = CGFloat.greatestFiniteMagnitude
133 |
134 | // horizontalCenter는 컨텐츠오프셋의 x좌표에 컬렉션뷰의 절반을 더해서 새로운 목표지점이 될 x좌표를 만들어준다.
135 | let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2
136 |
137 | // 여러 개 셀의 레이아웃 속성들을 비교하여,
138 | // magnitude는 소수의 절대값을 말한다. 즉, (itemHorizontalCenter - horizontalCenter).magnitude는 변위를 거리로 바꿔준다.
139 | // if 문의 역할은, 두 개의 셀 중에서 컬렉션뷰 중심까지의 거리가 더 가까운 셀의 거리를 offsetAdjustment에 저장해주는 것이다. if문 안의 대입값에는 magnitude가 없는데, 이는 -값과 +값(변위)을 모두 사용해야 하기 때문이다.
140 | for layoutAttributes in rectAttributes {
141 | let itemHorizontalCenter = layoutAttributes.center.x
142 | if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude {
143 | offsetAdjustment = itemHorizontalCenter - horizontalCenter
144 | }
145 | }
146 |
147 | // 이렇게 원래 스크롤 시 멈추려던 좌표에서, 조정될 변위를 더해줘서 셀의 중심과 컬렉션뷰의 중심을 일치시켜주면 페이징과 같이 기능하게 된다.
148 | return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Practice-JH/Practice-JH/carousel/CarouselVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CarouselVC.swift
3 | // Practice-JH
4 | //
5 | // Created by Junho Lee on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 | import SnapKit
10 |
11 | enum carouselCase{
12 | case firstView
13 | case secondView
14 | case thirdView
15 | }
16 |
17 | class CarouselVC: UIViewController {
18 |
19 | // @IBOutlet weak var carouselCV: UICollectionView!
20 | private var isClicked: Bool = false
21 | private let cellCaseList: [carouselCase] = [.firstView, .secondView, .thirdView]
22 |
23 | let firstViewButton = MyButton()
24 | let secondViewButton = MyButton()
25 | let thirdViewButton = MyButton()
26 | var selectedItem = 3
27 |
28 | let imageNames: [String] = ["cell_one","cell_two","cell_three"
29 | ,"cell_four","cell_one","cell_two",
30 | "cell_three","cell_four","cell_one",
31 | "cell_two","cell_three","cell_four",
32 | "cell_one","cell_two","cell_three",
33 | "cell_four","cell_one","cell_two",
34 | "cell_three","cell_four"]
35 |
36 | let firstView = CarouselContainerView(putIdentifier: "firstView")
37 | let secondView = CarouselContainerView(putIdentifier: "secondView")
38 | let thirdView = CarouselContainerView(putIdentifier: "thirdView")
39 |
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 |
43 | // addCollectionView(isClicked)
44 | setDelegate()
45 | // carouselCV.backgroundColor = .clear
46 | self.view.backgroundColor = .white
47 |
48 | addSubviewss(firstViewButton, secondViewButton, thirdViewButton)
49 |
50 | //버튼 관련
51 | setButtons()
52 | addTargets(firstViewButton, secondViewButton, thirdViewButton)
53 |
54 | //캐러셀 컨테이너 관련
55 | addContainerViews(firstView, secondView, thirdView)
56 | print("안녕하세ㅛㅇ")
57 | print(self.firstView.collectionView.frame.width)
58 | setContainerViews()
59 | setCarousels()
60 | }
61 |
62 | override func viewWillAppear(_ animated: Bool) {
63 | super.viewWillAppear(animated)
64 |
65 | }
66 |
67 | func setCarousels() {
68 | let layout = CarouselLayout()
69 |
70 | let centerItemWidthScale: CGFloat = 327/375
71 | let centerItemHeightScale: CGFloat = 1
72 |
73 | layout.itemSize = CGSize(width: 500*centerItemWidthScale, height: 300*centerItemHeightScale)
74 |
75 |
76 | layout.sideItemScale = 464/520
77 | layout.spacing = 12
78 | layout.sideItemAlpha = 0.4
79 |
80 |
81 | let layout1 = CarouselLayout()
82 |
83 | layout1.itemSize = CGSize(width: 500*centerItemWidthScale, height: 300*centerItemHeightScale)
84 |
85 | layout1.sideItemScale = 464/520
86 | layout1.spacing = 12
87 | layout1.sideItemAlpha = 0.4
88 |
89 | let layout2 = CarouselLayout()
90 |
91 | layout2.itemSize = CGSize(width: 500*centerItemWidthScale, height: 300*centerItemHeightScale)
92 |
93 | layout2.sideItemScale = 464/520
94 | layout2.spacing = 12
95 | layout2.sideItemAlpha = 0.4
96 |
97 | firstView.collectionView.collectionViewLayout = layout
98 | firstView.collectionView.register(thirdCVC.self, forCellWithReuseIdentifier: "thirdCVC")
99 |
100 | secondView.collectionView.collectionViewLayout = layout1
101 |
102 | secondView.collectionView.register(thirdCVC.self, forCellWithReuseIdentifier: "thirdCVC")
103 | //
104 | thirdView.collectionView.collectionViewLayout = layout2
105 |
106 | thirdView.collectionView.register(thirdCVC.self, forCellWithReuseIdentifier: "thirdCVC")
107 |
108 | firstView.collectionView.reloadData()
109 | secondView.collectionView.reloadData()
110 | thirdView.collectionView.reloadData()
111 | }
112 |
113 | func setButtons() {
114 | firstViewButton.statusCV = 0
115 | firstViewButton.backgroundColor = .clear
116 | firstViewButton.setTitle("캐러셀 1", for: .normal)
117 | firstViewButton.setTitleColor(.black, for: .normal)
118 | firstViewButton.setTitleColor(.gray, for: .highlighted)
119 | secondViewButton.statusCV = 1
120 | secondViewButton.backgroundColor = .clear
121 | secondViewButton.setTitle("캐러셀 2", for: .normal)
122 | secondViewButton.setTitleColor(.black, for: .normal)
123 | secondViewButton.setTitleColor(.gray, for: .highlighted)
124 | thirdViewButton.statusCV = 2
125 | thirdViewButton.backgroundColor = .clear
126 | thirdViewButton.setTitle("캐러셀 3", for: .normal)
127 | thirdViewButton.setTitleColor(.black, for: .normal)
128 | thirdViewButton.setTitleColor(.gray, for: .highlighted)
129 |
130 | secondViewButton.snp.makeConstraints { make in
131 | make.top.equalToSuperview().offset(150)
132 | make.centerX.equalToSuperview()
133 | }
134 |
135 | firstViewButton.snp.makeConstraints { make in
136 | make.centerY.equalTo(secondViewButton.snp.centerY)
137 | make.trailing.equalTo(secondViewButton.snp.leading).offset(-40)
138 | }
139 |
140 | thirdViewButton.snp.makeConstraints { make in
141 | make.centerY.equalTo(secondViewButton.snp.centerY)
142 | make.leading.equalTo(secondViewButton.snp.trailing).offset(40)
143 | }
144 | }
145 |
146 | func addContainerViews(_ views: UIView...) {
147 | for view in views {
148 | self.view.addSubview(view)
149 | view.snp.makeConstraints { make in
150 | make.leading.trailing.equalToSuperview()
151 | make.top.equalToSuperview().offset(300)
152 | make.bottom.equalToSuperview().offset(-95)
153 | }
154 | }
155 | }
156 |
157 | func setContainerViews() {
158 | secondView.backgroundColor = .blue
159 | thirdView.backgroundColor = .purple
160 | }
161 |
162 | func addSubviewss(_ views: UIView...) {
163 | for view in views {
164 | self.view.addSubview(view)
165 | }
166 | }
167 |
168 | func addTargets(_ buttons: MyButton...) {
169 | for button in buttons {
170 | button.addTarget(self, action: #selector(changeCollectionView), for: .touchUpInside)
171 | }
172 | }
173 |
174 | func setDelegate() {
175 | self.firstView.collectionView.delegate = self
176 | self.firstView.collectionView.dataSource = self
177 | self.secondView.collectionView.delegate = self
178 | self.secondView.collectionView.dataSource = self
179 | self.thirdView.collectionView.delegate = self
180 | self.thirdView.collectionView.dataSource = self
181 | }
182 |
183 | // func addCollectionView(_ status: Bool){
184 | //
185 | // let layout = CarouselLayout()
186 | //
187 | // // TODO: 컬렉션뷰 자체의 비율을 고정하는 작업 하기
188 | // // 현재 페이지의 크기 비율을 지정, 피그마 참고했습니다.
189 | // let centerItemWidthScale: CGFloat = 327/375
190 | // let centerItemHeightScale: CGFloat = 1
191 | //
192 | // layout.itemSize = CGSize(width: carouselCV.frame.size.width*centerItemWidthScale, height: carouselCV.frame.size.height*centerItemHeightScale)
193 | //
194 | // layout.sideItemScale = 464/520
195 | // layout.spacing = 12
196 | // layout.sideItemAlpha = 0.4
197 | // layout.animationStatus = status
198 | // carouselCV.collectionViewLayout = layout
199 | //
200 | // self.carouselCV?.register(carouselCVC.self, forCellWithReuseIdentifier: "carouselCVC")
201 | //
202 | // self.carouselCV?.reloadData()
203 | // }
204 |
205 | // 버튼을 터치함에 따라서 animationStatus을 변경해준다.
206 | @IBAction func touchChangeAnimation(_ sender: Any) {
207 | if !isClicked {
208 | isClicked = true
209 | // addCollectionView(isClicked)
210 | } else {
211 | isClicked = false
212 | // addCollectionView(isClicked)
213 | }
214 | }
215 |
216 | @objc func changeCollectionView(sender: MyButton) {
217 | let status: Int = (sender.statusCV)!
218 | switch status {
219 | case 0:
220 | firstView.isHidden = false
221 | print(self.firstView.frame.width)
222 | secondView.isHidden = true
223 | thirdView.isHidden = true
224 | selectedItem = 1
225 | print(firstView.layer.name)
226 | // firstView.position.z
227 | case 1:
228 | firstView.isHidden = true
229 | secondView.isHidden = false
230 | thirdView.isHidden = true
231 | selectedItem = 2
232 | secondView.collectionView.reloadData()
233 | firstView.collectionView.reloadData()
234 | thirdView.collectionView.reloadData()
235 | // secondVIew.position.z
236 | default:
237 | firstView.isHidden = true
238 | secondView.isHidden = true
239 | thirdView.isHidden = false
240 | selectedItem = 3
241 | // thirdView.position.z
242 | }
243 | }
244 | }
245 |
246 | extension CarouselVC: UICollectionViewDelegate, UICollectionViewDataSource {
247 | func numberOfSections(in collectionView: UICollectionView) -> Int {
248 | return 1
249 | }
250 |
251 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
252 | return 20
253 | }
254 |
255 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
256 | //
257 | // let selected = collectionView.superview?.layer.name
258 | //// let superView = collectionView.superview.layer.name
259 | //
260 | // if selected == "firstView" {
261 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCVC", for: indexPath) as! thirdCVC
262 |
263 | cell.customView.image = UIImage(named: imageNames[indexPath.row])
264 | return cell
265 | // }
266 | // if selected == "firstView" {
267 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCVC", for: indexPath) as! firstCVC
268 | //
269 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
270 | // return cell
271 | // } else
272 | // {
273 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCVC", for: indexPath) as! firstCVC
274 | //
275 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
276 | // return cell
277 | // }
278 |
279 | // print("2왔당")
280 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCVC", for: indexPath) as! secondCVC
281 | //
282 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
283 | // return cell\
284 | // print("3왔당")
285 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCVC", for: indexPath) as! thirdCVC
286 | //
287 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
288 | // return cell
289 |
290 |
291 | // switch superView {
292 | // case .firstView:
293 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCVC", for: indexPath) as! firstCVC
294 | //
295 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
296 | // return cell
297 | // case .secondView:
298 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCVC", for: indexPath) as! secondCVC
299 | //
300 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
301 | // return cell
302 | // case .thirdView:
303 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCVC", for: indexPath) as! secondCVC
304 | //
305 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
306 | // return cell
307 | // default:
308 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCVC", for: indexPath) as! secondCVC
309 | //
310 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
311 | // return cell
312 | // }
313 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCVC", for: indexPath) as! firstCVC
314 | //
315 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
316 | // return cell
317 |
318 |
319 |
320 | // let cellcase = cellCaseList[indexPath.row]
321 | //
322 | // switch(cellcase){
323 | // case .first:
324 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "carouselCVC", for: indexPath) as! carouselCVC
325 | //
326 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
327 | // return cell
328 | // case .second:
329 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCVC", for: indexPath) as! secondCVC
330 | //
331 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
332 | // return cell
333 | // default:
334 | // let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCVC", for: indexPath) as! secondCVC
335 | //
336 | // cell.customView.image = UIImage(named: imageNames[indexPath.row])
337 | // return cell
338 | // }
339 |
340 | }
341 | }
342 | //
343 | //extension CarouselVC: UICollectionViewDelegateFlowLayout {
344 | // func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
345 | // let centerItemWidthScale: CGFloat = 327/375
346 | // let centerItemHeightScale: CGFloat = 1
347 | //
348 | // let itemSize = CGSize(width: firstView.frame.width*centerItemWidthScale, height: firstView.frame.height*centerItemHeightScale)
349 | //
350 | // return itemSize
351 | // }
352 | //}
353 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // FloatingButton
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/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 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/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 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/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 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/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 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/BaseViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseViewController.swift
3 | // FloatingButton
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | class BaseViewController: UIViewController {
11 |
12 | // MARK: - View Life Cycles
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | setLayout()
17 | setUI()
18 | }
19 |
20 | // MARK: - Override Method
21 | func setLayout() {
22 | // Override Layout
23 | }
24 |
25 | func setUI() {
26 | view.backgroundColor = .black
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // FloatingButton
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/FloatingButton/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // FloatingButton
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | import JJFloatingActionButton
11 | import SnapKit
12 |
13 | class ViewController: BaseViewController {
14 |
15 | // MARK: - Properties
16 | /// 뷰에 올릴 프로퍼티 선언
17 | let titleLabel = UILabel()
18 | let subTitleLabel = UILabel()
19 | let actionButton = JJFloatingActionButton()
20 | let tap = UITapGestureRecognizer()
21 |
22 | // MARK: - View Life Cycles
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | /// BaseViewController를 상속받기 때문에, BaseViewController에서 viewDidLoad()에 넣어둔 함수는 다시 안넣어도 적용O
28 | setButtonUI()
29 | setAddTarget()
30 | }
31 |
32 | // MARK: - Method
33 |
34 | override func setUI() {
35 | super.setUI()
36 |
37 | /// 프로퍼티 특성 설정
38 | titleLabel.text = "Floating Button Test"
39 | titleLabel.textColor = .white
40 |
41 | subTitleLabel.text = "화이팅"
42 | subTitleLabel.textColor = .gray
43 |
44 | /// gesture 지정할 요소 외의 모든 다른 부분의 touch도 받아들이도록 false 설정
45 | tap.cancelsTouchesInView = false
46 | view.addGestureRecognizer(tap)
47 | }
48 |
49 | override func setLayout() {
50 | /// 프로퍼티를 View에 넣어줌 (storyboard에서 viewcontroller에 올리는 과정)
51 | view.addSubview(titleLabel)
52 | view.addSubview(actionButton)
53 | view.addSubview(subTitleLabel)
54 |
55 | /// textLabel의 레이아웃 설정
56 | titleLabel.snp.makeConstraints { make in
57 | /// superView를 기준으로 center에 배치
58 | make.center.equalToSuperview()
59 | }
60 |
61 | subTitleLabel.snp.makeConstraints { make in
62 | /// subTitleLabel의 top을 titleLabel의 bottom으로부터 20만큼 띄우겠다
63 | make.top.equalTo(titleLabel.snp.bottom).offset(20)
64 | make.centerX.equalTo(titleLabel.snp.centerX)
65 | }
66 |
67 | actionButton.snp.makeConstraints { make in
68 | /// actionButton의 trailing과 bottom을 superView의 trailing과 bottom으로부터 각각 30씩 안쪽으로 constraint를 주겠다
69 | /// 두개를 아래처럼 따로 써도 되고, 같은 값이라면 합칠 수도 O
70 |
71 | // make.trailing.equalToSuperview().inset(30)
72 | // make.bottom.equalToSuperview().inset(30)
73 |
74 | make.trailing.bottom.equalToSuperview().inset(30)
75 | }
76 | }
77 |
78 | func setButtonUI() {
79 | /// 버튼 컬러
80 | actionButton.buttonColor = .purple
81 | /// 버튼 이미지
82 | actionButton.buttonImage = UIImage(systemName: "moon")
83 | /// 버튼 이미지 컬러
84 | actionButton.buttonImageColor = .white
85 | /// 버튼 이미지 크기
86 | actionButton.buttonImageSize = CGSize(width: 24, height: 24)
87 | /// 버튼 탭했을 때 순간 컬러
88 | actionButton.highlightedButtonColor = .orange
89 | /// item 버튼 크기
90 | actionButton.itemSizeRatio = CGFloat(1)
91 | /// item 버튼 추가
92 | actionButton.addItem(title: "첫번째", image: UIImage(systemName: "house")) { _ in
93 | print("첫번째 눌림")
94 | }
95 |
96 | actionButton.addItem(title: "두번째", image: UIImage(systemName: "house.fill")) { _ in
97 | print("두번째 눌림")
98 | }
99 |
100 | /// item 버튼 등장하는 방식
101 | actionButton.itemAnimationConfiguration = .popUp() /// default
102 | /// 버튼 shadow 컬러
103 | actionButton.layer.shadowColor = UIColor.white.cgColor
104 | /// 버튼 shadow 방향
105 | actionButton.layer.shadowOffset = CGSize(width: 0, height: 0)
106 | /// 버튼 shadow 투명도
107 | actionButton.layer.shadowOpacity = Float(0.4)
108 | /// 버튼 shadow 퍼지는 정도
109 | actionButton.layer.shadowRadius = CGFloat(10)
110 | }
111 |
112 | func setAddTarget() {
113 | actionButton.addTarget(self, action: #selector(setButtonColor), for: .touchUpInside)
114 | tap.addTarget(self, action: #selector(setButtonColor))
115 | }
116 |
117 | /// actionButton을 클릭했을 때, buttonState.rawValue 값에 따라 버튼 컬러 재설정 시도
118 | @objc
119 | func setButtonColor() {
120 | /// buttonState == 2 -> 플로팅 버튼 안펼쳐진 상태
121 | if actionButton.buttonState.rawValue == 2 {
122 | actionButton.buttonColor = .red
123 | } else {
124 | actionButton.buttonColor = .purple
125 | }
126 | }
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'FloatingButton' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for FloatingButton
9 | pod 'SnapKit', '~> 5.0.0'
10 | pod 'JJFloatingActionButton'
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/Practice-YSB/FloatingButton/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - JJFloatingActionButton (2.5.0)
3 | - SnapKit (5.0.1)
4 |
5 | DEPENDENCIES:
6 | - JJFloatingActionButton
7 | - SnapKit (~> 5.0.0)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - JJFloatingActionButton
12 | - SnapKit
13 |
14 | SPEC CHECKSUMS:
15 | JJFloatingActionButton: fdc3aefb0f28f04d0f988fa25d5fac97028a9ea7
16 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
17 |
18 | PODFILE CHECKSUM: 2664582dfab0b1714414828d663ec2ebd9706ba3
19 |
20 | COCOAPODS: 1.11.2
21 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 2B05709227897C2400D50E6D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B05709127897C2400D50E6D /* AppDelegate.swift */; };
11 | 2B05709427897C2400D50E6D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B05709327897C2400D50E6D /* SceneDelegate.swift */; };
12 | 2B05709627897C2400D50E6D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B05709527897C2400D50E6D /* ViewController.swift */; };
13 | 2B05709927897C2400D50E6D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2B05709727897C2400D50E6D /* Main.storyboard */; };
14 | 2B05709B27897C2500D50E6D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B05709A27897C2500D50E6D /* Assets.xcassets */; };
15 | 2B05709E27897C2500D50E6D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2B05709C27897C2500D50E6D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 2B05708E27897C2400D50E6D /* Photo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Photo.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 2B05709127897C2400D50E6D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
21 | 2B05709327897C2400D50E6D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
22 | 2B05709527897C2400D50E6D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23 | 2B05709827897C2400D50E6D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
24 | 2B05709A27897C2500D50E6D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
25 | 2B05709D27897C2500D50E6D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
26 | 2B05709F27897C2500D50E6D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | 2B05708B27897C2400D50E6D /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | );
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXFrameworksBuildPhase section */
38 |
39 | /* Begin PBXGroup section */
40 | 2B05708527897C2400D50E6D = {
41 | isa = PBXGroup;
42 | children = (
43 | 2B05709027897C2400D50E6D /* Photo */,
44 | 2B05708F27897C2400D50E6D /* Products */,
45 | );
46 | sourceTree = "";
47 | };
48 | 2B05708F27897C2400D50E6D /* Products */ = {
49 | isa = PBXGroup;
50 | children = (
51 | 2B05708E27897C2400D50E6D /* Photo.app */,
52 | );
53 | name = Products;
54 | sourceTree = "";
55 | };
56 | 2B05709027897C2400D50E6D /* Photo */ = {
57 | isa = PBXGroup;
58 | children = (
59 | 2B05709127897C2400D50E6D /* AppDelegate.swift */,
60 | 2B05709327897C2400D50E6D /* SceneDelegate.swift */,
61 | 2B05709527897C2400D50E6D /* ViewController.swift */,
62 | 2B05709727897C2400D50E6D /* Main.storyboard */,
63 | 2B05709A27897C2500D50E6D /* Assets.xcassets */,
64 | 2B05709C27897C2500D50E6D /* LaunchScreen.storyboard */,
65 | 2B05709F27897C2500D50E6D /* Info.plist */,
66 | );
67 | path = Photo;
68 | sourceTree = "";
69 | };
70 | /* End PBXGroup section */
71 |
72 | /* Begin PBXNativeTarget section */
73 | 2B05708D27897C2400D50E6D /* Photo */ = {
74 | isa = PBXNativeTarget;
75 | buildConfigurationList = 2B0570A227897C2500D50E6D /* Build configuration list for PBXNativeTarget "Photo" */;
76 | buildPhases = (
77 | 2B05708A27897C2400D50E6D /* Sources */,
78 | 2B05708B27897C2400D50E6D /* Frameworks */,
79 | 2B05708C27897C2400D50E6D /* Resources */,
80 | );
81 | buildRules = (
82 | );
83 | dependencies = (
84 | );
85 | name = Photo;
86 | productName = Photo;
87 | productReference = 2B05708E27897C2400D50E6D /* Photo.app */;
88 | productType = "com.apple.product-type.application";
89 | };
90 | /* End PBXNativeTarget section */
91 |
92 | /* Begin PBXProject section */
93 | 2B05708627897C2400D50E6D /* Project object */ = {
94 | isa = PBXProject;
95 | attributes = {
96 | BuildIndependentTargetsInParallel = 1;
97 | LastSwiftUpdateCheck = 1300;
98 | LastUpgradeCheck = 1300;
99 | TargetAttributes = {
100 | 2B05708D27897C2400D50E6D = {
101 | CreatedOnToolsVersion = 13.0;
102 | };
103 | };
104 | };
105 | buildConfigurationList = 2B05708927897C2400D50E6D /* Build configuration list for PBXProject "Photo" */;
106 | compatibilityVersion = "Xcode 13.0";
107 | developmentRegion = en;
108 | hasScannedForEncodings = 0;
109 | knownRegions = (
110 | en,
111 | Base,
112 | );
113 | mainGroup = 2B05708527897C2400D50E6D;
114 | productRefGroup = 2B05708F27897C2400D50E6D /* Products */;
115 | projectDirPath = "";
116 | projectRoot = "";
117 | targets = (
118 | 2B05708D27897C2400D50E6D /* Photo */,
119 | );
120 | };
121 | /* End PBXProject section */
122 |
123 | /* Begin PBXResourcesBuildPhase section */
124 | 2B05708C27897C2400D50E6D /* Resources */ = {
125 | isa = PBXResourcesBuildPhase;
126 | buildActionMask = 2147483647;
127 | files = (
128 | 2B05709E27897C2500D50E6D /* LaunchScreen.storyboard in Resources */,
129 | 2B05709B27897C2500D50E6D /* Assets.xcassets in Resources */,
130 | 2B05709927897C2400D50E6D /* Main.storyboard in Resources */,
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXResourcesBuildPhase section */
135 |
136 | /* Begin PBXSourcesBuildPhase section */
137 | 2B05708A27897C2400D50E6D /* Sources */ = {
138 | isa = PBXSourcesBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | 2B05709627897C2400D50E6D /* ViewController.swift in Sources */,
142 | 2B05709227897C2400D50E6D /* AppDelegate.swift in Sources */,
143 | 2B05709427897C2400D50E6D /* SceneDelegate.swift in Sources */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | /* End PBXSourcesBuildPhase section */
148 |
149 | /* Begin PBXVariantGroup section */
150 | 2B05709727897C2400D50E6D /* Main.storyboard */ = {
151 | isa = PBXVariantGroup;
152 | children = (
153 | 2B05709827897C2400D50E6D /* Base */,
154 | );
155 | name = Main.storyboard;
156 | sourceTree = "";
157 | };
158 | 2B05709C27897C2500D50E6D /* LaunchScreen.storyboard */ = {
159 | isa = PBXVariantGroup;
160 | children = (
161 | 2B05709D27897C2500D50E6D /* Base */,
162 | );
163 | name = LaunchScreen.storyboard;
164 | sourceTree = "";
165 | };
166 | /* End PBXVariantGroup section */
167 |
168 | /* Begin XCBuildConfiguration section */
169 | 2B0570A027897C2500D50E6D /* Debug */ = {
170 | isa = XCBuildConfiguration;
171 | buildSettings = {
172 | ALWAYS_SEARCH_USER_PATHS = NO;
173 | CLANG_ANALYZER_NONNULL = YES;
174 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
176 | CLANG_CXX_LIBRARY = "libc++";
177 | CLANG_ENABLE_MODULES = YES;
178 | CLANG_ENABLE_OBJC_ARC = YES;
179 | CLANG_ENABLE_OBJC_WEAK = YES;
180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
181 | CLANG_WARN_BOOL_CONVERSION = YES;
182 | CLANG_WARN_COMMA = YES;
183 | CLANG_WARN_CONSTANT_CONVERSION = YES;
184 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
187 | CLANG_WARN_EMPTY_BODY = YES;
188 | CLANG_WARN_ENUM_CONVERSION = YES;
189 | CLANG_WARN_INFINITE_RECURSION = YES;
190 | CLANG_WARN_INT_CONVERSION = YES;
191 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
192 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
193 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
195 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
196 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
197 | CLANG_WARN_STRICT_PROTOTYPES = YES;
198 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
199 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
200 | CLANG_WARN_UNREACHABLE_CODE = YES;
201 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
202 | COPY_PHASE_STRIP = NO;
203 | DEBUG_INFORMATION_FORMAT = dwarf;
204 | ENABLE_STRICT_OBJC_MSGSEND = YES;
205 | ENABLE_TESTABILITY = YES;
206 | GCC_C_LANGUAGE_STANDARD = gnu11;
207 | GCC_DYNAMIC_NO_PIC = NO;
208 | GCC_NO_COMMON_BLOCKS = YES;
209 | GCC_OPTIMIZATION_LEVEL = 0;
210 | GCC_PREPROCESSOR_DEFINITIONS = (
211 | "DEBUG=1",
212 | "$(inherited)",
213 | );
214 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
215 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
216 | GCC_WARN_UNDECLARED_SELECTOR = YES;
217 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
218 | GCC_WARN_UNUSED_FUNCTION = YES;
219 | GCC_WARN_UNUSED_VARIABLE = YES;
220 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
221 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
222 | MTL_FAST_MATH = YES;
223 | ONLY_ACTIVE_ARCH = YES;
224 | SDKROOT = iphoneos;
225 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
226 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
227 | };
228 | name = Debug;
229 | };
230 | 2B0570A127897C2500D50E6D /* Release */ = {
231 | isa = XCBuildConfiguration;
232 | buildSettings = {
233 | ALWAYS_SEARCH_USER_PATHS = NO;
234 | CLANG_ANALYZER_NONNULL = YES;
235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
237 | CLANG_CXX_LIBRARY = "libc++";
238 | CLANG_ENABLE_MODULES = YES;
239 | CLANG_ENABLE_OBJC_ARC = YES;
240 | CLANG_ENABLE_OBJC_WEAK = YES;
241 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
242 | CLANG_WARN_BOOL_CONVERSION = YES;
243 | CLANG_WARN_COMMA = YES;
244 | CLANG_WARN_CONSTANT_CONVERSION = YES;
245 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
246 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
247 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
248 | CLANG_WARN_EMPTY_BODY = YES;
249 | CLANG_WARN_ENUM_CONVERSION = YES;
250 | CLANG_WARN_INFINITE_RECURSION = YES;
251 | CLANG_WARN_INT_CONVERSION = YES;
252 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
253 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
254 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
256 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
261 | CLANG_WARN_UNREACHABLE_CODE = YES;
262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu11;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | MTL_FAST_MATH = YES;
278 | SDKROOT = iphoneos;
279 | SWIFT_COMPILATION_MODE = wholemodule;
280 | SWIFT_OPTIMIZATION_LEVEL = "-O";
281 | VALIDATE_PRODUCT = YES;
282 | };
283 | name = Release;
284 | };
285 | 2B0570A327897C2500D50E6D /* Debug */ = {
286 | isa = XCBuildConfiguration;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
290 | CODE_SIGN_STYLE = Automatic;
291 | CURRENT_PROJECT_VERSION = 1;
292 | DEVELOPMENT_TEAM = 4QG3GC35LA;
293 | GENERATE_INFOPLIST_FILE = YES;
294 | INFOPLIST_FILE = Photo/Info.plist;
295 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
296 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
297 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
298 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
299 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
300 | LD_RUNPATH_SEARCH_PATHS = (
301 | "$(inherited)",
302 | "@executable_path/Frameworks",
303 | );
304 | MARKETING_VERSION = 1.0;
305 | PRODUCT_BUNDLE_IDENTIFIER = subin.Photo;
306 | PRODUCT_NAME = "$(TARGET_NAME)";
307 | SWIFT_EMIT_LOC_STRINGS = YES;
308 | SWIFT_VERSION = 5.0;
309 | TARGETED_DEVICE_FAMILY = "1,2";
310 | };
311 | name = Debug;
312 | };
313 | 2B0570A427897C2500D50E6D /* Release */ = {
314 | isa = XCBuildConfiguration;
315 | buildSettings = {
316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
317 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
318 | CODE_SIGN_STYLE = Automatic;
319 | CURRENT_PROJECT_VERSION = 1;
320 | DEVELOPMENT_TEAM = 4QG3GC35LA;
321 | GENERATE_INFOPLIST_FILE = YES;
322 | INFOPLIST_FILE = Photo/Info.plist;
323 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
324 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
325 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
326 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
327 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
328 | LD_RUNPATH_SEARCH_PATHS = (
329 | "$(inherited)",
330 | "@executable_path/Frameworks",
331 | );
332 | MARKETING_VERSION = 1.0;
333 | PRODUCT_BUNDLE_IDENTIFIER = subin.Photo;
334 | PRODUCT_NAME = "$(TARGET_NAME)";
335 | SWIFT_EMIT_LOC_STRINGS = YES;
336 | SWIFT_VERSION = 5.0;
337 | TARGETED_DEVICE_FAMILY = "1,2";
338 | };
339 | name = Release;
340 | };
341 | /* End XCBuildConfiguration section */
342 |
343 | /* Begin XCConfigurationList section */
344 | 2B05708927897C2400D50E6D /* Build configuration list for PBXProject "Photo" */ = {
345 | isa = XCConfigurationList;
346 | buildConfigurations = (
347 | 2B0570A027897C2500D50E6D /* Debug */,
348 | 2B0570A127897C2500D50E6D /* Release */,
349 | );
350 | defaultConfigurationIsVisible = 0;
351 | defaultConfigurationName = Release;
352 | };
353 | 2B0570A227897C2500D50E6D /* Build configuration list for PBXNativeTarget "Photo" */ = {
354 | isa = XCConfigurationList;
355 | buildConfigurations = (
356 | 2B0570A327897C2500D50E6D /* Debug */,
357 | 2B0570A427897C2500D50E6D /* Release */,
358 | );
359 | defaultConfigurationIsVisible = 0;
360 | defaultConfigurationName = Release;
361 | };
362 | /* End XCConfigurationList section */
363 | };
364 | rootObject = 2B05708627897C2400D50E6D /* Project object */;
365 | }
366 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Photo
4 | //
5 | // Created by 양수빈 on 2022/01/08.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/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 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSCameraUsageDescription
6 | 카메라를 열어도 될까요?
7 | NSPhotoLibraryUsageDescription
8 | 앨범을 열어봐도 될까요?
9 | UIApplicationSceneManifest
10 |
11 | UIApplicationSupportsMultipleScenes
12 |
13 | UISceneConfigurations
14 |
15 | UIWindowSceneSessionRoleApplication
16 |
17 |
18 | UISceneConfigurationName
19 | Default Configuration
20 | UISceneDelegateClassName
21 | $(PRODUCT_MODULE_NAME).SceneDelegate
22 | UISceneStoryboardFile
23 | Main
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Photo
4 | //
5 | // Created by 양수빈 on 2022/01/08.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Practice-YSB/Photo/Photo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Photo
4 | //
5 | // Created by 양수빈 on 2022/01/08.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 |
12 | // MARK: - @IBOutlet
13 |
14 | @IBOutlet weak var imageView: UIImageView!
15 |
16 | // MARK: - Properties
17 |
18 | let picker = UIImagePickerController()
19 |
20 | // MARK: - View Life Cycles
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | picker.delegate = self
26 | }
27 |
28 | // MARK: - @IBAction
29 |
30 | @IBAction func getPhotoImage(_ sender: Any) {
31 | /// alterController 생성
32 | let alter = UIAlertController(title: "사진가져오기", message: "진짜로 사진을 가져오시겠어요?", preferredStyle: .actionSheet)
33 |
34 | /// alter에 들어갈 액션 생성
35 | let library = UIAlertAction(title: "앨범에서 가져오기", style: .default) { action in
36 | self.openLibrary()
37 | }
38 |
39 | let camera = UIAlertAction(title: "카메라 열기", style: .default) { action in
40 | self.openCamera()
41 | }
42 |
43 | let cancel = UIAlertAction(title: "취소", style: .cancel, handler: nil)
44 |
45 | /// alter에 액션을 넣어줌
46 | alter.addAction(library)
47 | alter.addAction(camera)
48 | alter.addAction(cancel)
49 |
50 | /// button tap했을 때 alter present
51 | present(alter, animated: true, completion: nil)
52 | }
53 |
54 | func openLibrary() {
55 | /// UIImagePickerController에서 어떤 식으로 image를 pick해올지 -> 앨범에서 픽해오겠다
56 | picker.sourceType = .photoLibrary
57 | present(picker, animated: false, completion: nil)
58 | }
59 |
60 | func openCamera() {
61 | /// 카메라 촬영 타입이 가능하다면
62 | if UIImagePickerController.isSourceTypeAvailable(.camera) {
63 | /// UIImagePickerController에서 어떤 식으로 image를 pick해올지 -> 카메라 촬영헤서 픽해오겠다
64 | picker.sourceType = .camera
65 | present(picker, animated: false, completion: nil)
66 | } else {
67 | print("카메라 안됩니다.")
68 | }
69 | }
70 | }
71 |
72 | extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
73 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
74 | // print("------------------")
75 | // print(info)
76 | // print("------------------")
77 | /// UIImage 타입인 originalImage를 빼옴
78 | if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
79 | imageView.image = image
80 | imageView.contentMode = .scaleAspectFill /// 1:1 로 맞춰둔 imageView에 꽉 채우겠다.
81 | }
82 | dismiss(animated: true, completion: nil)
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'StickyHeader' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for StickyHeader
9 | pod 'SnapKit'
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SnapKit (5.0.1)
3 |
4 | DEPENDENCIES:
5 | - SnapKit
6 |
7 | SPEC REPOS:
8 | trunk:
9 | - SnapKit
10 |
11 | SPEC CHECKSUMS:
12 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
13 |
14 | PODFILE CHECKSUM: 984ec16e67572a32e8bb28ae5cb764e733d0a406
15 |
16 | COCOAPODS: 1.11.2
17 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // StickyHeader
4 | //
5 | // Created by 양수빈 on 2022/01/10.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/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 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/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 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/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 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/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 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/CustomCVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomCVC.swift
3 | // StickyHeader
4 | //
5 | // Created by 양수빈 on 2022/01/10.
6 | //
7 |
8 | import UIKit
9 |
10 | import SnapKit
11 |
12 | class CustomCVC: UICollectionViewCell {
13 | static let identifier = "CustomCVC"
14 |
15 | // MARK: - Properties
16 | let titleLabel = UILabel()
17 | let userNameLabel = UILabel()
18 |
19 | // MARK: - View Life Cycles
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 |
23 | setUI()
24 | setLayout()
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | override func prepareForReuse() {
32 | super.prepareForReuse()
33 |
34 | titleLabel.text = "---"
35 | userNameLabel.text = "사용자이름"
36 | }
37 |
38 | // MARK: - Methods
39 | func setUI() {
40 | titleLabel.text = "---"
41 | userNameLabel.text = "사용자이름"
42 |
43 | titleLabel.font = .systemFont(ofSize: 20)
44 | userNameLabel.font = .systemFont(ofSize: 14)
45 | userNameLabel.textColor = .gray
46 | }
47 |
48 | func setLayout() {
49 | addSubview(titleLabel)
50 | addSubview(userNameLabel)
51 |
52 | titleLabel.snp.makeConstraints { make in
53 | make.leading.equalToSuperview().offset(16)
54 | make.top.equalToSuperview().offset(16)
55 | }
56 |
57 | userNameLabel.snp.makeConstraints { make in
58 | make.leading.equalToSuperview().offset(16)
59 | make.top.equalTo(titleLabel.snp.bottom).offset(10)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/CustomHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomHeaderView.swift
3 | // StickyHeader
4 | //
5 | // Created by 양수빈 on 2022/01/10.
6 | //
7 |
8 | import UIKit
9 |
10 | import SnapKit
11 |
12 | class CustomHeaderView: UICollectionReusableView {
13 | static let identifier = "CustomHeaderView"
14 |
15 | // MARK: - Properties
16 | let dateLabel = UILabel()
17 |
18 | // MARK: - View Life Cycles
19 | override init(frame: CGRect) {
20 | super.init(frame: frame)
21 |
22 | setUI()
23 | setLayout()
24 | }
25 |
26 | required init?(coder: NSCoder) {
27 | fatalError("init(coder:) has not been implemented")
28 | }
29 |
30 | override func prepareForReuse() {
31 | super.prepareForReuse()
32 |
33 | dateLabel.text = ""
34 | }
35 |
36 | // MARK: - Methods
37 | func setUI() {
38 | dateLabel.text = "Header"
39 | dateLabel.font = .systemFont(ofSize: 30)
40 | }
41 |
42 | func setLayout() {
43 | addSubview(dateLabel)
44 |
45 | dateLabel.snp.makeConstraints { make in
46 | make.leading.equalToSuperview().offset(16)
47 | make.centerY.equalToSuperview()
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // StickyHeader
4 | //
5 | // Created by 양수빈 on 2022/01/10.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Practice-YSB/StickyHeader/StickyHeader/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // StickyHeader
4 | //
5 | // Created by 양수빈 on 2022/01/10.
6 | //
7 |
8 | import UIKit
9 |
10 | import SnapKit
11 |
12 | class ViewController: UIViewController {
13 |
14 | // MARK: - Dummy Data
15 | var dummyDataList: Array = [ ["date": "2022-11-11", "userName": "양수빈", "sparkTitle": "간식먹기", "sparkCount": "1"],
16 | ["date": "2022-11-11", "userName": "감자", "sparkTitle": "간식먹기222", "sparkCount": "2"],
17 | ["date": "2022-11-11", "userName": "김", "sparkTitle": "아아아간식먹기", "sparkCount": "3"],
18 | ["date": "2022-11-10", "userName": "옹", "sparkTitle": "아아 마셔", "sparkCount": "4"],
19 | ["date": "2022-11-10", "userName": "우와", "sparkTitle": "커피 홀짝", "sparkCount": "5"],
20 | ["date": "2022-11-9", "userName": "우와", "sparkTitle": "홀짝 커피", "sparkCount": "6"],
21 | ["date": "2022-11-9", "userName": "뭐지", "sparkTitle": "하하하하", "sparkCount": "7"],
22 | ["date": "2022-11-9", "userName": "하하", "sparkTitle": "쉽지않네", "sparkCount": "8"],
23 | ["date": "2022-11-9", "userName": "호호", "sparkTitle": "덤이데잍어", "sparkCount": "9"],
24 | ["date": "2022-11-7", "userName": "s", "sparkTitle": "덤이데sdfdfdfdf잍어", "sparkCount": "10"],
25 | ["date": "2022-11-7", "userName": "호sgggg호", "sparkTitle": "덤이sddd데잍어", "sparkCount": "11"] ]
26 |
27 | var newDummyDataList: Array = [ ["date": "2022-11-7", "userName": "양수빈", "sparkTitle": "간식먹기", "sparkCount": "12"],
28 | ["date": "2022-11-7", "userName": "감자", "sparkTitle": "간식먹기222", "sparkCount": "13"],
29 | ["date": "2022-11-4", "userName": "김", "sparkTitle": "아아아간식먹기", "sparkCount": "14"],
30 | ["date": "2022-11-4", "userName": "옹", "sparkTitle": "아아 마셔", "sparkCount": "15"],
31 | ["date": "2022-11-3", "userName": "우와", "sparkTitle": "커피 홀짝", "sparkCount": "16"],
32 | ["date": "2022-11-3", "userName": "우와", "sparkTitle": "홀짝 커피", "sparkCount": "17"],
33 | ["date": "2022-11-3", "userName": "뭐지", "sparkTitle": "하하하하", "sparkCount": "18"],
34 | ["date": "2022-11-3", "userName": "하하", "sparkTitle": "쉽지않네", "sparkCount": "19"],
35 | ["date": "2022-11-3", "userName": "호호", "sparkTitle": "덤이데잍어", "sparkCount": "20"],
36 | ["date": "2022-11-2", "userName": "s", "sparkTitle": "덤이데sdfdfdfdf잍어", "sparkCount": "21"],
37 | ["date": "2022-11-2", "userName": "호sgggg호", "sparkTitle": "덤이sddd데잍어", "sparkCount": "22"] ]
38 |
39 | // MARK: - Properties
40 | let collectionViewFlowlayout = UICollectionViewFlowLayout()
41 | lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowlayout)
42 | var index = 0
43 | var dateList: [String] = []
44 | var firstList: [Any] = []
45 | var secondList: [Any] = []
46 | var thirdList: [Any] = []
47 | var fourthList: [Any] = []
48 | var fifthList: [Any] = []
49 | var sixthList: [Any] = []
50 | var seventhList: [Any] = []
51 |
52 | // MARK: - View Life Cycles
53 | override func viewDidLoad() {
54 | super.viewDidLoad()
55 |
56 | setLayout()
57 | setCollcetionView()
58 | setData(datalist: dummyDataList)
59 | }
60 |
61 | // MARK: - Methods
62 | func setLayout() {
63 | view.addSubview(collectionView)
64 |
65 | collectionView.snp.makeConstraints { make in
66 | make.edges.equalTo(view.safeAreaLayoutGuide)
67 | }
68 | }
69 |
70 | func setData(datalist: Array>) {
71 | var indexPath = 0
72 | var sectionCount = 0 /// section을 돌기 위한 변수
73 |
74 | // print(dummyDataList[indexPath]["date"])
75 | // print(type(of: dummyDataList[indexPath]["date"]))
76 | // print(dummyDataList[indexPath])
77 |
78 | // print(datalist)
79 | // print(type(of: datalist))
80 | // print(dummyDataList)
81 | // print(type(of: dummyDataList))
82 | while indexPath < datalist.count {
83 | if dateList.isEmpty {
84 | dateList.append(datalist[indexPath]["date"] as! String)
85 | indexPath += 1
86 | } else {
87 | let day: String = datalist[indexPath]["date"] ?? ""
88 |
89 | if !(dateList.contains(day)) {
90 | dateList.append(day)
91 | }
92 |
93 | indexPath += 1
94 | }
95 | }
96 |
97 | // TODO: - section별 리스트 생성
98 | while sectionCount < dateList.count {
99 | var index = 0
100 | while index < datalist.count && !datalist.isEmpty && sectionCount != dateList.count {
101 |
102 | if dateList[sectionCount] == datalist[index]["date"] {
103 | switch sectionCount {
104 | case 0:
105 | firstList.append(datalist[index])
106 | case 1:
107 | secondList.append(datalist[index])
108 | case 2:
109 | thirdList.append(datalist[index])
110 | case 3:
111 | fourthList.append(datalist[index])
112 | case 4:
113 | fifthList.append(datalist[index])
114 | case 5:
115 | sixthList.append(datalist[index])
116 | case 6:
117 | seventhList.append(datalist[index])
118 | default:
119 | seventhList.append(datalist[index])
120 | }
121 |
122 | }
123 | index += 1
124 | }
125 | sectionCount += 1
126 | }
127 |
128 | // print("1: ", firstList)
129 | // print("2: ", secondList)
130 | // print("2: ", thirdList)
131 | // print("4: ", fourthList)
132 | // print("5: ", fifthList)
133 | // print("6: ", sixthList)
134 | // print("7: ", seventhList)
135 | // print("-------------------------------------------------------")
136 | }
137 |
138 | func setCollcetionView() {
139 | collectionView.backgroundColor = .lightGray
140 | collectionViewFlowlayout.scrollDirection = .vertical
141 | collectionView.showsVerticalScrollIndicator = false
142 |
143 | /// cell register
144 | collectionView.register(CustomCVC.self, forCellWithReuseIdentifier: CustomCVC.identifier)
145 | collectionView.register(CustomHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CustomHeaderView.identifier)
146 |
147 | /// collectionView를 상단바 부분부터 시작하도록 하는 코드
148 | // collectionView.contentInset.top = -UIApplication.shared.statusBarFrame.height
149 | // collectionView.contentInsetAdjustmentBehavior = .never
150 |
151 | /// delegate, datasource
152 | collectionView.delegate = self
153 | collectionView.dataSource = self
154 |
155 | /// sticky header을 가능하도록 하는 코드
156 | collectionViewFlowlayout.sectionHeadersPinToVisibleBounds = true
157 | }
158 | }
159 |
160 | // MARK: - extension
161 | extension ViewController: UICollectionViewDelegate {
162 | func numberOfSections(in collectionView: UICollectionView) -> Int {
163 | return dateList.count
164 | }
165 | }
166 |
167 | extension ViewController: UICollectionViewDataSource {
168 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
169 | var itemCount = 0
170 | var indexPath = 0
171 |
172 | while indexPath < dummyDataList.count {
173 | if dateList[section] == dummyDataList[indexPath]["date"] {
174 | itemCount += 1
175 | indexPath += 1
176 | } else {
177 | indexPath += 1
178 | }
179 | }
180 |
181 | return itemCount
182 | }
183 |
184 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
185 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCVC.identifier, for: indexPath) as? CustomCVC else { return UICollectionViewCell() }
186 |
187 | var alist: [String:String] = [:]
188 |
189 | // TODO: - section별 데이터 넣기
190 | switch indexPath.section {
191 | case 0:
192 | alist = firstList[indexPath.item] as! [String : String]
193 | case 1:
194 | alist = secondList[indexPath.item] as! [String : String]
195 | case 2:
196 | alist = thirdList[indexPath.item] as! [String : String]
197 | case 3:
198 | alist = fourthList[indexPath.item] as! [String : String]
199 | case 4:
200 | alist = fifthList[indexPath.item] as! [String : String]
201 | case 5:
202 | alist = sixthList[indexPath.item] as! [String : String]
203 | case 6:
204 | alist = seventhList[indexPath.item] as! [String : String]
205 | default:
206 | alist = firstList[indexPath.item] as! [String : String]
207 | }
208 |
209 | cell.titleLabel.text = "\(String(describing: alist["sparkCount"]!))"
210 | cell.userNameLabel.text = "\(String(describing: alist["userName"]!))"
211 | cell.backgroundColor = .white
212 |
213 | return cell
214 | }
215 |
216 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
217 | guard let cell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "CustomHeaderView", for: indexPath) as? CustomHeaderView else { return UICollectionReusableView() }
218 | cell.dateLabel.text = "\(dateList[indexPath.section])"
219 | cell.backgroundColor = .systemIndigo
220 |
221 | return cell
222 | }
223 |
224 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
225 | let width = UIScreen.main.bounds.width
226 |
227 | return CGSize(width: width, height: 60)
228 | }
229 |
230 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
231 | if (scrollView.contentOffset.y + 1) >= (scrollView.contentSize.height - scrollView.frame.size.height) {
232 | if index < 1 {
233 | dummyDataList.append(contentsOf: newDummyDataList)
234 | setData(datalist: newDummyDataList)
235 | collectionView.reloadData()
236 | index += 1
237 | } else {
238 | print("끝입니다")
239 | }
240 | }
241 | }
242 | }
243 |
244 | extension ViewController: UICollectionViewDelegateFlowLayout {
245 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
246 |
247 | let width = UIScreen.main.bounds.width
248 |
249 | return CGSize(width: width, height: 120)
250 | }
251 |
252 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
253 |
254 | return 20
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'Stopwatch' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for Stopwatch
9 | pod 'SnapKit'
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SnapKit (5.0.1)
3 |
4 | DEPENDENCIES:
5 | - SnapKit
6 |
7 | SPEC REPOS:
8 | trunk:
9 | - SnapKit
10 |
11 | SPEC CHECKSUMS:
12 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
13 |
14 | PODFILE CHECKSUM: 70ac4033f551479a325066b8fa35bd6a8b59e8e2
15 |
16 | COCOAPODS: 1.11.2
17 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Stopwatch
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/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 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Stopwatch
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/Stopwatch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stopwatch.swift
3 | // Stopwatch
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import Foundation
9 |
10 | class Stopwatch: NSObject {
11 | var counter: Double
12 | var timer: Timer
13 |
14 | override init() {
15 | counter = 0.0
16 | timer = Timer()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Practice-YSB/Stopwatch/Stopwatch/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Stopwatch
4 | //
5 | // Created by 양수빈 on 2022/01/07.
6 | //
7 |
8 | import UIKit
9 |
10 | import SnapKit
11 |
12 | class ViewController: UIViewController {
13 |
14 | let stopwatch: Stopwatch = Stopwatch()
15 | var timerLabel = UILabel()
16 | var resetButton = UIButton()
17 | var startButton = UIButton()
18 | var stopTimeLabel = UILabel() /// 멈췄을 때 시간 표시하는 라벨
19 | var isPlay: Bool = false
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 |
24 | setLayout()
25 | setUI()
26 | setAddTarget()
27 | }
28 |
29 | func setLayout() {
30 | view.addSubview(timerLabel)
31 | view.addSubview(resetButton)
32 | view.addSubview(startButton)
33 | view.addSubview(stopTimeLabel)
34 |
35 | timerLabel.snp.makeConstraints { make in
36 | make.centerX.equalToSuperview()
37 | make.top.equalToSuperview().offset(300)
38 | }
39 |
40 | startButton.snp.makeConstraints { make in
41 | make.top.equalTo(timerLabel.snp.bottom).offset(40)
42 | make.leading.equalToSuperview().offset(80)
43 | make.width.height.equalTo(100)
44 | }
45 |
46 | resetButton.snp.makeConstraints { make in
47 | make.top.equalTo(timerLabel.snp.bottom).offset(40)
48 | make.trailing.equalToSuperview().inset(80)
49 | make.width.height.equalTo(100)
50 | }
51 |
52 | stopTimeLabel.snp.makeConstraints { make in
53 | make.centerX.equalToSuperview()
54 | make.top.equalTo(resetButton.snp.bottom).offset(40)
55 | }
56 | }
57 |
58 | func setUI() {
59 | timerLabel.text = "00:00:00"
60 | timerLabel.font = .systemFont(ofSize: 40)
61 |
62 | stopTimeLabel.text = "-"
63 |
64 | startButton.setTitle("start", for: .normal)
65 | resetButton.setTitle("reset", for: .normal)
66 |
67 | startButton.backgroundColor = .blue
68 | resetButton.backgroundColor = .red
69 |
70 | startButton.layer.cornerRadius = 50
71 | resetButton.layer.cornerRadius = 50
72 | }
73 |
74 | func setAddTarget() {
75 | startButton.addTarget(self, action: #selector(startPauseTimer(_:)), for: .touchUpInside)
76 | resetButton.addTarget(self, action: #selector(resetTimer(_:)), for: .touchUpInside)
77 | }
78 |
79 | /// start 버튼을 눌렀을 때 isPlay의 상태에 따라 버튼, 라벨 상태 변경
80 | @objc
81 | func startPauseTimer(_ sender: AnyObject) {
82 | /// 실행중 X, 스톱워치 시작 + 버튼 변경
83 | /// 실행중 O, 스톱워치
84 | if !isPlay {
85 | unowned let weakSelf = self
86 |
87 | /// 0.035초마다 updateMainTimer 함수 호출하는 타이머
88 | stopwatch.timer = Timer.scheduledTimer(timeInterval: 0.035, target: weakSelf, selector: #selector(updateMainTimer), userInfo: nil, repeats: true)
89 |
90 | /// RunLoop에서 timer 객체를 추가해서 관리
91 | RunLoop.current.add(stopwatch.timer, forMode: .common)
92 |
93 | /// 실행X -> 실행O
94 | isPlay = true
95 | changeButton(startButton, title: "Stop", titleColor: .white)
96 | } else {
97 | /// 버튼 눌렀을 때의 timerLabel의 값을 stopTimeLabel에 넣음
98 | stopTimeLabel.text = timerLabel.text
99 |
100 | /// RunLoop 객체로부터 timer를 제거하기 위한 함수 (반복 타이머 중지)
101 | stopwatch.timer.invalidate()
102 |
103 | /// 실행O -> 실행X
104 | isPlay = false
105 | changeButton(startButton, title: "Start", titleColor: .white)
106 | }
107 | }
108 |
109 | @objc
110 | func resetTimer(_ sender: AnyObject) {
111 | /// 실행중 X (스톱워치가 멈춘 상태라면), reset
112 | /// 실행중 O (스톱워치가 돌아가는 상태라면), print
113 | if !isPlay {
114 | resetMainTimer()
115 | } else {
116 | print("먼저 멈추지?")
117 | }
118 | }
119 |
120 | @objc
121 | func updateMainTimer() {
122 | updateTimer(stopwatch, label: timerLabel)
123 | }
124 |
125 | func changeButton(_ button: UIButton, title: String, titleColor: UIColor) {
126 | button.setTitle(title, for: UIControl.State())
127 | button.setTitleColor(titleColor, for: UIControl.State())
128 | }
129 |
130 | func resetMainTimer() {
131 | resetTimer(stopwatch, label: timerLabel)
132 | stopTimeLabel.text = "-"
133 | }
134 |
135 | /// stopwatch를 멈추고, label을 reset하는 함수
136 | func resetTimer(_ stopwatch: Stopwatch, label: UILabel) {
137 | stopwatch.timer.invalidate()
138 | stopwatch.counter = 0.0
139 | label.text = "00:00:00"
140 | }
141 |
142 | /// timer를 증가시키면서 label의 값에 반영시키는 함수
143 | func updateTimer(_ stopwatch: Stopwatch, label: UILabel) {
144 | stopwatch.counter = stopwatch.counter + 0.035
145 |
146 | var minutes: String = "\((Int)(stopwatch.counter / 60))"
147 | if (Int)(stopwatch.counter / 60) < 10 {
148 | minutes = "0\((Int)(stopwatch.counter / 60))"
149 | }
150 |
151 | var seconds: String = String(format: "%.2f", (stopwatch.counter.truncatingRemainder(dividingBy: 60)))
152 | if stopwatch.counter.truncatingRemainder(dividingBy: 60) < 10 {
153 | seconds = "0" + seconds
154 | }
155 |
156 | label.text = minutes + ":" + seconds
157 | }
158 | }
159 |
160 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'socialLogin' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for socialLogin
9 | pod 'SnapKit', '~> 5.0.0'
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SnapKit (5.0.1)
3 |
4 | DEPENDENCIES:
5 | - SnapKit (~> 5.0.0)
6 |
7 | SPEC REPOS:
8 | trunk:
9 | - SnapKit
10 |
11 | SPEC CHECKSUMS:
12 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
13 |
14 | PODFILE CHECKSUM: fb4ca78265042e20c9a4bf8dfa049e55b9cb7f34
15 |
16 | COCOAPODS: 1.11.2
17 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // socialLogin
4 | //
5 | // Created by 양수빈 on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 | import AuthenticationServices
10 |
11 | @main
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | let appleIDProvider = ASAuthorizationAppleIDProvider()
19 | appleIDProvider.getCredentialState(forUserID: UserDefaults.standard.string(forKey: "setUserIdentifier") ?? "") { credentialState, error in
20 | switch credentialState {
21 | case .authorized:
22 | // Apple ID Credential is valid
23 | // 로그인된 상태 -> 홈뷰
24 | print("해당 ID는 연동되어 있습니다.")
25 | case .revoked:
26 | // Apple ID Credential revoked, handle unlink
27 | // 로그아웃된 상태 -> 로그인뷰
28 | print("해당 ID는 연동되어 있지 않습니다.")
29 | case .notFound:
30 | // Credential not found, show login UI
31 | // 잘못된 userIdentifier -> 로그인뷰
32 | print("해당 ID를 찾을 수 없습니다.")
33 | default:
34 | break
35 | }
36 | }
37 |
38 | return true
39 | }
40 |
41 | // MARK: UISceneSession Lifecycle
42 |
43 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
44 | // Called when a new scene session is being created.
45 | // Use this method to select a configuration to create the new scene with.
46 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
47 | }
48 |
49 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
50 | // Called when the user discards a scene session.
51 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
52 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
53 | }
54 |
55 |
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/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 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/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 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/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 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/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 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // socialLogin
4 | //
5 | // Created by 양수빈 on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // socialLogin
4 | //
5 | // Created by 양수빈 on 2022/01/05.
6 | //
7 |
8 | import UIKit
9 |
10 | import AuthenticationServices
11 | import SnapKit
12 |
13 | class ViewController: UIViewController {
14 |
15 | let appleLoginButton = ASAuthorizationAppleIDButton(type: .continue, style: .black)
16 | var nameLabel = UILabel()
17 | var emailLabel = UILabel()
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | setLayout()
23 | setUI()
24 | }
25 |
26 | func setLayout() {
27 | view.addSubview(appleLoginButton)
28 | view.addSubview(nameLabel)
29 | view.addSubview(emailLabel)
30 |
31 | appleLoginButton.snp.makeConstraints { make in
32 | make.center.equalToSuperview()
33 | }
34 |
35 | nameLabel.snp.makeConstraints { make in
36 | make.centerX.equalToSuperview()
37 | make.top.equalTo(appleLoginButton.snp.bottom).offset(30)
38 | }
39 |
40 | emailLabel.snp.makeConstraints { make in
41 | make.centerX.equalToSuperview()
42 | make.top.equalTo(nameLabel.snp.bottom).offset(20)
43 | }
44 | }
45 |
46 | func setUI() {
47 | nameLabel.text = "name"
48 | emailLabel.text = "email"
49 |
50 | appleLoginButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
51 | }
52 |
53 | @objc
54 | func handleAuthorizationAppleIDButtonPress() {
55 | let appleIDProvider = ASAuthorizationAppleIDProvider()
56 | let request = appleIDProvider.createRequest()
57 | request.requestedScopes = [.fullName, .email]
58 |
59 | let authorizationController = ASAuthorizationController(authorizationRequests: [request])
60 | authorizationController.delegate = self
61 | authorizationController.presentationContextProvider = self
62 | authorizationController.performRequests()
63 | }
64 | }
65 |
66 | extension ViewController: ASAuthorizationControllerDelegate {
67 | func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
68 | switch authorization.credential {
69 | case let appleIDCredential as ASAuthorizationAppleIDCredential:
70 |
71 | let userIdentifier = appleIDCredential.user
72 |
73 | if let fullName = appleIDCredential.fullName,
74 | let email = appleIDCredential.email {
75 |
76 | let username: String = "\(fullName.familyName ?? "")" + "\(fullName.givenName ?? "")"
77 |
78 | print("fullName: \(fullName)")
79 | print("userName: \(username), email: \(email)")
80 |
81 | UserDefaults.standard.set(username, forKey: "setFullName")
82 | UserDefaults.standard.set(email, forKey: "setEmail")
83 | }
84 |
85 | if let fullName = appleIDCredential.fullName,
86 | let email = appleIDCredential.email,
87 | let authorizationCode = appleIDCredential.authorizationCode,
88 | let identityToken = appleIDCredential.identityToken,
89 | let authString = String(data: authorizationCode, encoding: .utf8) {
90 |
91 | print("authorizationCode: \(authorizationCode)")
92 | print("identityToken: \(identityToken)")
93 | print("authString: \(authString)")
94 | }
95 |
96 | print("useridentifier: \(userIdentifier)")
97 | UserDefaults.standard.set(userIdentifier, forKey: "setUserIdentifier")
98 |
99 | nameLabel.text = UserDefaults.standard.string(forKey: "setFullName")
100 | emailLabel.text = UserDefaults.standard.string(forKey: "setEmail")
101 |
102 | case let passwordCredential as ASPasswordCredential:
103 |
104 | let username = passwordCredential.user
105 | let password = passwordCredential.password
106 |
107 | print("username: \(username), password: \(password)")
108 |
109 | default:
110 | break
111 | }
112 | }
113 |
114 | func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
115 | print("login error wow")
116 | }
117 | }
118 |
119 | extension ViewController: ASAuthorizationControllerPresentationContextProviding {
120 | func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
121 | return self.view.window!
122 | }
123 | }
124 |
125 |
--------------------------------------------------------------------------------
/Practice-YSB/socialLogin/socialLogin/socialLogin.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.applesignin
6 |
7 | Default
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spark-Practice-iOS
2 |
3 | Spark🎇
4 |
5 | | # | 작업 내용 | 작업한 사람 | 📎 |
6 | |:----------:|:-------------:|:------:|:------:|
7 | | 1 | 카카오 소셜로그인 구현 | [L-j-h-c](https://github.com/L-j-h-c) | [📎](https://github.com/TeamSparker/Spark-Practice-iOS/pull/9) |
8 | | 2 | 애플 소셜로그인 구현 | [yangsubinn](https://github.com/yangsubinn) | [📎](https://github.com/TeamSparker/Spark-Practice-iOS/pull/12) |
9 | | 3 | 플로팅버튼 구현 | [L-j-h-c](https://github.com/L-j-h-c) | |
10 | | 4 | 플로팅버튼 구현 | [yangsubinn](https://github.com/yangsubinn) | [📎](https://github.com/TeamSparker/Spark-Practice-iOS/pull/18) |
11 | | 5 | Carousel 구현 및 애니메이션 연습 | [L-j-h-c](https://github.com/L-j-h-c) | |
12 | | 6 | 스톱워치 | [yangsubinn](https://github.com/yangsubinn) | [📎](https://github.com/TeamSparker/Spark-Practice-iOS/pull/15) |
13 | | 7 | StickyHeader + 무한스크롤 | [yangsubinn](https://github.com/yangsubinn) | [📎](https://github.com/TeamSparker/Spark-Practice-iOS/pull/23) |
14 |
--------------------------------------------------------------------------------