Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

박스오피스II [STEP 2] yetti, maxhyunm #111

Open
wants to merge 7 commits into
base: ic_9_yetti
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 8 additions & 4 deletions BoxOffice.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
3B8F5FD42A73556200D1A7F6 /* BoxOfficeNetworkingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B8F5FD32A73556200D1A7F6 /* BoxOfficeNetworkingTest.swift */; };
3BF7147F2A6E75C6006F274D /* BoxOfficeDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7147E2A6E75C6006F274D /* BoxOfficeDecodingTests.swift */; };
3BF714862A6E78E3006F274D /* DecodingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF714852A6E78E3006F274D /* DecodingManager.swift */; };
3BF7D9AE2A79E69800996A5E /* BoxOfficeRankingCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingCell.swift */; };
3BF7D9AE2A79E69800996A5E /* BoxOfficeRankingListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingListCell.swift */; };
3BF7D9B12A7C7D0600996A5E /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7D9B02A7C7D0600996A5E /* String+.swift */; };
3BF7D9B62A81C28400996A5E /* DaumImageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7D9B52A81C28400996A5E /* DaumImageEntity.swift */; };
3BF7D9B82A81D69300996A5E /* MovieDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF7D9B72A81D69300996A5E /* MovieDetailViewController.swift */; };
Expand All @@ -25,6 +25,7 @@
63DF20F82970E1A1005DF7D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63DF20F72970E1A1005DF7D1 /* Assets.xcassets */; };
63DF20FB2970E1A1005DF7D1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63DF20F92970E1A1005DF7D1 /* LaunchScreen.storyboard */; };
BA1A55AE2A81DF3D0012C89D /* MovieDetailStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1A55AD2A81DF3D0012C89D /* MovieDetailStackView.swift */; };
BA1A55B82A8C99320012C89D /* BoxOfficeRankingIconCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1A55B72A8C99320012C89D /* BoxOfficeRankingIconCell.swift */; };
BAAC6F2E2A6E6B7700AD34ED /* box_office_sample.json in Resources */ = {isa = PBXBuildFile; fileRef = BAAC6F2D2A6E6B7700AD34ED /* box_office_sample.json */; };
BAAC6F302A6E6C4B00AD34ED /* BoxOfficeEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAC6F2F2A6E6C4B00AD34ED /* BoxOfficeEntity.swift */; };
BAAC6F362A710D8100AD34ED /* MovieDetailEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAC6F352A710D8100AD34ED /* MovieDetailEntity.swift */; };
Expand Down Expand Up @@ -53,7 +54,7 @@
3BF7147C2A6E75C6006F274D /* BoxOfficeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BoxOfficeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3BF7147E2A6E75C6006F274D /* BoxOfficeDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeDecodingTests.swift; sourceTree = "<group>"; };
3BF714852A6E78E3006F274D /* DecodingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingManager.swift; sourceTree = "<group>"; };
3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeRankingCell.swift; sourceTree = "<group>"; };
3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeRankingListCell.swift; sourceTree = "<group>"; };
3BF7D9B02A7C7D0600996A5E /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = "<group>"; };
3BF7D9B52A81C28400996A5E /* DaumImageEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaumImageEntity.swift; sourceTree = "<group>"; };
3BF7D9B72A81D69300996A5E /* MovieDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDetailViewController.swift; sourceTree = "<group>"; };
Expand All @@ -66,6 +67,7 @@
63DF20FA2970E1A1005DF7D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
63DF20FC2970E1A1005DF7D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
BA1A55AD2A81DF3D0012C89D /* MovieDetailStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDetailStackView.swift; sourceTree = "<group>"; };
BA1A55B72A8C99320012C89D /* BoxOfficeRankingIconCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeRankingIconCell.swift; sourceTree = "<group>"; };
BAAC6F2D2A6E6B7700AD34ED /* box_office_sample.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = box_office_sample.json; sourceTree = "<group>"; };
BAAC6F2F2A6E6C4B00AD34ED /* BoxOfficeEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeEntity.swift; sourceTree = "<group>"; };
BAAC6F352A710D8100AD34ED /* MovieDetailEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieDetailEntity.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -133,7 +135,8 @@
isa = PBXGroup;
children = (
63DF20F92970E1A1005DF7D1 /* LaunchScreen.storyboard */,
3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingCell.swift */,
3BF7D9AD2A79E69800996A5E /* BoxOfficeRankingListCell.swift */,
BA1A55B72A8C99320012C89D /* BoxOfficeRankingIconCell.swift */,
BA1A55AD2A81DF3D0012C89D /* MovieDetailStackView.swift */,
);
path = View;
Expand Down Expand Up @@ -303,6 +306,7 @@
buildActionMask = 2147483647;
files = (
BAAC6F4A2A734D1B00AD34ED /* URLSessionProtocol.swift in Sources */,
BA1A55B82A8C99320012C89D /* BoxOfficeRankingIconCell.swift in Sources */,
3BF7D9B82A81D69300996A5E /* MovieDetailViewController.swift in Sources */,
3BF7D9BC2A85DFCE00996A5E /* CalendarViewController.swift in Sources */,
63DF20F32970E1A0005DF7D1 /* BoxOfficeViewController.swift in Sources */,
Expand All @@ -316,7 +320,7 @@
3BF714862A6E78E3006F274D /* DecodingManager.swift in Sources */,
3B8F5F9E2A72186D00D1A7F6 /* NetworkConfiguration.swift in Sources */,
3B8F5F9C2A71032C00D1A7F6 /* Error.swift in Sources */,
3BF7D9AE2A79E69800996A5E /* BoxOfficeRankingCell.swift in Sources */,
3BF7D9AE2A79E69800996A5E /* BoxOfficeRankingListCell.swift in Sources */,
3BF7D9B12A7C7D0600996A5E /* String+.swift in Sources */,
63DF20F12970E1A0005DF7D1 /* SceneDelegate.swift in Sources */,
);
Expand Down
78 changes: 69 additions & 9 deletions BoxOffice/Controller/BoxOfficeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ final class BoxOfficeViewController: UIViewController, URLSessionDelegate {
private var refreshControl = UIRefreshControl()
private var dataSource: UICollectionViewDiffableDataSource<NetworkConfiguration, BoxOfficeEntity.BoxOfficeResult.DailyBoxOffice>?
private var date: Date = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()

private let collectionView: UICollectionView = {
let configuration = UICollectionLayoutListConfiguration(appearance: .plain)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.translatesAutoresizingMaskIntoConstraints = false
view.register(BoxOfficeRankingCell.self, forCellWithReuseIdentifier: BoxOfficeRankingCell.cellIdentifier)
view.register(BoxOfficeRankingListCell.self, forCellWithReuseIdentifier: BoxOfficeRankingListCell.cellIdentifier)
view.register(BoxOfficeRankingIconCell.self, forCellWithReuseIdentifier: BoxOfficeRankingIconCell.cellIdentifier)

return view
}()
Expand All @@ -42,6 +43,14 @@ final class BoxOfficeViewController: UIViewController, URLSessionDelegate {
}
}
}

private var isListMode = true {
didSet {
setUpCollectionViewLayout()
setUpDataSource()
passFetchedData()
}
}

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -58,11 +67,15 @@ extension BoxOfficeViewController {
private func setUpUI() {
let safeArea = view.safeAreaLayoutGuide
let dateSelectionButton = UIBarButtonItem(title: "날짜선택", style: .plain, target: self, action: #selector(showCalendar))
let modeChangeButton = UIBarButtonItem(title: "화면 모드 변경", style: .plain, target: self, action: #selector(hitChangeModeButton))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)

view.backgroundColor = .systemBackground
view.addSubview(collectionView)
view.addSubview(indicatorView)

self.navigationController?.isToolbarHidden = false
self.toolbarItems = [flexibleSpace, modeChangeButton, flexibleSpace]
self.title = getDateString(format: Namespace.dateWithHyphen)
self.navigationItem.rightBarButtonItem = dateSelectionButton

Expand All @@ -77,13 +90,45 @@ extension BoxOfficeViewController {
])
}

private func setUpCollectionViewLayout() {
if isListMode {
let configuration = UICollectionLayoutListConfiguration(appearance: .plain)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)

collectionView.collectionViewLayout = layout
} else {
let layout = UICollectionViewFlowLayout()
let width = (view.frame.width - 45) / 2.0
Comment on lines +94 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금은 리스트, 아이콘 형태의 UI 뿐인데, 좀 다르게 생긴 UI등이 더 추가된다면 어떨까요?
isListMode라는 불 타입의 변수로 관리하는 것 말고 다른 좋은 방법이 있을 것 같아요.


layout.sectionInset = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 15
layout.itemSize = CGSize(width: width, height: width)

collectionView.collectionViewLayout = layout
}
}

@objc func showCalendar(_ sender: UIButton) {
let viewController = CalendarViewController(date)
viewController.delegate = self
viewController.modalPresentationStyle = UIModalPresentationStyle.automatic

self.present(viewController, animated: true)
}

@objc func hitChangeModeButton() {
let mode: String = isListMode == true ? "아이콘" : "리스트"
let alert = UIAlertController(title: "화면 모드 변경", message: nil, preferredStyle: .actionSheet)
let modeChangeAction = UIAlertAction(title: mode, style: .default) { _ in
self.isListMode.toggle()
}
let cancelAction = UIAlertAction(title: "취소", style: .cancel)

alert.addAction(modeChangeAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
}

extension BoxOfficeViewController {
Expand All @@ -104,14 +149,26 @@ extension BoxOfficeViewController {
}

private func setUpDataSource() {
dataSource = UICollectionViewDiffableDataSource<NetworkConfiguration, BoxOfficeEntity.BoxOfficeResult.DailyBoxOffice>(collectionView: self.collectionView) { (collectionView, indexPath, data) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BoxOfficeRankingCell.cellIdentifier, for: indexPath) as? BoxOfficeRankingCell else {
return UICollectionViewCell()
if isListMode {
dataSource = UICollectionViewDiffableDataSource<NetworkConfiguration, BoxOfficeEntity.BoxOfficeResult.DailyBoxOffice>(collectionView: self.collectionView) { (collectionView, indexPath, data) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BoxOfficeRankingListCell.cellIdentifier, for: indexPath) as? BoxOfficeRankingListCell else {
return BoxOfficeRankingListCell()
}

cell.setUpLabelText(data)

return cell
}
} else {
dataSource = UICollectionViewDiffableDataSource<NetworkConfiguration, BoxOfficeEntity.BoxOfficeResult.DailyBoxOffice>(collectionView: self.collectionView) { (collectionView, indexPath, data) -> UICollectionViewCell? in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BoxOfficeRankingIconCell.cellIdentifier, for: indexPath) as? BoxOfficeRankingIconCell else {
return BoxOfficeRankingIconCell()
}

cell.setUpLabelText(data)

return cell
}
Comment on lines +152 to 171
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isListMode 분기를 데이터소스 클로져 안에서 진행하는 편이 코드 가독성 등 더 깔끔할 것 같습니다


cell.setUpLabelText(data)

return cell
}
}

Expand Down Expand Up @@ -186,6 +243,9 @@ extension BoxOfficeViewController: BoxOfficeDelegate {
func setUpDate(_ date: Date) {
self.date = date
self.title = getDateString(format: Namespace.dateWithHyphen)

setUpCollectionViewLayout()
setUpDataSource()
Comment on lines 243 to +248
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문 남겨주신 사항 중 여기서 두 메서드 다시 호출하는 방식보다 아마 말씀해주신 셀의 UIStackView쪽을 한번 살펴보시는 걸 추천드려요! 스택뷰의 arrangedSubview에 보여줄 게 없으면 크기가 적절히 줄어드는 게 장점이긴 한데, 어느정도 고정된 높이를 가져야하는 경우에는 또 잘 줄어들어버려서 말씀하신 그런 문제가 종종 발생하는 것 같더라고요.

passFetchedData()
}
}
3 changes: 3 additions & 0 deletions BoxOffice/Model/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum NetworkingError: Error {
case notHttpUrlResponse
case invalidResponse(statusCode: Int)
case invalidURL
case invalidAPIKey

var description: String {
switch self {
Expand All @@ -21,6 +22,8 @@ enum NetworkingError: Error {
return "서버 응답 오류입니다. Status Code: \(statusCode)"
case .invalidURL:
return "유효하지 않은 URL입니다."
case .invalidAPIKey:
return "유효하지 않은 API Key입니다."
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions BoxOffice/Model/NetworkingManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,24 @@ struct NetworkingManager {
}

func load(_ networkType: NetworkConfiguration, completion: @escaping (Result<Data, NetworkingError>) -> Void) {
var urlComponents = URLComponents(string: networkType.url)
guard let urlString = networkType.url, let header = networkType.header else {
completion(.failure(NetworkingError.invalidAPIKey))
return
}
Comment on lines +18 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

var urlComponents = URLComponents(string: urlString)

networkType.query.forEach {
urlComponents?.queryItems = [URLQueryItem(name: $0.name, value: $0.value)]
}

guard let url = urlComponents?.url else {
completion(.failure(NetworkingError.invalidURL))
return
return
}

var request = URLRequest(url: url)
networkType.header.forEach {

header.forEach {
request.setValue($0.value, forHTTPHeaderField: $0.forHTTPHeaderField)
}

Expand Down
16 changes: 14 additions & 2 deletions BoxOffice/Resource/NetworkConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ enum NetworkConfiguration: Hashable {
case movieDetail(_ movieCode: String)
case daumImage(_ movieName: String)

var url: String {
var url: String? {
switch self {
case .boxOffice(let targetDate):
if NetworkConfiguration.apiKey == "" {
return nil
}

return String(format: "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=%@&targetDt=%@", NetworkConfiguration.apiKey, targetDate)
case .movieDetail(let movieCode):
if NetworkConfiguration.apiKey == "" {
return nil
}

return String(format: "https://www.kobis.or.kr/kobisopenapi/webservice/rest/movie/searchMovieInfo.json?key=%@&movieCd=%@", NetworkConfiguration.apiKey, movieCode)
case .daumImage:
return "https://dapi.kakao.com/v2/search/image"
Expand All @@ -32,9 +40,13 @@ enum NetworkConfiguration: Hashable {
}
}

var header: [(value: String, forHTTPHeaderField: String)] {
var header: [(value: String, forHTTPHeaderField: String)]? {
switch self {
case .daumImage:
if NetworkConfiguration.daumApiKey == "" {
return nil
}

return [(value: "KakaoAK \(NetworkConfiguration.daumApiKey)", forHTTPHeaderField: "Authorization")]
default:
return []
Expand Down
Loading