Skip to content

ryuchanghwi/SparkleApp

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

  • ์—ฐ์ธ ๊ฐ„ ๋ฏธ์…˜ ์Šน๋ถ€๋ฅผ ํ†ตํ•œ ์†Œ์›๊ถŒ ๋‚ด๊ธฐ ์„œ๋น„์Šค ์•ฑ
  • ์ปคํ”Œ์ด ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์•ฑ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๋ช…์ด ์„œ๋กœ ์—ฐ๊ฒฐํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ์•ฑ์ž…๋‹ˆ๋‹ค.
  • ํ•ด๋‹น ์•ฑ์˜ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์œ„ํ•ด SPM์„ ํ™œ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋””์ž์ธ ์‹œ์Šคํ…œ ๋ฆฌ๋“œ๋ฏธ ๋งํฌ

๐Ÿ”— ์•ฑ ๋‹ค์šด๋กœ๋“œ ๋งํฌ

ย ย ย ย ย ย ย ย 

  • ์ง„ํ–‰ ๊ธฐ๊ฐ„
    • ๊ฐœ๋ฐœ : 2023.07 ~ 2023.12(์ง„ํ–‰์ค‘)
  • ์ถœ์‹œ
    • 1.0.0 : 2023.12.11
    • 1.0.1 : 2023.12.22(ํƒ€์ด๋จธ, ๋กœ๊ทธ์ธ ์ˆ˜์ •)
  • ๊ธฐ์ˆ  ์Šคํƒ
    • iOS : UIKit, SwiftUI, Combine, SwiftLint
    • Deployment Target : iOS 15.0

Architecture

MVC -> MVVM + Clean Architecture(์ง„ํ–‰ ์ค‘)

  • ์žฆ์€ ๊ธฐํš ๋ณ€๊ฒฝ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ๋“ฑ ์—…๋ฌด ํšจ์œจ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋„์ž…์ค‘์ž…๋‹ˆ๋‹ค.

Domain

Entity

  • ์„œ๋น„์Šค์— ์“ฐ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„์™€์˜ ์†Œํ†ต ์˜ค๋ฅ˜๋กœ ๋Ÿฐํƒ€์ž„์—๋Ÿฌ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์˜ต์…”๋„๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
struct BattleHistoryResultDTO: Codable {
    let roundGameId: Int?
    let date: String?
    let result: String?
    let title: String?
    let image: String?
    let winner: String?
    let myMission: MyMission
    let partnerMission: PartnerMission
}

UseCase

  • Entity๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
protocol BattleHistoryResultUseCaseProtocol {
    func execute() -> AnyPublisher<[BattleHistoryResultDTO], ErrorType>
}

final class BattleHistoryResultUseCase: BattleHistoryResultUseCaseProtocol {
    private let battleHistoryResultRepository: BattleHistoryResultRepositoryInterface
    init(battleHistoryResultRepository: BattleHistoryResultRepositoryInterface) {
        self.battleHistoryResultRepository = battleHistoryResultRepository
    }
    func execute() -> AnyPublisher<[BattleHistoryResultDTO], ErrorType> {
        return self.battleHistoryResultRepository.data()
    }
}

Repository Interface

  • ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ์˜ ๋” ์•ˆ์ชฝ์— ์œ„์น˜ํ•œ Domain ์˜์—ญ์ด Data ์˜์—ญ์˜ Repository์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์˜์กด์„ฑ ์—ญ์ „๋ฅผ ํ†ตํ•ด ๋ ˆํผ์ง€ํ† ๋ฆฌ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
protocol RoundBattleMissionRepositoryInterface {
    func data(roundId: Int) -> AnyPublisher<RoundBattleMissionDTO, ErrorType>
}

Data

Repository

  • ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘์ ์œผ๋กœ ํš๋“ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ํš๋“ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์žฅ์„ฑ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
final class RoundBattleMissionRepository: RoundBattleMissionRepositoryInterface {
    private let service: GetServiceCombine
    init(service: GetServiceCombine) {
        self.service = service
    }
    func data(roundId: Int) -> AnyPublisher<RoundBattleMissionDTO, ErrorType> {
        self.service.getService(from: Config.baseURL + "api/game/short/\(roundId)", isUseHeader: true)
    }
}

Presentation

View

  • ๊ธฐ์กด MVC ํŒจํ„ด ์‹œ UIViewController์˜ loadView ์ƒ๋ช…์ฃผ๊ธฐ์— UIView๋ฅผ ๋ฐ”๊ฟ”์ฃผ์–ด ViewController์˜ ๋ ˆ์ด์•„์›ƒ ์ฝ”๋“œ๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.
    // MARK: - Life Cycle
    override func loadView() {
        super.loadView()
        historyView = HistoryView(frame: self.view.frame)
        self.view = historyView
    }
  • ํ˜„์žฌ๋Š” UIHostingController๋ฅผ ์‚ฌ์šฉํ•ด SwiftUI๋กœ UI๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    // MARK: - Setting
    override func setConfig() {
        battleHistoryHostingController = UIHostingController(rootView: BattleProgressView(data: battleHistoryViewData))
        self.addChild(battleHistoryHostingController)
        view.addSubview(battleHistoryHostingController.view)
        battleHistoryHostingController.didMove(toParent: self)
    }
    override func setLayout() {
        battleHistoryHostingController.view.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }

ViewModel

  • SwiftUI์™€ UIKit์— ๋ชจ๋‘ ๋น„๋™๊ธฐํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ ์šฉํ•˜๊ณ ์ž Combine์„ ํ•™์Šต ์ค‘์ž…๋‹ˆ๋‹ค.
  • UIKit์˜ ViewModel์€ Input/Output Modeling์„ ํ†ตํ•ด View๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋œ ์ด๋ฒคํŠธ๋Š” Input, View๋กœ ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ๋Š” Output์„ ํ†ตํ•ด Bindingํ•ฉ๋‹ˆ๋‹ค.
struct BattleHistoryDetailViewModel {
    struct Input {
        let viewLoad: AnyPublisher<BattleHistoryResultDTO, Never>
    }
    struct Output {
        let historyDetailData: AnyPublisher<BattleHistoryDetailItemViewData, Never>
    }
    func transform(input: Input) -> Output {
        let historyData = input.viewLoad.map {
            return BattleHistoryDetailItemViewData(battleHistoryItem: $0)
        }.eraseToAnyPublisher()
        return Output(historyDetailData: historyData)
    }
}
  • SwiftUI์˜ ViewModel์€ ObservableObject์™€ @Publishedํ”„๋กœํผํ‹ฐ ํŒจํ„ด๋ฅผ ํ™œ์šฉํ•ด Bindingํ•ฉ๋‹ˆ๋‹ค.
final class MyPageViewModel: ObservableObject {
    @Published var myPageName: String = ""
    @Published var myPageDate: String = ""
    @Published var startDate: String = ""
    private var cancellables: Set<AnyCancellable> = []
    private let myPageGetUseCase: MyPageGetUseCaseProtocol
    private let navigationController: UINavigationController
    private let viewController: UIViewController
    init(myPageGetUseCase: MyPageGetUseCaseProtocol, navigationController: UINavigationController, viewController: UIViewController) {
        self.myPageGetUseCase = myPageGetUseCase
        self.navigationController = navigationController
        self.viewController = viewController
    }
    // MARK: - Custom Method
    func getMyPageData() {
        myPageGetUseCase.excute().sink { [weak self] completion in
            guard let self = self else { return }
            switch completion {
            case .failure(let errorType):
                errorResponse(status: errorType)
            case .finished:
                break
            }
        } receiveValue: { [weak self] data in
            guard let self = self else { return }
            self.myPageDate = MyPageGetItemViewData(myPageGetItem: data).date
            self.myPageName = MyPageGetItemViewData(myPageGetItem: data).name
            self.startDate = data.startDate ?? ""
        }
        .store(in: &cancellables)
    }
}

ItemViewData

  • Entity๋ฅผ View์—์„œ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
struct BattleHistoryItemViewData {
    let date: String
    let gameTitle: String
    let imagePath: URL?
    let result: String
    let battleHistoryItem: BattleHistoryResultDTO
    init(battleHistoryItem: BattleHistoryResultDTO) {
        self.battleHistoryItem = battleHistoryItem
        self.date = battleHistoryItem.date ?? ""
        self.gameTitle = battleHistoryItem.title ?? ""
        self.imagePath = URL(string: battleHistoryItem.image ?? "")
        switch battleHistoryItem.result {
        case "DRAW":
            self.result = "๋ฌด์Šน๋ถ€"
        case "WIN":
            self.result = "์Šน๋ฆฌ"
        case "LOSE":
            self.result = "ํŒจ๋ฐฐ"
        default:
            self.result = battleHistoryItem.result ?? ""
        }
    }
}

โš ๏ธTrouble Shooting

๋ฌธ์ œ์ 

SwiftUI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ํ™”๋ฉด์ด ๋งค๋„๋Ÿฝ์ง€ ์•Š๊ฒŒ ๋Š๊ปด์ง€๋Š” ๊ฒฝ์šฐ๋ฅผ ์ข…์ข… ๋ฐœ๊ฒฌํ•จ. ์ด์— ๋”ฐ๋ผ SwiftUI์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•œ ๊ณ ๋ฏผ์˜ ํ•„์š”์„ฑ์„ ๋Š๋‚Œ

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. ๋ทฐ๋ฅผ ํ•˜์œ„ ๋ทฐ๋กœ ๋‚˜๋ˆ„๊ธฐ
  • ๋ทฐ๋ฅผ ํ•˜๋‚˜์˜ ๋ทฐ๋กœ ์ž‘์„ฑํ•œ๋‹ค๋ฉด ๊ทธ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๊ฐ’์ด ๋ณ€๊ฒฝ๋จ์— ๋”ฐ๋ผ ๋ทฐ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 10๊ฐœ์˜ ๋ฒ„ํŠผ์ด ์žˆ์œผ๋ฉด ๊ฐ ๋ฒ„ํŠผ์ด ๋ˆŒ๋ฆด ๋•Œ๋งˆ๋‹ค ๊ฐ๊ฐ์˜ ๋ฒ„ํŠผ์— ํ•ด๋‹นํ•˜๋Š” ๋ทฐ๋งŒ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ฒŒ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ•˜๋‚˜์˜ ์ „์ฒด ๋ทฐ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๊ฒŒ ๋˜๋ฉด ์„ฑ๋Šฅ์˜ ์ฐจ์ด๊ฐ€ ๋‚˜๊ฒŒ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์ƒ๊ด€์—†๋Š” ๋ทฐ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๋Š” ๊ฒƒ์„ ๋ง‰๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.

ย ย 

  • ์•„๋ž˜์˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ํ•˜์œ„ ์ปจํ…Œ์ด๋„ˆ ๋ทฐ๋กœ ๋‚˜๋ˆ„๊ฒŒ ๋˜๋ฉด ์ƒ์œ„ ๋ทฐ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค์‹œ ๊ทธ๋ ค์ง€๋Š” ๊ฒƒ๊ณผ ๋ณ„๊ฐœ๋กœ ๋‹ค์‹œ ๋ทฐ๊ฐ€ ๊ทธ๋ ค์ง€์ง€ ์•Š๊ธฐ ๋–„๋ฌธ์— ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์œ„ ๋ทฐ ๋ถ„๋ฆฌ๋กœ ๊ฐœ์„ ํ•œ ์ฝ”๋“œ

struct TimerTimeTextView: View {
    // MARK: - Property
    let text: String
    // MARK: - UI Property
    var body: some View {
        Text(text)
            .font(Font(SDSFont.body1.font))
            .foregroundColor(Color(.gray600))
            .background(.random)
    }
}
  1. ๋ทฐ๊ฐ€ ๊ผญ ํ•„์š”ํ•œ ์ข…์†์„ฑ๋งŒ์„ ๊ฐ–๊ฒŒ ํ•˜๊ธฐ
  • ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ข…์†์„ฑ์„ ์ฒดํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
let _ = Self._printChanges()

ย ย 

  • ObservableObject์— ์ข…์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ ํƒ€์ด๋จธ๊ฐ€ ํ๋ฆ„์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ™”๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ข…์†์„ฑ์„ ์ตœ์†Œํ™” ํ•˜๋ฉด์„œ ๊ฐœ์„ ํ•œ ์ฝ”๋“œ

  TimerProgressView(remainingTime: timerData.remainingTime, totalTime: timerData.totalTime, isTimerRunning: timerData.isTimerRunning)
     .padding(.horizontal, 24)
     .padding(.top, 32)
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ทฐ๊ฐ€ ObservableObject์— ์ข…์†์„ฑ์„ ๊ฐ€์ง€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ์„ ๊ฐ€์ง€๊ฒŒ ๋จ์œผ๋กœ์จ ์—…๋ฐ์ดํŠธ ํšŸ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Todo

  • ๊พธ์ค€ํžˆ MVCํŒจํ„ด์—์„œ MVVM + Clean Architecture ํŒจํ„ด์œผ๋กœ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
  • ๊ฐ๊ฐ ๋ถ„๋ฆฌ๋œ ๋ ˆ์ด์–ด๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•ด๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ฑ ์ฃผ์š” ํ™”๋ฉด ๋ฐ ๊ธฐ๋Šฅ

๐Ÿ”– ๋กœ๊ทธ์ธ ํ”Œ๋กœ์šฐ - ์นด์นด์˜ค, ์• ํ”Œ๋กœ ๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ปคํ”Œ์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋‹ค๋ฉด ํ™ˆ ํ™”๋ฉด, ์—†๋‹ค๋ฉด ์ปคํ”Œ์„ ์—ฐ๊ฒฐํ•˜๋Š” ํ”Œ๋กœ์šฐ๋กœ ๋„˜์–ด๊ฐ€์š”.

๐ŸŽž๏ธ ์Šน๋ถ€ ์ƒ์„ฑ - ์ปคํ”Œ์ด ํ•จ๊ป˜ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒŒ์ž„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ฏธ ์ƒ๋Œ€๊ฐ€ ๊ฒŒ์ž„์„ ์ƒ์„ฑํ–ˆ๋‹ค๋ฉด ์ƒ์„ฑํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์•Œ๋žŒ์ด ๋‚˜ํƒ€๋‚˜์š”.

๐ŸŽž๏ธ ์Šน๋ถ€ ๊ฒฐ๊ณผ ํ™•์ธ - ์ƒ๋Œ€๋ฐฉ์˜ ๊ฒฐ๊ณผ์„ ๋น„๊ตํ•˜์—ฌ ์ŠนํŒจ๋ฅผ ์ •ํ•˜๊ณ  ์•„์ง ์Šน๋ถ€๊ฐ€ ๋‚˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์•Œ๋žŒ์ด ๋‚˜ํƒ€๋‚˜์š”.

๐ŸŽซ ์†Œ์›๊ถŒ ์‚ฌ์šฉ - ์Šน๋ถ€์—์„œ ์ด๊ฒผ๋‹ค๋ฉด ์†Œ์›๊ถŒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ““ ์Šน๋ถ€ ํžˆ์Šคํ† ๋ฆฌ ํ”Œ๋กœ์šฐ - ์ปคํ”Œ๊ณผ ํ•จ๊ป˜ ํ•œ ์Šน๋ถ€์˜ ๊ธฐ๋ก๋“ค์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”

โœ๏ธ ๋งˆ์ดํŽ˜์ด์ง€ ํ”Œ๋กœ์šฐ - ๋‚ด ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๊ณ  ๋กœ๊ทธ์•„์›ƒ, ์ปคํ”Œ ์—ฐ๊ฒฐ ๋Š๊ธฐ, ๊ณ„์ • ํƒˆํ‡ด๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด์š”.

Commit message

๐Ÿ”จ[FIX] : ๋ฒ„๊ทธ, ์˜ค๋ฅ˜ ํ•ด๊ฒฐ
โž•[ADD] : Feat ์ด์™ธ์˜ ๋ถ€์ˆ˜์ ์ธ ์ฝ”๋“œ ์ถ”๊ฐ€, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€, ์ƒˆ๋กœ์šด ํŒŒ์ผ ์ƒ์„ฑ ์‹œ
โœจ[FEAT] : ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๊ตฌํ˜„
โœ…[CHORE] : ์ฝ”๋“œ ์ˆ˜์ •, ๋‚ด๋ถ€ ํŒŒ์ผ ์ˆ˜์ •
โšฐ๏ธ[DEL] : ์“ธ๋ชจ์—†๋Š” ํŒŒ์ผ,์ฝ”๋“œ ์‚ญ์ œ
โ™ป๏ธ[REFACTOR] : ์ „๋ฉด ์ˆ˜์ •์ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
๐Ÿ”€[MERGE]: ๋‹ค๋ฅธ๋ธŒ๋ Œ์น˜๋ฅผ merge ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ ์ปจ๋ฒค์…˜

๐ŸŽ ๋„ค์ด๋ฐ

๐Ÿ’งํด๋ž˜์Šค, ๊ตฌ์กฐ์ฒด

  • UpperCamelCase ์‚ฌ์šฉ
// - example

struct MyTicketResponseDTO {
}

class UserInfo {
}

๐Ÿ’งํ•จ์ˆ˜

  • lowerCamelCase ์‚ฌ์šฉํ•˜๊ณ  ๋™์‚ฌ๋กœ ์‹œ์ž‘
// - example

private func setDataBind() {
}

๐Ÿ’ง๋ทฐ ์ „ํ™˜

  • pop, push, present, dismiss
  • ๋™์‚ฌ + To + ๋ชฉ์ ์ง€ ๋ทฐ (๋‹ค์Œ์— ๋ณด์ผ ๋ทฐ)
  • dismiss๋Š” dismiss + ํ˜„์žฌ ๋ทฐ
// - example pop, push, present

popToFirstViewController()
pushToFirstViewController()
presentToFirstViewController()

dismissFirstViewController()

๐Ÿ’งregister

  • register + ๋ชฉ์ ์–ด
// - example

registerXib()
registerCell()

๐Ÿ’ง์„œ๋ฒ„ ํ†ต์‹ 

  • ์„œ๋น„์Šคํ•จ์ˆ˜๋ช… + WithAPI
// - example

fetchListWithAPI()

requestListWithAPI()

fetch๋Š” ๋ฌด์กฐ๊ฑด ์„ฑ๊ณต

request๋Š” ์‹คํŒจํ•  ์ˆ˜๋„ ์žˆ๋Š” ์š”์ฒญ

๐Ÿ’ง์• ๋‹ˆ๋ฉ”์ด์…˜

  • ๋™์‚ฌ์›ํ˜• + ๋ชฉ์ ์–ด + WithAnimation
showButtonsWithAnimation()

๐Ÿ’ง๋ธ๋ฆฌ๊ฒŒ์ดํŠธ

delegate ๋ฉ”์„œ๋“œ๋Š” ํ”„๋กœํ† ์ฝœ๋ช…์œผ๋กœ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๊ตฌ๋ถ„

์ข‹์€ ์˜ˆ:

protocol UserCellDelegate {
  func userCellDidSetProfileImage(_ cell: UserCell)
  func userCell(_ cell: UserCell, didTapFollowButtonWith user: User)
}

protocol UITableViewDelegate {
    func tableview( ....) 
    func tableview...
}

protocol JunhoViewDelegate {
    func junhoViewTouched()
    func junhoViewScrolled()
}

Delegate ์•ž์ชฝ์— ์žˆ๋Š” ๋‹จ์–ด๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๋ฉ”์„œ๋“œ ๋„ค์ด๋ฐํ•˜๊ธฐ

๋‚˜์œ ์˜ˆ:

protocol UserCellDelegate {
    // userCellDidSetProfileImage() ๊ฐ€ ์˜ณ์Œ
  func didSetProfileImage()
  func followPressed(user: User)

  // `UserCell`์ด๋ผ๋Š” ํด๋ž˜์Šค๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ  (userCell ๋กœ ํ•ด์ฃผ์ž)
  func UserCell(_ cell: UserCell, didTapFollowButtonWith user: User)
}

ํ•จ์ˆ˜ ์ด๋ฆ„ ์•ž์—๋Š” ๋˜๋„๋ก์ด๋ฉด get ์„ ๋ถ™์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๐Ÿ’ง๋ณ€์ˆ˜, ์ƒ์ˆ˜

  • lowerCamelCase ์‚ฌ์šฉ
let userName: String

๐Ÿ’ง์—ด๊ฑฐํ˜•

  • ๊ฐ case ์—๋Š” lowerCamelCase ์‚ฌ์šฉ
enum UserType {
    case viewDeveloper
    case serverDeveloper
}

๐Ÿ’ง์•ฝ์–ด

์•ฝ์–ด๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ ์†Œ๋ฌธ์ž๋กœ ํ‘œ๊ธฐ, ๊ทธ ์™ธ์—๋Š” ํ•ญ์ƒ ๋Œ€๋ฌธ์ž

// ์ข‹์€ ์˜ˆ:
let userID: Int?
let html: String?
let websiteURL: URL?
let urlString: String?
// ๋‚˜์œ ์˜ˆ:
let userId: Int?
let HTML: String?
let websiteUrl: NSURL?
let URLString: String?

๐Ÿ’ง๊ธฐํƒ€ ๋„ค์ด๋ฐ

setUI() : @IBOutlet ์†์„ฑ ์„ค์ •
setLayout() : ๋ ˆ์ด์•„์›ƒ ๊ด€๋ จ ์ฝ”๋“œ
setDataBind() : ๋ฐฐ์—ด ํ•ญ๋ชฉ ์„ธํŒ…. ์ปฌ๋ ‰์…˜๋ทฐ ์—์„œ ๋ฆฌ์ŠคํŠธ ์ดˆ๊ธฐ ์„ธํŒ…ํ• ๋•Œ
setAddTarget() : addtarget ๋ชจ์Œ
setDelegate() : delegate, datasource ๋ชจ์Œ
setCollectionView() : ์ปฌ๋ ‰์…˜๋ทฐ ๊ด€๋ จ ์„ธํŒ…
setTableView() : ํ…Œ์ด๋ธ”๋ทฐ ๊ด€๋ จ ์„ธํŒ…
initCell() : ์…€ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
registerXib() : ์…€ xib ๋“ฑ๋ก.
setNotification() : NotificationCenter addObserver ๋ชจ์Œ

ํ—ท๊ฐˆ๋ฆฐ๋‹ค? set์„ ์“ฐ์„ธ์š” ^^
๐ŸŽ ์ฝ”๋“œ ๋ ˆ์ด์•„์›ƒ

๐Ÿ’ง๋“ค์—ฌ์“ฐ๊ธฐ ๋ฐ ๋„์–ด์“ฐ๊ธฐ

  • ๋“ค์—ฌ์“ฐ๊ธฐ์—๋Š” ํƒญ(tab) ๋Œ€์‹  4๊ฐœ์˜ space๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • ์ฝœ๋ก (:)์„ ์“ธ ๋•Œ์—๋Š” ์ฝœ๋ก ์˜ ์˜ค๋ฅธ์ชฝ์—๋งŒ ๊ณต๋ฐฑ์„ ๋‘ก๋‹ˆ๋‹ค.

    let names: [String: String]?

    let name: String

  • ์—ฐ์‚ฐ์ž ์˜ค๋ฒ„๋กœ๋”ฉ ํ•จ์ˆ˜ ์ •์˜์—์„œ๋Š” ์—ฐ์‚ฐ์ž์™€ ๊ด„ํ˜ธ ์‚ฌ์ด์— ํ•œ ์นธ ๋„์–ด์”๋‹ˆ๋‹ค.

    func ** (lhs: Int, rhs: Int)

๐Ÿ’ง์ค„๋ฐ”๊ฟˆ

  • ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ตœ๋Œ€ ๊ธธ์ด๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์„ ๊ธฐ์ค€์œผ๋กœ ์ค„๋ฐ”๊ฟˆํ•ฉ๋‹ˆ๋‹ค. ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ 3๊ฐœ ์ด์ƒ์ด๋ฉด ์ค„๋ฐ”๊ฟˆํ•˜๋„๋ก!!

    ๋‹จ, ํŒŒ๋ผ๋ฏธํ„ฐ์— ํด๋กœ์ €๊ฐ€ 2๊ฐœ ์ด์ƒ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ฌด์กฐ๊ฑด ๋‚ด๋ ค์“ฐ๊ธฐํ•ฉ๋‹ˆ๋‹ค.

    UIView.animate(
      withDuration: 0.25,
      animations: {
        // doSomething()
      },
      completion: { finished in
        // doSomething()
      }
    )
  • if let ๊ตฌ๋ฌธ์ด ๊ธธ ๊ฒฝ์šฐ์—๋Š” ์ค„๋ฐ”๊ฟˆํ•˜๊ณ  ํ•œ ์นธ ๋“ค์—ฌ์”๋‹ˆ๋‹ค.

    if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female {
      // ...
    }
  • guard let ๊ตฌ๋ฌธ์ด ๊ธธ ๊ฒฝ์šฐ์—๋Š” ์ค„๋ฐ”๊ฟˆํ•˜๊ณ  ํ•œ ์นธ ๋“ค์—ฌ์”๋‹ˆ๋‹ค. else๋Š” ๋งˆ์ง€๋ง‰ ์ค„์— ๋ถ™์—ฌ์“ฐ๊ธฐ

    guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female else { return }
    
    guard let self = self 
    else { return } (X)
    
    guard let self = self else { return } (O)
  • else ๊ตฌ๋ฌธ์ด ๊ธธ ์‹œ ์ค„๋ฐ”๊ฟˆ

๐Ÿ’ง๋นˆ ์ค„

  • ํด๋ž˜์Šค ์„ ์–ธ ๋‹ค์Œ์— , extension ๋‹ค์Œ์— ํ•œ ์ค„ ๋„์–ด์ฃผ๊ธฐ

  • ๋นˆ ์ค„์—๋Š” ๊ณต๋ฐฑ์ด ํฌํ•จ๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ( ๋„์–ด์“ฐ๊ธฐ ์“ธ๋ฐ์—†์ด ๋„ฃ์ง€ ๋ง๊ธฐ )

  • ๋ชจ๋“  ํŒŒ์ผ์€ ๋นˆ ์ค„๋กœ ๋๋‚˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ( ๋์— ์—”ํ„ฐ ํ•˜๋‚˜ ๋„ฃ๊ธฐ)

  • MARK ๊ตฌ๋ฌธ ์œ„์™€ ์•„๋ž˜์—๋Š” ๊ณต๋ฐฑ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    // MARK: Layout
    
    override func layoutSubviews() {
      // doSomething()
    }
    
    // MARK: Actions
    
    override func menuButtonDidTap() {
      // doSomething()
    }

๐Ÿ’ง์ž„ํฌํŠธ

๋ชจ๋“ˆ ์ž„ํฌํŠธ๋Š” ์•ŒํŒŒ๋ฒณ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์žฅ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋จผ์ € ์ž„ํฌํŠธํ•˜๊ณ , ๋นˆ ์ค„๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์„œ๋“œํŒŒํ‹ฐ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค.

import UIKit

import Moya
import SnapKit
import SwiftyColor
import Then
import UIKit

import SwiftyColor
import SwiftyImage
import JunhoKit
import Then
import URLNavigator
๐ŸŽ ํด๋กœ์ €
  • ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋ฆฌํ„ด ํƒ€์ž…์ด ์—†๋Š” Closure ์ •์˜์‹œ์—๋Š” () -> Void๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

    ์ข‹์€ ์˜ˆ:

    let completionBlock: (() -> Void)?
    

    ๋‚˜์œ ์˜ˆ:

    let completionBlock: (() -> ())? let completionBlock: ((Void) -> (Void))?

  • Closure ์ •์˜์‹œ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋Š” ๊ด„ํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    ์ข‹์€ ์˜ˆ:

    { operation, responseObject in
      // doSomething()
    }

    ๋‚˜์œ ์˜ˆ:

    { (operation, responseObject) in
      // doSomething()
    }
  • Closure ์ •์˜์‹œ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ํƒ€์ž… ์ •์˜๋ฅผ ์ƒ๋žตํ•ฉ๋‹ˆ๋‹ค.

    ์ข‹์€ ์˜ˆ:

    ...,
    completion: { finished in
      // doSomething()
    }

    ๋‚˜์œ ์˜ˆ:

    ...,
    completion: { (finished: Bool) -> Void in
      // doSomething()
    }
    
    completion: { data -> Void in
      // doSomething()
    } (X)
  • Closure ํ˜ธ์ถœ์‹œ ๋˜๋‹ค๋ฅธ ์œ ์ผํ•œ Closure๋ฅผ ๋งˆ์ง€๋ง‰ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š” ๊ฒฝ์šฐ, ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์„ ์ƒ๋žตํ•ฉ๋‹ˆ๋‹ค.

    ์ข‹์€ ์˜ˆ:

    UIView.animate(withDuration: 0.5) {
      // doSomething()
    }

    ๋‚˜์œ ์˜ˆ:

    UIView.animate(withDuration: 0.5, animations: { () -> Void in
      // doSomething()
    })
๐ŸŽ ์ฃผ์„

์ฝ”๋“œ๋Š” ๊ฐ€๋Šฅํ•˜๋ฉด ์ž์ฒด์ ์œผ๋กœ ๋ฌธ์„œ๊ฐ€ ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ, ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์žˆ๋Š” ์ธ๋ผ์ธ(inline) ์ฃผ์„์€ ํ”ผํ•œ๋‹ค.

๐Ÿ’งMARK ์ฃผ์„

class ViewController: UIViewController {
    // MARK: - Property
    // MARK: - UI Property
    // MARK: - Life Cycle
    // MARK: - Setting
    // MARK: - Action Helper
    // MARK: - @objc Methods
    // MARK: - Custom Method
}

// MARK: - Extensions

๐Ÿ’งํ€ตํ—ฌํ”„ ์ฃผ์„

์ปค์Šคํ…€ ๋ฉ”์„œ๋“œ, ํ”„๋กœํ† ์ฝœ, ํด๋ž˜์Šค์˜ ๊ฒฝ์šฐ์— ํ€ตํ—ฌํ”„ ์ฃผ์„ ๋‹ฌ๊ธฐ

/// (์„œ๋จธ๋ฆฌ ๋ถ€๋ถ„)
/// (๋””์Šคํฌ๋ฆฝ์…˜ ๋ถ€๋ถ„)
class MyClass {
    let myProperty: Int

    init(myProperty: Int) {
        self.myProperty = myProperty
    }
}

/**summary
(์„œ๋จธ๋ฆฌ ๋ถ€๋ถ„)
> (๋””์Šคํฌ๋ฆฝ์…˜ ๋ถ€๋ถ„)

- parameters:
    - property: ํ”„๋กœํผํ‹ฐ
- throws: ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด customError์˜ ํ•œ ์ผ€์ด์Šค๋ฅผ throw
- returns: "\\(name)๋Š” ~" String
*/
func printProperty(property: Int) {
        print(property)
    }
  • ์ฐธ๊ณ  :
๐ŸŽ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ถŒ์žฅ์‚ฌํ•ญ

๐Ÿ’งType Annotation ์‚ฌ์šฉ

์ข‹์€ ์˜ˆ:

let name: String = "์ฒ ์ˆ˜"
let height: Float = "10.0"

๋‚˜์œ ์˜ˆ:

let name = "์ฒ ์ˆ˜"
let height = "10.0"

๐Ÿ’งUICollectionViewDelegate, UICollectionViewDatsource ๋“ฑ ์‹œ์Šคํ…œ ํ”„๋กœํ† ์ฝœ

ํ”„๋กœํ† ์ฝœ์„ ์ ์šฉํ•  ๋•Œ์—๋Š” extension์„ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ จ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ๋ชจ์•„๋‘ก๋‹ˆ๋‹ค.

์ข‹์€ ์˜ˆ:

final class MyViewController: UIViewController {
  // ...
}

// MARK: - UITableViewDataSource

extension MyViewController: UITableViewDataSource {
  // ...
}

// MARK: - UITableViewDelegate

extension MyViewController: UITableViewDelegate {
  // ...
}

๋‚˜์œ ์˜ˆ:

final class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
  // ...
}

// ํ”„๋กœํ† ์ฝœ ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ํ•œ๊ณณ์— ๋ชฐ์•„์„œ ๋•Œ๋ ค๋„ฃ์ง€ ๋ง์ž!
๐ŸŽ ๊ธฐํƒ€๊ทœ์น™
  • self ๋Š” ์ตœ๋Œ€ํ•œ ์‚ฌ์šฉ์„ ์ง€์–‘ โ†’ **์•Œ์ž˜๋”ฑ๊น”์„ผ selfโ€ฆ**

  • viewDidLoad() ์—์„œ๋Š” ํ•จ์ˆ˜ํ˜ธ์ถœ๋งŒ

  • delegate ์ง€์ •, UI๊ด€๋ จ ์„ค์ • ๋“ฑ๋“ฑ ๋ชจ๋‘ ํ•จ์ˆ˜์™€ ์—ญํ• ์— ๋”ฐ๋ผ์„œ extension ์œผ๋กœ ๋นผ๊ธฐ

  • ํ•„์š”์—†๋Š” ์ฃผ์„ ๋ฐ Mark ๊ตฌ๋ฌธ๋“ค ์ œ๊ฑฐ

  • deinit{} ๋ชจ๋“  ๋ทฐ์ปจ์—์„œ ํ™œ์„ฑํ™”

  • guard let ์œผ๋กœ unwrapping ํ•  ์‹œ, nil ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์€ ๊ฒฝ์šฐ์—๋Š” else{} ์•ˆ์— print() ํ•ด์„œ ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ธฐ

  • return ์‚ฌ์šฉ์‹œ ๋‘ ์ค„ ์ด์ƒ ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ์‹œ, ํ•œ ์ค„ ๋„๊ณ  return ์‚ฌ์šฉ

    func fetchFalse() -> Bool {
            return false
    } (O)
    
    func isDataValid(data: Data?) -> Bool {
            guard let data else { return false }
            
            return true
    } (O)
    
    func isDataValid(data: Data?) -> Bool {
            guard let data else {
                    return false 
            }
            return true
    } (X)

    ์ถ”๊ฐ€ ๊ทœ์น™

    • ์•ฝ์–ด ์ง€์–‘ โ†’ TVC๋ณด๋‹ค๋Š” TableViewCell

    Function naming Rule

    • set_ ํ˜•ํƒœ๋กœ ์ž‘์„ฑ โ†’ setUI, setData
      • setLayout(), setStyle(), setDelegate()

    MARK ์ฃผ์„

    class ViewController: UIViewController {
        // MARK: - Property
        // MARK: - UI Property
        // MARK: - Life Cycle
            // MARK: - Setting
        // MARK: - Action Helper
        // MARK: - Custom Method
    }
    
    // MARK: - UITableView Delegate
    • ๋งˆํฌ ์ฃผ์„ ๋ฏธ์‚ฌ์šฉ์‹œ ์‚ญ์ œ

    ํ”„๋กœํผํ‹ฐ ์ƒ์„ฑ์€ ๋ ˆ์ด์•„์›ƒ ์ˆœ์„œ๋Œ€๋กœ์ง€๋งŒ, collectionView, tableView๋Š” ์ตœ์ƒ๋‹จ์— ์ ์์‹œ๋‹ค

    private let tableView: UITableView = {
            let view = UITableView()
            // ...
            return view
    }()
    
    private let view = UIView()
    
    private let view2 = UIView()

    ๋ทฐ์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜ ์•ˆ์—๋Š”, ์ง์ ‘์ ์ธ ๊ตฌํ˜„ ๋ณด๋‹ค๋Š” ํ•จ์ˆ˜ ํ˜ธ์ถœ๋งŒ ์ง„ํ–‰

    ~~override viewDidLoad() {
            super.viewDidLoad()
            self.view.addsubView(uniView)
    }~~
    override viewDidLoad() {
            super.viewDidLoad()
            self.addUniView()
    }
    private func addUniView() {
            super.viewDidLoad()
            self.view.addsubView(uniView)
    }

foldering

image

๊ฐ€์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

SPM์„ ์ด์šฉ

Kingfisher //์ด๋ฏธ์ง€์ฒ˜๋ฆฌ
Alamofire // ๋„คํŠธ์›Œํฌ
Snapkit //๋ ˆ์ด์•„์›ƒ
Then //์ฝ”๋“œ ๊ฐ„๊ฒฐํ™”
Sentry //error tracking
kakao-ios-sdk //socialLogin
firebase-auth //socialLogin

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%