Skip to content

๐Ÿ’ฟ Spotify Web API๋ฅผ ํ† ๋Œ€๋กœ ๋งŒ๋“  ๋‚˜๋งŒ์˜ Custom Spotify App

License

Notifications You must be signed in to change notification settings

onthelots/Spotify_App

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

31 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐ŸŽง Spotify (Custom)

Spotify Web API๋ฅผ ํ™œ์šฉํ•œ ๋‚˜๋งŒ์˜ ์Œ์•… ์žฌ์ƒ ์•ฑ

Group 1


๋ชฉ์ฐจ

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

2-Architecture

3-ํ”„๋กœ์ ํŠธ ํŠน์ง•

4-ํ”„๋กœ์ ํŠธ ์„ธ๋ถ€๊ณผ์ •

5-์—…๋ฐ์ดํŠธ ๋ฐ ๋ฆฌํŒฉํ† ๋ง ์‚ฌํ•ญ


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

1-1 ๊ฐœ์š”

Spotify Web API๋ฅผ ํ™œ์šฉํ•œ ๋‚˜๋งŒ์˜ ์Œ์•… ์žฌ์ƒ ์•ฑ

  • ๊ฐœ๋ฐœ๊ธฐ๊ฐ„ : 2023.06.10 ~ 2023.07.28 (์•ฝ 6์ฃผ)
  • ์ฐธ์—ฌ์ธ์› : 1์ธ (๊ฐœ์ธ ํ”„๋กœ์ ํŠธ)
  • ์ฃผ์š”ํŠน์ง•
    • ์„ธ๊ณ„ ์ตœ๋Œ€ ์Œ์› ์ŠคํŠธ๋ฆฌ๋ฐ ์„œ๋น„์Šค์ธ Spotify๋ฅผ ์ปค์Šคํ…€ UI ๋””์ž์ธ์œผ๋กœ ๊ตฌํ˜„
    • ์ƒˆ๋กœ๋‚˜์˜จ ์•จ๋ฒ”, ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ๋ฐ ์žฅ๋ฅด๋ณ„ ์Œ์•…, ์•„ํ‹ฐ์ŠคํŠธ ๋ฐ ์•จ๋ฒ” ์ฐพ๊ธฐ, ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ ์™ธ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ ์ œ๊ณต

1-2 ์ฃผ์š”๋ชฉํ‘œ

  • OAuth2.0์˜ ๋™์ž‘ ๋ฉ”์ปค๋‹ˆ์ฆ˜(Resource Owner - Client - Authorization & Resource Server) ์ดํ•ด
  • Spotify Web API์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฌธ์„œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ RESTFul API ๊ตฌํ˜„
  • Code-base UI AutoLayout ๊ตฌํ˜„

1-3 ๊ฐœ๋ฐœํ™˜๊ฒฝ

  • ํ™œ์šฉ๊ธฐ์ˆ  ์™ธ ํ‚ค์›Œ๋“œ

    • iOS : swift 5.8, xcode 14.3.1, UIKit
    • Network: URLSession, OAuth(RESTful API)
    • UI : ScrollView, TableView, CollectionView, TabBar, StackView
    • Layout : AutoLayout(Code-base), Compositional Layout
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

    • KingFisher (7.0.0)

1-4 ๊ตฌ๋™๋ฐฉ๋ฒ•

  • ๐Ÿ—ฃ๏ธ ๋ฐ˜๋“œ์‹œ ์•„๋ž˜ ์ ˆ์ฐจ์— ๋”ฐ๋ผ ๊ตฌ๋™ํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.
  • Spotify ์ „์šฉ ํšŒ์›๊ฐ€์ž… ๋ฐ ๊ฐœ๋ฐœ์ž ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค (Google, Facebook, Apple ๋ฏธ ์ง€์›)
  • ๊ตฌ๋™์ด ์ž˜ ๋˜์ง€ ์•Š๊ฑฐ๋‚˜, ๋กœ๊ทธ์ธ ํ›„ ํ™”๋ฉด์— ์•„๋ฌด๊ฒƒ๋„ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค๋ฉด ์•„๋ž˜ ๋ฉ”์ผ๋กœ ๋ฌธ์˜ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค
  • [email protected]
์ˆœ์„œ ๋‚ด์šฉ ๋น„๊ณ 
1 ์Šคํฌํ‹ฐํŒŒ์ด ๊ณ„์ •์„ ์ƒ์„ฑ ํ›„, ๋กœ๊ทธ์ธํ•ฉ๋‹ˆ๋‹ค(Google ์™ธ ๊ธฐํƒ€ ๊ฒฝ๋กœ ๊ฐ€์ž… X) ์Šคํฌํ‹ฐํŒŒ์ด ๊ฐœ๋ฐœ์ž ๋งํฌ
2 ์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์— ์žˆ๋Š” ๋‚ด ์•„์ด๋”” ๋ฅผ ํด๋ฆญํ•œ ํ›„, Dashboard๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค ์•ฝ๊ด€ ๋™์˜ ํ›„, ์ด๋ฉ”์ผ ์ธ์ฆ์„ ์‹ค์‹œ
3 ์•ฑ ์ƒ์„ฑ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ํ›„, ์•ฑ ์ด๋ฆ„๊ณผ ์†Œ๊ฐœ, RedirectURL์„ ์ž‘์„ฑํ•œ ํ›„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค RedirectURL(์ž„์‹œ ์›น ํŽ˜์ด์ง€ ์ฃผ์†Œ)
4 ์•ฑ์ด ์ƒ์„ฑ๋˜์—ˆ๋‹ค๋ฉด, ์˜ค๋ฅธ์ชฝ ์ƒ๋‹จ์— Settings ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค -
5 Base Infromation ํƒญ์—์„œ, ClientID ์™€ Client secret ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค -
6 ํด๋ก ๋ฐ›์€ ํ”„๋กœ์ ํŠธ ํŒŒ์ผ ๋‚ด ClientID+Secret.swift ํŒŒ์ผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค ClientID ํด๋” ๋‚ด ์œ„์น˜
7 ๋น„์–ด์žˆ๋Š” ๋ฌธ์ž์—ด์— ์ˆœ์„œ๋Œ€๋กœ ClientID, ClientSecret, RedirectURL ์„ ๊ธฐ์ž…ํ•ฉ๋‹ˆ๋‹ค. -
8 ๋นŒ๋“œ ํ›„, ์•ž์„œ ์ƒ์„ฑํ•œ ์Šคํฌํ‹ฐํŒŒ์ด ๊ณ„์ •์œผ๋กœ ์ง์ ‘ ๋กœ๊ทธ์ธ ํ•ฉ๋‹ˆ๋‹ค Google, Facebook, Apple ๋กœ๊ทธ์ธ X

2-Architecture

2-1 ๊ตฌ์กฐ๋„

Proeject Architecture

MVC Architecture

  • ์Œ์•… ์•ฑ์˜ ํŠน์„ฑ์ƒ, Track ๋ฆฌ์ŠคํŠธ, ์žฌ์ƒ ํ”Œ๋ ˆ์ด์–ด ๋“ฑ ์œ ์‚ฌํ•œ View๋ฅผ ์ง€์†์ ์œผ๋กœ ์žฌ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, MVC ํŒจํ„ด ํ™œ์šฉ
  • AuthManager ๊ฐ์ฒด์—์„œ API Fetching ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ ๋กœ์ง์„ ๋‹ด๋‹น, ViewController์—์„œ ์ง์ ‘ ํ˜ธ์ถœ
  • OAuth2.0 ์ธ์ฆ์„ ์œ„ํ•ด ์ฝ”๋“œ ๋ฐ ํ† ํฐ๋ฐœํ–‰์„ ์œ„ํ•œ AuthManager ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , UserDefault ๊ฐ’์œผ๋กœ ์ €์žฅ

Presenter ํ™œ์šฉ

  • Music Player์˜ ๊ฒฝ์šฐ, 2๊ฐ€์ง€ ๊ฒฝ์šฐ์˜ ์ˆ˜(ํ•˜๋‚˜์˜ ํŠธ๋ž™ ํ˜น์€ ์ „์ฒด ํŠธ๋ž™)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
  • ํ•˜๋‚˜์˜ VC์—์„œ ์—ญํ• ์ด ๋‹ค์†Œ ๊ณผ์ค‘๋œ๋‹ค ํŒ๋‹จ, Presenter๋ฅผ ํ†ตํ•ด View ๋ฐ Model์˜ ์ƒํƒœ๋ฅผ ํ™•์ธ, ์—…๋ฐ์ดํŠธ ๋กœ์ง์„ ๊ตฌํ˜„

2-2 ํŒŒ์ผ ๋””๋ ‰ํ† ๋ฆฌ

Spotify_App
 โ”ฃ ๐Ÿ“‚App
 โ”ฃ ๐Ÿ“‚ClientID
 โ”ฃ ๐Ÿ“‚Controllers
 โ”ƒ โ”ฃ ๐Ÿ“‚Core
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“œHomeViewController.swift
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“œLibraryViewController.swift
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“œSearchViewController.swift
 โ”ƒ โ”ƒ โ”— ๐Ÿ“œTabBarViewController.swift
 โ”ƒ โ”— ๐Ÿ“‚Others
 โ”ฃ ๐Ÿ“‚Managers
 โ”ƒ โ”ฃ ๐Ÿ“œAPICaller.swift
 โ”ƒ โ”ฃ ๐Ÿ“œAuthManager.swift
 โ”ƒ โ”— ๐Ÿ“œHapticManager.swift
 โ”ฃ ๐Ÿ“‚Models
 โ”ƒ โ”ฃ ๐Ÿ“‚Auth
 โ”ƒ โ”ฃ ๐Ÿ“‚Common
 โ”ƒ โ”ฃ ๐Ÿ“‚Response
 โ”ƒ โ”— ๐Ÿ“‚User
 โ”ฃ ๐Ÿ“‚View
 โ”ƒ โ”ฃ ๐Ÿ“‚HomeTab
 โ”ƒ โ”ฃ ๐Ÿ“‚LibraryTab
 โ”ƒ โ”ฃ ๐Ÿ“‚Player
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚PlayBackPresenter
 โ”ƒ โ”ƒ โ”ƒ โ”— ๐Ÿ“œPlayBackPresenter.swift
 โ”ƒ โ”— ๐Ÿ“‚SearchTab

3-ํ”„๋กœ์ ํŠธ ํŠน์ง•

3-1 ์ƒˆ๋กœ๋‚˜์˜จ ์•จ๋ฒ”, ์ธ๊ธฐ์žˆ๋Š” ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ, ์ทจํ–ฅ์— ๋งž๋Š” ์Œ์•… ์ œ๊ณต (HomeTab)

  • Spotify Web API ๋ฐ์ดํ„ฐ ํŒŒ์‹ฑ์— ๋”ฐ๋ผ, ์‹ค์‹œ๊ฐ„์œผ๋กœ ์Œ์•…์ •๋ณด๋ฅผ ํ™•์ธํ•จ
  • ๋‹จ์ผ ์•จ๋ฒ”์„ ๋น„๋กฏ, ์ฃผ์ œ์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•œ ํŠธ๋ž™์ด ํ˜ผํ•ฉ๋œ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์ œ๊ณต
HomeTab1 HomeTab_to_Playlists HomeTab_to_Album
Home Album Playlist


3-2 ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด ์Œ์•…, ์•„ํ‹ฐ์ŠคํŠธ, ์•จ๋ฒ” ๊ฒ€์ƒ‰(SearchTab)

  • Search API ํŒŒ์‹ฑ ๋ฐ์ดํ„ฐ๋ฅผ SearchResults ๋ชจ๋ธ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์Œ์•…, ์•„ํ‹ฐ์ŠคํŠธ, ์•จ๋ฒ” ๊ฒ€์ƒ‰๊ฒฐ๊ณผ๋ฅผ ์„น์…˜๋ณ„๋กœ ๋‚˜ํƒ€๋ƒ„
  • ์ฃผ์ œ(์นดํ…Œ๊ณ ๋ฆฌ)์— ๋”ฐ๋ผ ๋™์ผํ•œ ์†์„ฑ๊ฐ’(category.id)์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ๋ฅผ ์ œ๊ณต
SearchTab Search Search_Category_Playlists
Search Keyword Category Playlists


3-3 ๋งˆ์Œ์— ๋“œ๋Š” ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ํ˜น์€ ์•จ๋ฒ”์„ ์ €์žฅ&๊ด€๋ฆฌ(LibraryTab)

  • ๋กœ๊ทธ์ธ ์œ ์ €๊ฐ€ ์ง์ ‘ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ์˜ ๋ช…์นญ๊ณผ ํฌํ•จ๋  ํŠธ๋ž™์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
  • Long Tap Gesture๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ๋‚ด ํŠธ๋ž™ ์ถ”๊ฐ€ (ํ˜„์žฌ๋Š” HomeTab ํ•˜๋‹จ ์ถ”์ฒœ ํŠธ๋ž™๋งŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)
  • ๋งˆ์Œ์— ๋“œ๋Š” ์•จ๋ฒ”์ด ์žˆ์„ ๊ฒฝ์šฐ, ์•จ๋ฒ” ํŽ˜์ด์ง€ ์šฐ์ธก ์ƒ๋‹จ์— (+) ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์ฆ๊ฒจ์ฐพ๋Š” ์•จ๋ฒ” ์ถ”๊ฐ€
LibraryTab_Playlists_Add MyPlaylists LibraryTabAlbums
Create Playlist Playlists tracks Favorite Album


3-4 ๋‹จ์ผ ํŠธ๋ž™ ํ˜น์€ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ(์•จ๋ฒ”) ๋ณ„ ์Œ์•… ์žฌ์ƒ (Player)

  • AVPlayer Framework๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํŠธ๋ž™ ๋ฐ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ(์•จ๋ฒ”) ์ „์ฒด ์žฌ์ƒ ๊ฐ€๋Šฅ
    • ๐Ÿ–๐Ÿป ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋กœ ๊ตฌ๋™์‹œ, delay๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ, ์‹ค์ œ ๋””๋ฐ”์ด์Šค์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์™„๋ฃŒ)
    • ์‹œ์ž‘/์ •์ง€(PlayPause), ๋‹ค์Œ ํŠธ๋ž™(Forward), ์ด์ „ ํŠธ๋ž™(Backward) ๊ธฐ๋Šฅ ๊ตฌํ˜„
image 93 Group 2047 Group 2047
Audio Player Play All Button Volume Slider

4-ํ”„๋กœ์ ํŠธ ์„ธ๋ถ€๊ณผ์ •

4-1 [Feature 1] ์–ด๋–ค ์•ฑ์„ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€? (+ UI Design)

UIKit ๊ฐœ๋ฐœ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉ, OPEN API ๋„คํŠธ์›Œํฌ ๊ตฌํ˜„, ๊ตฌ์ƒํ™” ๋ชฉํ‘œ

  • ์Šคํ† ๋ฆฌ๋ณด๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  AutoLayout์„ ๊ตฌํ˜„ํ•˜๋Š” Code-base๋ฅผ ํ™œ์šฉ
  • ๋‹จ์ˆœํ•˜๋ฉด์„œ, ๊ธฐ์ดˆ์ ์œผ๋กœ ์•ฑ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ ์ž MVC ํŒจํ„ด ์ ์šฉ
  • ํ˜„์žฌ ์ƒ์šฉํ™” ๋œ ์•ฑ์˜ ์ „๋ฐ˜์ ์ธ ๋ชจ์Šต์„ ๋”ฐ๋ผ๊ฐ€๋˜, 1์ฐจ์ ์œผ๋กœ UI ์ธก๋ฉด๋ณด๋‹ค๋Š” ๊ธฐ๋Šฅ ์ค‘์‹ฌ์˜ ๊ตฌํ˜„ ๋ชฉํ‘œ

4-2 [Feature 2] ์‚ฌ์šฉ์ž ์ธ์ฆ ๋ฐ ๋กœ๊ทธ์ธ, ํ”„๋กœํ•„ ๊ธฐ๋Šฅ ๊ตฌํ˜„

OAuth 2.0 ๋กœ๊ทธ์ธ ๊ณผ์ •์— ๋Œ€ํ•œ ํ•™์Šต์„ ๊ธฐ๋ฐ˜์œผ๋กœ User Authmetication ๊ตฌํ˜„

  • Spotify Web API ๊ฐ€์ด๋“œ์— ๋”ฐ๋ผ ๋กœ๊ทธ์ธ ์š”์ฒญ โžŸ ํŽ˜์ด์ง€ ์ œ๊ณต โžŸ Auth Code ๋ฐœ๊ธ‰ ๋ฐ Token ๊ตํ™˜ โžŸ DB ์ €์žฅ โžŸ API ํ˜ธ์ถœ(Finish!)
  • UserDefaults๋ฅผ ํ™œ์šฉํ•˜์—ฌ Token ์ €์žฅ โžŸ ์ฒ˜์Œ ๋กœ๊ทธ์ธ ์ดํ›„, ์•ฑ์„ ์žฌ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ์žฌ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š๋„๋ก ํ•จ
  • TroubleShooting : 401 repsponse Error ๋ฐœ์ƒ ๋ฐ ํ•ด๊ฒฐ๊ณผ์ •

4-3 [Feature 3] ํƒญ(Tab)๋ณ„ API ๋ฐ์ดํ„ฐ ๊ตฌ์ถ• ๋ฐ UI ๊ตฌ์„ฑ

HomeTab(์ƒˆ๋กœ๋‚˜์˜จ ์•จ๋ฒ”, ์ถ”์ฒœ ์žฌ์ƒ๋ชฉ๋ก, ์œ ์‚ฌํ•œ ์•„ํ‹ฐ์ŠคํŠธ&ํŠธ๋ž™)

  • ๊ฐ๊ฐ์˜ Section๋ณ„ ๊ตฌ๋ถ„์„ ๋ชฉํ‘œ๋กœ, ํ•ด๋‹น View์—์„œ ํ™œ์šฉ๋˜๋Š” ViewModel์„ Associated Values์œผ๋กœ ์„ค์ •ํ•˜๋Š” BrowseSectionType์„ ์ƒ์„ฑ
enum BrowseSectionType {
    case newRelease(viewModels: [NewReleasesCellViewModel])
    case featuredPlaylists(viewModels: [FeaturedPlaylistsCellViewModel])
    case recommendedTracks(viewModels: [RecommendedTrackCellViewModel])
   ...
  • ์ˆ˜์ง ์Šคํฌ๋กค ํ™”๋ฉด ๋‚ด, ์ˆ˜ํ‰ ์Šคํฌ๋กค๋กœ ๊ตฌ์„ฑ๋œ ์ƒˆ๋กœ๋‚˜์˜จ ์•จ๋ฒ”, ์ถ”์ฒœ ์žฌ์ƒ๋ชฉ๋ก์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Compositional Layout์„ ์‚ฌ์šฉ

Search Tab(์•„ํ‹ฐ์ŠคํŠธ, ์•จ๋ฒ” ์™ธ ๊ฒ€์ƒ‰๊ธฐ๋Šฅ, ํ…Œ๋งˆ๋ณ„ ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ)

  • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ(Query ์ž…๋ ฅ ๋ฐ Search ์™„๋ฃŒ ๋ฒ„ํŠผ) ์ž…๋ ฅ๊ฐ’์— ๋”ฐ๋ผ, UI๋ฅผ 4๊ฐœ์˜ ์„น์…˜์œผ๋กœ ๊ตฌ๋ถ„(SearchResult)ํ•˜์—ฌ ์—…๋ฐ์ดํŠธ
   func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let resultsController = searchController.searchResultsController as? SearchResultViewController,
              let query = searchBar.text,
              !query.trimmingCharacters(in: .whitespaces).isEmpty else {
            return
        }

        resultsController.delegate = self
        APICaller.shared.search(with: query) { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let result):
                    resultsController.update(with: result)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }
        }
    }

Player Music(์Œ์•… ์žฌ์ƒ)

  • AVPlayer framework๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์žฌ์ƒ&์ผ์‹œ์ •์ง€, ์ด์ „ ํŠธ๋ž™, ๋‹ค์Œ ํŠธ๋ž™ ๊ธฐ๋Šฅ ๊ตฌํ˜„ (AVPlayer, AVQueuePlayer)
  • PlayerController์˜ ๊ณผ๋„ํ•œ ์—ญํ• ๋ถ€๋‹ด์„ ์ค„์ด๊ธฐ ์œ„ํ•ด, Presenter๋ฅผ ์ƒ์„ฑ, ํ™œ์šฉ
  • Track(Cell), Album&Playlists(Play All Button) ์„ ํƒ์— ๋”ฐ๋ผ ๊ธฐ๋Šฅ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ ์‹ค์‹œ
   var player: AVPlayer? // single Track
   var playerQueue: AVQueuePlayer? // playlist or album Player
  
  // ํ˜„์žฌ ์žฌ์ƒ๋˜๊ณ  ์žˆ๋Š” ํŠธ๋ž™์˜ ์„ฑ๊ฒฉ(ํ˜น์€ ์„ ํƒ๋œ ์•„์ดํ…œ)์— ๋”ฐ๋ผ track(single) ํ˜น์€ tracks(album, playlists)๋ฅผ ๋ฐ˜ํ™˜
   var currentTrack: AudioTrack? {
        if let track = track, tracks.isEmpty {
            return track
        }
        else if !tracks.isEmpty {
            return tracks[index]
        }
        return nil
    }
   ...

Library Tab (์œ ์ €์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‚˜๋งŒ์˜ Playlist ์ƒ์„ฑ ๋ฐ ์‚ญ์ œ, Album ์ €์žฅ๊ธฐ๋Šฅ)

  • Child ViewController(Playlists, Albums)๋‚ด ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ ์—ฌ๋ถ€๋ฅผ ํ™•์ธ(GET), 'ActionLabelView(๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Œ)'๋ฅผ ํ† ๊ธ€ํ•จ
  • Playlists : ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ์ƒ์„ฑ(POST) ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋งŒ๋“ค๊ณ , 'UILongPressGestureRecognizer'๋ฅผ ํ™œ์šฉํ•ด ์ €์žฅ(POST), ์‚ญ์ œ(DELETE)ํ•จ
  • Album : ๊ธฐ์กด ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์ƒ์— ์กด์žฌํ•˜๋ฏ€๋กœ, ์ €์žฅ(PUT)์„ ์‹ค์‹œํ•จ
    private func addChildren() {
        addChild(playlistVC)
        scrollView.addSubview(playlistVC.view)
        playlistVC.view.frame = CGRect(x: 0, y: 0, width: scrollView.width, height: scrollView.height) // frame์„ ํ†ตํ•œ paging
        playlistVC.didMove(toParent: self)
        
        addChild(albumsVC)
        scrollView.addSubview(albumsVC.view)
        albumsVC.view.frame = CGRect(x: view.width, y: 0, width: scrollView.width, height: scrollView.height) // frame์„ ํ†ตํ•œ paging
        albumsVC.didMove(toParent: self)
    }

5-์—…๋ฐ์ดํŠธ ๋ฐ ๋ฆฌํŒฉํ† ๋ง ์‚ฌํ•ญ

5-1 ์šฐ์„  ์ˆœ์œ„๋ณ„ ๊ฐœ์„ ํ•ญ๋ชฉ

  1. API Caller ๋ฉ”์„œ๋“œ ๋กœ์ง ์ˆ˜์ •
  • Library ๋‚ด ์ €์žฅ๋˜๋Š” Playlists์™€ Albums์˜ ๊ฒฝ์šฐ, ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ค‘๋ณต์ ์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์ด์Šˆ ํ•ด๊ฒฐํ•„์š”
  1. SNS ๋ฐ Google ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ๊ฐœ๋ณ„ SDK ์ ์šฉ ๋ฐ ๊ธฐ์กด ์ธ์›น(WKWeb)์„ SFSafariview๋กœ ๋Œ€์ฒดํ•˜๋Š” ๋Œ€์•ˆ ๋น„๊ต ๋ฐ ์ ์šฉ
  1. Player ๊ธฐ๋Šฅ
  • Playlists๋‚˜ Albums์„ ์ „์ฒด ์žฌ์ƒํ•  ๋•Œ, ๋˜๊ฐ๊ธฐ ํ˜น์€ ๋ฐ˜๋ณต๊ธฐ๋Šฅ ์ถ”๊ฐ€ (AudioTrack ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋‚ด ์žฌ์ƒ์‹œ๊ฐ„ ๊ด€๋ จ ๊ฐ์ฒด ํ™•์ธ)
  • Volume์„ ๋‹ด๋‹นํ•˜๋Š” UISilder ๊ธฐ๋Šฅ ์ œ๊ฑฐ, ์žฌ์ƒ ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ UISlider ์—…๋ฐ์ดํŠธ ๋ฐฉ์‹์œผ๋กœ ๋ฆฌํŒฉํ† ๋ง ํ•„์š”
  1. ์ผ๋ถ€ Track์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์žฌ์ƒ(Preview_urls)๊ฐ’์ด ์žˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์žฌ์ƒ์ด ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ
  • Tracks์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์žฌ์ƒ์ด ์—†๋Š” ๊ฒฝ์šฐ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•˜์—ฌ UI ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜์ง€ ์•Š๋„๋ก ์ œํ•œ

5-2 ๊ทธ ์™ธ ํ•ญ๋ชฉ

  1. UI ๊ฐœ์„ 
  • frame-base layout์„ AutoLayout์œผ๋กœ ์ „๋ถ€ ๋Œ€์ฒดํ•˜๊ธฐ
  • Color Palette๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ณด๋‹ค ํ†ต์ผ๊ฐ ์žˆ๋Š” ๋””์ž์ธ ๊ตฌ์„ฑํ•˜๊ธฐ
  1. Architecture ์žฌ ๊ฒ€ํ† 
  • ViewController ๋ฐ API Caller ๋‚ด ๊ณผ๋„ํ•œ ์—ญํ• ์ง‘์ค‘์œผ๋กœ ์ธํ•œ ๋ถ€์ฐจ์ ์ธ ๋ฌธ์ œ ์šฐ๋ ค, MVVM ํŒจํ„ด์œผ๋กœ์˜ ๋ฆฌํŒฉํ† ๋ง ๊ณ ๋ ค

About

๐Ÿ’ฟ Spotify Web API๋ฅผ ํ† ๋Œ€๋กœ ๋งŒ๋“  ๋‚˜๋งŒ์˜ Custom Spotify App

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages