UY | ์ฐ์ง |
---|---|
![]() |
![]() |
- ์๋ฒ API๋ฅผ ์ด์ฉํ์ฌ ๋ฆฌ๋ทฐ ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๊ณ , ์๋ก์ด ๋ฆฌ๋ทฐ๋ฅผ ์์ฑ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ํ๋ฉด์ ํค๋ณด๋ ์ํ ๋ฒํผ๋ค์ ๋ฐฐ์นํด์, ๋๋ฅด๋ฉด ํ๊ธ ํค๋ณด๋์ฒ๋ผ ์กฐํฉ๋๋ ํ๊ธ ์ฟผํฐ ํ๋ฉด์ ๊ตฌํํ์ต๋๋ค.
- Keyboard Extension์ ์ด์ฉํ์ฌ ์ค์ ํ๊ธ ํค๋ณด๋๋ฅผ ๋ง๋ค์ด๋ด ๋๋ค.
์ฑ ์ง์ ๊ณผ ๋ฆฌ๋ทฐ ๋ชฉ๋ก | ๋ฆฌ๋ทฐ ์์ฑ | ํค๋ณด๋ ์ ๋ ฅ | ๋ฆฌ๋ทฐ ์์ฑ ์๋ฃ์ ํฌ์คํ | ๋น์ด ์๋ ๋ฆฌ๋ทฐ ๊ฒฝ๊ณ |
---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
- ๊ฐ๋ฐ ๊ธฐ๊ฐ: 2022.07.11 ~ 2022.07.23 (2์ฃผ)
- ์ฌ์ฉ ๊ธฐ์ :
UIKit
,URLSession
,ExtensionKeyboard
,NSCache
,MVC
- MVC๋ฅผ ์ ํํ ์ด์
-
๊ท๋ชจ๊ฐ ํฌ์ง ์์ ํ๋ก์ ํธ์์ ๋ณด์ฌ์ค ๋ทฐ์ ์๊ฐ ๋ง์ง ์์
-
๊ธฐ๋ฅ์ ์ง๊ด์ ์ธ ๋ถ๋ฆฌ
-
๋ชจ๋ํ๋ฅผ ํตํ VC์ ์ฑ ์ ๋ถ์ฐ -> ๊ธฐ์กด MVC์ ๋จ์ ํด์
-
์์ด์ ํ๊ธ์ ์ฐจ์ด - ์กฐํฉํ์ธ๊ฐ ๋์ดํ์ธ๊ฐ?
=> ์๊ณ ๋ฆฌ์ฆ์ ๋ง๋ค์ด ์กฐํฉํ์!! -
ํต์ฌ - stage์ ๋ฐ๋ฅธ ์กฐํฉ
struct HangulKeyboardData {
enum HangulState: Int {
case empty = 0
case cho = 1
case doubleCho = 2
case jung = 3
case doubleJung = 4
case jong = 5
case doubleJong = 6
}
var hangul: String = ""
var unicode: Int = 0
var bornState: HangulState = .empty
...
}
switch processingBuffer.currentState {
case .empty:
processingBuffer = emptyStage(status: processingBuffer, input: inputData)
case .cho:
processingBuffer = singleChoStage(status: processingBuffer, input: inputData)
case .doubleCho:
processingBuffer = doubleChoStage(status: processingBuffer, input: inputData)
case .jung:
processingBuffer = singleJungStage(status: processingBuffer, input: inputData)
case .doubleJung:
processingBuffer = doubleJungStage(status: processingBuffer, input: inputData)
case .jong:
processingBuffer = singleJongStage(status: processingBuffer, input: inputData)
case .doubleJong:
processingBuffer = doubleJongStage(status: processingBuffer, input: inputData)
}
func [์์๋ณ ์คํ
์ด์ง] (ํ์ฌ ์คํ
์ด์ง, ํค๋ณด๋ ์
๋ ฅ) -> ์ํ๊ฐ[ํ์ฌ์ํ, ๊ธ์๋ฐฐ์ด, ๋ชจ๋] {
...
alphaRepository.append(๋ค์ด์จ ํค๋ณด๋ ๋ฐ์ดํฐ ์ถ๊ฐ)
if ์
๋ ฅ๊ฐ์ด ์ด์ฑ์ธ์ง ์ค์ฑ์ธ์ง ์ข
์ฑ์ธ์ง {
...
์ํ.๋ชจ๋ = ์ด์ค ์๋ชจ or ์๋ฃ์ฌ๋ถ
์ํ.์คํ
์ด์ง = ๋ค์ด์จ ํค๋ณด๋ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ๋ค์ ์ด๋ ํ stage๊ฐ ๊ฒฐ์ ๋๋ค
...
}
if ์ญ์ ์ธ์ง {
๋ชจ๋ = ์ญ์
}
return currentStatus
}
โ ๋คํธ์ํฌ ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์ ๋ ํจ์จ์ ์ธ network layer๋ฅผ ๋ง๋ค ์ ์์์ง์ ๋ํ ๊ณ ๋ฏผ๊ณผ ์๋ฌ ๋ฐ ์์ธ ์ฒ๋ฆฌ์ ๋ํ ๊ณ ๋ฏผ
โ ์ฌ๋ฌ ์๋ ํ URL, NetworkError, HTTPMethod, URLSession, URLRequest, API, Resource ๋ฑ์ผ๋ก ๋๋์ด ๊ตฌํ
โ ConstanURLย : โGETโ, โPOSTโ ํต์ ์ ํ๋ URL ๋ฑ์ ์ง์ ํ๋ ๋ณ๋ ํ์ผ
โ NetworkError : ๋คํธ์ํฌ ๋ฐ ์๋น์ค ๊ด๋ จ ์ค์ ํ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋๋ก ์์ฑ
โ Resource : Encodable, Decodable type์ Genericํ๊ฒ ์
๋ ฅ๋ฐ์ ์ ์๋๋ก ์์ฑ
โ HTTPMethod : HTTPMethod๋ฅผ enum type ์ผ๋ก ์ ๋ฌ
โ URLSession : URLSession์ request๋ฅผ Resource์ ๋ง์ถฐ requestํ ์ ์๋๋ก, upload, load ํจ์ ์์ฑ
โ API : Singleton ๋ฐฉ์์ผ๋ก API ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๊ด๋ฆฌํ๊ณ ํต์ ์ ์๋ํ๋ ๊ฐ์ฒด
โ ํ์ฌ URL์ด ์ ์ด URL์ฃผ์ ์ ์ฒด๋ฅผ ์ ์ฉํ์ผ๋ ์ถํ ๋ง์ ์์ URL์ฃผ์๊ฐ ์์ ์
URL์ scheme, host, path, parameter(questyString) ๋ฑ์ผ๋ก ๋๋์ด ๊ตฌํํ๋ ๋ฐฉ๋ฒ๋ ์ ์ฉํด๋ณด๋ ๊ฒ๋ ์ข์ ๊ฒ ๊ฐ์
โ ์ฒ์ ๋คํธ์ํฌ ๊ตฌํ ์ init(contesntsOf: url)๋ฉ์๋ ์ฌ์ฉ
โ init(contesntsOf: url) ๋ฉ์๋๋ ๋๊ธฐ์ ์ผ๋ก ์๋ํด ํ์ฌ ์์
์ค์ธ ์ค๋ ๋์ ๋ชจ๋ ์์
์ ํด๋น ์์
์ ์ํํ๋ ๋์ ๋ฉ์ถ๊ฒํ ์ํ์ด ์์ด
DispatchQueue.global().async๋ฅผ ํตํด ์ค๋ ๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด๋ GCD์ ์ ํ๋ ์์
์ค๋ ๋ ์ค ํ๋๋ฅผ ๋ฌถ๋ ๊ฒ์ด ๋์ด ์ง์ ์ ์ด์ง ์์๋
๊ฐ์ ์ ์ผ๋ก ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์ด ๊ถ์ฅํ์ง ์์
โ URLSession์์๋ ์ค๋ฅ๊ฐ ๋คํธ์ํฌ ์ค๋ฅ์ธ์ง, HTTP ์ค๋ฅ์ธ์ง, contents ์ค๋ฅ ์ธ์ง ๋ฑ์ ํํ ์ ์๋ ๋ฐ๋ฉด
init(contentsOf:)์์๋ ์ด๋ฅผ ํ์ธํ ์ ์์
โ URLSession์ผ๋ก ๋ณ๊ฒฝ
-
- TableView Cell ์ฌ์ฌ์ฉ์ผ๋ก ์ธํด ์คํฌ๋กค ์ ์ด๋ฏธ์ง๊ฐ ๋ง์ง ์๋ ๊ฒฝ์ฐ ๋ฐ์
- prepareForReuse() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ์์
override func prepareForReuse() {
profileImageView.image = UIImage(systemName: "person.crop.circle.fill")
}
๋๋ถ๋ถ์ ์ธ๊ตญ์ด๋ ๊ธ์๋ฅผ ์กฐํฉํ๋๊ฒ ์๋ ๋์ด ํ๋ ํ์์ด๋ค.
UIInputViewContoller์ textDocumentProxy๋ ํค๋ณด๋์ ๊ธ์๋ฅผ ์
๋ ฅ ๋ฐ์ UIKeyInput ํ๋กํ ์ฝ์์ ์ ๊ณตํ๋ inserText ๋ฉ์๋๋ฅผ ํตํด ์ ์ฅํ๋ค.
์ด ๋๋ฌธ์ ์ง์ ์ ์ธ text์ ์ฅ์์ ์ ๊ทผ์ ๋ถ๊ฐํ๋ฉฐ, ์ค๋ก์ง insert ๋๋ delete๋ง ๊ฐ๋ฅํ๋ค.
๋๋ฌธ์ ๊ธฐ์กด text๋ฅผ ์๋ก ๋ง๋ text๋ก ๋์ฒดํ๋ ๋ฐฉ์์ ์คํ ๋งํ๋ก keyboard extension์ ๊ตฌํํ ๊ฒฝ์ฐ text๊ฐ ์ค๋ณต๋์ด ์์ด๋ ํ์์ด ๋ฐ์ ํ๋ค.
- ํด๊ฒฐ
์คํ ๋งํ๋ฅผ ์ด์ ๋ง๊ฒ ์์ ํ์ฌ ํค๋ณด๋๋ฅผ ์
๋ ฅ ๋ฐ์๋ ๋ง๋ค ์ด์ ๋ง๊ฒ ๊ธฐ์กด์ ์๋ ๊ธ์๋ฅผ ์ง์ฐ๊ณ ์๋ก์ด ๊ธ์๋ฅผ ์ฝ์
ํด์ค ์๋ ์์ง๋ง,
textDocumentProxt์ hasText๋ฅผ ํตํด ๊ธ์๋ฅผ ์ง์ดํ ์ถ๋ ฅ ๋๋ text๋ฅผ ๋ค์ ์ฝ์
ํด์ฃผ๋ ๋ฐฉ์์ ์ฑํํ๋ค.
while textDocumentProxy.hasText {
textDocumentProxy.deleteBackward()
}
์คํ ๋งํ์ ๊ธฐ์กด ๋ฐฐ์ด์์์ ์ข
์ฑ๊ณผ ์ด์ค์ข
์ฑ์ ๊ตฌ๋ณํ ์ ์์ด ์ญ์ ์ ์ด๋ ค์์ ๊ฒช๊ณ ์๋ ๋์ค ๋ชจ๋ ์์๋ฅผ ์ ์ฅ ํ ๋ฐฐ์ด์ ํ๋ ๋ ์ถ๊ฐํ์ฌ
์์ฑ๋ ํ๊ธ์ ๋ถํดํ์ฌ ๋น๊ต๋ฅผ ํตํด ํด๊ฒฐ ํ์๋ค.
[ํ๊ธ ์คํ ๋งํ ๊ตฌํ ์ฝ๋] https://github.com/ScutiUY/ios-wanted-CustomKeyboard/blob/fix/automata/CustomKeyboard/Hangul/KeyboardMaker.swift
- ์ด๋ฏธ์ง๋ก๋ Data(contentsOf: url?) โ URLsession ์ผ๋ก ๋ณ๊ฒฝ
// ๋ณ๊ฒฝ ์
if let data = try? Data(contentsOf: imageUrl) {
guard let image = UIImage(data: data) else { return }
self.imageCache.setObject(image, forKey: imageUrl.lastPathComponent as NSString)
DispatchQueue.main.async {
complition(.success(image))
} else {
DispatchQueue.main.async {
complition(.failure(ImageLoaderError.noImage))
}
// ๋ณ๊ฒฝ ํ
guard let imageUrl = URL(string: url) else { return }
let session = URLSession(configuration: .ephemeral)
let task = session.dataTask(with: imageUrl) { data, response, error in
if let error = error {
completion(.failure(NetworkError.networkError(error)))
} else {
guard let httpResponse = response as? HTTPURLResponse else { return }
guard 200..<300 ~= httpResponse.statusCode else { completion(.failure(SevericeError.noReponseError))
return
}
if let data = data {
guard let image = UIImage(data: data) else { return }
ImageLoder.imageCache.setObject(image, forKey: url as NSString)
DispatchQueue.main.async {
completion(.success(image))
}
} else {
DispatchQueue.main.async {
completion(.failure(NetworkError.invalidData))
}
}
- UIImage ๊ด๋ จ string โ enum Type ์ผ๋ก ๊ด๋ฆฌ ๋ฐ ๋ณ๊ฒฝ
// ๋ณ๊ฒฝ ์
profileImageVIew.image = UIImage(systemName: "person.crop.circle.fill")
// ๋ณ๊ฒฝ ํ
profileImageVIew.image = Icon.personFill.image
- ๋ฆฌ๋ทฐ ์๊ฐ๋ณํ Cell์์ ๊ตฌํ โ Class ๊ฐ์ฒด ๋ฐ ๋ฐ์ดํฐ๋ชจ๋ธ์์ ๋ณ๊ฒฝ
// ๋ณ๊ฒฝ ์
// ReviewTableViewCell.swift
guard let reviewDate = data.createdAt.stringToDate else { return }
if reviewDate > Date(timeIntervalSinceNow: -86400) {
timeLabel.text = reviewDate.dateToRelativeTimeString
} else {
timeLabel.text = reviewDate.dateToOverTimeString
}
// ๋ณ๊ฒฝ ํ
// ReviewDateConverter.swift
class ReviewDateConverter {
func convertReviewDate(rawData: String) -> String {
if rawData.stringToDate ?? Date() > Date(timeIntervalSinceNow: -86400) {
return rawData.stringToDate?.dateToRelativeTimeString ?? rawData
} else {
return rawData.stringToDate?.dateToOverTimeString ?? rawData
}
}
}
// ReviewData.swift
let date = try values.decode(String.self, forKey: .createdAt)
createdAt = ReviewDateConverter().convertReviewDate(rawData: date)