diff --git a/BoxOffice.xcodeproj/project.pbxproj b/BoxOffice.xcodeproj/project.pbxproj index 8876e05f..d46b5527 100644 --- a/BoxOffice.xcodeproj/project.pbxproj +++ b/BoxOffice.xcodeproj/project.pbxproj @@ -11,17 +11,22 @@ 1D313A7C2B8498740088297F /* BoxOfficeMovieDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D313A7B2B8498740088297F /* BoxOfficeMovieDTO.swift */; }; 1D313A7F2B8499840088297F /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D313A7E2B8499840088297F /* NetworkManager.swift */; }; 1D313A812B849ABF0088297F /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D313A802B849ABF0088297F /* NetworkError.swift */; }; - 1D7A8E072B89C76000ACCDC7 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7A8E062B89C76000ACCDC7 /* Date+Extensions.swift */; }; + 1D7A8E072B89C76000ACCDC7 /* Date+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7A8E062B89C76000ACCDC7 /* Date+.swift */; }; 1DF9F8872B7C819700458068 /* box_office.json in Resources */ = {isa = PBXBuildFile; fileRef = 1DF9F8862B7C819700458068 /* box_office.json */; }; 1DF9F88F2B7C81B000458068 /* BoxOfficeJsonDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF9F88E2B7C81B000458068 /* BoxOfficeJsonDataTests.swift */; }; 63DF20EF2970E1A0005DF7D1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DF20EE2970E1A0005DF7D1 /* AppDelegate.swift */; }; 63DF20F12970E1A0005DF7D1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DF20F02970E1A0005DF7D1 /* SceneDelegate.swift */; }; - 63DF20F32970E1A0005DF7D1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DF20F22970E1A0005DF7D1 /* ViewController.swift */; }; + 63DF20F32970E1A0005DF7D1 /* BoxOfficeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DF20F22970E1A0005DF7D1 /* BoxOfficeViewController.swift */; }; 63DF20F62970E1A0005DF7D1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63DF20F42970E1A0005DF7D1 /* Main.storyboard */; }; 63DF20F82970E1A1005DF7D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63DF20F72970E1A1005DF7D1 /* Assets.xcassets */; }; 63DF20FB2970E1A1005DF7D1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 63DF20F92970E1A1005DF7D1 /* LaunchScreen.storyboard */; }; F32E6F032B84861D0000A504 /* MovieInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F32E6F022B84861D0000A504 /* MovieInfoResponse.swift */; }; - F39648222B7C6178002DCB96 /* BoxOfficeData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39648212B7C6178002DCB96 /* BoxOfficeData.swift */; }; + F3446EB32BAA7873007A4430 /* BoxOfficeCollectionViewListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3446EB22BAA7873007A4430 /* BoxOfficeCollectionViewListCell.swift */; }; + F3446EB52BAA84C2007A4430 /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3446EB42BAA84C2007A4430 /* UIViewController+.swift */; }; + F3446EB72BAAC729007A4430 /* UILabel+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3446EB62BAAC729007A4430 /* UILabel+.swift */; }; + F3446EB92BAACAE6007A4430 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3446EB82BAACAE6007A4430 /* String+.swift */; }; + F3446EBD2BAACF06007A4430 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3446EBC2BAACF06007A4430 /* LoadingView.swift */; }; + F39648222B7C6178002DCB96 /* BoxOfficeDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39648212B7C6178002DCB96 /* BoxOfficeDTO.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,20 +44,25 @@ 1D313A7B2B8498740088297F /* BoxOfficeMovieDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeMovieDTO.swift; sourceTree = ""; }; 1D313A7E2B8499840088297F /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; 1D313A802B849ABF0088297F /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; - 1D7A8E062B89C76000ACCDC7 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; + 1D7A8E062B89C76000ACCDC7 /* Date+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+.swift"; sourceTree = ""; }; 1DF9F8862B7C819700458068 /* box_office.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = box_office.json; sourceTree = ""; }; 1DF9F88C2B7C81B000458068 /* BoxOfficeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BoxOfficeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1DF9F88E2B7C81B000458068 /* BoxOfficeJsonDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeJsonDataTests.swift; sourceTree = ""; }; 63DF20EB2970E1A0005DF7D1 /* BoxOffice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BoxOffice.app; sourceTree = BUILT_PRODUCTS_DIR; }; 63DF20EE2970E1A0005DF7D1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 63DF20F02970E1A0005DF7D1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 63DF20F22970E1A0005DF7D1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 63DF20F22970E1A0005DF7D1 /* BoxOfficeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeViewController.swift; sourceTree = ""; }; 63DF20F52970E1A0005DF7D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63DF20F72970E1A1005DF7D1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 63DF20FA2970E1A1005DF7D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63DF20FC2970E1A1005DF7D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F32E6F022B84861D0000A504 /* MovieInfoResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieInfoResponse.swift; sourceTree = ""; }; - F39648212B7C6178002DCB96 /* BoxOfficeData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeData.swift; sourceTree = ""; }; + F3446EB22BAA7873007A4430 /* BoxOfficeCollectionViewListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeCollectionViewListCell.swift; sourceTree = ""; }; + F3446EB42BAA84C2007A4430 /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; }; + F3446EB62BAAC729007A4430 /* UILabel+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+.swift"; sourceTree = ""; }; + F3446EB82BAACAE6007A4430 /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; + F3446EBC2BAACF06007A4430 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + F39648212B7C6178002DCB96 /* BoxOfficeDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxOfficeDTO.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -111,12 +121,12 @@ 63DF20ED2970E1A0005DF7D1 /* BoxOffice */ = { isa = PBXGroup; children = ( - 1D7A8E062B89C76000ACCDC7 /* Date+Extensions.swift */, - 1D313A7D2B8499640088297F /* Network */, - F396481E2B7C6138002DCB96 /* Model */, 63DF20EE2970E1A0005DF7D1 /* AppDelegate.swift */, 63DF20F02970E1A0005DF7D1 /* SceneDelegate.swift */, - 63DF20F22970E1A0005DF7D1 /* ViewController.swift */, + F3446EBA2BAACE63007A4430 /* Extensions */, + 1D313A7D2B8499640088297F /* Network */, + F396481E2B7C6138002DCB96 /* Model */, + F3446EBB2BAACEE3007A4430 /* Present */, 63DF20F42970E1A0005DF7D1 /* Main.storyboard */, 63DF20F72970E1A1005DF7D1 /* Assets.xcassets */, 63DF20F92970E1A1005DF7D1 /* LaunchScreen.storyboard */, @@ -125,10 +135,31 @@ path = BoxOffice; sourceTree = ""; }; + F3446EBA2BAACE63007A4430 /* Extensions */ = { + isa = PBXGroup; + children = ( + F3446EB82BAACAE6007A4430 /* String+.swift */, + F3446EB62BAAC729007A4430 /* UILabel+.swift */, + 1D7A8E062B89C76000ACCDC7 /* Date+.swift */, + F3446EB42BAA84C2007A4430 /* UIViewController+.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + F3446EBB2BAACEE3007A4430 /* Present */ = { + isa = PBXGroup; + children = ( + F3446EBC2BAACF06007A4430 /* LoadingView.swift */, + F3446EB22BAA7873007A4430 /* BoxOfficeCollectionViewListCell.swift */, + 63DF20F22970E1A0005DF7D1 /* BoxOfficeViewController.swift */, + ); + path = Present; + sourceTree = ""; + }; F396481E2B7C6138002DCB96 /* Model */ = { isa = PBXGroup; children = ( - F39648212B7C6178002DCB96 /* BoxOfficeData.swift */, + F39648212B7C6178002DCB96 /* BoxOfficeDTO.swift */, 1D313A7B2B8498740088297F /* BoxOfficeMovieDTO.swift */, F32E6F022B84861D0000A504 /* MovieInfoResponse.swift */, 1D313A792B8498200088297F /* MovieDetailDTO.swift */, @@ -191,7 +222,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1530; TargetAttributes = { 1DF9F88B2B7C81B000458068 = { CreatedOnToolsVersion = 15.0.1; @@ -255,16 +286,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 63DF20F32970E1A0005DF7D1 /* ViewController.swift in Sources */, + 63DF20F32970E1A0005DF7D1 /* BoxOfficeViewController.swift in Sources */, 1D313A7A2B8498200088297F /* MovieDetailDTO.swift in Sources */, + F3446EB52BAA84C2007A4430 /* UIViewController+.swift in Sources */, + F3446EB72BAAC729007A4430 /* UILabel+.swift in Sources */, + F3446EB32BAA7873007A4430 /* BoxOfficeCollectionViewListCell.swift in Sources */, 63DF20EF2970E1A0005DF7D1 /* AppDelegate.swift in Sources */, - 1D7A8E072B89C76000ACCDC7 /* Date+Extensions.swift in Sources */, + 1D7A8E072B89C76000ACCDC7 /* Date+.swift in Sources */, 63DF20F12970E1A0005DF7D1 /* SceneDelegate.swift in Sources */, + F3446EBD2BAACF06007A4430 /* LoadingView.swift in Sources */, 1D313A7F2B8499840088297F /* NetworkManager.swift in Sources */, F32E6F032B84861D0000A504 /* MovieInfoResponse.swift in Sources */, 1D313A812B849ABF0088297F /* NetworkError.swift in Sources */, 1D313A7C2B8498740088297F /* BoxOfficeMovieDTO.swift in Sources */, - F39648222B7C6178002DCB96 /* BoxOfficeData.swift in Sources */, + F3446EB92BAACAE6007A4430 /* String+.swift in Sources */, + F39648222B7C6178002DCB96 /* BoxOfficeDTO.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -482,7 +518,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -511,7 +547,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/BoxOffice/AppDelegate.swift b/BoxOffice/AppDelegate.swift index db2e674e..aff281fe 100644 --- a/BoxOffice/AppDelegate.swift +++ b/BoxOffice/AppDelegate.swift @@ -8,9 +8,7 @@ import UIKit @main -class AppDelegate: UIResponder, UIApplicationDelegate { - - +final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true diff --git a/BoxOffice/Base.lproj/Main.storyboard b/BoxOffice/Base.lproj/Main.storyboard index 25a76385..43bf48fd 100644 --- a/BoxOffice/Base.lproj/Main.storyboard +++ b/BoxOffice/Base.lproj/Main.storyboard @@ -1,24 +1,52 @@ - + + - + + + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BoxOffice/Date+Extensions.swift b/BoxOffice/Extensions/Date+.swift similarity index 100% rename from BoxOffice/Date+Extensions.swift rename to BoxOffice/Extensions/Date+.swift diff --git a/BoxOffice/Extensions/String+.swift b/BoxOffice/Extensions/String+.swift new file mode 100644 index 00000000..54613c6b --- /dev/null +++ b/BoxOffice/Extensions/String+.swift @@ -0,0 +1,22 @@ +// +// String+.swift +// BoxOffice +// +// Created by Jin-Mac on 3/20/24. +// + +import Foundation + +extension String { + + func formatDecimalNumberString() -> String { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.locale = Locale.current + guard let number = Double(self), + let changedString = formatter.string(from: NSNumber(value: number)) else { + return self + } + return changedString + } +} diff --git a/BoxOffice/Extensions/UILabel+.swift b/BoxOffice/Extensions/UILabel+.swift new file mode 100644 index 00000000..6bde8885 --- /dev/null +++ b/BoxOffice/Extensions/UILabel+.swift @@ -0,0 +1,32 @@ +// +// UILabel+.swift +// BoxOffice +// +// Created by Jin-Mac on 3/20/24. +// + +import UIKit + +extension UILabel { + + func setTextColor(_ color: UIColor, range: NSRange) { + guard let attributedString = self.mutableAttributedString() else { return } + + attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range) + self.attributedText = attributedString + } + + private func mutableAttributedString() -> NSMutableAttributedString? { + guard let labelText = self.text, let labelFont = self.font else { return nil } + + var attributedString: NSMutableAttributedString? + if let attributedText = self.attributedText { + attributedString = attributedText.mutableCopy() as? NSMutableAttributedString + } else { + attributedString = NSMutableAttributedString(string: labelText, + attributes: [NSAttributedString.Key.font :labelFont]) + } + + return attributedString + } +} diff --git a/BoxOffice/Extensions/UIViewController+.swift b/BoxOffice/Extensions/UIViewController+.swift new file mode 100644 index 00000000..9140bf73 --- /dev/null +++ b/BoxOffice/Extensions/UIViewController+.swift @@ -0,0 +1,24 @@ +// +// UIViewController+.swift +// BoxOffice +// +// Created by Jin-Mac on 3/20/24. +// + +import UIKit + +extension UIViewController { + + func presentAlert(title: String, + message: String? = nil, + confirmTitle: String = "확인", + confirmAction: ((UIAlertAction) -> Void)? = nil, + completion: (() -> Void)? = nil) { + let alertViewController = UIAlertController(title: title, message: message, preferredStyle: .alert) + + let confirmAction = UIAlertAction(title: confirmTitle, style: .default) + alertViewController.addAction(confirmAction) + + self.present(alertViewController, animated: true, completion: completion) + } +} diff --git a/BoxOffice/Model/BoxOfficeData.swift b/BoxOffice/Model/BoxOfficeDTO.swift similarity index 53% rename from BoxOffice/Model/BoxOfficeData.swift rename to BoxOffice/Model/BoxOfficeDTO.swift index de773da5..cc583b73 100644 --- a/BoxOffice/Model/BoxOfficeData.swift +++ b/BoxOffice/Model/BoxOfficeDTO.swift @@ -5,57 +5,58 @@ // Created by Jin-Mac on 2/14/24. // -struct BoxOfficeData: Codable { +struct BoxOfficeDTO: Decodable { let boxOfficeResult: BoxOfficeResult } -struct BoxOfficeResult: Codable { +struct BoxOfficeResult: Decodable { let boxofficeType: String let showRange: String let dailyBoxOfficeList: [DailyBoxOfficeList] } -struct DailyBoxOfficeList: Codable { +struct DailyBoxOfficeList: Decodable { let number: String let rank: String let rankIncrement: String let rankOldAndNew: RankOldAndNew - let movieCd: String + let movieCode: String let movieName: String let openDate: String - let salesAmount: String - let salesShare: String + let salesDailyAmount: String + let salesDailyShare: String let salesIncrement: String - let salesChange: String - let salesAccount: String - let audienceCount: String + let salesChangeRatio: String + let salesTotalAmount: String + let audienceDailyCount: String let audienceIncrement: String - let audienceChange: String - let audienceAccount: String + let audienceChangeRatio: String + let audienceTotalAmount: String let screenCount: String let showCount: String enum CodingKeys: String, CodingKey{ case number = "rnum" case rankIncrement = "rankInten" + case movieCode = "movieCd" case movieName = "movieNm" case openDate = "openDt" - case salesAmount = "salesAmt" - case salesShare = "salesShare" + case salesDailyAmount = "salesAmt" + case salesDailyShare = "salesShare" case salesIncrement = "salesInten" - case salesChange = "salesChange" - case salesAccount = "salesAcc" - case audienceCount = "audiCnt" + case salesChangeRatio = "salesChange" + case salesTotalAmount = "salesAcc" + case audienceDailyCount = "audiCnt" case audienceIncrement = "audiInten" - case audienceChange = "audiChange" - case audienceAccount = "audiAcc" + case audienceChangeRatio = "audiChange" + case audienceTotalAmount = "audiAcc" case screenCount = "scrnCnt" case showCount = "showCnt" - case rankOldAndNew, rank, movieCd + case rankOldAndNew, rank } } -enum RankOldAndNew: String, Codable { +enum RankOldAndNew: String, Decodable { case new = "NEW" case old = "OLD" } diff --git a/BoxOffice/Network/NetworkManager.swift b/BoxOffice/Network/NetworkManager.swift index 8027746a..1f646425 100644 --- a/BoxOffice/Network/NetworkManager.swift +++ b/BoxOffice/Network/NetworkManager.swift @@ -4,6 +4,7 @@ // // Created by 박찬호 on 2/20/24. // + enum KobisRequesUrl: String { case apiKey = "f5eef3421c602c6cb7ea224104795888" case scheme = "http" @@ -58,7 +59,7 @@ final class NetworkManager { task.resume() } - func fetchDailyBoxOffice(for date: String, completion: @escaping (Result<[BoxOfficeMovieDTO], NetworkError>) -> Void) { + func fetchDailyBoxOffice(for date: String, completion: @escaping (Result) -> Void) { var components = URLComponents() components.scheme = KobisRequesUrl.scheme.rawValue components.host = KobisRequesUrl.host.rawValue @@ -73,17 +74,10 @@ final class NetworkManager { return } - performRequest(with: url) { (result: Result) in + performRequest(with: url) { (result: Result) in switch result { case .success(let data): - let boxOfficeMovies = data.boxOfficeResult.dailyBoxOfficeList.map { boxOffice -> BoxOfficeMovieDTO in - BoxOfficeMovieDTO(rank: boxOffice.rank, - movieName: boxOffice.movieName, - openDate: boxOffice.openDate, - audienceCount: boxOffice.audienceCount, - movieCode: boxOffice.movieCd) - } - completion(.success(boxOfficeMovies)) + completion(.success(data)) case .failure(let error): completion(.failure(error)) } diff --git a/BoxOffice/Present/BoxOfficeCollectionViewListCell.swift b/BoxOffice/Present/BoxOfficeCollectionViewListCell.swift new file mode 100644 index 00000000..199dcceb --- /dev/null +++ b/BoxOffice/Present/BoxOfficeCollectionViewListCell.swift @@ -0,0 +1,139 @@ +// +// BoxOfficeCollectionViewCell.swift +// BoxOffice +// +// Created by Jin-Mac on 3/20/24. +// + +import UIKit + +final class BoxOfficeCollectionViewListCell: UICollectionViewListCell { + // MARK: - Public Property + + static let identifier = "BoxOfficeCollectionViewListCell" + + // MARK: - Private Property + + var movieData: DailyBoxOfficeList? { + didSet { + updateDailyBoxOfficeCellLabel() + } + } + + private let rankLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 30, weight: .semibold) + return label + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 20, weight: .medium) + return label + }() + + private let detailLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 14) + label.numberOfLines = 0 + return label + }() + + private let rankChangeLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 14) + return label + }() + + private lazy var rankStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [rankLabel, rankChangeLabel]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.distribution = .fillProportionally + stackView.alignment = .center + stackView.axis = .vertical + return stackView + }() + + private lazy var titleStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [titleLabel, detailLabel]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 5 + return stackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + accessories = [.disclosureIndicator()] + configureUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension BoxOfficeCollectionViewListCell { + + // MARK: - Private Function + + private func configureUI() { + contentView.addSubview(titleStackView) + contentView.addSubview(rankStackView) + + configureConstraint() + } + + private func configureConstraint() { + NSLayoutConstraint.activate([ + rankStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + rankStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), + rankStackView.widthAnchor.constraint(equalToConstant: 40) + ]) + + NSLayoutConstraint.activate([ + titleStackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + titleStackView.leadingAnchor.constraint(equalTo: rankStackView.trailingAnchor, constant: 30), + titleStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20) + ]) + } + + private func updateDailyBoxOfficeCellLabel() { + rankLabel.text = movieData?.rank + titleLabel.text = movieData?.movieName + detailLabel.text = "오늘 \(movieData?.audienceDailyCount.formatDecimalNumberString() ?? "Null") / 총 \(movieData?.audienceTotalAmount.formatDecimalNumberString() ?? "Null")" + formatMovieLabel() + } + + private func formatMovieLabel() { + + guard let newOrOld = movieData?.rankOldAndNew else { + rankChangeLabel.text = "Null" + return + } + + guard newOrOld != .new else { + rankChangeLabel.text = "신작" + rankChangeLabel.textColor = .red + return + } + + guard let rankChangeValue = movieData?.rankIncrement, let rankChangeValue = Int(rankChangeValue) else { return } + + guard rankChangeValue != 0 else { + rankChangeLabel.text = "-" + rankChangeLabel.textColor = .black + return + } + + var rankChangString = rankChangeValue < 0 ? "▼" : "▲" + let charactersColor: UIColor = rankChangeValue < 0 ? .blue : .red + rankChangString += String(rankChangeValue).formatDecimalNumberString() + rankChangeLabel.text = rankChangString + rankChangeLabel.setTextColor(charactersColor, range: NSRange(location: 0, length: 1)) + } +} diff --git a/BoxOffice/Present/BoxOfficeViewController.swift b/BoxOffice/Present/BoxOfficeViewController.swift new file mode 100644 index 00000000..7822c842 --- /dev/null +++ b/BoxOffice/Present/BoxOfficeViewController.swift @@ -0,0 +1,170 @@ +// +// ViewController.swift +// BoxOffice +// +// Created by kjs on 13/01/23. +// + +import UIKit + +final class BoxOfficeViewController: UIViewController { + + // MARK: - Private Property + + private var boxOfficeData: BoxOfficeDTO? + private var isAppStartLoading: Bool = true + + // MARK: - View + + private lazy var loadingView: LoadingView = { + let view = LoadingView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private lazy var movieCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 0 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + + return collectionView + }() + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.loadingView.isLoading = true + configureNavigationBar() + configureUI() + configureCollectionView() + requstBoxOfficeData() + configureRefreshControl() + } + + // MARK: - Private Function + + private func formatDateString(_ dateString: String) -> String { + let yearIndex = dateString.index(dateString.startIndex, offsetBy: 0) + let monthIndex = dateString.index(dateString.startIndex, offsetBy: 4) + let dayIndex = dateString.index(dateString.startIndex, offsetBy: 6) + + let year = dateString[yearIndex.. Int { + + guard let sectionCount = boxOfficeData?.boxOfficeResult.dailyBoxOfficeList.count else { return 0 } + return sectionCount + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BoxOfficeCollectionViewListCell.identifier, for: indexPath) as? BoxOfficeCollectionViewListCell else { + return UICollectionViewCell() + } + + guard let singleData = boxOfficeData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row] else { + return UICollectionViewCell() + } + + cell.movieData = singleData + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + + return CGSize(width: collectionView.bounds.width, height: 80) + } +} diff --git a/BoxOffice/Present/LoadingView.swift b/BoxOffice/Present/LoadingView.swift new file mode 100644 index 00000000..c39e3e05 --- /dev/null +++ b/BoxOffice/Present/LoadingView.swift @@ -0,0 +1,64 @@ +// +// LoadingView.swift +// BoxOffice +// +// Created by Jin-Mac on 3/20/24. +// + +import UIKit + +final class LoadingView: UIView { + + private let backgroundView: UIView = { + let view = UIView() + view.backgroundColor = .white + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + private let activityIndicatorView: UIActivityIndicatorView = { + let view = UIActivityIndicatorView(style: .large) + view.translatesAutoresizingMaskIntoConstraints = false + view.color = .systemIndigo + return view + }() + private let loadingLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 20, weight: .bold) + label.text = "Loading..." + return label + }() + + var isLoading = false { + didSet { + self.isHidden = self.isLoading == false + self.isLoading ? self.activityIndicatorView.startAnimating() : self.activityIndicatorView.stopAnimating() + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.addSubview(self.activityIndicatorView) + self.addSubview(self.loadingLabel) + + NSLayoutConstraint.activate([ + self.backgroundView.leftAnchor.constraint(equalTo: self.leftAnchor), + self.backgroundView.rightAnchor.constraint(equalTo: self.rightAnchor), + self.backgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + self.backgroundView.topAnchor.constraint(equalTo: self.topAnchor), + ]) + NSLayoutConstraint.activate([ + self.activityIndicatorView.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.activityIndicatorView.centerXAnchor.constraint(equalTo: self.centerXAnchor), + ]) + NSLayoutConstraint.activate([ + self.loadingLabel.centerXAnchor.constraint(equalTo: activityIndicatorView.centerXAnchor), + self.loadingLabel.topAnchor.constraint(equalTo: activityIndicatorView.centerYAnchor, constant: 50) + ]) + } + required init?(coder: NSCoder) { + fatalError() + } +} diff --git a/BoxOffice/SceneDelegate.swift b/BoxOffice/SceneDelegate.swift index 1231a998..b5e44b84 100644 --- a/BoxOffice/SceneDelegate.swift +++ b/BoxOffice/SceneDelegate.swift @@ -7,7 +7,7 @@ import UIKit -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? diff --git a/BoxOffice/ViewController.swift b/BoxOffice/ViewController.swift deleted file mode 100644 index 5284bb89..00000000 --- a/BoxOffice/ViewController.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// ViewController.swift -// BoxOffice -// -// Created by kjs on 13/01/23. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - fatchMovieDatail() - fetchDailyBoxOffice() - } - // 일별 박스오피스 테스트 용 - private func fetchDailyBoxOffice() { - let date = Date.yesterdayFormatted - NetworkManager.shared.fetchDailyBoxOffice(for: date ) { result in - DispatchQueue.main.async { - switch result { - case .success(let boxOfficeMovies): - for movie in boxOfficeMovies { - print("-----------------------------") - print("순위: \(movie.rank)") - print("영화 이름: \(movie.movieName)") - print("개봉일: \(movie.openDate)") - print("관객 수: \(movie.audienceCount)명") - print("무비 코드: \(movie.movieCode)") - print("-----------------------------") - } - case .failure(let error): - print("박스오피스 데이터를 가져오는데 실패 \(error)") - - } - } - } - } - // 영화 상세정보 테스트 용 - private func fatchMovieDatail() { - let movieCode = "20236180" - - NetworkManager.shared.fetchMovieDetail(for: movieCode) { result in - DispatchQueue.main.async { - switch result { - case .success(let movieDetail): - print("-----------------------------") - print("영화 코드: \(movieDetail.movieCode)") - print("영화 이름: \(movieDetail.movieName)") - print("영화 영문 이름: \(movieDetail.movieNameEnglish)") - print("상영 시간: \(movieDetail.runningTime)분") - print("제작 연도: \(movieDetail.productionYear)") - print("개봉일: \(movieDetail.openDate)") - print("영화 타입: \(movieDetail.movieType)") - print("장르: \(movieDetail.genres.joined(separator: ", "))") - - if !movieDetail.directors.isEmpty { - print("감독: \(movieDetail.directors.joined(separator: ", "))") - } else { - print("감독 정보 없음") - } - - print("-----------------------------") - case .failure(let error): - print("영화 상세 정보를 가져오는데 실패했습니다: \(error)") - } - } - } - } -}