Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ad2aa38
프로젝트 파일 생성
coduhee Mar 11, 2026
778568b
Package Dependencies 설치
coduhee Mar 12, 2026
f7f6f46
✨feat: Folder 구조 정리
coduhee Mar 12, 2026
f818f5b
✨feat: 앱 전체에서 공통으로 쓸 모델 ContentItem 생성
coduhee Mar 12, 2026
b981169
✨feat: 홈 화면 섹션 모델 HomeSection 생성
coduhee Mar 12, 2026
5d0fcae
✨Feat: ListCollectionViewCell 구현
coduhee Mar 14, 2026
0e0d914
✨Feat: HomeCollectionViewCell 구현
coduhee Mar 14, 2026
a5a2a23
🎨 Art: Layout 수정
coduhee Mar 15, 2026
d3a5907
🔥Fire: HomeCollectionViewCell -> CardCollectionViewCell 이름 변경
coduhee Mar 15, 2026
d751a7d
✨Feat: HomeSectionHeaderView 구현
coduhee Mar 15, 2026
f7a34d4
✨Feat: HomeViewController 구현
coduhee Mar 15, 2026
0d0aea5
✨Feat: HomeReactor 구현
coduhee Mar 15, 2026
0ce646b
✨Feat: SceneDelegate에 HomeViewController() 연결
coduhee Mar 15, 2026
af6c6ba
✨Feat: FetchHomeContentUseCase 구현
coduhee Mar 15, 2026
2088b40
✨Feat: APIEndpoint 구현
coduhee Mar 15, 2026
14b8ef2
✨Feat: NetworkManager 구현
coduhee Mar 15, 2026
5c8d2c6
✨Feat: ItunesResponseDTO 구현
coduhee Mar 15, 2026
696d5f1
✨Feat: Extensions 구현
coduhee Mar 15, 2026
aaada90
✨Feat: SearchRepository 구현
coduhee Mar 15, 2026
18cb5f8
✨Feat: SearchRepositoryType Protocol 구현
coduhee Mar 15, 2026
a593533
✨Feat: SPM(Kingfisher, ReactorKit) 추가
coduhee Mar 15, 2026
5720b94
♻️Refactor: fetchMusic 삭제 -> fetchContent로 통합
coduhee Mar 15, 2026
42318e1
♻️Refactor: #Preview 수정, identifier 네이밍 수정
coduhee Mar 15, 2026
5e44004
✨Feat: HomeSection에 RxDataSources 구현위해 추가 기능 구현
coduhee Mar 15, 2026
958f151
✨Feat: RxDataSources로 로직 업데이트
coduhee Mar 15, 2026
7da01e8
♻️Refactor: viewDidLoad -> bind(reactor:)안에 Rx로 묶어두기
coduhee Mar 16, 2026
9457b5d
✨Feat: 앨범 사진 고화질로 크기 변환
coduhee Mar 16, 2026
2e9b041
🐞Fix: CollectionViewCell 버그 수정
coduhee Mar 16, 2026
b16ca5d
♻️Refactor: 이미지 크기 조정
coduhee Mar 16, 2026
62e376f
✨Feat: RxDataSources 패키지 추가
coduhee Mar 16, 2026
ecb0b5d
♻️Refactor: 30 -> 10 개씩 추출
coduhee Mar 16, 2026
fc90910
♻️Refactor: 300x300으로 이미지 크기 조정
coduhee Mar 16, 2026
ed79153
♻️Refactor: NetworkManager 싱글톤 패턴으로 변경
coduhee Mar 17, 2026
9052723
♻️Refactor: Error 타입 정의
coduhee Mar 17, 2026
cd3a644
✨Feat: SearchUseCase 구현
coduhee Mar 17, 2026
d750ab9
✨Feat: Extension 사진의 평균 색상 추출하는 함수
coduhee Mar 17, 2026
fe8aab5
♻️Refactor: UI 조정, 프리뷰 삭제
coduhee Mar 17, 2026
c97b21c
🐞Fix: movie -> musicVideo 데이터 변경
coduhee Mar 17, 2026
4fa90e3
♻️Refactor: 평균 색상 추출 함수 삭제
coduhee Mar 18, 2026
6f668af
♻️Refactor: country, limit 변수 mediaType별로 커스텀하게 설정
coduhee Mar 18, 2026
281d108
✨Feat: M/V URL 추출 기능 추가
coduhee Mar 18, 2026
02f4d4c
✨Feat: ellipsis Button 구현
coduhee Mar 18, 2026
bdb008a
♻️Reactor: SceneDelegate 수정
coduhee Mar 18, 2026
bc647db
✨Feat: SearchViewController 구현
coduhee Mar 18, 2026
0863cd7
✨Feat: SearchReactor 구현
coduhee Mar 18, 2026
661632d
✨Feat: SearchCollectionViewCell 구현
coduhee Mar 18, 2026
8a48093
✨Feat: SearchCellReactor 구현
coduhee Mar 18, 2026
ece79b4
♻️Reactor: HomeVC 리팩토링
coduhee Mar 18, 2026
238539a
✨Feat: 값 비교를 위해 Equatable 프로토콜 추가
coduhee Mar 18, 2026
0900571
♻️Reactor: errorMessage @Pulse 추가
coduhee Mar 18, 2026
f67038b
🐞Fix: playerLayer 선언 버그 수정
coduhee Mar 18, 2026
81aeeb8
📦 파일이동
coduhee Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
504 changes: 504 additions & 0 deletions Challenge/Challenge.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions Challenge/Challenge/Base.lproj/LaunchScreen.storyboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
48 changes: 48 additions & 0 deletions Challenge/Challenge/Extensions/Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// UIImageView+Extension.swift
// Challenge
//
// Created by 김주희 on 3/15/26.
//

import UIKit
import Kingfisher


// MARK: - loadImage
extension UIImageView {

func loadImage(from urlString: String) {
guard let url = URL(string: urlString) else {
self.image = nil
return
}

self.kf.setImage(
with: url,
placeholder: nil,
options: [
.transition(.fade(0.2)), // 부드럽게 이미지 페이드 인
.cacheOriginalImage // 원본 이미지를 캐시에 저장
]
)
}
}


// MARK: - Alert
extension UIViewController {

func showErrorAlert(message: String) {
let alert = UIAlertController(
title: "에러",
message: message,
preferredStyle: .alert
)

let okAction = UIAlertAction(title: "확인", style: .default)
alert.addAction(okAction)

present(alert, animated: true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions Challenge/Challenge/Resources/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
23 changes: 23 additions & 0 deletions Challenge/Challenge/Resources/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
36 changes: 36 additions & 0 deletions Challenge/Challenge/Sources/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// AppDelegate.swift
// Challenge
//
// Created by 김주희 on 3/11/26.
//

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}


}

67 changes: 67 additions & 0 deletions Challenge/Challenge/Sources/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// SceneDelegate.swift
// Challenge
//
// Created by 김주희 on 3/11/26.
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

guard let windowScene = (scene as? UIWindowScene) else { return }

let repository = SearchRepository()
let fetchHomeContentsuseCase = FetchHomeContentUseCase(repository: repository)
let homereactor = HomeReactor(fetchHomeContentsUseCase: fetchHomeContentsuseCase)

let searchUseCase = SearchUseCase(repository: repository)
let searchReactor = SearchReactor(searchUseCase: searchUseCase)

let searchVC = SearchViewController()
let homeVC = HomeViewController(reactor: homereactor, searchVC: searchVC)

let navigationController = UINavigationController(rootViewController: homeVC)

let window = UIWindow(windowScene: windowScene)
window.rootViewController = navigationController
window.makeKeyAndVisible()
self.window = window
}

func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}

func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}

func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}

func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}

func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}


}

26 changes: 26 additions & 0 deletions Challenge/Challenge/Sources/Data/APIEndpoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// APIEndpoint.swift
// Challenge
//
// Created by 김주희 on 3/12/26.
//

import Foundation


// MARK: 어떤 term, mediaType으로 요청할지를 url 형태로 만들기

enum APIEndpoint {
static let baseURL = "https://itunes.apple.com/search"

static func search(term: String, media: String, country: String, limit: String) -> URL? {
var components = URLComponents(string: baseURL) // 인코딩 기능
components?.queryItems = [
URLQueryItem(name: "term", value: term),
URLQueryItem(name: "media", value: media),
URLQueryItem(name: "country", value: country),
URLQueryItem(name: "limit", value: limit)
]
return components?.url
}
}
Loading