- ์๊ฐ
- ํ์
- ํ์๋ผ์ธ ๋ฐ ํต์ฌ๊ฒฝํ
- ํ์ผํธ๋ฆฌ
- ์คํ ํ๋ฉด
- ํธ๋ฌ๋ธ ์ํ
- ์ฃผ์ ํ์ต ๋ด์ฉ
- ํ ํ๊ณ
- ์ฐธ๊ณ ๋งํฌ
์ผ์ผ ๋ฐ์ค์คํผ์ค
๊ฐ ๊ถ๊ธํ์ ๊ฐ์?
ํน์ ์ํ ๊ฐ๋ณ ์์ธ ์กฐํ
๋ฅผ ์ํ์๋์?
์ ํฌ์๊ฒ ๋ฌผ์ด๋ณด์ธ์!
โ๏ธ ์บ๋ฆฐ๋์์ ์ํ์๋ ๋ ์ง๋ฅผ ์ ํํด์ฃผ์ธ์ ๐
โ๏ธ ํด๋น ๋ ์ง์ 1๏ธโฃ~๐์ ๋ฐ์ค์คํผ์ค๋ฅผ ์ ๊ณตํด๋๋ฆฝ๋๋ค!
โ๏ธ ์๋ก๊ณ ์นจ์ ์ํ์๋ฉด ๋ฆฌ์คํธ๋ฅผ ์๋๋ก ์ก์ ๋์ด์ฃผ์ธ์!
โ๏ธ ์ํ ๋ณ ์์ธ์ ๋ณด๋ ํ์ธ ๊ฐ๋ฅํ๋ ๋์น์ง ๋ง์๊ณ ํ์ธํด ๋ณด์ธ์๐
ํต์ฌ ๊ฐ๋ ์คํ API / URLSession / JSON Decoding / CodingKeys / UNIT Test /
CollectionView / ModernCollectionView / UIActivityIndicatorView /
UIRefreshControl / NSMutableAttributedString /
API KEY ๋ฐ๊ธ ๋ฐ ๋ ธ์ถ ๋ฐฉ์ง / Image Fetch / NSCache /
Modal / UICalendarView / DateManager / Image Loading View
Serena ๐ท | BMO ๐ค |
---|---|
ํ๋ก์ ํธ ๊ธฐ๊ฐ : 2023-07-24 ~ 2023-08-18
ํ์๋ผ์ธ
๋ ์ง | ๋ด์ฉ |
---|---|
2023.07.24 | โฝ๏ธ ์ผ๋ณ ๋ฐ์ค ์คํผ์ค ์ํ json dataset ์ถ๊ฐ โฝ๏ธ ์ผ๋ณ ๋ฐ์ค์คํผ์ค Model ์ถ๊ฐ |
2023.07.25 | โฝ๏ธ ์ผ๋ณ ๋ฐ์ค์คํผ์ค ๊ด๋ จ ํ
์คํธ ์ถ๊ฐ ๋ฐ ํ
์คํธ ์์ฑ โฝ๏ธ ๋ฐ์ค์คํผ์ค Model ์ถ๊ฐ โฝ๏ธ ์ ์ฒด Model CodingKey ์ ์ฉ |
2023.07.27 | โฝ๏ธ ๋คํธ์ํฌ ๊ด๋ จ ๋ก์ง์ ์ฒ๋ฆฌํ๋ NetworkManager ํ์
์ถ๊ฐ โฝ๏ธ ์ํ์งํฅ์์ํ๋ก๋ถํฐ ์ผ๋ณ ๋ฐ์ค์คํผ์ค ์กฐํํ๋ ๋ก์ง ์์ฑ |
2023.07.28 | โฝ๏ธ ์ํ ์์ธ์ ๋ณด ์กฐํ๋ฅผ ์ํ DTO ์์ฑ ๋ฐ CodingKey ์ ์ฉ โฝ๏ธ ์ํ ์์ธ์ ๋ณด ์กฐํ ๋ฉ์๋ ์ถ๊ฐ โฝ๏ธ ๋งค์ง๋ฆฌํฐ๋ด ๊ด๋ฆฌ๋ฅผ ์ํ NameSpace ์ถ๊ฐ |
2023.07.31 | โฝ๏ธ Result ํ์
์ ํ์ฉํ์ฌ Model ๋ฐ์ธ๋ฉ ๋ฐ ์๋ฌ์ฒ๋ฆฌ |
2023.08.02 | โฝ๏ธ ์คํ ๋ฆฌ๋ณด๋ ์ ๊ฑฐ ๋ฐ ์ฝ๋๋ฒ ์ด์ค UI ๊ตฌํ โฝ๏ธ ์ผ๋ฒฝ ๋ฐ์ค์คํผ์ค Cell ์์ฑ โฝ๏ธ ์ผ๋ณ ๋ฐ์ค์คํผ์ค CollectionView ๋ก ๊ตฌํ |
2023.08.03 | โฝ๏ธ CollectionView ์ DataSoruce ๋ฅผ DiffableDataSource ๋ก ๋ณ๊ฒฝ โฝ๏ธ CollectionView ์ refresh control ์ถ๊ฐ |
2023.08.04 | โฝ๏ธ ์ ๊ทผ์ฑ ํฅ์์ ์ํด adjustsFontSize ์ ์ฉ โฝ๏ธ ์๋ฌ ๋ฐ์์ Alert ์ถ๋ ฅ |
2023.08.06 | โฝ๏ธ BoxOfficeService ๋ฅผ ์ฌ์ฉํ๋ ๊ณณ์ ๋ชจ๋ ์์กด์ฑ ์ฃผ์
์ ๋ฐ์ ์ฌ์ฉํ๋๋ก ๋ณ๊ฒฝ โฝ๏ธ ๊ณตํต Alert ์ค๋ณต ๋ฉ์๋ ๋ถ๋ฆฌ โฝ๏ธ ์ํ ์์ธ์ ๋ณด ViewController ์์ฑ ๋ฐ ๊ตฌํ โฝ๏ธ ๋ค์ ์ด๋ฏธ์ง ๊ฒ์ ๊ด๋ จ DTO ์ถ๊ฐ |
2023.08.07 | โฝ๏ธ NetworkManager ๋ก์ง ๋ณ๊ฒฝ ๋ฐ ์ฑ๊ธํค ํด๋์ค๋ก ๋ณ๊ฒฝ โฝ๏ธ Dynamic Type ์ ์ฉ |
2023.08.08 | โฝ๏ธ Kakao Developer ์ ํ ์ฑ ์์ฑ โฝ๏ธ KakaoAPIKey ๋ฅผ plist ์ ๋ฑ๋ก |
2023.08.09 | โฝ๏ธ ์ด๋ฏธ์ง ๋ก๋ ์ ๋๋ฉ์ด์
์์ฑ โฝ๏ธ ์ด๋ฏธ์ง ์บ์ ์ ์ฅ ๋ก์ง ์ถ๊ฐ |
2023.08.10 | โฝ๏ธ ๋ฐ์ค์คํผ์ค ๋ ์ง ์ ํ ViewController ์ถ๊ฐ โฝ๏ธ BoxOfficeService ์ ๋ ์ง ๋ก์ง์ DateManager ์ฑ๊ธํค ํด๋์ค๋ก ๋ถ๋ฆฌ |
ํต์ฌ๊ฒฝํ
- ์ํ์งํฅ์์์ํ ์คํ API๋ฅผ ์ฐธ๊ณ ํ์ฌ '์ค๋์ ์ผ์ผ ๋ฐ์ค์คํผ์ค ๋ฐ์ดํฐ'์ '์ํ ๊ฐ๋ณ ์์ธ ๋ฐ์ดํฐ'
Model
์ ๊ตฌํModel
์ ํ์ฉํ์ฌURLSession
์ผ๋กJSON
ํ์ผ์Fetch
JSON
ํ์ผDecode
์ ๋ํUnit Test
์์ฑ- iOS 14.0 ๋ฏธ๋ง ๋ฒ์ ์ ์ํ
CollecionView
/ iOS 14.0 ์ด์ ๋ฒ์ ์ ์ํModernCollecionView
๊ตฌ์ฑKakao API Key
๋ฅผ ํ์ฉํ์ฌ ์ํ ํฌ์คํฐfetch
ํ๊ธฐfetch
ํ ์ด๋ฏธ์ง ๋ฐ ๋ฐ์ดํฐ๋ฅผStackView
์ScrollView
์ ๋ฃ๊ธฐ
BoxOffice
โโโ App
โย ย โโโ AppDelegate.swift
โย ย โโโ SceneDelegate.swift
โโโ Base.lproj
โย ย โโโ LaunchScreen.storyboard
โโโ Error
โย ย โโโ AlertManager.swift
โย ย โโโ JSONDecoderError.swift
โย ย โโโ NetworkManagerError.swift
โโโ Extension
โย ย โโโ Bundle+.swift
โย ย โโโ JSONDecoder+.swift
โย ย โโโ String+.swift
โย ย โโโ UIFont+.swift
โโโ Info.plist
โโโ KakaoAPIKey.plist
โโโ Model
โย ย โโโ DTO
โย ย โย ย โโโ BoxOffice
โย ย โย ย โย ย โโโ BoxOffice.swift
โย ย โย ย โย ย โโโ BoxOfficeResult.swift
โย ย โย ย โย ย โโโ DailyBoxOffice.swift
โย ย โย ย โโโ DaumSearch
โย ย โย ย โย ย โโโ DaumSearchMainText.swift
โย ย โย ย โย ย โโโ DaumSearchMeta.swift
โย ย โย ย โย ย โโโ ImageDocument.swift
โย ย โย ย โโโ Movie
โย ย โย ย โโโ Audit.swift
โย ย โย ย โโโ Company.swift
โย ย โย ย โโโ Genre.swift
โย ย โย ย โโโ Movie.swift
โย ย โย ย โโโ MovieInfo.swift
โย ย โย ย โโโ MovieInfoResult.swift
โย ย โย ย โโโ Nation.swift
โย ย โย ย โโโ People.swift
โย ย โย ย โโโ ShowType.swift
โย ย โโโ Section.swift
โโโ NameSpace
โย ย โโโ CustomDateFormatStyle.swift
โย ย โโโ KakaoNameSpace.swift
โย ย โโโ KobisNameSpace.swift
โย ย โโโ MimeType.swift
โย ย โโโ MovieDetailNameSpace.swift
โโโ Protocol
โย ย โโโ CalendarViewControllerDelegate.swift
โย ย โโโ DaumSearchDocumentable.swift
โโโ Service
โย ย โโโ BoxOfficeService.swift
โโโ Util
โย ย โโโ DateManager.swift
โย ย โโโ ImageCacheManager.swift
โย ย โโโ NetworkManager.swift
โโโ View
โย ย โโโ BoxOfficeCell.swift
โย ย โโโ Custom
โย ย โย ย โโโ DetailLabel.swift
โย ย โย ย โโโ LabelsStack.swift
โย ย โย ย โโโ TitleLabel.swift
โย ย โโโ MovieDetailView.swift
โโโ ViewController
โโโ BoxOfficeViewController.swift
โโโ CalendarViewController.swift
โโโ MovieDetailViewController.swift
๋ฐ์ค์คํผ์ค ๋ก๋ฉ ํ๋ฉด | ๋ฐ์ค์คํผ์ค ๋ฆฌ์คํธ ์๋ก๊ณ ์นจ |
---|---|
์ด๋ฏธ์ง ๋ก๋ฉ ํ๋ฉด | ์บ๋ฆฐ๋๋ก ๋ ์ง ์ ํํ๊ธฐ |
- ์ด๋ฐ ์ฝ๋ ์์ฑ ์
decode
๊ณผ์ ์์ ์ ์ ์๋ ์๋ฌ๊ฐ ๊ณ์ ๋ฐ์ํ์์ต๋๋ค.decode
๊ณผ์ ์์ ์ด๋ ๋ถ๋ถ์์ ์๋ชป๋์๋์ง๋ฅผ ์ฐพ๊ธฐ ์ํด ์ฝ๋๋ฅผ ์ฒ์๋ถํฐ ๋ค์ ์์ฑํ๋ค๋ณด๋,CodingKey
๋ฅผ ์๋ชป ์์ฑํ์ฌJSON decode
๊ฐ ๋์ง ์์๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค.
-
CodingKey
๋ฅผ ์ฌ์ฉํ ๋ ํ๋กํผํฐ๋ช ์swift
์์ ์ฌ์ฉํ ์ด๋ฆ์ผ๋ก ์ง์ ํ๊ณ ,enum case
์ ๊ฐ์ผ๋ก ๊ธฐ์กด์JSON
ํค ๊ฐ์ ์ด๋ฆ์ ์ง์ ํด์ผํฉ๋๋ค. ํ์ง๋ง ์ด๋ฅผ ๋ฐ๋๋ก ์์ฑํ์๋๋, ์ ์ ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ฌ ์ด๋ฅผ ๋๋ฒ๊น ํ๋๋ฐ ๋ง์ ์๊ฐ์ ์์ํ์์ต๋๋ค.CodingKey ์๋ชป ์์ฑ ์์
struct BoxOfficeResult: Decodable { let boxofficeType: String ... enum CodingKeys: String, CodingKey { case boxofficeType = "boxOfficeType" ... } }
CodingKey ์ฌ๋ฐ๋ฅธ ์์
struct BoxOfficeResult: Decodable { let boxOfficeType: String ... private enum CodingKeys: String, CodingKey { case boxOfficeType = "boxofficeType" ... } }
Unit Test
์์JSON
ํ์ผDecode
์ ๋ํ ํ ์คํธ๋ฅผ ์งํ ์do-catch
๋ฌธ์ ํ์ฉํ์์ต๋๋ค. ์ด๋ ํ ์คํธ๊ฐ ์คํจํ์ ๋XCTTest
๋ฉ์๋๋ฅผretrun
๋ง ํ๊ฒ ๋๋ฉด ํ ์คํธ๊ฐSuccess
์ฒ๋ฆฌ ๋๋๊ฒ์ ํ์ธํ์ต๋๋ค.- ํ
์คํธ๊ฐ ๊ผญ
Then
๊ณผ์ ๊น์ง ๋๋ฌํ์ง ์๋๋ผ๋Given
๊ณผWhen
๊ณผ์ ๋ํ ํ ์คํธ์ ์ ์ ํ์ง ์์ ๊ฐ์ด ๋ค์ด์ค๊ฑฐ๋, ๊ฐ์ด ์ฒ๋ฆฌ๋๋ ๊ณผ์ ์ ๋ํ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
-
XCTFail
๋ฉ์๋๋ฅผ ์ฐพ์ ํ ์คํธ ์งํ ์ค ์ ์ ํ์ง ์์ ๋ถ๋ถ์ ์ฝ์ ํด ์ฃผ์์ต๋๋ค. Apple Developer ๊ณต์๋ฌธ์์ ๋ฐ๋ฅด๋ฉดXCTFail
์ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.This function generates a failure immediately and unconditionally. (์ด ํจ์๋ ์ฆ์ ๋ฌด์กฐ๊ฑด ์คํจ๋ฅผ ์์ฑํฉ๋๋ค.)
func test_box_office_sample_json_ํ์ผ์_๋์ฝ๋ฉ_ํ _์_์๋ค() { // Given guard let result: BoxOffice = JSONDecoder.decode(fileName: "box_office_sample") else { XCTFail("ํ์ผ๋ช 'box_office_sample'๋ก JSON ๋์ฝ๋ฉ ํ ์ ์์ต๋๋ค.") return } // When // Then XCTAssertNotNil(result) }
-
ViewController
์์ ์์ฒญํdataTask
์์ ์์ ๋์ค ์๋ฌ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ, ๋ฐ์ํ ์๋ฌ๋ฅผViewController
์์ ์ ๋ฌ๋ฐ์ ์ฒ๋ฆฌํ๊ณ ์ถ์์ต๋๋ค. ํ์ง๋งdataTask
ํด๋ก์ ๋ด๋ถ์์ ๋ฐ์ผ๋ก ๊ฐ์ ๋ฆฌํด์ํฌ์ ์๋๊ฒ์ฒ๋ผ, ์๋ฌ๋ ๋ฐ์ผ๋ก ๋์ง ์ ์์์ต๋๋ค.Invalid conversion from throwing function of type '@sendable (Data?, URLResponse?, (any Error)?) throws -> Void' to non-throwing function type '@sendable (Data?, URLResponse?, (any Error)?) -> Void'
-
Return
๊ฐ์ด ์๋ ๊ฒฝ์ฐ๋Error throws
๋ฅผ ํ ์ ์๋ ์ํฉ์์Result
ํ์ ์ ํ์ฉํ์ฌError Handling
์ ํ ์ ์์ต๋๋ค. ํนํURLSession
์dataTask
์ฒ๋ผVoid
ํ์ ์ผ๋ก ๊ธฐ๋ณธ ๊ตฌํ๋์ด ์๋ ๋ฉ์๋์์์ ๋ฐ์ํError
๋ฅผ ์ธ๋ถ๋ก ์ ๋ฌํ๊ณ ์ถ์ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.์ฝ๋ ์์
Error
ํ์ ์ ์์ฑ
enum NetworkManagerError: Error { case notExistedUrl ... }
Result Type
์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ ํจ์ ์ ์ (Success: Generic
,Failure: NetworkManagerError
)
struct NetworkManager { // completion Handler ํ๋ผ๋ฏธํฐ๋ก Result Type์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ Void ํด๋ก์ static func loadData<T: Decodable>(_ components: URLComponents?,_ dataType: T.Type,_ completion: @escaping (Result<T, NetworkManagerError>) -> Void) { ... // Void ํ์ ์ผ๋ก ๊ธฐ๋ณธ ๊ตฌํ๋์ด ์๋ ๋ฉ์๋(dataTask)์์์ Error ๋ฐ์ do { ... // ์ฑ๊ณตํ ๊ฒฝ์ฐ completion(.success(result)) } catch let error as JSONDecoderError { ... // ์คํจํ ๊ฒฝ์ฐ completion(.failure(NetworkManagerError.failureJsonDecode)) } catch { ... completion(.failure(NetworkManagerError.unknown)) } ... }
Generic
ํ์ ์ผ๋ก ์ ๋ฌ๋ฐ์Success
ํ์ ์ ๊ตฌ์ฒด์ ํ์ ์ผ๋ก ๋ค์ ์ ๋ฌ
func loadDailyBoxOfficeData(_ completion: @escaping (Result<BoxOffice, NetworkManagerError>) -> Void) { ... NetworkManager.loadData(components, BoxOffice.self) { result in switch result { case .success(let data): completion(.success(data)) case .failure(let error): completion(.failure(error)) } } }
ViewController
์์Success
/Failure
์ฒ๋ฆฌ
private func fetchBoxOffice(_ result: Result<BoxOffice, NetworkManagerError>) { switch result { case .success(let boxOffice): ... case .failure(let error): ... } }
- ๋ญํน ๋ณ๋ ์ ๋ณด๋ฅผ ํ์ํ๊ธฐ ์ํด ํ์ดํ ํน์๋ฌธ์๋ฅผ ํ์ฉํ์์ต๋๋ค. ์ด๋ ๋ณ๋ ์ ๋ณด์ ๋ง์ถ์ด
Label Text
์ ํด๋น ํน์๋ฌธ์์ ์์๋ง ๋ฐ๊พธ๊ณ ์ ํ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ญํน์ด ๋์์ง ๊ฒฝ์ฐ ๋นจ๊ฐ์์ ์๋ก ํฅํ๋ ํ์ดํ๋ฅผ, ๋ญํน์ด ๋ฎ์์ง ๊ฒฝ์ฐ ํ๋์์ ์๋๋ก ํฅํ๋ ํ์ดํ๋ฅผ ํ์ํ๊ณ ์ ํ์ต๋๋ค.
UILable
์String Text
๋ฟ๋ง์ด ์๋๋ผattributedText
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.String Text
๋ฅผ ์ฌ์ฉํ๊ฒ๋๋ฉด ๊ธ์์foregroundColor
๋ฅผ ์ค ์ ์๊ธฐ ๋๋ฌธ์,attributeText
๋ฅผ ์ฌ์ฉํ๊ณ ์ ํ์์ต๋๋ค.attributedText
์๋NSMutableAttributedString
ํ์ ์ ๊ฐ์ ๋์ ํ ์ ์์ต๋๋ค. ํ์ฌString
์NSMutableAttributedString
ํ์ ์ผ๋ก ๋ณํํ์ฌ ์ฌ์ฉํ์ต๋๋ค.- addAttribute(_:value:range:) ๋ฉ์๋๋ฅผ ์ด์ฉํ์ฌ ํด๋นํ๋ ๋ฌธ์๋ฅผ ์ง์ ํ ์์์ผ๋ก ๋ณ๊ฒฝํ์ต๋๋ค.
์ด๋ฏธ์ง ๊ฒ์ API
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํดKakao Developer
์์ ์ฑ์ ์์ฑํ์ฌREST API Key
๋ฅผ ๋ฐ๊ธ๋ฐ์์ต๋๋ค. ๋ฐ๊ธ๋ฐ์REST API Key
๋ฅผ ์ด์ฉํด์ด๋ฏธ์ง ๊ฒ์ API
๋ฅผ ์ด์ฉํ๋๋ฐ ์ฑ๊ณตํ๊ณ , ํด๋น ๋ด์ฉ์ ์ปค๋ฐํ๋ ค๊ณ ํ์ต๋๋ค.- ๋ณ๊ฒฝ ๋ด์ญ์ ํ์ธํ๋ ์ค
API Key
๊ฐ ํฌํจ๋ ์ฝ๋๊ฐ ์ปค๋ฐ๋๋ค๋ฉด ์ดํ ๋ณ๋ ๊ด๋ฆฌ๋ฅผ ์ํด ํด๋น ์ฝ๋๋ฅผ ์ ๊ฑฐํ๋๋ผ๋ ๊น ์ปค๋ฐ ์ด๋ ฅ์API Key
๊ฐ ๊ทธ๋๋ก ๋ ธ์ถ ๋๋ ์ํฉ์ด ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค.
- ์ ํฌ๋ ์ด๋ฌํ ์ํฉ์ด ๋ฐ์ํ์ง ์๋๋ก ํ๊ธฐ ์ํด
KakaoAPIKey.plist
ํ์ผ์ ๋ง๋ค๊ณgitignore
์ ์ถ๊ฐํ์ต๋๋ค. ํด๋น ํ์ผ์ ๊น์ ํตํด ๋ฐ์ ์ ์๊ฒ ๋์๊ธฐ ๋๋ฌธ์ ํ์์๊ฒ ์ง์ ํ์ผ ์ ๋ฌ์ ํ๋ ๋ฐฉ์์ผ๋ก ์์ ํ๊ฒ ๋ฉ๋๋ค. plist
๋ด๋ถ์ ๋ฐ์ดํฐ๋Bundle
์ ํ์ฅํ์ฌ ์ฝ๊ธฐ์ ์ฉ ํ๋กํผํฐ๋ฅผ ํตํด ๊ฐ์ ธ์ค๋๋ก ํ์ต๋๋ค.extension Bundle { var kakaoApiKey: String { guard let file = self.path(forResource: "KakaoAPIKey", ofType: "plist") else { return "" } guard let resource = NSDictionary (contentsOfFile: file) else { return "" } guard let key = resource["Authorization"] as? String else { fatalError("KakaoAPIKey.plist์ Authorization๋ฅผ ์ค์ ํด์ฃผ์ธ์.") } return key } } enum KakaoNameSpace { ... static let authorization = "Authorization" static let apiKey = Bundle.main.kakaoApiKey // Bundle์ ๋ฑ๋ก๋ Key๋ฅผ NameSpace๋ก ๊ด๋ฆฌ } // ์ดํ URLRequest์ header์ ํ์ํ ์ ๋ณด๋ฅผ ์ฃผ์ let headers = [ KakaoNameSpace.authorization : KakaoNameSpace.apiKey ]
Bundle
์ ์คํ ๊ฐ๋ฅํ ์ฝ๋์ ํด๋น ์ฝ๋์ ์์์ ํฌํจํ๋ ๋๋ ํ ๋ฆฌ์ ๋๋ค.Bundle
์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์๋๋ฐ, ๊ทธ ์คmain
์ ์ฑ์ด ์คํ๋๋ ์ฝ๋๊ฐ ์๋Bundle
๋๋ ํ ๋ฆฌ์ ์ ๊ทผํ ์ ์๋bundle
์ ๋๋ค.
- ์ด๋ฏธ์ง ๊ฒ์ API๋ฅผ ํตํด ์ด๋ค ์ฌ์ด์ฆ์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ง ์๋ ์ด๋ฏธ์ง์
width
๋contentView
์width
์ ๋ง์ถ๋ฉด ๋์์ต๋๋ค. ํ์ง๋งUIImage.contentMode
๋ฅผ ์ด๋ป๊ฒ ์กฐ์ ํด๋ ๊ฐ๋ก ํน์ ์ธ๋ก ์ฌ์ด์ฆ์ ์๊ตฌ์กฐ๊ฑด์ ๋ง์ถ ์ ์์์ต๋๋ค.
UIImageView.contentMode
์ ์๊ด์์ด, ๋น์จ์ ๊ณ์ฐํ์ฌ ์ธ๋ก ์ฌ์ด์ฆ๋ฅผ ์กฐ์ ํด์ฃผ๊ธฐ๋ก ํ์ต๋๋ค. ๋คํํ๋ ์ด๋ฏธ์ง ๊ฒ์ ์ ๊ฐ๋ก, ์ธ๋ก ์ฌ์ด์ฆ ์ ๋ณด๊ฐ ํจ๊ป ์ ๊ณต๋์๊ธฐ ๋๋ฌธ์ ์ด๋ ต์ง ์๊ฒ ๋์ด๋ฅผ ๋์ ์ผ๋ก ์ ๋ ฅํ ์ ์์์ต๋๋ค.private func setPosterImage(_ imageDocument: ImageDocument, _ image: UIImage) { // ๋น์จ = UIImage ํ๋ ์ ๊ฐ๋ก รท ๋ก๋๋ ์ด๋ฏธ์ง ์ค์ ๊ฐ๋ก ์ฌ์ด์ฆ let ratio = self.movieDetailView.posterImage.frame.width / CGFloat(integerLiteral: imageDocument.width) // ๋์ด = ๋น์จ ร ๋ก๋๋ ์ด๋ฏธ์ง ์ค์ ์ธ๋ก ์ฌ์ด์ฆ let height = ratio * CGFloat(integerLiteral: imageDocument.height) self.movieDetailView.posterImage.heightAnchor.constraint(equalToConstant: height).isActive = true self.movieDetailView.posterImage.image = image }
- ํน์ ๋ฌธ์์ ๋๊ป๋ฅผ ๋ณ๊ฒฝํ๊ณ ์ ํ ๋ ์ด๋ค ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ง ๊ณ ๋ฏผํ์์ต๋๋ค.
- swift ๊ธฐ๋ณธ ์ ๊ณต ๋ฉ์๋๋ฅผ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์์ง๋ง, ์ด๋
Font
์ ์ฌ์ด์ฆ๊ฐ ๊ณ ์ ๋๋ค๋ ๋จ์ ์ด ์กด์ฌํ์ต๋๋ค..systemFont(ofSize: 17, weight: .bold)
Label
๊ณผButton
์Dynamic Type
์ ๋ํ ๋์์ด ๋์ด์ผํ๋ค๊ณ ์๊ฐํ๊ธฐ ๋๋ฌธ์,Font
์ ์ฌ์ด์ฆ๊ฐ ๊ณ ์ ๋์ง ์์ผ๋ฉด์ ํน์ Font
์ ๋๊ป๋ฅผ ์กฐ์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ฐพ๊ณ ์ ํ์์ต๋๋ค.
UIFont
๋ฅผextension
ํ์ฌ ํฐํธ๋ฅผCustom
ํ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ๋์ด ์ด๋ฅผ ํ์ฉํ์์ต๋๋ค.extension UIFont { static func preferredFont(for style: TextStyle, weight: Weight) -> UIFont { let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) let font = UIFont.systemFont(ofSize: descriptor.pointSize, weight: weight) let metrics = UIFontMetrics(forTextStyle: style) return metrics.scaledFont(for: font) } }
- ์ด์ Step์์๋ ์ด์ ์ ๋ ์ง ๊ธฐ์ค์ผ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ฉด ๋์์ผ๋, ์ด๋ฒ Step๋ถํฐ๋ ๋ค์ํ ๋ ์ง๋ฅผ ๋์ํด์ผ ํ์ต๋๋ค. ์ฌ๋ฌ
ViewController
์์ ์ง์ ๋ ์ง๋ฅผ ๊ณต์ ํด์ผ ํ๋ ์ํฉ์์ ์ด๋ค ๋ฐฉ์์ผ๋ก ๋์ํ ์ง ๊ณ ๋ฏผ์ ํ์ต๋๋ค. ViewController
๊ฐ ๋ ์ง ์ ๋ณด๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ง๋ง, ๋ ์ง ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์๋ViewController
๊ฐ ๋ชจ๋ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋ ๊ฒฝ์ฐ ๋ ์ง ์ ๋ณด๊ฐ ์ด๊ธฐํ ๋๋ ์ํ์ด ์์์ต๋๋ค.- ๊ธฐ์กด์ ์์ฑํ
BoxOfficeService
๋ ์ฑ์ ์๋ช ์ฃผ๊ธฐ์ ํจ๊ปํ๋ ๊ตฌ์กฐ์ฒด์ด๊ธฐ ๋๋ฌธ์, ๊ธฐ์กด ๋ ์ง ๊ด๋ จ ๋ก์ง์ ์ด๊ณณ์์ ๊ด๋ฆฌํ์์ต๋๋ค. ํ์ง๋ง ๋ ์ง ๊ด๋ จ ๋ก์ง์ด ์ฆ๊ฐํ๋ฉด์ ์ด์ ๊ณผ ๊ฐ์ดBoxOfficeService
์์ ์ด๋ฅผ ๋ชจ๋ ๊ด๋ฆฌํ๋ ๊ฒ์ ์ ์ ํ์ง ์๋ค๊ณ ์๊ฐํ์ต๋๋ค.
DateManager
๋ฅผ ์์ฑํ์ฌ ๋ ์ง ๊ด๋ จ ํ๋กํผํฐ๋ฅผ ํด๋น ํด๋์ค์์ ์ฒ๋ฆฌํ๋๋ก ํ์ต๋๋ค.class DateManager { static private let dateFormatter = DateFormatter() static let yesterday: Date = .now - (24 * 60 * 60) static var selectedDate: Date = yesterday ... private init() {} }
- ๋ ์ง์ ํ ํ๋ฉด์ ๋ฌ๋ ฅ์๋ ํ์ฌ ์ ํ๋ ๋ ์ง๊ฐ ๋ฏธ๋ฆฌ ์ ํ๋์ด ์์ด์ผ ํ๋ค๋ ๋ด์ฉ์ด ์์์ต๋๋ค. ํด๋น ์๊ตฌ์ฌํญ์ ๊ตฌํํ๊ธฐ ์ํด
UICalendarView.Decoration
๋ฅผ ์ด์ฉํ์ต๋๋ค.์ปค์คํ ๋ฐ์ฝ๋ ์ด์ ์ ์ ์ฉํ ์ฝ๋
private func customDecoration() -> UIView { let view = UIView() view.backgroundColor = .red view.clipsToBounds = false view.frame = CGRect(x: 0, y: 0, width: 50, height: 50) return view }
- ๊ฒฐ๊ณผ๋ ๋ ์ง์ ํ๋จ ์ผ๋ถ ์์ญ์๋ง ์ปค์คํ ์ ํ ์ ์์ ๋ฟ, ๋ ์ง ์์ฒด๊ฐ ์ ํ๋ ํจ๊ณผ๋ฅผ ์ค ์ ์์์ต๋๋ค.
UICalendarSelectionSingleDate
๋ฅผ ์ธ์คํด์คํ ํ๊ณ (UICalendarSelectionSingleDateDelegate
๋ ์ฑํํฉ๋๋ค.) ์๋ ์ฝ๋๋ฅผ ์ ์ฉํ๋ฉด, ์ํ๋ ํจ๊ณผ๊ฐ ์ ์ฉ๋๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.let dateSelection = UICalendarSelectionSingleDate(delegate: self) calendarView.selectionBehavior = dateSelection
- ์ถ๊ฐ๋ก ์๋ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์บ๋ฆฐ๋๋ทฐ๊ฐ ์ด๋ฆด ๋๋ถํฐ ๋ ์ง๊ฐ ์ ํ๋ ํจ๊ณผ๋ฅผ ์ ์ฉํ ์ ์์์ต๋๋ค.
dateSelection.selectedDate = DateComponents(year: year, month: month, day: day)
- ์ผ๋ฐ
CollectionView
ViewController
์UICollectionViewDataSource
ํ๋กํ ์ฝ ์ฑํ ํ ํ์ ๋ฉ์๋๋ฅผ ๊ตฌํํฉ๋๋ค.- ์ดํ
CollectionView
์dataSource
๋ฅผself
๋ก ์ง์ ํฉ๋๋ค.
- Modern Collection View
dataSource
๋ก ์ฌ์ฉํUICollectionViewDiffableDataSource
ํด๋์ค๋ฅผ ์ธ์คํด์คํ ํฉ๋๋ค.DiffableDataSource
๋ฅผ ์ธ์คํด์คํ ํ ๋๋CollectionView
, ์ฌ์ฉํ ๋ฐ์ดํฐ(e.g.DTO
,Model
๋ฑ),Section
,IndexPat
h๊ฐ ํ์๋ก ํฉ๋๋ค.DiffableDataSource
์์ ์ฌ์ฉํ๋ ๋ฐ์ดํฐ ์ ๋ณด๊ฐ ์ถ๊ฐ, ์ญ์ , ๋ณ๊ฒฝ์ด ๋๋ ๊ฒฝ์ฐ๋NSDiffableDataSourceSnapshot
ํด๋์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค.SnapShot
์๋Section
์ ๋ณด์Items
์ ๋ณด๋ฅผ ๊ฐ๊ฐ ์ ๋ฌํฉ๋๋ค.- ์ดํ
dataSource
์ธ์คํด์ค์Snapshot
์apply
ํฉ๋๋ค.
- CollectionView ์์๋ฅผ ๊ตฌ์ฑํ ๋, ๋ฒ์ ํธํ ๋ฌธ์ ์ ์ง๋ฉดํ์์ต๋๋ค.
-
๋ํ ์ผ ์ ์ธ์ฌ๋ฆฌ ๊ตฌํ ์ ์์์ ์ ์๋์ด ์๋ ๋ํ ์ผ ์ ์ธ์ฌ๋ฆฌ๋ฅผ
UICellAccessory
์์ ์ง์ํด์ฃผ์์ต๋๋ค.cell.accessories = [ .disclosureIndicator(options: .init(tintColor: .systemGray)), ]
ํ์ง๋ง
UICellAccessory
์ iOS 14.0 ์ด์๋ถํฐ ์ง์์ด ๊ฐ๋ฅํ์์ต๋๋ค.์ฐธ๊ณ - UICellAccessory ๊ณต์ ๋ฌธ์
์ด๋ฅผ ์ฌ์ฉํ๋ฉด iOS14.0 ์ดํ ๋ฒ์ ์ ์ง์ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ๊ตฌํํด๋ณด๊ณ ์ ํ์ต๋๋ค. ํ์ฌ ๋ณ๋์
label
์ ๋ง๋ค์ด ๋ํ ์ผ ์ ์ธ์ฌ๋ฆฌ๋ทฐ์ ๊ฐ์ ํํ๋ฅผ ๋ ์ ์๋๋ก ํ์์ต๋๋ค.private let disclosureIndicatorLabel: UILabel = { var label = UILabel() label.text = "ใ" label.textColor = UIColor(displayP3Red: 0.8, green: 0.8, blue: 0.8, alpha: 1) return label }()
-
์ Separator ๊ตฌํ ์
UICollectionViewListCell
์ ํ๋กํผํฐ ์คseparator
๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ CollecionViewCell ๋ณ๋ก ๊ตฌ๋ถ์ ์ ์์ฑํ ์ ์์ต๋๋ค. ํ์ง๋งUICollectionViewListCell
์ iOS14.0 ์ด์ ๋ฒ์ ์์๋ง ์ง์์ด ๊ฐ๋ฅํฉ๋๋ค.ํ์ฌ iOS14.0์ดํ ๋ฒ์ ๊ณผ์ ํธํ์ ์ํด CustomCell์ ๋ด์ฉ์ View๋ก ๊ฐ์ธ์ ์,์๋ ๋์ด 1pt ์ฌ๋ฐฑ ๊ณต๊ฐ์ ๋ง๋ค์ด separator๋ก ๋ณด์ผ ์ ์๊ฒ๊ธ ์ฝ๋ ์์ฑ์ ํ์ต๋๋ค.
private lazy var separatorLineView: UIView = { let view = UIView() view.backgroundColor = .init(displayP3Red: 0.9, green: 0.9, blue: 0.9, alpha: 1) view.frame.size.width = view.frame.width view.translatesAutoresizingMaskIntoConstraints = false return view }()
- ์ด๋ฏธ์ง ๋ก๋ฉ ํ๋ฉด์ ๊ตฌ์ฑ ์ ์ด๋ค ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ง ๊ณ ๋ฏผ์ด ๋ง์์ต๋๋ค.
BoxOfficeViewController
์ฒ๋ผactivityIndicatorView
๋ฅผ ์ฌ์ฉํ ์๋ ์์์ง๋ง, ๋ค๋ฅธ ์ข ๋ฅ์ ๋ก๋ฉํ๋ฉด๋ ๊ตฌํํด๋ณด๊ณ ์ ํ์์ต๋๋ค. ๊ณ ๋ฏผ์ ํ๋ ์ค ํต์์ ์ธ ์ฑ์์ ๋ก๋ฉํ๋ฉด์ ์์ง์ด๋ ์ด๋ฏธ์ง๋ฅผ ์ฐธ๊ณ ํ์ฌ ์ด์ ๋น์ทํ๊ฒ ๊ตฌํ์ ํด๋ณด๊ณ ์ ํ์์ต๋๋ค. asset
์gif
์ด๋ฏธ์ง๋ฅผframe
๋ณpng
ํ์ผ๋ก ๋ถ๋ฆฌํ์ฌ ์ ์ฅํ์์ต๋๋ค. ์ด๋ฅผUIImage
์์animatedImage
๋ฅผ ํ์ฉํ์ฌ ์์์duration
์ ์ง์ ํ์ฌ ์์ฐ์ค๋ฝ๊ฒ ์์ง์ด๋ ํ์์ ๋ณด์ฌ์ค ์ ์๋๋ก ํ์์ต๋๋ค.- ํ์ฌ ์ ํฌ ํ๋ก์ ํธ์์ ๋ก๋ฉํ๋ ์ด๋ฏธ์ง๋ ๋น ๋ฅธ ์๋๋ก ์ฒ๋ฆฌ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ์ ํฌ๊ฐ ๊ตฌ์ฑํ
Image Loading
ํ๋ฉด์ด ์งง์ ์ฐฐ๋์ ๊น๋นก์ด๊ณ ์ฌ๋ผ์ง๋ ํ์์ ๋๊ฒ ๋์์ต๋๋ค. ์ ํฌ๋ ์คํ๋ ค ์ด๋ ๊ฒ ์งง์ ๋ก๋ฉํ๋ฉด์ดuser
์๊ฒ ์ค๋ฅ๊ฐ ๋๋ ํ์์ฒ๋ผ ๋ณด์ฌ์ง ์ ์๋ค ์๊ฐํ์์ต๋๋ค. ํ์ฌ ์ด๋ฏธ์ง๊ฐ ๋ก๋ฉ ์ค์ด๋ผ๋ ๊ฒ์user
์๊ฒ ๋ช ์ํ๊ธฐ ์ํดusleep(500000)
์ ์ฃผ์ดImage Loading
์ ๊ณผ์ ์ด ๋ณด๋ค ์ ํฌ์ ์๋์ ๋ง๊ฒ๋ ์กฐ์ ํ์์ต๋๋ค.
- ์ ํฌ๋ ํ๋ก์ ํธ์
NSCache
๋ฅผ ์ ์ฉํ์ง๋ง,URLCache
๋ ๊ณต๋ถํด ๋ณด์์ต๋๋ค. URLCache
๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์บ์ ์ ์ฅ์ดondisk
์ธ ๊ฒ์ ํ์ธํ๊ณ , ์ด๊ฒ์ ๋ณ๊ฒฝํ๊ธฐ ์ํดStoragePolicy
๋ฅผallowedInMemoryOnly
๋ก ์ง์ ํด ๋ณด์์ต๋๋ค. ํ์ง๋ง ์ ํฌ์ ์์๊ณผ ๋ฌ๋ฆฌStoragePolicy
๋ฅผ ๋ณ๊ฒฝํ์์์๋ ์บ์ ๋ฐ์ดํฐ๊ฐMemory
์ ์ ์ฅ ๋์ง ์์์ต๋๋ค.- ์๋์ ๊ฐ์ด ์ฌ๋ฌ ์คํ ๋์
(30 * 1024 * 1024)
๋ถํฐ๋URLCache
๊ฐ๋ฉ๋ชจ๋ฆฌ
์ ์ ์ฅ์ด ๋๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.------------------------------------------------------------------------------ URLCache.shared์ memoryCapacity: 512,000 bytes diskCapacity: 10,000,000 bytes CachedURLResponse์ storagePolicy๊ฐ .allowedInMemoryOnly์ผ ๋, memoryCapacity: 10, 20 (* 1024 * 1024)์ผ ๋๋ ์คํจํจ. 30๋ถํฐ ์ฑ๊ณต. 31,457,280 bytes ์ฒซ๋ฒ์งธ data - 1,469,837 bytes ๋๋ฒ์งธ data - 1,078,478 bytes ------------------------------------------------------------------------------
- ๋๋ฌธ์ ์ ํฌ๋ ์ ์ฅ๋์ด์ผํ๋ ๋ฐ์ดํฐ ๋ณด๋ค ์ง์
memoryCapacity
๊ฐ ํด ๋๋งURLCache
์inmemory Policy
๊ฐ ์ ์ฉ๋๋ค๊ณ ์ถ์ธกํ์ต๋๋ค.
ํ ํ๊ณ
์ถํ ์์ฑ ์์
์ถํ ์์ฑ ์์
๐ Developer Apple
- XCTFail
- URLSession
- Fetching Website Data into Memory
- Escaping Closures
- UICollectionView
- Modern cell configuration
- Lists in UICollectionView
- Implementing Modern Collection Views
- UIAlertController
- Hashable
- Sendable
- Bundle
- NSCache
- URLCache
- URLRequest.CachePolicy
- URLCache.StoragePolicy
- UICalendarView
- UICalendarView.Decoration
- addTarget
- addAction
- UIRefreshControl
๐ Blog
- ๐ณ Cache
- ๐ณ NSCache vs URLCache
- ์ด๋ฏธ์ง ์บ์ ์ฒ๋ฆฌ์ NSCache
- URLSession Cahce Policy
- SwiftUI : @escaping
- XCTAssert Failure Messages
- ์์ธ์ฒ๋ฆฌ (throws, do-catch, try) ํ๊ธฐ
- do-try-catch ์ ๋ํ ์คํธ ํ๊ธฐ ์ํ ์ฝ๋
- Xcode13 HTTP ํต์ ๋ฐฉ๋ฒ
- DiffableDataSource
- UIActivityIndicator
- UIActivityIndicatorView
- ์ผ์นํ๋ ๋ชจ๋ ๋ฌธ์์ด์ Attribute๋ฅผ ๋ฐ๊พธ๊ณ ์ถ์ ๋
- github์ ์ฌ๋ฆฌ๋ฉด ์๋๋ APIKEY ์จ๊ธฐ๊ธฐ
- Dynamic Type์ ์ง์ํ๋, weight๋ ์ปค์คํ ํ๊ธฐ
- ๋ฌ๋ ฅ UICalendarView Custom ์์