π± μ§λλ₯Ό ν΅ν΄ μ£Όλ³μ μμΉλ€μ νμΈνκ³ , μ€ν°λλ₯Ό μμ² λ° 1:1 μ±ν μ νλ μλΉμ€
Service Level Project
- μ€μ μ
무 νκ²½κ³Ό λμΌν νλ‘μΈμ€λ‘ μ§νλλ κ°μΈ νλ‘μ νΈ
- μλΉμ€ λ 벨μ API/κΈ°ν λͺ
μΈμμ λμμΈ λ¦¬μμ€λ₯Ό μ λ¬ λ°μμ μ± κ°λ° μ§ν
- 2022.11.07 ~ 2022.12.07
- κΈ°ν λͺ μΈμ : Conference
- λμμΈ μμ€ν λ° UI : Figma
- μλ² API : Conference/Swagger
- ν μ§μμλ΅ : Zep
- MVC, MVVM(Input/Output) Pattern
- Swift, UIKit
- AutoLayout, Snapkit, Then
- RxSwift
- Alamofire (Alamofire+URLRequestConvertible)
- CLLocation, MapKit
- Firebase
- Push Notification
- Login (μ νλ²νΈ λ‘κ·ΈμΈ/jwt Token)
- CompositionalLayout, DiffableDataSource
- ν ν° λ§λ£ (jwt token λ§λ£μ, 401 errorμ λν λμ)
func refreshIdToken(completion: @escaping (Result<String, Error>) -> Void) {
Auth.auth().currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
if let error = error {
completion(.failure(error))
} else {
guard let idToken = idToken else { return }
UserData.idtoken = idToken
completion(.success(idToken))
}
}
}
- enumμΌλ‘ API Error λΆκΈ°μ²λ¦¬
@frozen
enum APIError: Int, Error {
case takenUser = 201
case invalidNickname = 202
case invalidAuthorization = 401
case unsubscribedUser = 406
case serverError = 500
case emptyParameters = 501
}
extension APIError: LocalizedError {
var errorDescription: String? {
switch self {
case .takenUser:
return "μ΄λ―Έ κ°μ
ν μ μ μ
λλ€."
case .invalidNickname:
return "μ ν¨νμ§ μμ λλ€μμ
λλ€."
case .invalidAuthorization:
return "ν ν°μ΄ λ§λ£λμμ΅λλ€. λ€μ λ‘κ·ΈμΈ ν΄μ£ΌμΈμ."
case .unsubscribedUser:
return "μμ§ κ°μ
νμ§ μμ μ μ μ
λλ€."
case .serverError:
return "μλ² μλ¬μ
λλ€. μ μ ν μ΄μ©ν΄μ£ΌμΈμ."
case .emptyParameters:
return "request header/bodyλ₯Ό νμΈν΄μ£ΌμΈμ."
}
}
}
private func checkIdToken() {
GenericAPI.shared.requestDecodableData(type: Login.self, router: UserRouter.login) { [weak self] response in
print("κΈ°μ‘΄ - ", UserData.idtoken)
guard let self = self else { return }
switch response {
case .success(let data):
UserData.nickName = data.nick
Helper.convertNavigationRootViewController(view: self.view, controller: TabBarViewController())
case .failure(let error):
switch error {
case .takenUser, .invalidNickname:
return
case .invalidAuthorization:
UserAPI.shared.refreshIdToken { result in
switch result {
case .success(let idtoken):
print("κ°±μ - ", UserData.idtoken)
self.refreshToken(idtoken)
case .failure(let error):
print(error.localizedDescription)
return
}
}
case .unsubscribedUser:
Helper.convertNavigationRootViewController(view: self.view, controller: NicknameViewController())
case .serverError:
self.showToast(message: "μλ² λ΄λΆ μ€λ₯μ
λλ€. μ μ ν μ¬μΈμ¦ ν΄μ£ΌμΈμ.")
case .emptyParameters:
self.showToast(message: "Client Error")
}
}
}
}
- λ΄ μνμ λ°λ₯Έ μ§λ νλ©΄ Floating Button νμ λ° νλ©΄μ ν λΆκΈ°μ²λ¦¬
enum MDSFloatingButtonType {
case plain
case matching
case matched
var image: UIImage {
switch self {
case .plain:
return Constant.Image.search
case .matching:
return Constant.Image.antenna
case .matched:
return Constant.Image.message
}
}
}
floatingButton.rx.tap
.withUnretained(self)
.bind { vc, _ in
if vc.floatingButton.type == .plain {
let viewController = StudyViewController()
viewController.viewModel.mapLatitude.accept(vc.mapLatitude)
viewController.viewModel.mapLongitude.accept(vc.mapLongitude)
vc.navigationController?.pushViewController(viewController, animated: true)
} else if vc.floatingButton.type == .matching {
// 맀μΉμ€
let studyViewController = StudyViewController()
let searchViewController = SearchSesacViewController()
searchViewController.mapLatitude = vc.mapLatitude
searchViewController.mapLongitude = vc.mapLongitude
searchViewController.stateType = .matching
vc.navigationController?.pushViewControllers([studyViewController, searchViewController], animated: false)
} else {
// 맀μΉλ > μ±ν
νλ©΄μΌλ‘ μ΄λ
let viewController = ChatViewController()
vc.navigationController?.pushViewController(viewController, animated: true)
}
}
.disposed(by: disposeBag)
π€ λ€λΉκ²μ΄μ μΌλ‘ νλ©΄μ΄ μ°κ²°λμ΄ μμ λ, νλ²μ μ¬λ¬ νλ©΄μ pushνκ³ μΆλ€λ©΄?
extension UINavigationController {
func pushViewControllers(_ inViewControllers: [UIViewController], animated: Bool) {
var stack = self.viewControllers
stack.append(contentsOf: inViewControllers)
self.setViewControllers(stack, animated: animated)
}
}
- λμ λ μ΄μμ ꡬν Card Typeμ λ°λΌμ 보μ¬μ§λ μμκ° λ€λ₯΄λ―λ‘, enumμΌλ‘ caseλ₯Ό λλ μ κ΄λ¦¬
@frozen
enum CardViewType {
case plain
case info
}
SLP νλ‘μ νΈλ₯Ό ν΅ν΄ μ€μ μλΉμ€(νμ¬) κ·λͺ¨μ κΈ°ν, λμμΈ, μλ²λ₯Ό νμΈνκ³ κ·Έμ λ§κ² κΈ°μ μ€νμ μ ν λ€ μΌμ μ λ§κ² νλ‘μ νΈλ₯Ό ꡬννλ κ²½νμ ν μ μμλ€. λͺ¨λ κΈ°νκ³Ό λμμΈ, μλ²κ° νμ λ μνλ‘ κ°λ°μ μμν κ²μ΄ μλλΌ, κ°λ°μ μ§ννλ λμ€μ μλ²κ° λ³κ²½λκΈ°λ νκ³ κΈ°νμ΄ λ μΆκ°λκΈ°λ νλ μν© μμμ μ μ°νκ² λμ²νλ©° κ°λ°μ μ§ννλ€. μ΄ κ³Όμ μμμ μ½λλ₯Ό μ¬μ¬μ©ν μ μκ² λΆλ¦¬νλ κ²κ³Ό λ‘μ°ν κ°μ μ¬μ©νμ§ μμμΌ νλ€λ κ²μ κΉ¨λ¬μλ€.
Figmaμ κΈ°νλͺ μΈμλ₯Ό 보면μ μ¬μ¬μ©ν μ μλ λΆλΆλ€μ΄ μλ€λ©΄ λ°λ‘ λμμΈμμ€ν μΌλ‘ κ΄λ¦¬νκ±°λ λͺ¨λΈλ‘ κ΄λ¦¬νλ μ μ°ν μ¬κ³ λ₯Ό λ°°μ λ€. νΉν, λΉμ·ν UIλ‘ λμλλ νλ©΄λ€μ΄ λ§μκΈ° λλ¬Έμ μ΅λν λΆλ¦¬ν΄μ enumμΌλ‘ caseλ₯Ό λλ κ΄λ¦¬νμλ€.
Firebase μ νλ²νΈ λ‘κ·ΈμΈ, WebSocketμ μ΄μ©ν 1:1 μ±ν
, IAP λ±μ μλ‘μ΄ κΈ°λ₯/κΈ°μ μ ꡬννλ©΄μ λ€μν 곡λΆλ₯Ό ν μ μμλ€.
μ±ν
μ κ²½μ° μμΌκ³Ό μΈμ μ°κ²°μ νκ³ ν΄μ νλ©°, Realmμ κΈ°λ°μΌλ‘ μ±ν
λ΄μμ μ μ₯ λ° λΆλ¬μ€κ³ λ€μ μλ‘μ΄ μ±ν
μ μΆκ°νλ λ±μ μ λ°μ μΈ νλ‘μΈμ€λ₯Ό λ°°μΈ μ μμλ€.
μ½λλ₯Ό κΉλνκ² μμ±νλ κ²λ μ€μνμ§λ§, μ΄λ νλ‘μ νΈμ λ§μ°¬κ°μ§λ‘ κΈ°νμ΄ μκΈ° λλ¬Έμ λμμ΄ λκ³ μμ±λκ° μλ κ²°κ³Όλ¬Όμ λ΄μΌ νλ€λ μ λ μμ§ μμΌλ©΄μ μμ μ μ§ννλ€. κ·Έλ κΈ°μ, λμμ΄ λλλ‘ κ΅¬ννκ³ κ°μ νλ κ³Όμ μ λ°λ³΅νλ©΄μ μ§λλ₯Ό λκ°λ€. κ·ΈλΌμλ μ½λκ° λ μ 리λ λΆλΆλ€μ΄ μμ΄ μμ¬μμ΄ λ¨λλ€.