Skip to content

CulLecting/CulLecting-iOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

45 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CulLecting (μ»¬λ ‰νŒ…)

곡곡데이터 기반 문화행사 정보λ₯Ό ν‹°μΌ“ ν˜•μ‹μœΌλ‘œ μ•„μΉ΄μ΄λΉ™ν•˜κ³ , μ‚¬μš©μž μ·¨ν–₯에 λ§žλŠ” 문화행사λ₯Ό μΆ”μ²œν•˜λŠ” iOS μ•±


πŸ“± ν”„λ‘œμ νŠΈ μ†Œκ°œ

CulLecting은 κ΄€λžŒν•œ 문화행사λ₯Ό ν‹°μΌ“ ν˜•μ‹μœΌλ‘œ μˆ˜μ§‘ν•˜κ³ , μ·¨ν–₯ 뢄석을 톡해 개인 λ§žμΆ€ν˜• 문화행사λ₯Ό μΆ”μ²œν•˜λŠ” iOS μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μž…λ‹ˆλ‹€.

μ£Όμš” κΈ°λŠ₯

  • πŸ“Έ ν‹°μΌ“ 아카이빙: κ΄€λžŒν•œ 문화행사λ₯Ό ν‹°μΌ“ ν˜•μ‹μœΌλ‘œ μ €μž₯
  • 🎭 μ·¨ν–₯ μΉ΄λ“œ: ν‹°μΌ“ μˆ˜μ§‘ μ‹œ μ·¨ν–₯ 뢄석을 ν†΅ν•œ 'μ·¨ν–₯ μΉ΄λ“œ' 생성
  • πŸ” λ§žμΆ€ν˜• μΆ”μ²œ: μ‚¬μš©μž μ„ ν˜Έλ„ 기반 문화행사 μΆ”μ²œ
  • πŸ”Ž ν•„ν„° 검색: μ§€μ—­, μΉ΄ν…Œκ³ λ¦¬, μ—°λ Ή, λΉ„μš© 기반 검색
  • πŸ‘€ μ‚¬μš©μž 관리: 이메일 인증 기반 νšŒμ›κ°€μž… 및 둜그인
ν™ˆ, 검색
image image image
아카이빙
image image image
νλ ˆμ΄νŒ…
image image image

πŸ† μ„±κ³Ό

  • μ„œμšΈ 열린데이터광μž₯ 곡λͺ¨μ „ μΆœν’ˆ
  • App Store 정식 배포

πŸ›  기술 μŠ€νƒ

Architecture & Design Pattern

  • Clean Architecture (Data-Domain-Presentation 3계측)
  • MVVM-C (Model-View-ViewModel-Coordinator)
  • Coordinator Pattern (ν™”λ©΄ μ „ν™˜ 둜직 쀑앙 관리)
  • Repository Pattern (데이터 μ†ŒμŠ€ 좔상화)

Framework & Library

  • Language: Swift
  • UI: UIKit, FlexLayout, PinLayout, SnapKit
  • Reactive: RxSwift, RxCocoa
  • DI: Swinject
  • Network: Alamofire
  • Database: CoreData
  • Security: Keychain Services
  • Image: Kingfisher
  • Dependency Manager: Swift Package Manager

πŸ— μ•„ν‚€ν…μ²˜

Clean Architecture 3계측 ꡬ쑰

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Presentation Layer              β”‚
β”‚  (ViewController, ViewModel, View)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ depends on
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Domain Layer                   β”‚
β”‚  (Entity, UseCase, Repository Protocol) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ implements
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           Data Layer                    β”‚
β”‚  (Repository, API Client, DTO)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

μ˜μ‘΄μ„± μ—­μ „ 원칙

  • Domain Layer의 Repository Protocol을 Data Layerκ°€ κ΅¬ν˜„
  • ViewModel β†’ UseCase β†’ Repository Protocol 순으둜 의쑴
  • 도메인 둜직의 독립성 확보

🎯 기술적 도전과 ν•΄κ²°

1. Clean Architecture 적용

문제 인식

λ°±μ—”λ“œ κ°œλ°œμžμ™€ ν˜‘μ—…ν•˜λ©΄μ„œ λ‹€μ–‘ν•œ API μ—”λ“œν¬μΈνŠΈκ°€ μΆ”κ°€λ˜κ³ , 이에 따라 Network κ΄€λ ¨ 파일, API μ •μ˜, DTO 등이 κΈ‰κ²©νžˆ μ¦κ°€ν–ˆμŠ΅λ‹ˆλ‹€. λ‹¨μˆœνžˆ ViewController만 λΉ„λŒ€ν•΄μ§€λŠ” 것이 μ•„λ‹ˆλΌ, ViewModel에도 λ„€νŠΈμ›Œν¬ 둜직, 데이터 λ³€ν™˜ 둜직, λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 ν˜Όμž¬λ˜μ–΄ 역할이 뢈λͺ…ν™•ν•΄μ§€κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ μƒˆλ‘œμš΄ 화면을 μΆ”κ°€ν•  λ•Œλ§ˆλ‹€ "이 λ‘œμ§μ€ ViewModel에 둬야 ν•˜λ‚˜, 별도 Manager에 둬야 ν•˜λ‚˜?"와 같은 고민이 λ°˜λ³΅λ˜μ—ˆκ³ , νŒ€μ› κ°„ μ½”λ“œ μž‘μ„± 방식이 ν†΅μΌλ˜μ§€ μ•Šμ•„ 일관성이 λ–¨μ–΄μ§€λŠ” λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. ν”„λ‘œμ νŠΈ μ΄ˆκΈ°μ— λͺ…ν™•ν•œ μ•„ν‚€ν…μ²˜ κ·œμΉ™μ„ μ •λ¦½ν•˜μ§€ μ•ŠμœΌλ©΄, ν”„λ‘œμ νŠΈκ°€ 컀질수둝 μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μ›Œμ§ˆ 것이라 νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²° λ°©μ•ˆ

Clean Architectureλ₯Ό λ„μž…ν•˜μ—¬ Data-Domain-Presentation 3κ³„μΈ΅μœΌλ‘œ λͺ…ν™•νžˆ λΆ„λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€.

  • Data Layer: API 톡신, DTO μ •μ˜, Repository κ΅¬ν˜„μ²΄λ₯Ό λ‹΄λ‹Ή. λ„€νŠΈμ›Œν¬ κ΄€λ ¨ λ‘œμ§μ€ λͺ¨λ‘ 이 κ³„μΈ΅μ—μ„œ 처리
  • Domain Layer: λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ UseCase둜 μΆ”μƒν™”ν•˜κ³ , Repository Protocol둜 데이터 μ†ŒμŠ€λ₯Ό 좔상화. μ™ΈλΆ€ μ˜μ‘΄μ„± 없이 μˆœμˆ˜ν•œ 도메인 둜직만 포함
  • Presentation Layer: ViewModel은 UseCase만 μ˜μ‘΄ν•˜κ³ , UI μƒνƒœ κ΄€λ¦¬μ—λ§Œ 집쀑. ViewλŠ” ViewModel을 ν†΅ν•΄μ„œλ§Œ 데이터에 μ ‘κ·Ό

이λ₯Ό 톡해 "어디에 μ–΄λ–€ λ‘œμ§μ„ 둬야 ν•˜λŠ”κ°€"에 λŒ€ν•œ λͺ…ν™•ν•œ 기쀀이 생겼고, μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€ μ‹œ μΌκ΄€λœ ꡬ쑰λ₯Ό μœ μ§€ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ UseCase λ‹¨μœ„λ‘œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ λΆ„λ¦¬ν•˜μ—¬ ν…ŒμŠ€νŠΈ κ°€λŠ₯ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

κ²°κ³Ό

  • ViewModel의 역할을 UI μƒνƒœ κ΄€λ¦¬λ‘œ μ œν•œν•˜μ—¬ λΉ„λŒ€ν™” λ°©μ§€
  • μƒˆλ‘œμš΄ API μΆ”κ°€ μ‹œ Data Layer만 μˆ˜μ •ν•˜λ©΄ λ˜μ–΄ λ³€κ²½ 영ν–₯ λ²”μœ„ μ΅œμ†Œν™”
  • νŒ€μ› κ°„ μ½”λ“œ μž‘μ„± 방식 ν†΅μΌλ‘œ μ½”λ“œ 리뷰 및 ν˜‘μ—… νš¨μœ¨μ„± ν–₯상

2. 토큰 μžλ™ κ°±μ‹  λ©”μ»€λ‹ˆμ¦˜

문제 인식

AccessToken은 λ³΄μ•ˆμ„ μœ„ν•΄ 짧은 만료 μ‹œκ°„(예: 1μ‹œκ°„)을 κ°€μ§€λŠ”λ°, 만료될 λ•Œλ§ˆλ‹€ μ‚¬μš©μžκ°€ λ‹€μ‹œ λ‘œκ·ΈμΈν•΄μ•Ό ν•œλ‹€λ©΄ μ‚¬μš©μž κ²½ν—˜μ΄ 크게 μ €ν•˜λ©λ‹ˆλ‹€. λ˜ν•œ μ•± μ‚¬μš© 쀑 κ°‘μžκΈ° 둜그인 ν™”λ©΄μœΌλ‘œ νŠ€λŠ” 것은 μ‚¬μš©μžμ—κ²Œ ν˜Όλž€μ„ 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²° λ°©μ•ˆ

RefreshToken을 ν™œμš©ν•œ μžλ™ 토큰 κ°±μ‹  λ©”μ»€λ‹ˆμ¦˜μ„ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

  1. 401 μ—λŸ¬ 감지: λ„€νŠΈμ›Œν¬ μš”μ²­ μ‹œ 401 Unauthorized μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ 토큰 만료둜 νŒλ‹¨
  2. μžλ™ κ°±μ‹ : RefreshToken을 μ‚¬μš©ν•΄ μƒˆλ‘œμš΄ AccessToken을 λ°œκΈ‰λ°›λŠ” API 호좜
  3. μš”μ²­ μž¬μ‹œλ„: μƒˆ 토큰을 Keychain에 μ €μž₯ ν›„, μ‹€νŒ¨ν–ˆλ˜ μ›λž˜ μš”μ²­μ„ μžλ™μœΌλ‘œ μž¬μ‹œλ„
  4. RxSwift catch μ—°μ‚°μž: μ—λŸ¬ μŠ€νŠΈλ¦Όμ„ μΊμΉ˜ν•˜μ—¬ 토큰 κ°±μ‹  λ‘œμ§μ„ μ‚½μž…ν•˜κ³ , flatMap으둜 μ›λž˜ μš”μ²­ μž¬μ‹€ν–‰
func request<T: Decodable>(endpoint: URLRequestConvertible) -> Single<T> {
    return networkManager.request(endpoint)
        .catch { error in
            if error.isTokenExpired {
                return self.refreshToken()
                    .flatMap { _ in self.networkManager.request(endpoint) }
            }
            return Single.error(error)
        }
}

κ²°κ³Ό

  • μ‚¬μš©μžλŠ” 토큰 만료λ₯Ό μΈμ§€ν•˜μ§€ λͺ»ν•˜κ³  λŠκΉ€ 없이 μ•± μ‚¬μš© κ°€λŠ₯
  • μžλ™ 둜그인 μœ μ§€λ‘œ μ‚¬μš©μž κ²½ν—˜ 크게 κ°œμ„ 
  • RefreshToken 만료 μ‹œμ—λ§Œ 둜그인 ν™”λ©΄μœΌλ‘œ μ΄λ™ν•˜λ„λ‘ 처리

3. Coordinator νŒ¨ν„΄ λ„μž…

문제 인식

μ΄ˆκΈ°μ—λŠ” ViewControllerμ—μ„œ λ‹€μŒ 화면을 직접 pushν•˜λŠ” λ°©μ‹μœΌλ‘œ κ°œλ°œν–ˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ ν”„λ‘œμ νŠΈκ°€ μ»€μ§€λ©΄μ„œ ν™”λ©΄ μ „ν™˜ 둜직이 μ—¬λŸ¬ ViewController에 λΆ„μ‚°λ˜μ–΄ 전체 ν”Œλ‘œμš°λ₯Ό νŒŒμ•…ν•˜κΈ° μ–΄λ €μ› κ³ , ViewController κ°„ 결합도가 λ†’μ•„ μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μ›Œμ‘ŒμŠ΅λ‹ˆλ‹€.

특히 "둜그인 β†’ μ˜¨λ³΄λ”© β†’ 메인 ν™”λ©΄" 같은 λ³΅μž‘ν•œ ν”Œλ‘œμš°μ—μ„œ 각 화면이 λ‹€μŒ 화면을 μ•Œμ•„μ•Ό ν•˜λŠ” κ΅¬μ‘°λŠ” 변경에 μ·¨μ•½ν–ˆμŠ΅λ‹ˆλ‹€.

ν•΄κ²° λ°©μ•ˆ

Coordinator νŒ¨ν„΄μ„ λ„μž…ν•˜μ—¬ λͺ¨λ“  ν™”λ©΄ μ „ν™˜ λ‘œμ§μ„ μ€‘μ•™μ—μ„œ κ΄€λ¦¬ν•˜λ„λ‘ κ°œμ„ ν–ˆμŠ΅λ‹ˆλ‹€.

  • FirstCoordinator: μ•± μ‹œμž‘ μ‹œ 토큰 및 μ˜¨λ³΄λ”© μƒνƒœλ₯Ό κ²€μ¦ν•˜κ³ , LoginCoordinator/OnboardingCoordinator/TabbarCoordinator둜 λΆ„κΈ°
  • κΈ°λŠ₯별 Coordinator: LoginCoordinatorλŠ” 둜그인/νšŒμ›κ°€μž…/λΉ„λ°€λ²ˆν˜Έ μž¬μ„€μ • ν”Œλ‘œμš°λ₯Ό λ‹΄λ‹Ή, TabbarCoordinatorλŠ” νƒ­ λ„€λΉ„κ²Œμ΄μ…˜ 관리
  • ViewController 독립성: ViewControllerλŠ” Coordinatorμ—κ²Œ 이벀트만 μ „λ‹¬ν•˜κ³ (예: coordinator.didLoginSuccess()), λ‹€μŒ 화면에 λŒ€ν•΄ μ•Œμ§€ λͺ»ν•¨

κ²°κ³Ό

  • 전체 μ•± ν”Œλ‘œμš°λ₯Ό Coordinator μ½”λ“œλ§Œ 보고 νŒŒμ•… κ°€λŠ₯
  • ViewController κ°„ 결합도 제거둜 μž¬μ‚¬μš©μ„± ν–₯상
  • ν™”λ©΄ μ „ν™˜ 둜직 λ³€κ²½ μ‹œ Coordinator만 μˆ˜μ •ν•˜λ©΄ λ˜μ–΄ μœ μ§€λ³΄μˆ˜ 용이

4. μ»€μŠ€ν…€ CarouselView κ΅¬ν˜„

문제 인식

아카이빙 ν™”λ©΄μ—μ„œ 티켓을 μΉ΄λ“œ ν˜•νƒœμ˜ Carousel둜 λ³΄μ—¬μ£ΌλŠ” UIκ°€ ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. λ””μžμ΄λ„ˆλŠ” λ‹€μŒκ³Ό 같은 μš”κ΅¬μ‚¬ν•­μ„ μ œμ‹œν–ˆμŠ΅λ‹ˆλ‹€:

  1. 첫 번째 μΉ΄λ“œμ™€ λ§ˆμ§€λ§‰ μΉ΄λ“œλ„ ν™”λ©΄ 쀑앙에 μ •λ ¬
  2. 슀크둀 μ‹œ 쀑앙 μΉ΄λ“œλŠ” 크게, μ–‘μ˜† μΉ΄λ“œλŠ” μž‘κ²Œ λ³΄μ΄λŠ” 거리 기반 transform 효과
  3. 슀크둀 μ’…λ£Œ μ‹œ κ°€μž₯ κ°€κΉŒμš΄ μΉ΄λ“œλ‘œ λΆ€λ“œλŸ½κ²Œ μŠ€λƒ…

UICollectionView둜 κ΅¬ν˜„μ„ κ³ λ €ν–ˆμœΌλ‚˜, λ‹€μŒκ³Ό 같은 λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€:

  • μ…€ μž¬μ‚¬μš©μœΌλ‘œ 인해 ν™”λ©΄ λ°– μ…€ 접근이 μ œν•œμ μ΄μ–΄μ„œ μ‹€μ‹œκ°„ transform 효과 κ΅¬ν˜„μ΄ λ³΅μž‘ν•¨
  • 첫/λ§ˆμ§€λ§‰ μΉ΄λ“œ 쀑앙 정렬을 μœ„ν•œ μ»€μŠ€ν…€ λ ˆμ΄μ•„μ›ƒ κ΅¬ν˜„μ΄ λ²ˆκ±°λ‘œμ›€

ν•΄κ²° λ°©μ•ˆ

UIScrollViewλ₯Ό 직접 ν™œμš©ν•˜μ—¬ μ»€μŠ€ν…€ CarouselViewλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

1. 쀑앙 μ •λ ¬ λ ˆμ΄μ•„μ›ƒ

let cardHeight = bounds.height * 0.85
let cardWidth = cardHeight * 0.62
let spacer = (bounds.width - cardWidth) / 2  // μ–‘μͺ½ μ—¬λ°± 계산

μ–‘μͺ½μ— spacerλ₯Ό μΆ”κ°€ν•˜μ—¬ 첫/λ§ˆμ§€λ§‰ μΉ΄λ“œλ„ ν™”λ©΄ 쀑앙에 μœ„μΉ˜ν•˜λ„λ‘ κ΅¬ν˜„

2. μ‹€μ‹œκ°„ 거리 기반 Transform 효과

private func updateTransforms() {
    let centerX = scrollView.contentOffset.x + bounds.width / 2
    
    for cardView in cardViews {
        let distance = abs(centerX - cardView.center.x)
        let maxDistance = bounds.width / 2 + cardView.bounds.width / 2
        let scale = max(0.9, 1 - distance / maxDistance * 0.1)
        let alpha = max(0.5, 1 - distance / maxDistance)
        
        cardView.transform = CGAffineTransform(scaleX: scale, y: scale)
        cardView.alpha = alpha
    }
}

scrollViewDidScroll λΈλ¦¬κ²Œμ΄νŠΈμ—μ„œ λͺ¨λ“  μΉ΄λ“œμ˜ 쀑앙 거리λ₯Ό κ³„μ‚°ν•˜μ—¬ scaleκ³Ό alphaλ₯Ό μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ. UIScrollViewλŠ” λͺ¨λ“  μΉ΄λ“œμ— 직접 μ ‘κ·Ό κ°€λŠ₯ν•˜μ—¬ UICollectionView보닀 κ΅¬ν˜„μ΄ λ‹¨μˆœν•¨

3. μ»€μŠ€ν…€ μŠ€λƒ… 둜직

private func snapToNearestCard() {
    let totalWidth = cardWidth + cardSpacing
    let centerOffset = scrollView.contentOffset.x + bounds.width / 2
    let adjustedOffset = centerOffset - spacer
    let index = Int(round((adjustedOffset - cardWidth / 2) / totalWidth))
    scrollToIndex(index: index, animated: true)
}

슀크둀 μ’…λ£Œ μ‹œ 쀑앙에 κ°€μž₯ κ°€κΉŒμš΄ μΉ΄λ“œ 인덱슀λ₯Ό κ³„μ‚°ν•˜μ—¬ λΆ€λ“œλŸ½κ²Œ μŠ€λƒ…

UIScrollView 선택 κ·Όκ±°

  • μ‹€μ‹œκ°„ μ œμ–΄: λͺ¨λ“  μΉ΄λ“œμ— λŒ€ν•œ 직접 μ ‘κ·ΌμœΌλ‘œ 거리 기반 transform 효과 κ΅¬ν˜„ 용이
  • μ •ν™•ν•œ λ ˆμ΄μ•„μ›ƒ: 첫/λ§ˆμ§€λ§‰ μΉ΄λ“œ 쀑앙 정렬을 spacer κ³„μ‚°λ§ŒμœΌλ‘œ κ°„λ‹¨νžˆ κ΅¬ν˜„
  • λ‹¨μˆœν•œ ꡬ쑰: μ…€ 등둝, DataSource/Delegate λΆˆν•„μš”, ν‹°μΌ“ μˆ˜κ°€ μ œν•œμ μ΄μ–΄μ„œ λ©”λͺ¨λ¦¬ νš¨μœ¨μ„± 문제 μ—†μŒ
  • μ»€μŠ€ν…€ μŠ€λƒ…: μŠ€λƒ… μœ„μΉ˜μ™€ λ™μž‘μ„ μ •ν™•νžˆ μ œμ–΄ κ°€λŠ₯

κ²°κ³Ό

  • λ””μžμ΄λ„ˆκ°€ μš”μ²­ν•œ 쀑앙 μ •λ ¬ 및 거리 기반 transform 효과 μ™„λ²½ κ΅¬ν˜„
  • λΆ€λ“œλŸ¬μš΄ μŠ€λƒ… μ• λ‹ˆλ©”μ΄μ…˜μœΌλ‘œ μ‚¬μš©μž κ²½ν—˜ ν–₯상
  • UICollectionView λŒ€λΉ„ λ‹¨μˆœν•œ ꡬ쑰둜 μœ μ§€λ³΄μˆ˜ 용이
  • ν‹°μΌ“ κ°œμˆ˜κ°€ 적어 UIScrollView둜 μΆ©λΆ„νžˆ μ„±λŠ₯ 확보

ν•œκ³„ 및 κ°œμ„  λ°©ν–₯

λ§Œμ•½ ν‹°μΌ“ κ°œμˆ˜κ°€ μˆ˜μ‹­ 개 μ΄μƒμœΌλ‘œ μ¦κ°€ν•œλ‹€λ©΄, UICollectionView둜 λ¦¬νŒ©ν† λ§ν•˜μ—¬ μ…€ μž¬μ‚¬μš©μ„ ν†΅ν•œ λ©”λͺ¨λ¦¬ μ΅œμ ν™”κ°€ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


πŸ‘₯ νŒ€ ꡬ성

  • iOS Developer: 1λͺ… (단독 개발)
  • Designer: 1λͺ…
  • BE: 1λͺ…
  • PM: 1λͺ…

πŸ“… 개발 κΈ°κ°„

2025.03 - 2025.05 (μ•½ 2κ°œμ›”)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages