diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment.xcodeproj/project.pbxproj b/ATSOPT36_Assignment/ATSOPT36_Assignment.xcodeproj/project.pbxproj index 21aac63..353264e 100644 --- a/ATSOPT36_Assignment/ATSOPT36_Assignment.xcodeproj/project.pbxproj +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 472D2DAE2DCC3B86006DEFD7 /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 472D2DA12DCC3B86006DEFD7 /* Secrets.xcconfig */; }; 4747F1FB2DB3CB3100B65239 /* Then in Frameworks */ = {isa = PBXBuildFile; productRef = 4747F1FA2DB3CB3100B65239 /* Then */; }; 4747F2032DB3CCA600B65239 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4747F2022DB3CCA600B65239 /* SnapKit */; }; + 47CE27272DE58B9C00C123BF /* Secrets.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 47CE27262DE58B9C00C123BF /* Secrets.xcconfig */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -23,9 +23,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 472D2DA12DCC3B86006DEFD7 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = ""; }; 475FC9702DAFE00C000045E9 /* ATSOPT36_Assignment.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ATSOPT36_Assignment.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47662D532DC7690B000803B5 /* ATSOPT36_AssignmentTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ATSOPT36_AssignmentTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 47CE27262DE58B9C00C123BF /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = ""; }; 47E677B82DCBAA7900E3B8E5 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Secrets.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -86,11 +86,11 @@ 475FC9672DAFE00C000045E9 = { isa = PBXGroup; children = ( + 47CE27262DE58B9C00C123BF /* Secrets.xcconfig */, 475FC9722DAFE00C000045E9 /* ATSOPT36_Assignment */, 47662D542DC7690B000803B5 /* ATSOPT36_AssignmentTests */, 475FC9712DAFE00C000045E9 /* Products */, 472D2D542DCC3B86006DEFD7 /* Recovered References */, - 472D2DA12DCC3B86006DEFD7 /* Secrets.xcconfig */, ); sourceTree = ""; }; @@ -201,7 +201,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 472D2DAE2DCC3B86006DEFD7 /* Secrets.xcconfig in Resources */, + 47CE27272DE58B9C00C123BF /* Secrets.xcconfig in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Application/SceneDelegate.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Application/SceneDelegate.swift index c4db51c..793256f 100644 --- a/ATSOPT36_Assignment/ATSOPT36_Assignment/Application/SceneDelegate.swift +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Application/SceneDelegate.swift @@ -6,6 +6,7 @@ // import UIKit +import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -62,7 +63,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { extension SceneDelegate: LoginViewControllerDelegate { func loginFlowDidComplete() { let mainViewController = MainViewController() - let navigationController = UINavigationController(rootViewController: mainViewController) + let hostingView = UIHostingController(rootView: SwiftUIView()) + let navigationController = UINavigationController(rootViewController: hostingView) + window?.rootViewController = navigationController } } diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/ContentModel.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/ContentModel.swift index cb73cf4..0618c86 100644 --- a/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/ContentModel.swift +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/ContentModel.swift @@ -7,7 +7,7 @@ import UIKit -struct ContentModel { +struct ContentModel: Hashable { let title: String let description: String let rating: Double diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/MockData.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/MockData.swift index d2e804e..9f1093e 100644 --- a/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/MockData.swift +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Model/MockData.swift @@ -7,7 +7,7 @@ import UIKit -enum MockData { +enum MockData: Hashable { case thumbnail case banner case todayTving([ContentModel]) diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/FooterView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/FooterView.swift new file mode 100644 index 0000000..3e4c7f3 --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/FooterView.swift @@ -0,0 +1,63 @@ +// +// FooterView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct FooterView: View { + var body: some View { + VStack(alignment: .leading, spacing: 13) { + HStack(spacing:0) { + Text("공지") + .foregroundStyle(.gray) + Text("티빙 계정 공유 정책 추가 안내") + .foregroundStyle(.gray) + .fontWeight(.semibold) + .padding(.leading, 8) + Spacer() + Image(.moveButton) + } + .padding(16) + .background(.gray5) + .cornerRadius(5) + + VStack(spacing: 0) { + HStack { + Text("고객문의") + .font(.caption) + .foregroundStyle(.gray) + Circle() + .foregroundStyle(.gray) + .frame(width: 2, height: 2) + Text("이용약관") + .font(.caption) + .foregroundStyle(.gray) + Circle() + .foregroundStyle(.gray) + .frame(width: 2, height: 2) + } + + HStack { + Text("사업자정보") + .font(.caption) + .foregroundStyle(.gray) + Circle() + .foregroundStyle(.gray) + .frame(width: 2, height: 2) + Text("인재채용") + .font(.caption) + .foregroundStyle(.gray) + } + } + } + .padding(.top, 30) + .padding(.horizontal, 15) + } +} + +#Preview { + FooterView() +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/HeaderView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/HeaderView.swift new file mode 100644 index 0000000..f8090c9 --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/HeaderView.swift @@ -0,0 +1,24 @@ +// +// HeaderView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct HeaderView: View { + var body: some View { + HStack { + Image(.tvingLogo) + Spacer() + Image(.search) + Image(.tvingLogo2) + } + .padding(.horizontal, 11) + } +} + +#Preview { + HeaderView() +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/KimGaHyunBestListView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/KimGaHyunBestListView.swift new file mode 100644 index 0000000..76b4dad --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/KimGaHyunBestListView.swift @@ -0,0 +1,39 @@ +// +// KimGaHyunBestListView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct KimGaHyunBestListView: View { + let items: [ContentModel] + var body: some View { + VStack(spacing: 13) { + HStack { + Text("김가현PD의 인생작 TOP 5") + .foregroundStyle(.white) + .fontWeight(.semibold) + Spacer() + } + .padding(.leading, 15) + .padding(.vertical, 10) + ScrollView(.horizontal) { + HStack(alignment: .bottom, spacing: 8) { + ForEach(Array(items.enumerated()), id: \.self.offset) { offset, item in + Image(uiImage: item.thumbnail) + .resizable() + .frame(width: 160, height: 90) + .cornerRadius(3) + } + } + .padding(.leading, 15) + } + } + } +} + +#Preview { + KimGaHyunBestListView(items: []) +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/LivePopularListView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/LivePopularListView.swift new file mode 100644 index 0000000..dd52f3a --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/LivePopularListView.swift @@ -0,0 +1,57 @@ +// +// LivePopularListView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct LivePopularListView: View { + let items: [ContentModel] + var body: some View { + VStack(spacing: 13) { + HStack { + Text("실시간 인기 LIVE") + .foregroundStyle(.white) + .fontWeight(.semibold) + Spacer() + } + .padding(.leading, 15) + .padding(.vertical, 10) + ScrollView(.horizontal) { + HStack(spacing: 8) { + ForEach(Array(items.enumerated()), id: \.self.offset) { offset, item in + VStack(spacing: 10) { + Image(uiImage: item.thumbnail) + .resizable() + .frame(width: 160, height: 80) + HStack(alignment: .top) { + Image("number\(offset + 1)") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 23) + VStack(alignment: .leading) { + Text(item.title) + .foregroundStyle(.white) + Text(item.description) + .foregroundStyle(.gray) + .font(.caption) + Text("\(item.rating)%") + .foregroundStyle(.gray) + .font(.caption) + } + } + } + .frame(maxWidth: 160) + } + } + .padding(.leading, 15) + } + } + } +} + +#Preview { + LivePopularListView(items: []) +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MenuView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MenuView.swift new file mode 100644 index 0000000..e3c1400 --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MenuView.swift @@ -0,0 +1,27 @@ +// +// MenuView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct MenuView: View { + let items: [String] + var body: some View { + HStack { + Spacer() + ForEach(items, id: \.self) { item in + Text(item) + .foregroundStyle(.white) + Spacer() + } + } + .padding(.vertical, 16) + } +} + +#Preview { + MenuView(items: []) +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MovieListView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MovieListView.swift new file mode 100644 index 0000000..8bb231a --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/MovieListView.swift @@ -0,0 +1,45 @@ +// +// MovieListView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct MovieListView: View { + let items: [ContentModel] + let title: String + var isShowRanking = false + + var body: some View { + VStack(spacing: 13) { + HStack { + Text(title) + .foregroundStyle(.white) + .fontWeight(.semibold) + Spacer() + } + .padding(.leading, 15) + .padding(.vertical, 10) + ScrollView(.horizontal) { + HStack(alignment: .bottom, spacing: 8) { + ForEach(Array(items.enumerated()), id: \.self.offset) { offset, item in + if isShowRanking { + Image("number\(offset+1)") + } + Image(uiImage: item.thumbnail) + .resizable() + .frame(width: 98, height: 146) + .cornerRadius(3) + } + } + .padding(.leading, 15) + } + } + } +} + +#Preview { + MovieListView(items: [], title: "") +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SportListView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SportListView.swift new file mode 100644 index 0000000..173c559 --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SportListView.swift @@ -0,0 +1,30 @@ +// +// SportListView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct SportListView: View { + let items: [ContentModel] + var body: some View { + ScrollView(.horizontal) { + HStack { + ForEach(items, id: \.self) { + Image(uiImage: $0.thumbnail) + .padding(.vertical, 6) + .padding(.horizontal, 10) + .background(.gray5) + .cornerRadius(3) + } + } + .padding(.leading, 15) + } + } +} + +#Preview { + SportListView(items: []) +} diff --git a/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SwiftUIView.swift b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SwiftUIView.swift new file mode 100644 index 0000000..f706eda --- /dev/null +++ b/ATSOPT36_Assignment/ATSOPT36_Assignment/Presentation/SwiftUI/SwiftUIView.swift @@ -0,0 +1,46 @@ +// +// SwiftUIView.swift +// ATSOPT36_Assignment +// +// Created by 권석기 on 5/29/25. +// + +import SwiftUI + +struct SwiftUIView: View { + private let items = ["홈", "드라마", "예능", "영화", "스포츠", "뉴스"] + var body: some View { + ScrollView { + HeaderView() + MenuView(items: items) + ForEach(MockData.items, id: \.self) { type in + switch type { + case .thumbnail: + Image(.main) + .resizable() + case let .todayTving(items): + MovieListView(items: items, title: "오늘의 티빙 TOP 20", isShowRanking: true) + case let .popularLive(items): + LivePopularListView(items: items) + case let .popularMovie(items): + MovieListView(items: items, title: "실시간 인기 영화") + case .banner: + Image(.banner) + .resizable() + .aspectRatio(contentMode: .fit) + .padding(.vertical, 20) + case let .sport(items): + SportListView(items: items) + case let .kimGahyunBest(items): + KimGaHyunBestListView(items: items) + } + } + FooterView() + } + .background(.black) + } +} + +#Preview { + SwiftUIView() +}