diff --git a/Tving_CloneProject/Tving_CloneProject.xcodeproj/project.pbxproj b/Tving_CloneProject/Tving_CloneProject.xcodeproj/project.pbxproj index 92c1de2..729e534 100644 --- a/Tving_CloneProject/Tving_CloneProject.xcodeproj/project.pbxproj +++ b/Tving_CloneProject/Tving_CloneProject.xcodeproj/project.pbxproj @@ -8,12 +8,10 @@ /* Begin PBXBuildFile section */ CA1F68F02BD5691D00439E33 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68EF2BD5691D00439E33 /* MainViewController.swift */; }; - CA1F68F32BD5698C00439E33 /* MainPosterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68F22BD5698C00439E33 /* MainPosterView.swift */; }; CA1F68F52BD578B500439E33 /* NavigationBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68F42BD578B500439E33 /* NavigationBarView.swift */; }; CA1F68F82BD57D3A00439E33 /* Contents.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68F72BD57D3A00439E33 /* Contents.swift */; }; CA1F68FB2BD5886600439E33 /* MainPosterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68FA2BD5886600439E33 /* MainPosterCell.swift */; }; CA1F68FD2BD58ED700439E33 /* PagerButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1F68FC2BD58ED700439E33 /* PagerButtonCell.swift */; }; - CA22B3C52BED37090013B7AB /* DailyBoxOfficeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA22B3C42BED37090013B7AB /* DailyBoxOfficeCell.swift */; }; CA3952E62BC31F9900B15E91 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3952E52BC31F9900B15E91 /* AppDelegate.swift */; }; CA3952E82BC31F9900B15E91 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3952E72BC31F9900B15E91 /* SceneDelegate.swift */; }; CA3952EA2BC31F9900B15E91 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3952E92BC31F9900B15E91 /* LoginViewController.swift */; }; @@ -36,6 +34,12 @@ CA3953202BC6F6BE00B15E91 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA39531F2BC6F6BE00B15E91 /* LoginView.swift */; }; CA3953222BC6FA9F00B15E91 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3953212BC6FA9F00B15E91 /* Constant.swift */; }; CA3953252BC6FB1100B15E91 /* ScreenUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3953242BC6FB1100B15E91 /* ScreenUtils.swift */; }; + CA3BA70F2BFE7E9800F77616 /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA3BA70E2BFE7E9800F77616 /* MainViewModel.swift */; }; + CA803D982C05B977006B8C35 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA803D972C05B977006B8C35 /* LoginViewModel.swift */; }; + CA803D9A2C0866BA006B8C35 /* ObservablePattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA803D992C0866BA006B8C35 /* ObservablePattern.swift */; }; + CA803D9C2C09C784006B8C35 /* MovieViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA803D9B2C09C784006B8C35 /* MovieViewModel.swift */; }; + CA803D9E2C09C818006B8C35 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA803D9D2C09C818006B8C35 /* String+.swift */; }; + CA803DA02C09D878006B8C35 /* PageControlButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA803D9F2C09D878006B8C35 /* PageControlButtonViewModel.swift */; }; CA8496E22BE7E36D0064E0CC /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = CA8496E12BE7E36D0064E0CC /* Moya */; }; CA8496E52BE7E54D0064E0CC /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496E42BE7E54D0064E0CC /* Config.swift */; }; CA8496E72BE7E8E60064E0CC /* GetMovieResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496E62BE7E8E60064E0CC /* GetMovieResponseModel.swift */; }; @@ -45,7 +49,6 @@ CA8496F22BE8A6700064E0CC /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496F12BE8A6700064E0CC /* NetworkResult.swift */; }; CA8496F62BE947040064E0CC /* LiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496F52BE947040064E0CC /* LiveView.swift */; }; CA8496F82BE947F90064E0CC /* TVProgramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496F72BE947F90064E0CC /* TVProgramView.swift */; }; - CA8496FA2BE9482C0064E0CC /* MovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496F92BE9482C0064E0CC /* MovieView.swift */; }; CA8496FC2BE948590064E0CC /* ParamountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8496FB2BE948590064E0CC /* ParamountView.swift */; }; CA902EBC2BD6F9F900560D26 /* ImageWithTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA902EBB2BD6F9F900560D26 /* ImageWithTitleCell.swift */; }; CA902EC02BD6FE1400560D26 /* BasicHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA902EBF2BD6FE1400560D26 /* BasicHeaderView.swift */; }; @@ -59,17 +62,17 @@ CA902EF72BDE9E5E00560D26 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA902EF62BDE9E5E00560D26 /* SearchViewController.swift */; }; CA902EF92BDE9E6700560D26 /* HistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA902EF82BDE9E6700560D26 /* HistoryViewController.swift */; }; CA902EFB2BDEB51500560D26 /* PageControlButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA902EFA2BDEB51500560D26 /* PageControlButtonView.swift */; }; + CAB6E7EC2C0485E300E49F9E /* DailyBoxOfficeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB6E7EB2C0485E300E49F9E /* DailyBoxOfficeCell.swift */; }; + CAB6E7EE2C04863200E49F9E /* MovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB6E7ED2C04863200E49F9E /* MovieView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ CA05B3AD2BF331B100DA77E1 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; CA1F68EF2BD5691D00439E33 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - CA1F68F22BD5698C00439E33 /* MainPosterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainPosterView.swift; sourceTree = ""; }; CA1F68F42BD578B500439E33 /* NavigationBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarView.swift; sourceTree = ""; }; CA1F68F72BD57D3A00439E33 /* Contents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contents.swift; sourceTree = ""; }; CA1F68FA2BD5886600439E33 /* MainPosterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainPosterCell.swift; sourceTree = ""; }; CA1F68FC2BD58ED700439E33 /* PagerButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerButtonCell.swift; sourceTree = ""; }; - CA22B3C42BED37090013B7AB /* DailyBoxOfficeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyBoxOfficeCell.swift; sourceTree = ""; }; CA3952E22BC31F9900B15E91 /* Tving_CloneProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tving_CloneProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; CA3952E52BC31F9900B15E91 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CA3952E72BC31F9900B15E91 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -91,6 +94,12 @@ CA39531F2BC6F6BE00B15E91 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; CA3953212BC6FA9F00B15E91 /* Constant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; CA3953242BC6FB1100B15E91 /* ScreenUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenUtils.swift; sourceTree = ""; }; + CA3BA70E2BFE7E9800F77616 /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; + CA803D972C05B977006B8C35 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; + CA803D992C0866BA006B8C35 /* ObservablePattern.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservablePattern.swift; sourceTree = ""; }; + CA803D9B2C09C784006B8C35 /* MovieViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieViewModel.swift; sourceTree = ""; }; + CA803D9D2C09C818006B8C35 /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; + CA803D9F2C09D878006B8C35 /* PageControlButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlButtonViewModel.swift; sourceTree = ""; }; CA8496E42BE7E54D0064E0CC /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; CA8496E62BE7E8E60064E0CC /* GetMovieResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMovieResponseModel.swift; sourceTree = ""; }; CA8496E92BE89A840064E0CC /* BaseTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTargetType.swift; sourceTree = ""; }; @@ -99,7 +108,6 @@ CA8496F12BE8A6700064E0CC /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = ""; }; CA8496F52BE947040064E0CC /* LiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveView.swift; sourceTree = ""; }; CA8496F72BE947F90064E0CC /* TVProgramView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVProgramView.swift; sourceTree = ""; }; - CA8496F92BE9482C0064E0CC /* MovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieView.swift; sourceTree = ""; }; CA8496FB2BE948590064E0CC /* ParamountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParamountView.swift; sourceTree = ""; }; CA902EBB2BD6F9F900560D26 /* ImageWithTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWithTitleCell.swift; sourceTree = ""; }; CA902EBF2BD6FE1400560D26 /* BasicHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicHeaderView.swift; sourceTree = ""; }; @@ -113,6 +121,8 @@ CA902EF62BDE9E5E00560D26 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; CA902EF82BDE9E6700560D26 /* HistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewController.swift; sourceTree = ""; }; CA902EFA2BDEB51500560D26 /* PageControlButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlButtonView.swift; sourceTree = ""; }; + CAB6E7EB2C0485E300E49F9E /* DailyBoxOfficeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyBoxOfficeCell.swift; sourceTree = ""; }; + CAB6E7ED2C04863200E49F9E /* MovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,7 +143,7 @@ CA1F68ED2BD568F300439E33 /* Main */ = { isa = PBXGroup; children = ( - CA1F68F92BD5884200439E33 /* Cells */, + CA3BA70D2BFE7E8700F77616 /* ViewModel */, CA1F68F62BD57D2900439E33 /* Models */, CA1F68F12BD5697500439E33 /* Views */, CA1F68EE2BD5690000439E33 /* ViewControllers */, @@ -152,7 +162,7 @@ CA1F68F12BD5697500439E33 /* Views */ = { isa = PBXGroup; children = ( - CA1F68F22BD5698C00439E33 /* MainPosterView.swift */, + CA1F68F92BD5884200439E33 /* Cells */, CA1F68F42BD578B500439E33 /* NavigationBarView.swift */, CA902EBF2BD6FE1400560D26 /* BasicHeaderView.swift */, CA902EC72BDB8D3900560D26 /* UnderlineSegmentedControlView.swift */, @@ -160,8 +170,8 @@ CA902EFA2BDEB51500560D26 /* PageControlButtonView.swift */, CA8496F52BE947040064E0CC /* LiveView.swift */, CA8496F72BE947F90064E0CC /* TVProgramView.swift */, - CA8496F92BE9482C0064E0CC /* MovieView.swift */, CA8496FB2BE948590064E0CC /* ParamountView.swift */, + CAB6E7ED2C04863200E49F9E /* MovieView.swift */, ); path = Views; sourceTree = ""; @@ -183,7 +193,7 @@ CA902EBB2BD6F9F900560D26 /* ImageWithTitleCell.swift */, CA902EC32BD9542000560D26 /* ImageOnlyCell.swift */, CA902EC52BDAA0E200560D26 /* PopularLiveCell.swift */, - CA22B3C42BED37090013B7AB /* DailyBoxOfficeCell.swift */, + CAB6E7EB2C0485E300E49F9E /* DailyBoxOfficeCell.swift */, ); path = Cells; sourceTree = ""; @@ -261,6 +271,7 @@ CA3953162BC45DA300B15E91 /* UIFont+.swift */, CA3953182BC47D3300B15E91 /* UILabel+.swift */, CA902EC12BD789FF00560D26 /* UIStackView+.swift */, + CA803D9D2C09C818006B8C35 /* String+.swift */, ); path = Extensions; sourceTree = ""; @@ -298,6 +309,7 @@ isa = PBXGroup; children = ( CA3953242BC6FB1100B15E91 /* ScreenUtils.swift */, + CA803D992C0866BA006B8C35 /* ObservablePattern.swift */, ); path = Utils; sourceTree = ""; @@ -305,7 +317,8 @@ CA3953262BC6FC0E00B15E91 /* Login */ = { isa = PBXGroup; children = ( - CA3953292BC6FC3D00B15E91 /* Views */, + CA803D962C05B959006B8C35 /* ViewModel */, + CA3953292BC6FC3D00B15E91 /* View */, CA3953282BC6FC2C00B15E91 /* ViewControllers */, ); path = Login; @@ -328,12 +341,12 @@ path = ViewControllers; sourceTree = ""; }; - CA3953292BC6FC3D00B15E91 /* Views */ = { + CA3953292BC6FC3D00B15E91 /* View */ = { isa = PBXGroup; children = ( CA39531F2BC6F6BE00B15E91 /* LoginView.swift */, ); - path = Views; + path = View; sourceTree = ""; }; CA39532A2BC6FC6A00B15E91 /* ViewControllers */ = { @@ -344,6 +357,24 @@ path = ViewControllers; sourceTree = ""; }; + CA3BA70D2BFE7E8700F77616 /* ViewModel */ = { + isa = PBXGroup; + children = ( + CA3BA70E2BFE7E9800F77616 /* MainViewModel.swift */, + CA803D9B2C09C784006B8C35 /* MovieViewModel.swift */, + CA803D9F2C09D878006B8C35 /* PageControlButtonViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + CA803D962C05B959006B8C35 /* ViewModel */ = { + isa = PBXGroup; + children = ( + CA803D972C05B977006B8C35 /* LoginViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; CA8496DA2BE7D6550064E0CC /* Network */ = { isa = PBXGroup; children = ( @@ -523,6 +554,7 @@ files = ( CA8496FC2BE948590064E0CC /* ParamountView.swift in Sources */, CA902EF92BDE9E6700560D26 /* HistoryViewController.swift in Sources */, + CA803D9A2C0866BA006B8C35 /* ObservablePattern.swift in Sources */, CA3953002BC44B9900B15E91 /* UIView+.swift in Sources */, CA1F68F02BD5691D00439E33 /* MainViewController.swift in Sources */, CA8496F22BE8A6700064E0CC /* NetworkResult.swift in Sources */, @@ -534,21 +566,23 @@ CA8496EE2BE8A1680064E0CC /* MainService.swift in Sources */, CA902EC02BD6FE1400560D26 /* BasicHeaderView.swift in Sources */, CA902EC22BD789FF00560D26 /* UIStackView+.swift in Sources */, - CA22B3C52BED37090013B7AB /* DailyBoxOfficeCell.swift in Sources */, CA3952EA2BC31F9900B15E91 /* LoginViewController.swift in Sources */, CA3952E62BC31F9900B15E91 /* AppDelegate.swift in Sources */, CA902EED2BDE9CD800560D26 /* TabBarViewController.swift in Sources */, CA1F68F82BD57D3A00439E33 /* Contents.swift in Sources */, CA3953192BC47D3300B15E91 /* UILabel+.swift in Sources */, + CA803D9C2C09C784006B8C35 /* MovieViewModel.swift in Sources */, CA902ECA2BDB8E5C00560D26 /* HeaderCategoryView.swift in Sources */, CA902EC82BDB8D3900560D26 /* UnderlineSegmentedControlView.swift in Sources */, + CA803D982C05B977006B8C35 /* LoginViewModel.swift in Sources */, + CAB6E7EC2C0485E300E49F9E /* DailyBoxOfficeCell.swift in Sources */, CA3953172BC45DA300B15E91 /* UIFont+.swift in Sources */, CA3952E82BC31F9900B15E91 /* SceneDelegate.swift in Sources */, CA1F68FB2BD5886600439E33 /* MainPosterCell.swift in Sources */, - CA1F68F32BD5698C00439E33 /* MainPosterView.swift in Sources */, CA3953252BC6FB1100B15E91 /* ScreenUtils.swift in Sources */, CA8496E72BE7E8E60064E0CC /* GetMovieResponseModel.swift in Sources */, CA8496F82BE947F90064E0CC /* TVProgramView.swift in Sources */, + CA803DA02C09D878006B8C35 /* PageControlButtonViewModel.swift in Sources */, CA3953202BC6F6BE00B15E91 /* LoginView.swift in Sources */, CA902EF72BDE9E5E00560D26 /* SearchViewController.swift in Sources */, CA8496F62BE947040064E0CC /* LiveView.swift in Sources */, @@ -556,8 +590,10 @@ CA8496F02BE8A5880064E0CC /* MoyaPlugin.swift in Sources */, CA8496EA2BE89A850064E0CC /* BaseTargetType.swift in Sources */, CA39531E2BC5AFE300B15E91 /* CreateNicknameViewController.swift in Sources */, + CA803D9E2C09C818006B8C35 /* String+.swift in Sources */, + CA3BA70F2BFE7E9800F77616 /* MainViewModel.swift in Sources */, CA1F68F52BD578B500439E33 /* NavigationBarView.swift in Sources */, - CA8496FA2BE9482C0064E0CC /* MovieView.swift in Sources */, + CAB6E7EE2C04863200E49F9E /* MovieView.swift in Sources */, CA902EC42BD9542000560D26 /* ImageOnlyCell.swift in Sources */, CA8496E52BE7E54D0064E0CC /* Config.swift in Sources */, CA902EC62BDAA0E200560D26 /* PopularLiveCell.swift in Sources */, diff --git a/Tving_CloneProject/Tving_CloneProject/Global/Components/TabBarViewController.swift b/Tving_CloneProject/Tving_CloneProject/Global/Components/TabBarViewController.swift index 08595b4..d3e02a5 100644 --- a/Tving_CloneProject/Tving_CloneProject/Global/Components/TabBarViewController.swift +++ b/Tving_CloneProject/Tving_CloneProject/Global/Components/TabBarViewController.swift @@ -9,7 +9,7 @@ import UIKit import Then -class TabBarViewController: UITabBarController { +final class TabBarViewController: UITabBarController { // MARK: - UI Components diff --git a/Tving_CloneProject/Tving_CloneProject/Global/Extensions/String+.swift b/Tving_CloneProject/Tving_CloneProject/Global/Extensions/String+.swift new file mode 100644 index 0000000..b700702 --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Global/Extensions/String+.swift @@ -0,0 +1,28 @@ +// +// String+.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/31/24. +// + +import Foundation + +extension String { + + static func calculateDate() -> String { + let today = Date() + let calendar = Calendar.current + var dateComponents = DateComponents() + dateComponents.day = -1 + + if let oneDayAgo = calendar.date(byAdding: dateComponents, to: today) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd" + + let oneDayAgoString = dateFormatter.string(from: oneDayAgo) + return oneDayAgoString + } else { + return "" + } + } +} diff --git a/Tving_CloneProject/Tving_CloneProject/Global/Utils/ObservablePattern.swift b/Tving_CloneProject/Tving_CloneProject/Global/Utils/ObservablePattern.swift new file mode 100644 index 0000000..fc9d622 --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Global/Utils/ObservablePattern.swift @@ -0,0 +1,28 @@ +// +// ObservablePattern.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/30/24. +// + +import Foundation + +final class ObservablePattern { // --- a + + var value: T? { // --- b + didSet { + self.listener?(value) + } + } + + init(_ value: T?) { + self.value = value + } + + private var listener: ((T?) -> Void)? // --- c + + func bind(_ listener: @escaping (T?) -> Void) { + listener(value) // 생략 가능, 여기선 시작되는 순간부터 초기값을 갖고 동작하기 위해 사용 + self.listener = listener + } +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Login/Views/LoginView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Login/View/LoginView.swift similarity index 87% rename from Tving_CloneProject/Tving_CloneProject/Scene/Login/Views/LoginView.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Login/View/LoginView.swift index 4503206..9111806 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Login/Views/LoginView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Login/View/LoginView.swift @@ -11,7 +11,7 @@ protocol LoginViewDelegate: AnyObject { func pushToWelcomeVC(id: String) } -class LoginView: UIView { +final class LoginView: UIView { // MARK: - UI Properties @@ -38,6 +38,8 @@ class LoginView: UIView { weak var delegate: LoginViewDelegate? + private let loginViewModel: LoginViewModel = LoginViewModel() + // MARK: - Life Cycles @@ -61,12 +63,10 @@ class LoginView: UIView { private extension LoginView { func setHierarchy() { - self.addSubviews(idTextField, pwTextField, loginButton) } func setLayout() { - idTextField.snp.makeConstraints { $0.top.centerX.width.equalToSuperview() $0.height.equalTo(52) @@ -87,7 +87,6 @@ private extension LoginView { } func setStyle() { - self.backgroundColor = UIColor(resource: .black) idTextField.do { @@ -203,17 +202,10 @@ private extension LoginView { @objc func textFieldChange() { - let id = self.idTextField.text ?? "" - let pw = self.pwTextField.text ?? "" - - if !id.isEmpty { - idClearButton.isHidden = false - } else { - pwClearButton.isHidden = false - maskButton.isHidden = false - } + let id = self.idTextField.text + let pw = self.pwTextField.text - setLoginButton(isEnabled: !id.isEmpty && !pw.isEmpty) + setLoginButton(isEnabled: loginViewModel.checkValid(id: id, pw: pw)) } @objc @@ -224,9 +216,9 @@ private extension LoginView { @objc func clearButtonTapped(_ sender: UIButton) { if sender.tag == 0 { - self.idTextField.text = "" + self.loginViewModel.clearText(textfield: self.idTextField) } else { - self.pwTextField.text = "" + self.loginViewModel.clearText(textfield: self.pwTextField) } setLoginButton(isEnabled: false) } @@ -246,23 +238,11 @@ private extension LoginView { extension LoginView: UITextFieldDelegate { func textFieldDidBeginEditing (_ textField: UITextField) { - - if textField.placeholder == "아이디" { - self.idTextField.layer.borderWidth = 1 - self.idTextField.layer.borderColor = UIColor(resource: .grey2).cgColor - } else { - self.pwTextField.layer.borderWidth = 1 - self.pwTextField.layer.borderColor = UIColor(resource: .grey2).cgColor - } - + textField.layer.borderWidth = 1 + textField.layer.borderColor = UIColor(resource: .grey2).cgColor } func textFieldDidEndEditing(_ textField: UITextField) { - - if textField.placeholder == "아이디" { - self.idTextField.layer.borderWidth = 0 - } else { - self.pwTextField.layer.borderWidth = 0 - } + textField.layer.borderWidth = 0 } } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/CreateNicknameViewController.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/CreateNicknameViewController.swift index 44ccafc..cda0888 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/CreateNicknameViewController.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/CreateNicknameViewController.swift @@ -11,7 +11,7 @@ protocol CreateNicknameVCDelegate: AnyObject { func saveUserNickname(nickname: String) } -class CreateNicknameViewController: UIViewController { +final class CreateNicknameViewController: UIViewController { // MARK: - UI Properties @@ -34,6 +34,8 @@ class CreateNicknameViewController: UIViewController { weak var delegate: CreateNicknameVCDelegate? + private let loginViewModel: LoginViewModel = LoginViewModel() + // MARK: - Life Cycles @@ -131,7 +133,6 @@ private extension CreateNicknameViewController { } warningLabel.do { - $0.text = "닉네임은 \"한글\"만 사용 가능해요!" $0.font = UIFont.pretendard(.subhead5) $0.textColor = UIColor(resource: .red) $0.isHidden = true @@ -167,8 +168,7 @@ private extension CreateNicknameViewController { @objc func textFieldChange() { - let nickname = self.nicknameTextField.text ?? "" - setSaveButton(isEnabled: !nickname.isEmpty) + setSaveButton(isEnabled: loginViewModel.checkValidNickname(nickname: self.nicknameTextField.text)) } @objc @@ -192,15 +192,14 @@ private extension CreateNicknameViewController { extension CreateNicknameViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - // 정규식 패턴 - let pattern = "^[ㄱ-ㅎㅏ-ㅣ가-힣]*$" // 입력된 문자열이 패턴과 일치하는지 확인 - if let _ = string.range(of: pattern, options: .regularExpression) { + if loginViewModel.checkValidNickname(nickname: textField.text) { self.warningLabel.isHidden = true return true } else { self.warningLabel.isHidden = false + self.warningLabel.text = loginViewModel.fetchErrMessage() setSaveButton(isEnabled: false) return false } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/LoginViewController.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/LoginViewController.swift index 95f24b7..65bd461 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/LoginViewController.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewControllers/LoginViewController.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class LoginViewController: UIViewController { +final class LoginViewController: UIViewController { // MARK: - UI Properties @@ -33,7 +33,9 @@ class LoginViewController: UIViewController { var isActivate: Bool = false - var nickname: String = "" + var nickname: String? = nil + + private let loginViewModel: LoginViewModel = LoginViewModel() // MARK: - Life Cycles @@ -79,7 +81,6 @@ private extension LoginViewController { findIdLabel.snp.makeConstraints { $0.top.equalTo(loginView.snp.bottom).offset(30) $0.leading.equalToSuperview().inset(85) -// $0.trailing.equalToSuperview().inset(225) } divider.snp.makeConstraints { @@ -92,7 +93,6 @@ private extension LoginViewController { findPwLabel.snp.makeConstraints { $0.top.equalTo(loginView.snp.bottom).offset(30) $0.leading.equalTo(divider.snp.trailing).offset(35) -// $0.width.equalTo(ScreenUtils.getWidth(75)) } messageLabel.snp.makeConstraints { @@ -180,7 +180,7 @@ extension LoginViewController: LoginViewDelegate { func pushToWelcomeVC(id: String) { let welcomeVC = WelcomeViewController() - welcomeVC.userInfo = nickname.isEmpty ? id : nickname + welcomeVC.userInfo = loginViewModel.checkValidNickname(nickname: self.nickname) ? nickname : id self.navigationController?.pushViewController(welcomeVC, animated: true) } } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewModel/LoginViewModel.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewModel/LoginViewModel.swift new file mode 100644 index 0000000..bfeb87a --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Login/ViewModel/LoginViewModel.swift @@ -0,0 +1,76 @@ +// +// LoginViewModel.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/28/24. +// + +import UIKit + +final class LoginViewModel { + + // MARK: - Properties + + var id: ObservablePattern = ObservablePattern.init(nil) + + var pw: ObservablePattern = ObservablePattern.init(nil) + + var nickname: ObservablePattern = ObservablePattern.init(nil) + + var errMessage: ObservablePattern = ObservablePattern.init(nil) + +} + +extension LoginViewModel { + + func checkEmptyNickname(nickname: String?) -> Bool { + guard let nickname else { + errMessage.value = "닉네임을 입력해주세요" + return false + } + + self.nickname.value = nickname + return true + } + + func checkValidNickname(nickname: String?) -> Bool { + guard let nickname else { + errMessage.value = "닉네임을 입력해주세요" + return false + } + + // 정규식 패턴 + let pattern = "^[ㄱ-ㅎㅏ-ㅣ가-힣]*$" + guard let _ = nickname.range(of: pattern, options: .regularExpression) else { + errMessage.value = "닉네임은 \"한글\"만 사용 가능해요!" + return false + } + + self.nickname.value = nickname + return true + } + + func checkValid(id: String?, pw: String?) -> Bool { + guard let id else { + errMessage.value = "아이디를 입력해주세요" + return false + } + self.id.value = id + + guard let pw else { + errMessage.value = "비밀번호를 입력해주세요" + return false + } + self.pw.value = pw + + return true + } + + func clearText(textfield: UITextField) { + textfield.text = "" + } + + func fetchErrMessage() -> String? { + return self.errMessage.value + } +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ObservablePattern.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ObservablePattern.swift new file mode 100644 index 0000000..9d8a9df --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ObservablePattern.swift @@ -0,0 +1,28 @@ +// +// Observable.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/27/24. +// + +import Foundation + +class ObservablePattern { + + var value: T? { + didSet { + self.listener?(value) + } + } + + init(_ value: T?) { + self.value = value + } + + private var listener: ((T?) -> Void)? // --- c + + func bind(_ listener: @escaping (T?) -> Void) { + listener(value) + self.listener = listener + } +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewControllers/MainViewController.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewControllers/MainViewController.swift index 0bb7a13..36d7524 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewControllers/MainViewController.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewControllers/MainViewController.swift @@ -10,10 +10,10 @@ import UIKit import SnapKit import Then -class MainViewController: UIViewController { +final class MainViewController: UIViewController { // MARK: - UI Properties - + private lazy var mainCollectionView = UICollectionView(frame: .zero, collectionViewLayout: self.makeFlowLayout()) private let navigationBarView = NavigationBarView() @@ -37,17 +37,7 @@ class MainViewController: UIViewController { // MARK: - Properties - private var mainData: [Contents] = [] - - private var recommendedData: [Contents] = [] - - private var popularData: [Contents] = [] - - private var paramountsData: [Contents] = [] - - private var categoryData: [Contents] = [] - - private let dataSource: [MainSection] = MainSection.dataSource + private var mainViewModel: MainViewModel = MainViewModel() private var prevValue: Int = 0 @@ -65,32 +55,33 @@ class MainViewController: UIViewController { private var selectedTabBarIndex: Int = 0 private var shouldShowSticky: Bool = false - + // MARK: - Life Cycles override func viewDidLoad() { super.viewDidLoad() - - getMovieInfo() + setHierarchy() setLayout() setStyle() setDelegate() registerCell() + setViewModel() + getMovieInfo() setSegmentDidChange() } - + } // MARK: - Private Methods private extension MainViewController { - + func setHierarchy() { - self.view.addSubviews(mainCollectionView, + self.view.addSubviews(mainCollectionView, navigationBarView, dimmedView, headerCategoryView, @@ -174,6 +165,30 @@ private extension MainViewController { } } + func setViewModel() { + mainViewModel.didUpdateNetworkResult.bind { [weak self] isSuccess in + guard let isSuccess else { return } + if isSuccess { + self?.mainCollectionView.reloadData() + } + } + + mainViewModel.didChangeLoadingIndicator.bind { [weak self] isLoading in + guard let isLoading else { return } + if isLoading { + self?.loadingIndicator.startAnimating() + } else { + self?.loadingIndicator.stopAnimating() + } + } + } + + func getMovieInfo() { + if mainViewModel.getMovieInfo() { + self.mainCollectionView.reloadData() + } + } + func setDelegate() { mainCollectionView.delegate = self mainCollectionView.dataSource = self @@ -212,7 +227,7 @@ private extension MainViewController { return UICollectionViewCompositionalLayout { section, ev -> NSCollectionLayoutSection? in - switch self.dataSource[section] { + switch self.mainViewModel.dataSource[section] { case .mainPoster: return self.makeMainPosterLayout() case .recommendedContents, .paramounts: @@ -226,21 +241,21 @@ private extension MainViewController { } func makeMainPosterLayout() -> NSCollectionLayoutSection { - - let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) - let item = NSCollectionLayoutItem(layoutSize: itemSize) - - let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(550 / 812)) - let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) - - let section = NSCollectionLayoutSection(group: group) + + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(550 / 812)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .groupPaging section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 23, trailing: 0) let footer = makePageControlButtonView() section.boundarySupplementaryItems = [footer] - - return section + + return section } func makeImageNTitleLayout() -> NSCollectionLayoutSection { @@ -259,7 +274,7 @@ private extension MainViewController { let header = makeHeaderView() section.boundarySupplementaryItems = [header] - + return section } @@ -279,16 +294,16 @@ private extension MainViewController { let header = makeHeaderView() section.boundarySupplementaryItems = [header] - + return section } - + func makeImageOnlyLayout() -> NSCollectionLayoutSection { - + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8) - + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(58 / 812)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) @@ -296,7 +311,7 @@ private extension MainViewController { let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .groupPaging section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 15, bottom: tabBarHeight + 10, trailing: 0) - + return section } @@ -320,55 +335,6 @@ private extension MainViewController { } - func getMovieInfo() { - let currentDate = calculateDate() - - loadingIndicator.startAnimating() - - MainService.shared.getMovieList(date: currentDate) { response in - switch response { - case .success(let data): - guard let data = data as? GetMovieResponseModel else { return } - var count = 0 - for i in data.boxOfficeResult.dailyBoxOfficeList { - self.mainData.append(Contents(image: Contents.posterImages[count])) - self.recommendedData.append(Contents(image: Contents.posterImages[count], title: i.movieNm)) - self.paramountsData.append(Contents(image: Contents.posterImages[count], title: i.movieNm)) - self.categoryData.append(Contents(image: Contents.categoryImages[count])) - self.popularData.append(Contents(image: Contents.posterImages[count], - title: i.movieNm, - ranking: "\(count + 1)", - channelName: "tvn", - rating: i.salesShare)) - count+=1 - } - self.loadingIndicator.stopAnimating() - self.mainCollectionView.reloadData() - - default: - return - } - } - } - - func calculateDate() -> String { - - let today = Date() - let calendar = Calendar.current - var dateComponents = DateComponents() - dateComponents.day = -1 - - if let oneDayAgo = calendar.date(byAdding: dateComponents, to: today) { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyyMMdd" - - let oneDayAgoString = dateFormatter.string(from: oneDayAgo) - return oneDayAgoString - } else { - return "" - } - } - @objc func didChangeValue(sender: UISegmentedControl) { setSegmentView(selectedIndex: sender.selectedSegmentIndex) @@ -397,7 +363,7 @@ extension MainViewController: PageControlButtonDelegate { let direction: UIPageViewController.NavigationDirection = prevValue < newValue ? .forward : .reverse for cell in mainCollectionView.visibleCells { if let mainPosterCell = cell as? MainPosterCell { - mainPosterCell.pageVC.setViewControllers([mainPosterCell.vcData[currentPage]], + mainPosterCell.pageVC.setViewControllers([mainPosterCell.vcData[currentPage]], direction: direction, animated: true, completion: nil) @@ -415,7 +381,7 @@ extension MainViewController: MainPosterDelegate { if let pageControlButtonView = mainCollectionView.supplementaryView(forElementKind: PageControlButtonView.elementKinds, at: IndexPath(item: 0, section: 0)) as? PageControlButtonView { pageControlButtonView.index = currentPage } } - + } extension MainViewController: UICollectionViewDelegate { @@ -445,51 +411,53 @@ extension MainViewController: UICollectionViewDelegate { extension MainViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { - return dataSource.count + return mainViewModel.dataSource.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - switch dataSource[section] { + switch mainViewModel.dataSource[section] { case .mainPoster: return 1 case .recommendedContents: - return recommendedData.count + return mainViewModel.fetchData(type: .recommendedContents).count case .popularLiveChannel: - return popularData.count + return mainViewModel.fetchData(type: .popularLiveChannel).count case .paramounts: - return paramountsData.count + return mainViewModel.fetchData(type: .paramounts).count case .categories: - return categoryData.count + return mainViewModel.fetchData(type: .categories).count } } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - switch dataSource[indexPath.section] { + switch mainViewModel.dataSource[indexPath.section] { case .mainPoster: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MainPosterCell.identifier, for: indexPath) as? MainPosterCell else { return UICollectionViewCell() } - cell.setPageVC(imageData: mainData) + cell.setPageVC(imageData: mainViewModel.fetchData(type: .mainPoster)) cell.delegate = self return cell case .recommendedContents, .paramounts: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageWithTitleCell.identifier, for: indexPath) as? ImageWithTitleCell else { return UICollectionViewCell() } - let data = dataSource[indexPath.section] == .recommendedContents ? recommendedData : paramountsData + let data = mainViewModel.dataSource[indexPath.section] == .recommendedContents + ? mainViewModel.fetchData(type: .recommendedContents) + : mainViewModel.fetchData(type: .paramounts) cell.setCell(contents: data[indexPath.row]) return cell case .popularLiveChannel: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PopularLiveCell.identifier, for: indexPath) as? PopularLiveCell else { return UICollectionViewCell() } - cell.setCell(contents: popularData[indexPath.row]) + cell.setCell(contents: mainViewModel.fetchData(type: .popularLiveChannel)[indexPath.row]) return cell case .categories: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageOnlyCell.identifier, for: indexPath) as? ImageOnlyCell else { return UICollectionViewCell() } - cell.setCell(contents: categoryData[indexPath.row]) + cell.setCell(contents: mainViewModel.fetchData(type: .categories)[indexPath.row]) return cell } @@ -501,7 +469,7 @@ extension MainViewController: UICollectionViewDataSource { guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: BasicHeaderView.identifier, for: indexPath) as? BasicHeaderView else { return UICollectionReusableView() } - switch dataSource[indexPath.section] { + switch mainViewModel.dataSource[indexPath.section] { case .mainPoster, .categories: return header case .recommendedContents: @@ -516,7 +484,7 @@ extension MainViewController: UICollectionViewDataSource { guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: PageControlButtonView.identifier, for: indexPath) as? PageControlButtonView else { return UICollectionReusableView() } - footer.buttonCount = mainData.count + footer.buttonCount = mainViewModel.fetchData(type: .mainPoster).count footer.delegate = self return footer } else { @@ -526,3 +494,4 @@ extension MainViewController: UICollectionViewDataSource { } } + diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MainViewModel.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MainViewModel.swift new file mode 100644 index 0000000..33e3c04 --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MainViewModel.swift @@ -0,0 +1,83 @@ +// +// MainViewModel.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/23/24. +// + +import Foundation + +final class MainViewModel { + + // MARK: - Properties + + var didUpdateNetworkResult: ObservablePattern = ObservablePattern(false) + + var didChangeLoadingIndicator: ObservablePattern = ObservablePattern(true) + + private var mainData: ObservablePattern<[Contents]> = ObservablePattern.init([]) + + private var recommendedData: ObservablePattern<[Contents]> = ObservablePattern.init([]) + + private var popularData: ObservablePattern<[Contents]> = ObservablePattern.init([]) + + private var paramountsData: ObservablePattern<[Contents]> = ObservablePattern.init([]) + + private var categoryData: ObservablePattern<[Contents]> = ObservablePattern.init([]) + + let dataSource: [MainSection] = MainSection.dataSource + +} + +extension MainViewModel { + + func fetchData(type: MainSection) -> [Contents] { + switch type { + case .mainPoster: + return self.mainData.value ?? [] + case .recommendedContents: + return self.recommendedData.value ?? [] + case .popularLiveChannel: + return self.popularData.value ?? [] + case .paramounts: + return self.paramountsData.value ?? [] + case .categories: + return self.categoryData.value ?? [] + } + } + + func getMovieInfo() -> Bool { + let currentDate = String.calculateDate() + + self.didChangeLoadingIndicator.value = true + + MainService.shared.getMovieList(date: currentDate) { response in + switch response { + case .success(let data): + guard let data = data as? GetMovieResponseModel else { return } + var count = 0 + for i in data.boxOfficeResult.dailyBoxOfficeList { + self.mainData.value?.append(Contents(image: Contents.posterImages[count])) + self.recommendedData.value?.append(Contents(image: Contents.posterImages[count], title: i.movieNm)) + self.paramountsData.value?.append(Contents(image: Contents.posterImages[count], title: i.movieNm)) + self.categoryData.value?.append(Contents(image: Contents.categoryImages[count])) + self.popularData.value?.append(Contents(image: Contents.posterImages[count], + title: i.movieNm, + ranking: "\(count + 1)", + channelName: "tvn", + rating: i.salesShare)) + count+=1 + } + self.didChangeLoadingIndicator.value = false + self.didUpdateNetworkResult.value = true + + default: + self.didUpdateNetworkResult.value = false + return + } + } + guard let networkResult = self.didUpdateNetworkResult.value else { return false} + return networkResult + } + +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MovieViewModel.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MovieViewModel.swift new file mode 100644 index 0000000..62a95de --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/MovieViewModel.swift @@ -0,0 +1,50 @@ +// +// MovieViewModel.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/31/24. +// + +import Foundation + +final class MovieViewModel { + + // MARK: - Properties + + private var dailyBoxOfficeData: ObservablePattern<[DailyBoxOfficeList]> = ObservablePattern([]) + + var didUpdateNetworkResult: ObservablePattern = ObservablePattern(false) + + var didChangeLoadingIndicator: ObservablePattern = ObservablePattern(true) + +} + +extension MovieViewModel { + + func fetchData() -> [DailyBoxOfficeList] { + return self.dailyBoxOfficeData.value ?? [] + } + + func getDailyBoxOffice() -> Bool { + let date = String.calculateDate() + + self.didChangeLoadingIndicator.value = true + + MainService.shared.getMovieList(date: date) { response in + switch response { + case .success(let data): + guard let data = data as? GetMovieResponseModel else { return } + self.dailyBoxOfficeData.value = data.boxOfficeResult.dailyBoxOfficeList + + self.didChangeLoadingIndicator.value = false + self.didUpdateNetworkResult.value = true + + default: + self.didUpdateNetworkResult.value = false + return + } + } + guard let networkResult = self.didUpdateNetworkResult.value else { return false } + return networkResult + } +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/PageControlButtonViewModel.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/PageControlButtonViewModel.swift new file mode 100644 index 0000000..ce0f79d --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/ViewModel/PageControlButtonViewModel.swift @@ -0,0 +1,30 @@ +// +// PageControlButtonViewModel.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/31/24. +// + +import UIKit + +final class PageControlButtonViewModel: NSObject { + var buttonCount: ObservablePattern = ObservablePattern.init(0) +} + +extension PageControlButtonViewModel: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + guard let count = buttonCount.value else { return 0 } + return count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PagerButtonCell.identifier, for: indexPath) as? PagerButtonCell + else { return UICollectionViewCell() } + + cell.pagerButton.tag = indexPath.row + + return cell + } + +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/BasicHeaderView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/BasicHeaderView.swift index ee277ec..579392c 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/BasicHeaderView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/BasicHeaderView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class BasicHeaderView: UICollectionReusableView { +final class BasicHeaderView: UICollectionReusableView { // MARK: - UI Properties @@ -52,13 +52,10 @@ class BasicHeaderView: UICollectionReusableView { private extension BasicHeaderView { func setHierarchy() { - self.addSubviews(titleLabel, viewAllButton) - } func setLayout() { - titleLabel.snp.makeConstraints { $0.bottom.equalToSuperview() $0.leading.equalToSuperview() @@ -73,7 +70,6 @@ private extension BasicHeaderView { } func setStyle() { - titleLabel.do { $0.font = UIFont.pretendard(.subhead3) $0.textColor = UIColor(resource: .white) diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/BasicHeaderCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/BasicHeaderCell.swift similarity index 100% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/BasicHeaderCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/BasicHeaderCell.swift diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/DailyBoxOfficeCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/DailyBoxOfficeCell.swift new file mode 100644 index 0000000..22c5451 --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/DailyBoxOfficeCell.swift @@ -0,0 +1,122 @@ +// DailyBoxOfficeCell.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/10/24. +// + +import UIKit + +class DailyBoxOfficeCell: UICollectionViewCell { + + // MARK: - UI Properties + + let rankingLabel: UILabel = UILabel() + + let movieInfoStackView: UIStackView = UIStackView() + + let titleLabel: UILabel = UILabel() + + let audiCntLabel: UILabel = UILabel() + + let openDateLabel: UILabel = UILabel() + + + // MARK: - Properties + + static let identifier: String = "DailyBoxOfficeCell" + + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + + setHierarchy() + setLayout() + setStyle() + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setCell(contents: DailyBoxOfficeList) { + rankingLabel.text = contents.rank + titleLabel.text = contents.movieNm + audiCntLabel.text = "누적 관객 수 \(contents.audiCnt)명" + + let date = contents.openDt.split(separator: "-").map{ String($0) } + openDateLabel.text = "\(date[0])년 \(date[1])월 \(date[2])일" + } + + } + + + // MARK: - Private Methods + + private extension DailyBoxOfficeCell { + + func setHierarchy() { + + self.addSubviews(movieInfoStackView, rankingLabel) + movieInfoStackView.addArrangedSubviews(titleLabel, audiCntLabel, openDateLabel) + + } + + func setLayout() { + + rankingLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(30) + $0.leading.trailing.equalToSuperview().inset(10) + } + + movieInfoStackView.snp.makeConstraints { + $0.leading.trailing.equalTo(rankingLabel) + $0.bottom.equalToSuperview().inset(40) + } + + } + + func setStyle() { + + self.do { + $0.layer.cornerRadius = 10 + $0.backgroundColor = UIColor(resource: .grey2).withAlphaComponent(0.5) + } + + rankingLabel.do { + $0.font = UIFont.pretendard(.head1) + $0.textColor = UIColor(resource: .grey4) + $0.textAlignment = .left + } + + movieInfoStackView.do { + $0.axis = .vertical + $0.alignment = .leading + $0.distribution = .fillProportionally + $0.spacing = 10 + } + + titleLabel.do { + $0.font = UIFont.pretendard(.subhead6) + $0.textColor = UIColor(resource: .white) + $0.textAlignment = .left + $0.numberOfLines = 0 + } + + audiCntLabel.do { + $0.font = UIFont.pretendard(.subhead2) + $0.textColor = UIColor(resource: .white) + $0.textAlignment = .left + } + + openDateLabel.do { + $0.font = UIFont.pretendard(.subhead2) + $0.textColor = UIColor(resource: .white) + $0.textAlignment = .left + } + } + + } + diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/ImageOnlyCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/ImageOnlyCell.swift similarity index 100% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/ImageOnlyCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/ImageOnlyCell.swift diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/ImageWithTitleCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/ImageWithTitleCell.swift similarity index 100% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/ImageWithTitleCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/ImageWithTitleCell.swift diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/MainPosterCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/MainPosterCell.swift similarity index 99% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/MainPosterCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/MainPosterCell.swift index b0ad4ea..19b15d3 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/MainPosterCell.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/MainPosterCell.swift @@ -11,7 +11,6 @@ import SnapKit import Then protocol MainPosterDelegate: AnyObject { - func didSwipePoster(index: Int, vc: UIPageViewController, vcData: [UIViewController]) } @@ -58,9 +57,7 @@ final class MainPosterCell: UICollectionViewCell { } vcData += [vc] } - setVCInPageVC() - } } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/PagerButtonCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/PagerButtonCell.swift similarity index 93% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/PagerButtonCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/PagerButtonCell.swift index a94cac7..efbc9db 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/PagerButtonCell.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/PagerButtonCell.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class PagerButtonCell: UICollectionViewCell { +final class PagerButtonCell: UICollectionViewCell { // MARK: - UI Properties @@ -44,13 +44,11 @@ class PagerButtonCell: UICollectionViewCell { private extension PagerButtonCell { func setHierarchy() { - self.addSubview(pagerButton) } func setLayout() { - pagerButton.snp.makeConstraints { $0.edges.equalToSuperview() } @@ -58,7 +56,6 @@ private extension PagerButtonCell { } func setStyle() { - pagerButton.do { $0.layer.cornerRadius = 2 $0.backgroundColor = .grey3 diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/PopularLiveCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/PopularLiveCell.swift similarity index 100% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/PopularLiveCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/Cells/PopularLiveCell.swift diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/HeaderCategoryView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/HeaderCategoryView.swift index 6629317..50aa8d3 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/HeaderCategoryView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/HeaderCategoryView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class HeaderCategoryView: UIView { +final class HeaderCategoryView: UIView { // MARK: - UI Property diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/LiveView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/LiveView.swift index 9c98159..b3cee9b 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/LiveView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/LiveView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class LiveView: UIView { +final class LiveView: UIView { // MARK: - UI Properties diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MainPosterView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MainPosterView.swift index 8abc781..8e1cf7d 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MainPosterView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MainPosterView.swift @@ -1,27 +1,27 @@ +//// +//// MainPosterView.swift +//// Tving_CloneProject +//// +//// Created by 윤희슬 on 4/22/24. +//// // -// MainPosterView.swift -// Tving_CloneProject +//import UIKit // -// Created by 윤희슬 on 4/22/24. +//class MainPosterView: UIView { +// +// // MARK: - UI Properties +// +// // MARK: - Properties +// +// override init(frame: CGRect) { +// super.init(frame: frame) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +//} // - -import UIKit - -class MainPosterView: UIView { - - // MARK: - UI Properties - - // MARK: - Properties - - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - -} - - -// MARK: - Private Methods +// +//// MARK: - Private Methods diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MovieView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MovieView.swift index fae267e..8fb2011 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MovieView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/MovieView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class MovieView: UIView { +final class MovieView: UIView { // MARK: - UI Properties @@ -21,7 +21,7 @@ class MovieView: UIView { // MARK: - Properties - private var dailyBoxOfficeData: [DailyBoxOfficeList] = [] + private var movieViewModel: MovieViewModel = MovieViewModel() // MARK: - Life Cycles @@ -29,12 +29,13 @@ class MovieView: UIView { override init(frame: CGRect) { super.init(frame: frame) - getDailyBoxOffice() setHierarchy() setLayout() setStyle() setDelegate() registerCell() + setViewModel() + getDailyBoxOffice() } required init?(coder: NSCoder) { @@ -46,12 +47,10 @@ class MovieView: UIView { private extension MovieView { func setHierarchy() { - self.addSubviews(dailyBoxOfficeCollectionView, loadingIndicator) } func setLayout() { - dailyBoxOfficeCollectionView.snp.makeConstraints { $0.top.equalToSuperview().inset(Constant.Screen.topSafeAreaHeight + 80) $0.horizontalEdges.bottom.equalToSuperview().inset(10) @@ -59,7 +58,6 @@ private extension MovieView { } func setStyle() { - dailyBoxOfficeCollectionView.do { $0.backgroundColor = UIColor(resource: .black) $0.showsVerticalScrollIndicator = false @@ -72,56 +70,40 @@ private extension MovieView { } } - func registerCell() { - - dailyBoxOfficeCollectionView.register(DailyBoxOfficeCell.self, forCellWithReuseIdentifier: DailyBoxOfficeCell.identifier) - } - - func setDelegate() { + func setViewModel() { + movieViewModel.didUpdateNetworkResult.bind { [weak self] isSuccess in + guard let isSuccess else { return } + if isSuccess { + self?.dailyBoxOfficeCollectionView.reloadData() + } + } - dailyBoxOfficeCollectionView.delegate = self - dailyBoxOfficeCollectionView.dataSource = self + movieViewModel.didChangeLoadingIndicator.bind { [weak self] isLoading in + guard let isLoading else { return } + if isLoading { + self?.loadingIndicator.startAnimating() + } else { + self?.loadingIndicator.stopAnimating() + } + } + } func getDailyBoxOffice() { - - let date = calculateDate() - - loadingIndicator.startAnimating() - - MainService.shared.getMovieList(date: date) { response in - switch response { - case .success(let data): - guard let data = data as? GetMovieResponseModel else { return } - self.dailyBoxOfficeData = data.boxOfficeResult.dailyBoxOfficeList - - self.loadingIndicator.stopAnimating() - self.dailyBoxOfficeCollectionView.reloadData() - - default: - return - } + if movieViewModel.getDailyBoxOffice() { + self.dailyBoxOfficeCollectionView.reloadData() } } - func calculateDate() -> String { - - let today = Date() - let calendar = Calendar.current - var dateComponents = DateComponents() - dateComponents.day = -1 - - if let oneDayAgo = calendar.date(byAdding: dateComponents, to: today) { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyyMMdd" - - let oneDayAgoString = dateFormatter.string(from: oneDayAgo) - return oneDayAgoString - } else { - return "" - } + func registerCell() { + dailyBoxOfficeCollectionView.register(DailyBoxOfficeCell.self, forCellWithReuseIdentifier: DailyBoxOfficeCell.identifier) } + func setDelegate() { + dailyBoxOfficeCollectionView.delegate = self + dailyBoxOfficeCollectionView.dataSource = self + } + } extension MovieView: UICollectionViewDelegateFlowLayout { @@ -147,19 +129,14 @@ extension MovieView: UICollectionViewDelegateFlowLayout { extension MovieView: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - - return self.dailyBoxOfficeData.count + return movieViewModel.fetchData().count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DailyBoxOfficeCell.identifier, for: indexPath) as? DailyBoxOfficeCell else { return UICollectionViewCell() } - cell.setCell(contents: dailyBoxOfficeData[indexPath.row]) + cell.setCell(contents: movieViewModel.fetchData()[indexPath.row]) return cell } - - - } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/NavigationBarView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/NavigationBarView.swift index 7738b74..1e39bad 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/NavigationBarView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/NavigationBarView.swift @@ -7,18 +7,14 @@ import UIKit -class NavigationBarView: UIView { +final class NavigationBarView: UIView { // MARK: - UI Properties private let tvingLogoButton = UIButton() private let profileImage = UIImageView() - - - // MARK: - Properties - - + // MARK: - Life Cycles @@ -42,13 +38,10 @@ class NavigationBarView: UIView { private extension NavigationBarView { func setHierarchy() { - self.addSubviews(tvingLogoButton, profileImage) - } func setLayout() { - tvingLogoButton.snp.makeConstraints { $0.top.bottom.equalToSuperview() $0.leading.equalToSuperview().inset(10) @@ -63,7 +56,6 @@ private extension NavigationBarView { } func setStyle() { - self.backgroundColor = .clear tvingLogoButton.do { diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/PageControlButtonView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/PageControlButtonView.swift index eb50559..cde4ab2 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/PageControlButtonView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/PageControlButtonView.swift @@ -11,7 +11,7 @@ protocol PageControlButtonDelegate: AnyObject { func didTapControlButton(index: Int) } -class PageControlButtonView: UICollectionReusableView { +final class PageControlButtonView: UICollectionReusableView { // MARK: - UI Properties @@ -28,6 +28,8 @@ class PageControlButtonView: UICollectionReusableView { static let identifier: String = "PageControlButtonView" + private let pageControlButtonViewModel: PageControlButtonViewModel = PageControlButtonViewModel() + private var prevIndex: Int = 0 { didSet { // 이전에 선택된 버튼을 찾기 @@ -51,6 +53,7 @@ class PageControlButtonView: UICollectionReusableView { var buttonCount: Int = 0 { didSet { + pageControlButtonViewModel.buttonCount.value = buttonCount buttonCollectionView.reloadData() } } @@ -104,7 +107,7 @@ private extension PageControlButtonView { func setDelegate() { buttonCollectionView.delegate = self - buttonCollectionView.dataSource = self + buttonCollectionView.dataSource = pageControlButtonViewModel } func setButtonStyle(isSelected: Bool, button: UIButton) { @@ -128,28 +131,17 @@ extension PageControlButtonView: UICollectionViewDelegateFlowLayout { } } -extension PageControlButtonView: UICollectionViewDelegate {} - -extension PageControlButtonView: UICollectionViewDataSource { - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return buttonCount - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PagerButtonCell.identifier, for: indexPath) as? PagerButtonCell - else { return UICollectionViewCell() } - - cell.pagerButton.tag = indexPath.row - cell.pagerButton.addTarget(self, action: #selector(didTapControlButton(_:)), for: .touchUpInside) - - if indexPath.row == index { - setButtonStyle(isSelected: true, button: cell.pagerButton) - } else { - setButtonStyle(isSelected: false, button: cell.pagerButton) +extension PageControlButtonView: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + if let pageButtonCell = cell as? PagerButtonCell { + pageButtonCell.pagerButton.addTarget(self, action: #selector(didTapControlButton(_:)), for: .touchUpInside) + + if indexPath.row == index { + setButtonStyle(isSelected: true, button: pageButtonCell.pagerButton) + } else { + setButtonStyle(isSelected: false, button: pageButtonCell.pagerButton) + } } - - return cell } - } diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/ParamountView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/ParamountView.swift index 110ba6d..859c41e 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/ParamountView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/ParamountView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class ParamountView: UIView { +final class ParamountView: UIView { // MARK: - UI Properties diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/TVProgramView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/TVProgramView.swift index 6014876..5887d64 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/TVProgramView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/TVProgramView.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class TVProgramView: UIView { +final class TVProgramView: UIView { // MARK: - UI Properties diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/UnderlineSegmentedControlView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/UnderlineSegmentedControlView.swift index acf3913..7c8ab77 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/UnderlineSegmentedControlView.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Main/Views/UnderlineSegmentedControlView.swift @@ -36,7 +36,6 @@ final class UnderlineSegmentedControlView: UISegmentedControl { } func moveUnderlineView(to index: Int) { - underlineView.backgroundColor = UIColor(resource: .white) guard let text = self.titleForSegment(at: index), diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/DailyBoxOfficeCell.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Movie/View/Cells/DailyBoxOfficeCell.swift similarity index 100% rename from Tving_CloneProject/Tving_CloneProject/Scene/Main/Cells/DailyBoxOfficeCell.swift rename to Tving_CloneProject/Tving_CloneProject/Scene/Movie/View/Cells/DailyBoxOfficeCell.swift diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Movie/View/MovieView.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Movie/View/MovieView.swift new file mode 100644 index 0000000..fae267e --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Movie/View/MovieView.swift @@ -0,0 +1,165 @@ +// +// MovieView.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/7/24. +// + +import UIKit + +import SnapKit +import Then + +class MovieView: UIView { + + // MARK: - UI Properties + + private let dailyBoxOfficeCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + + private var loadingIndicator = UIActivityIndicatorView() + + + // MARK: - Properties + + private var dailyBoxOfficeData: [DailyBoxOfficeList] = [] + + + // MARK: - Life Cycles + + override init(frame: CGRect) { + super.init(frame: frame) + + getDailyBoxOffice() + setHierarchy() + setLayout() + setStyle() + setDelegate() + registerCell() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +private extension MovieView { + + func setHierarchy() { + + self.addSubviews(dailyBoxOfficeCollectionView, loadingIndicator) + } + + func setLayout() { + + dailyBoxOfficeCollectionView.snp.makeConstraints { + $0.top.equalToSuperview().inset(Constant.Screen.topSafeAreaHeight + 80) + $0.horizontalEdges.bottom.equalToSuperview().inset(10) + } + } + + func setStyle() { + + dailyBoxOfficeCollectionView.do { + $0.backgroundColor = UIColor(resource: .black) + $0.showsVerticalScrollIndicator = false + } + + loadingIndicator.do { + $0.frame = self.bounds + $0.color = UIColor(resource: .white) + $0.backgroundColor = UIColor(resource: .black) + } + } + + func registerCell() { + + dailyBoxOfficeCollectionView.register(DailyBoxOfficeCell.self, forCellWithReuseIdentifier: DailyBoxOfficeCell.identifier) + } + + func setDelegate() { + + dailyBoxOfficeCollectionView.delegate = self + dailyBoxOfficeCollectionView.dataSource = self + } + + func getDailyBoxOffice() { + + let date = calculateDate() + + loadingIndicator.startAnimating() + + MainService.shared.getMovieList(date: date) { response in + switch response { + case .success(let data): + guard let data = data as? GetMovieResponseModel else { return } + self.dailyBoxOfficeData = data.boxOfficeResult.dailyBoxOfficeList + + self.loadingIndicator.stopAnimating() + self.dailyBoxOfficeCollectionView.reloadData() + + default: + return + } + } + } + + func calculateDate() -> String { + + let today = Date() + let calendar = Calendar.current + var dateComponents = DateComponents() + dateComponents.day = -1 + + if let oneDayAgo = calendar.date(byAdding: dateComponents, to: today) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd" + + let oneDayAgoString = dateFormatter.string(from: oneDayAgo) + return oneDayAgoString + } else { + return "" + } + } + +} + +extension MovieView: UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: Constant.Screen.width / 2 - 20, height: Constant.Screen.height / 3.5) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 20 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 10 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + } + +} + +extension MovieView: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + + return self.dailyBoxOfficeData.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DailyBoxOfficeCell.identifier, for: indexPath) as? DailyBoxOfficeCell else { return UICollectionViewCell() } + cell.setCell(contents: dailyBoxOfficeData[indexPath.row]) + + return cell + } + + + + +} diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Movie/ViewModel/MovieViewModel.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Movie/ViewModel/MovieViewModel.swift new file mode 100644 index 0000000..cd6eafc --- /dev/null +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Movie/ViewModel/MovieViewModel.swift @@ -0,0 +1,8 @@ +// +// MovieViewModel.swift +// Tving_CloneProject +// +// Created by 윤희슬 on 5/27/24. +// + +import Foundation diff --git a/Tving_CloneProject/Tving_CloneProject/Scene/Welcome/ViewControllers/WelcomeViewController.swift b/Tving_CloneProject/Tving_CloneProject/Scene/Welcome/ViewControllers/WelcomeViewController.swift index e13f83c..fbace65 100644 --- a/Tving_CloneProject/Tving_CloneProject/Scene/Welcome/ViewControllers/WelcomeViewController.swift +++ b/Tving_CloneProject/Tving_CloneProject/Scene/Welcome/ViewControllers/WelcomeViewController.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit import Then -class WelcomeViewController: UIViewController { +final class WelcomeViewController: UIViewController { // MARK: - UI Properties @@ -49,7 +49,6 @@ private extension WelcomeViewController { } func setLayout() { - logoImageView.snp.makeConstraints { $0.top.equalToSuperview().inset(60) $0.leading.trailing.equalToSuperview() @@ -70,7 +69,6 @@ private extension WelcomeViewController { } func setStyle() { - self.view.backgroundColor = UIColor(resource: .black) self.navigationController?.navigationBar.isHidden = true