diff --git a/Kream.xcodeproj/project.pbxproj b/Kream.xcodeproj/project.pbxproj index 888bca9..e4bdc06 100644 --- a/Kream.xcodeproj/project.pbxproj +++ b/Kream.xcodeproj/project.pbxproj @@ -8,13 +8,29 @@ /* Begin PBXBuildFile section */ 5239955B2CAEF016005A358A /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5239955A2CAEF016005A358A /* SnapKit */; }; + 52A7C0F82CEB6F5400F668E3 /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 52A7C0F72CEB6F5400F668E3 /* CombineMoya */; }; + 52A7C0FA2CEB6F5400F668E3 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 52A7C0F92CEB6F5400F668E3 /* Moya */; }; + 52A7C0FC2CEB6F5400F668E3 /* ReactiveMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 52A7C0FB2CEB6F5400F668E3 /* ReactiveMoya */; }; + 52A7C0FE2CEB6F5400F668E3 /* RxMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 52A7C0FD2CEB6F5400F668E3 /* RxMoya */; }; + 52C6308B2CE1FF740098C775 /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6308A2CE1FF740098C775 /* KakaoSDK */; }; + 52C6308D2CE1FF740098C775 /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6308C2CE1FF740098C775 /* KakaoSDKAuth */; }; + 52C6308F2CE1FF740098C775 /* KakaoSDKCert in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6308E2CE1FF740098C775 /* KakaoSDKCert */; }; + 52C630912CE1FF740098C775 /* KakaoSDKCertCore in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630902CE1FF740098C775 /* KakaoSDKCertCore */; }; + 52C630932CE1FF740098C775 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630922CE1FF740098C775 /* KakaoSDKCommon */; }; + 52C630952CE1FF740098C775 /* KakaoSDKFriend in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630942CE1FF740098C775 /* KakaoSDKFriend */; }; + 52C630972CE1FF740098C775 /* KakaoSDKFriendCore in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630962CE1FF740098C775 /* KakaoSDKFriendCore */; }; + 52C630992CE1FF740098C775 /* KakaoSDKNavi in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630982CE1FF740098C775 /* KakaoSDKNavi */; }; + 52C6309B2CE1FF740098C775 /* KakaoSDKShare in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6309A2CE1FF740098C775 /* KakaoSDKShare */; }; + 52C6309D2CE1FF740098C775 /* KakaoSDKTalk in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6309C2CE1FF740098C775 /* KakaoSDKTalk */; }; + 52C6309F2CE1FF740098C775 /* KakaoSDKTemplate in Frameworks */ = {isa = PBXBuildFile; productRef = 52C6309E2CE1FF740098C775 /* KakaoSDKTemplate */; }; + 52C630A12CE1FF740098C775 /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630A02CE1FF740098C775 /* KakaoSDKUser */; }; + 52C630E02CE4320C0098C775 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 52C630DF2CE4320C0098C775 /* KeychainAccess */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 52C34CF12CA47B3E00DB8986 /* Kream.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Kream.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ - /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 52C34D032CA47B4000DB8986 /* Exceptions for "Kream" folder in "Kream" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; @@ -31,11 +47,6 @@ exceptions = ( 52C34D032CA47B4000DB8986 /* Exceptions for "Kream" folder in "Kream" target */, ); - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 52C34CF32CA47B3E00DB8986 /* Kream */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = Kream; sourceTree = ""; }; @@ -47,6 +58,23 @@ buildActionMask = 2147483647; files = ( 5239955B2CAEF016005A358A /* SnapKit in Frameworks */, + 52C630952CE1FF740098C775 /* KakaoSDKFriend in Frameworks */, + 52C630E02CE4320C0098C775 /* KeychainAccess in Frameworks */, + 52A7C0FC2CEB6F5400F668E3 /* ReactiveMoya in Frameworks */, + 52C6308B2CE1FF740098C775 /* KakaoSDK in Frameworks */, + 52C630992CE1FF740098C775 /* KakaoSDKNavi in Frameworks */, + 52A7C0FE2CEB6F5400F668E3 /* RxMoya in Frameworks */, + 52C630912CE1FF740098C775 /* KakaoSDKCertCore in Frameworks */, + 52C6309F2CE1FF740098C775 /* KakaoSDKTemplate in Frameworks */, + 52C630972CE1FF740098C775 /* KakaoSDKFriendCore in Frameworks */, + 52A7C0F82CEB6F5400F668E3 /* CombineMoya in Frameworks */, + 52C6308D2CE1FF740098C775 /* KakaoSDKAuth in Frameworks */, + 52A7C0FA2CEB6F5400F668E3 /* Moya in Frameworks */, + 52C630A12CE1FF740098C775 /* KakaoSDKUser in Frameworks */, + 52C6309D2CE1FF740098C775 /* KakaoSDKTalk in Frameworks */, + 52C6309B2CE1FF740098C775 /* KakaoSDKShare in Frameworks */, + 52C630932CE1FF740098C775 /* KakaoSDKCommon in Frameworks */, + 52C6308F2CE1FF740098C775 /* KakaoSDKCert in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,6 +118,23 @@ name = Kream; packageProductDependencies = ( 5239955A2CAEF016005A358A /* SnapKit */, + 52C6308A2CE1FF740098C775 /* KakaoSDK */, + 52C6308C2CE1FF740098C775 /* KakaoSDKAuth */, + 52C6308E2CE1FF740098C775 /* KakaoSDKCert */, + 52C630902CE1FF740098C775 /* KakaoSDKCertCore */, + 52C630922CE1FF740098C775 /* KakaoSDKCommon */, + 52C630942CE1FF740098C775 /* KakaoSDKFriend */, + 52C630962CE1FF740098C775 /* KakaoSDKFriendCore */, + 52C630982CE1FF740098C775 /* KakaoSDKNavi */, + 52C6309A2CE1FF740098C775 /* KakaoSDKShare */, + 52C6309C2CE1FF740098C775 /* KakaoSDKTalk */, + 52C6309E2CE1FF740098C775 /* KakaoSDKTemplate */, + 52C630A02CE1FF740098C775 /* KakaoSDKUser */, + 52C630DF2CE4320C0098C775 /* KeychainAccess */, + 52A7C0F72CEB6F5400F668E3 /* CombineMoya */, + 52A7C0F92CEB6F5400F668E3 /* Moya */, + 52A7C0FB2CEB6F5400F668E3 /* ReactiveMoya */, + 52A7C0FD2CEB6F5400F668E3 /* RxMoya */, ); productName = Kream; productReference = 52C34CF12CA47B3E00DB8986 /* Kream.app */; @@ -121,6 +166,9 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( 523995592CAEF016005A358A /* XCRemoteSwiftPackageReference "SnapKit" */, + 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */, + 52C630DE2CE4320C0098C775 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */, ); preferredProjectObjectVersion = 77; productRefGroup = 52C34CF22CA47B3E00DB8986 /* Products */; @@ -356,6 +404,30 @@ minimumVersion = 5.7.1; }; }; + 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Moya/Moya"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 15.0.3; + }; + }; + 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kakao/kakao-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.23.0; + }; + }; + 52C630DE2CE4320C0098C775 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -364,6 +436,91 @@ package = 523995592CAEF016005A358A /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; + 52A7C0F72CEB6F5400F668E3 /* CombineMoya */ = { + isa = XCSwiftPackageProductDependency; + package = 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */; + productName = CombineMoya; + }; + 52A7C0F92CEB6F5400F668E3 /* Moya */ = { + isa = XCSwiftPackageProductDependency; + package = 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */; + productName = Moya; + }; + 52A7C0FB2CEB6F5400F668E3 /* ReactiveMoya */ = { + isa = XCSwiftPackageProductDependency; + package = 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */; + productName = ReactiveMoya; + }; + 52A7C0FD2CEB6F5400F668E3 /* RxMoya */ = { + isa = XCSwiftPackageProductDependency; + package = 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */; + productName = RxMoya; + }; + 52C6308A2CE1FF740098C775 /* KakaoSDK */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDK; + }; + 52C6308C2CE1FF740098C775 /* KakaoSDKAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKAuth; + }; + 52C6308E2CE1FF740098C775 /* KakaoSDKCert */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCert; + }; + 52C630902CE1FF740098C775 /* KakaoSDKCertCore */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCertCore; + }; + 52C630922CE1FF740098C775 /* KakaoSDKCommon */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCommon; + }; + 52C630942CE1FF740098C775 /* KakaoSDKFriend */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKFriend; + }; + 52C630962CE1FF740098C775 /* KakaoSDKFriendCore */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKFriendCore; + }; + 52C630982CE1FF740098C775 /* KakaoSDKNavi */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKNavi; + }; + 52C6309A2CE1FF740098C775 /* KakaoSDKShare */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKShare; + }; + 52C6309C2CE1FF740098C775 /* KakaoSDKTalk */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKTalk; + }; + 52C6309E2CE1FF740098C775 /* KakaoSDKTemplate */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKTemplate; + }; + 52C630A02CE1FF740098C775 /* KakaoSDKUser */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKUser; + }; + 52C630DF2CE4320C0098C775 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = 52C630DE2CE4320C0098C775 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 52C34CE92CA47B3E00DB8986 /* Project object */; diff --git a/Kream/AppDelegate.swift b/Kream/AppDelegate.swift index 662d98e..eadf48e 100644 --- a/Kream/AppDelegate.swift +++ b/Kream/AppDelegate.swift @@ -1,4 +1,5 @@ import UIKit +import KakaoSDKCommon @main class AppDelegate: UIResponder, UIApplicationDelegate { @@ -6,14 +7,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Kakao SDK 초기화 + KakaoSDK.initSDK(appKey: "2c8ab94e144d90c1bb9598fb2443dea1") // 윈도우 생성 및 설정 window = UIWindow(frame: UIScreen.main.bounds) - // ProductDetailViewController를 루트 뷰 컨트롤러로 설정 - let productDetailViewController = ProductDetailViewController() - let navigationController = UINavigationController(rootViewController: productDetailViewController) + + // ProductDetailViewController를 루트 뷰 컨트롤러로 설정 + let loginViewController = LoginViewController() + let navigationController = UINavigationController(rootViewController: loginViewController) window?.rootViewController = navigationController - window?.makeKeyAndVisible() return true diff --git a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/Contents.json b/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/Contents.json index 363e9a9..ce7a849 100644 --- a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/Contents.json +++ b/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/Contents.json @@ -1,21 +1,15 @@ { "images" : [ { - "filename" : "mypage_icon.png", - "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "scale" : "2x" }, { - - "filename" : "my_icon 2.png", - "idiom" : "universal", "scale" : "3x" } diff --git a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 1.png b/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 1.png deleted file mode 100644 index 347bf45..0000000 Binary files a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 1.png and /dev/null differ diff --git a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 2.png b/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 2.png deleted file mode 100644 index 347bf45..0000000 Binary files a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon 2.png and /dev/null differ diff --git a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon.png b/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon.png deleted file mode 100644 index 347bf45..0000000 Binary files a/Kream/Assets.xcassets/tab_bar_icon/mypage_icon.imageset/my_icon.png and /dev/null differ diff --git a/Kream/Models/KeywordModel.swift b/Kream/Models/KeywordModel.swift new file mode 100644 index 0000000..8dcae8d --- /dev/null +++ b/Kream/Models/KeywordModel.swift @@ -0,0 +1,13 @@ +// +// Untitled.swift +// Kream +// +// Created by 임소은 on 11/18/24. +// + +import Foundation + + +struct NewKeywordModel { + let keyword: String +} diff --git a/Kream/Models/LoginModel.swift b/Kream/Models/LoginModel.swift index 08fd74c..6a45ea5 100644 --- a/Kream/Models/LoginModel.swift +++ b/Kream/Models/LoginModel.swift @@ -1,6 +1,5 @@ import Foundation - class LoginViewModel { // UserDefaults에 저장할 키 정의 @@ -49,27 +48,3 @@ class LoginViewModel { } } - -// 이메일과 비밀번호를 처리하는 모델 -struct LoginModel { - var email: String = "" - var password: String = "" - - - func isValidEmail() -> Bool { - let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" - let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) - return emailTest.evaluate(with: email) - } - - - func isValidPassword() -> Bool { - return password.count >= 6 - } - - - func isLoginValid() -> Bool { - return isValidEmail() && isValidPassword() - } -} - diff --git a/Kream/Models/ProductModels.swift b/Kream/Models/ProductModels.swift new file mode 100644 index 0000000..8796dad --- /dev/null +++ b/Kream/Models/ProductModels.swift @@ -0,0 +1,19 @@ +import Foundation + +struct ProductSearchResponse: Decodable { + let products: [KeywordModel] + let total: Int + let skip: Int + let limit: Int +} + +struct KeywordModel: Decodable { + let id: Int + let title: String + let description: String + let price: Double + let brand: String + let category: String + let thumbnail: String +} + diff --git a/Kream/ProductAPI.swift b/Kream/ProductAPI.swift new file mode 100644 index 0000000..4377682 --- /dev/null +++ b/Kream/ProductAPI.swift @@ -0,0 +1,50 @@ +// +// ProductAPI.swift.swift +// Kream +// +// Created by 임소은 on 11/18/24. +// + +import UIKit +import SnapKit +import Moya +import Foundation + +enum ProductAPI { + case searchProducts(query: String) +} + +extension ProductAPI: TargetType { + var baseURL: URL { + return URL(string: "https://dummyjson.com")! + } + + var path: String { + switch self { + case .searchProducts: + return "/products/search" + } + } + + var method: Moya.Method { + switch self { + case .searchProducts: + return .get + } + } + + var task: Task { + switch self { + case .searchProducts(let query): + return .requestParameters(parameters: ["q": query], encoding: URLEncoding.default) + } + } + + var headers: [String: String]? { + return ["Content-Type": "application/json"] + } + + var sampleData: Data { + return Data() + } +} diff --git a/Kream/SceneDelegate.swift b/Kream/SceneDelegate.swift index 7e11277..f93bd14 100644 --- a/Kream/SceneDelegate.swift +++ b/Kream/SceneDelegate.swift @@ -15,15 +15,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } - window = UIWindow(frame: windowScene.coordinateSpace.bounds) window?.windowScene = windowScene // ProductDetailViewController를 초기 루트 뷰 컨트롤러로 설정 - let productDetailViewController = ProductDetailViewController() + let loginViewController = LoginViewController() - window?.rootViewController = UINavigationController(rootViewController: ProductDetailViewController()) - + window?.rootViewController = UINavigationController(rootViewController: LoginViewController()) window?.makeKeyAndVisible() } diff --git a/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift b/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift index d27665f..370ef56 100644 --- a/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift +++ b/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift @@ -3,7 +3,6 @@ import SnapKit class JustDroppedCollectionViewCell: UICollectionViewCell { - let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill @@ -11,7 +10,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { imageView.layer.cornerRadius = 10 return imageView }() - @@ -37,7 +35,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { }() - let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.boldSystemFont(ofSize: 12) @@ -73,15 +70,12 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { bookmarkButton.addTarget(self, action: #selector(toggleBookmark), for: .touchUpInside) } - - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupLayout() { contentView.addSubview(imageView) - contentView.addSubview(bookmarkButton) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) @@ -90,7 +84,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { imageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() - make.height.equalTo(142) } @@ -103,21 +96,11 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { titleLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom).offset(4) - make.height.equalTo(142) // 이미지 높이를 142로 설정 - } - - titleLabel.snp.makeConstraints { make in - make.top.equalTo(imageView.snp.bottom).offset(4) // 이미지와의 간격을 줄임 - make.leading.trailing.equalToSuperview().inset(8) } descriptionLabel.snp.makeConstraints { make in - - - - make.top.equalTo(titleLabel.snp.bottom).offset(2) // 타이틀과의 간격을 줄임 - + make.top.equalTo(titleLabel.snp.bottom).offset(2) make.leading.trailing.equalToSuperview().inset(8) } @@ -132,7 +115,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { make.bottom.equalToSuperview().offset(-8) } } - @objc func bookmarkButtonTapped() { bookmarkButton.isSelected.toggle() @@ -145,7 +127,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { bookmarkButton.isSelected.toggle() } - func configure(with item: JustDroppedItem) { imageView.image = item.image diff --git a/Kream/VIews/Cell/NewKeywordCollectionViewCell.swift b/Kream/VIews/Cell/NewKeywordCollectionViewCell.swift new file mode 100644 index 0000000..e9cc702 --- /dev/null +++ b/Kream/VIews/Cell/NewKeywordCollectionViewCell.swift @@ -0,0 +1,41 @@ +import UIKit +import Foundation + +import SnapKit + +class KeywordCollectionViewCell: UICollectionViewCell { + let keywordLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 14) + label.textColor = .black + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupCellLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupCellLayout() { + contentView.backgroundColor = .white + contentView.layer.cornerRadius = 20 + contentView.layer.borderWidth = 1 + contentView.layer.borderColor = UIColor.clear.cgColor + contentView.clipsToBounds = true + + contentView.addSubview(keywordLabel) + keywordLabel.snp.makeConstraints { make in + make.edges.equalToSuperview().inset(UIEdgeInsets(top: 7, left: 11, bottom: 7, right: 11)) + } + } + + // configure 메서드를 KeywordModel 타입을 받도록 수정 + func configure(with keyword: KeywordModel) { + keywordLabel.text = keyword.title + } +} + diff --git a/Kream/VIews/HomeViewController.swift b/Kream/VIews/HomeViewController.swift deleted file mode 100644 index 1f559b2..0000000 --- a/Kream/VIews/HomeViewController.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// HomeViewController.swift -// Kream -// -// Created by 임소은 on 10/4/24. -// - -import UIKit - -class HomeViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Do any additional setup after loading the view. - } - - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - -} diff --git a/Kream/VIews/LoginView.swift b/Kream/VIews/LoginView.swift index 2a574a5..69ab203 100644 --- a/Kream/VIews/LoginView.swift +++ b/Kream/VIews/LoginView.swift @@ -81,19 +81,11 @@ class LoginView: UIView { let kakaoLoginButton: UIButton = { let button = UIButton(type: .system) - let customFont = UIFont(name: "Inter-Bold", size: 13) ?? UIFont.boldSystemFont(ofSize: 16) let title = NSAttributedString(string: "카카오로 로그인", attributes: [ .font: customFont - - // 'Inter-Bold' 폰트 사용 (프로젝트에 포함되어 있어야 함) - let customFont = UIFont(name: "Inter-Bold", size: 13) ?? UIFont.boldSystemFont(ofSize: 16) - - let title = NSAttributedString(string: "카카오로 로그인", attributes: [ - .font: customFont // Inter-Bold 폰트로 설정 - ]) button.setAttributedTitle(title, for: .normal) @@ -118,18 +110,11 @@ class LoginView: UIView { let appleLoginButton: UIButton = { let button = UIButton(type: .system) - let customFont = UIFont(name: "Inter-Bold", size: 13) ?? UIFont.boldSystemFont(ofSize: 16) let title = NSAttributedString(string: "Apple로 로그인", attributes: [ .font: customFont - - let customFont = UIFont(name: "Inter-Bold", size: 13) ?? UIFont.boldSystemFont(ofSize: 16) - - let title = NSAttributedString(string: "Apple로 로그인", attributes: [ - .font: customFont // Inter-Bold 폰트로 설정 - ]) button.setAttributedTitle(title, for: .normal) diff --git a/Kream/VIews/MainTabBarViewController.swift b/Kream/VIews/MainTabBarViewController.swift index f02e720..25b4a03 100644 --- a/Kream/VIews/MainTabBarViewController.swift +++ b/Kream/VIews/MainTabBarViewController.swift @@ -12,12 +12,10 @@ class MainTabBarController: UITabBarController { super.viewDidLoad() - let homeViewController = createNavController(for: MainViewController(), title: "HOME", image: UIImage(named: "home_icon")!) let styleViewController = createNavController(for: StyleViewController(), title: "STYLE", image: UIImage(named: "style_icon")!) let shopViewController = createNavController(for: ShopViewController(), title: "SHOP", image: UIImage(named: "shop_icon")!) let savedViewController = createNavController(for: SavedViewController(), title: "Saved", image: UIImage(named: "saved_icon")!) - let myViewController = createNavController(for: MyViewController(), title: "My", image: UIImage(named: "mypage_icon")!) diff --git a/Kream/VIews/MyViewController.swift b/Kream/VIews/MyViewController.swift deleted file mode 100644 index 014e99b..0000000 --- a/Kream/VIews/MyViewController.swift +++ /dev/null @@ -1,186 +0,0 @@ -import UIKit -import SnapKit - -class MyViewController: UIViewController { - - - let settingsButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(systemName: "gearshape"), for: .normal) - button.tintColor = .black - return button - }() - - let cameraButton: UIButton = { - let button = UIButton() - button.setImage(UIImage(systemName: "camera"), for: .normal) - button.tintColor = .black - return button - }() - - let profileImageView: UIImageView = { - let imageView = UIImageView() - imageView.image = UIImage(named: "profile_image") // 프로필 이미지 설정 - imageView.layer.cornerRadius = 45 // 원형 이미지로 만들기 위한 설정 (이미지 크기에 맞춤) - imageView.layer.masksToBounds = true - imageView.contentMode = .scaleAspectFill - return imageView - }() - - let usernameLabel: UILabel = { - let label = UILabel() - label.text = "Jeong_iOS" - label.font = UIFont.systemFont(ofSize: 16, weight: .bold) - label.textAlignment = .left // 왼쪽 정렬로 설정 - return label - }() - - let followersLabel: UILabel = { - let label = UILabel() - label.text = "팔로워 326 팔로잉 20" - label.font = UIFont.systemFont(ofSize: 14) - label.textColor = .black - label.textAlignment = .left // 왼쪽 정렬로 설정 - return label - }() - - // 사용자 이름과 팔로워/팔로잉 레이블을 수직으로 정렬할 StackView - let nameAndFollowersStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - stackView.alignment = .leading - stackView.spacing = 4 - return stackView - }() - - - let profileInfoStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.alignment = .center - stackView.spacing = 16 - return stackView - }() - - let profileEditButton: UIButton = { - let button = UIButton() - button.setTitle("프로필 관리", for: .normal) - button.setTitleColor(.black, for: .normal) - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.gray.cgColor - button.titleLabel?.font = UIFont.systemFont(ofSize: 9, weight: .light) - button.layer.cornerRadius = 10 - return button - }() - - let profileShareButton: UIButton = { - let button = UIButton() - button.setTitle("프로필 공유", for: .normal) - button.setTitleColor(.black, for: .normal) - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.lightGray.cgColor - button.titleLabel?.font = UIFont.systemFont(ofSize: 9, weight: .light) - button.layer.cornerRadius = 10 - return button - }() - - // 하단의 빈 뷰 (구분선 대신 사용) - let bottomSpacingView: UIView = { - let view = UIView() - view.backgroundColor = .systemGray6 - return view - }() - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .white - - - navigationItem.title = nil - - - navigationController?.setNavigationBarHidden(true, animated: false) - - - view.addSubview(settingsButton) - view.addSubview(cameraButton) - view.addSubview(profileInfoStackView) - view.addSubview(profileEditButton) - view.addSubview(profileShareButton) - view.addSubview(bottomSpacingView) - - - nameAndFollowersStackView.addArrangedSubview(usernameLabel) - nameAndFollowersStackView.addArrangedSubview(followersLabel) - - profileInfoStackView.addArrangedSubview(profileImageView) - profileInfoStackView.addArrangedSubview(nameAndFollowersStackView) - - - setupLayout() - - - settingsButton.addTarget(self, action: #selector(handleSettingsButtonTapped), for: .touchUpInside) - } - - - @objc func handleSettingsButtonTapped() { - let settingVC = SettingViewController() - - - navigationController?.pushViewController(settingVC, animated: true) - } - - func setupLayout() { - - settingsButton.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide).offset(16) - make.leading.equalToSuperview().offset(16) - make.width.equalTo(25) - make.height.equalTo(25) - } - - - cameraButton.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide).offset(16) - make.trailing.equalToSuperview().offset(-16) - make.width.height.equalTo(25) - } - - // 프로필 정보 StackView - profileInfoStackView.snp.makeConstraints { make in - make.top.equalTo(settingsButton.snp.bottom).offset(20) - make.leading.equalToSuperview().offset(30) // 왼쪽 여백 설정 - } - - // 프로필 - profileImageView.snp.makeConstraints { make in - make.width.height.equalTo(90) // 원형 이미지로 설정 - } - - // 프로필 관리 버튼 - profileEditButton.snp.makeConstraints { make in - make.top.equalTo(profileInfoStackView.snp.bottom).offset(20) - make.leading.equalToSuperview().offset(40) - make.trailing.equalTo(view.snp.centerX).offset(-10) - make.height.equalTo(26) - } - - // 프로필 공유 버튼 - profileShareButton.snp.makeConstraints { make in - make.top.equalTo(profileInfoStackView.snp.bottom).offset(20) - make.leading.equalTo(view.snp.centerX).offset(10) - make.trailing.equalToSuperview().offset(-40) - make.height.equalTo(26) - } - - // 하단 빈 뷰 (구분선 대체) 레이아웃 설정 - bottomSpacingView.snp.makeConstraints { make in - make.top.equalTo(profileEditButton.snp.bottom).offset(20) - make.leading.trailing.equalToSuperview() - make.height.equalTo(20) // 빈 뷰 높이 설정 - } - } -} - diff --git a/Kream/VIews/SavedViewController.swift b/Kream/VIews/SavedViewController.swift index d90cb70..9a6a502 100644 --- a/Kream/VIews/SavedViewController.swift +++ b/Kream/VIews/SavedViewController.swift @@ -1,4 +1,3 @@ - import UIKit class SavedViewController: UIViewController { @@ -73,4 +72,3 @@ extension SavedViewController: UITableViewDelegate, UITableViewDataSource { } } - diff --git a/Kream/VIews/SettingViewController.swift b/Kream/VIews/SettingViewController.swift index f45e0e8..2ceda74 100644 --- a/Kream/VIews/SettingViewController.swift +++ b/Kream/VIews/SettingViewController.swift @@ -2,6 +2,8 @@ import UIKit import SnapKit + + class SettingViewController: UIViewController { // 뒤로가기 버튼 @@ -58,9 +60,7 @@ class SettingViewController: UIViewController { textField.layer.borderColor = UIColor.lightGray.cgColor textField.layer.cornerRadius = 5 textField.font = UIFont.systemFont(ofSize: 14) - textField.isUserInteractionEnabled = false - return textField }() @@ -73,7 +73,7 @@ class SettingViewController: UIViewController { button.layer.borderWidth = 1 button.layer.borderColor = UIColor.black.cgColor button.layer.cornerRadius = 5 - + return button }() @@ -96,9 +96,7 @@ class SettingViewController: UIViewController { textField.layer.borderColor = UIColor.lightGray.cgColor textField.layer.cornerRadius = 5 textField.font = UIFont.systemFont(ofSize: 14) - textField.isUserInteractionEnabled = false - return textField }() @@ -113,11 +111,9 @@ class SettingViewController: UIViewController { button.layer.cornerRadius = 5 return button }() - - //이메일 // 비밀번호 버튼 변경을 위한 플래그 변수 + //이메일 / 비밀번호 버튼 변경을 위한 플래그 변수 var isEmailEditing: Bool = false var isPasswordEditing: Bool = false - override func viewDidLoad() { super.viewDidLoad() @@ -142,7 +138,6 @@ class SettingViewController: UIViewController { backButton.addTarget(self, action: #selector(handleBackButtonTapped), for: .touchUpInside) - // 이메일 변경 버튼과 비밀번호 변경 버튼에 액션 추가 changeEmailButton.addTarget(self, action: #selector(handleChangeEmailButtonTapped), for: .touchUpInside) @@ -212,25 +207,6 @@ class SettingViewController: UIViewController { } //뒤로가기 매서드 - - // 이메일 변경 버튼과 비밀번호 변경 버튼에 액션 추가 - changeEmailButton.addTarget(self, action: #selector(handleChangeEmailButtonTapped), for: .touchUpInside) - changePasswordButton.addTarget(self, action: #selector(handleChangePasswordButtonTapped), for: .touchUpInside) - } - // 이메일 변경 버튼 클릭 시 호출되는 메서드 - @objc func handleChangeEmailButtonTapped() { - emailTextField.text = "" // 기존 텍스트 제거 - emailTextField.placeholder = "새로운 이메일을 입력해주세요 !" // 플레이스홀더 변경 - } - - // 비밀번호 변경 버튼 클릭 시 호출되는 메서드 - @objc func handleChangePasswordButtonTapped() { - passwordTextField.text = "" // 기존 텍스트 제거 - passwordTextField.placeholder = "새로운 비밀번호를 입력해주세요!" // 플레이스홀더 변경 - } - - - @objc func handleBackButtonTapped() { navigationController?.popViewController(animated: true) } diff --git a/Kream/ViewControllers/LoginViewController.swift b/Kream/ViewControllers/LoginViewController.swift index dad23d4..8dad63b 100644 --- a/Kream/ViewControllers/LoginViewController.swift +++ b/Kream/ViewControllers/LoginViewController.swift @@ -1,11 +1,14 @@ import UIKit +import Alamofire +import KakaoSDKAuth +import KakaoSDKUser +import KeychainAccess class LoginViewController: UIViewController { - let loginView = LoginView() let loginViewModel = LoginViewModel() // 뷰모델 인스턴스 생성 - + let keychain = Keychain(service: "com.yourapp.kakaoLogin") // Keychain override func viewDidLoad() { super.viewDidLoad() @@ -25,11 +28,11 @@ class LoginViewController: UIViewController { // 로그인 버튼에 액션 추가 loginView.loginButton.addTarget(self, action: #selector(handleLoginButtonTapped), for: .touchUpInside) + loginView.kakaoLoginButton.addTarget(self, action: #selector(handleKakaoLoginButtonTapped), for: .touchUpInside) } // 로그인 버튼 클릭 시 호출되는 메서드 @objc func handleLoginButtonTapped() { - // LoginView의 이메일 및 비밀번호 텍스트 필드 값 가져오기 guard let email = loginView.emailTextField.text, !email.isEmpty, let password = loginView.passwordTextField.text, !password.isEmpty else { @@ -53,17 +56,78 @@ class LoginViewController: UIViewController { } } + // 카카오 로그인 버튼 클릭 시 호출되는 메서드 + @objc func handleKakaoLoginButtonTapped() { + // 카카오톡 설치 여부에 따라 로그인 진행 방식 결정 + if UserApi.isKakaoTalkLoginAvailable() { + UserApi.shared.loginWithKakaoTalk { (oauthToken, error) in + if let error = error { + print("카카오톡 로그인 에러: \(error.localizedDescription)") + self.showAlert(title: "로그인 오류", message: "카카오톡 로그인 중 오류가 발생했습니다.") + } else { + print("카카오톡 로그인 성공") + if let token = oauthToken?.accessToken { + self.saveTokenToKeychain(token: token) + } + self.fetchUserNickname() + self.navigateToMainTabBarController() + } + } + } else { + UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in + if let error = error { + print("카카오 계정 로그인 에러: \(error.localizedDescription)") + self.showAlert(title: "로그인 오류", message: "카카오 계정 로그인 중 오류가 발생했습니다.") + } else { + print("카카오 계정 로그인 성공") + if let token = oauthToken?.accessToken { + self.saveTokenToKeychain(token: token) + } + self.fetchUserNickname() + self.navigateToMainTabBarController() + } + } + } + } + + // 토큰을 키체인에 저장하는 메서드 + private func saveTokenToKeychain(token: String) { + do { + try keychain.set(token, key: "kakaoAccessToken") + } catch { + print("키체인에 토큰 저장 실패: \(error.localizedDescription)") + } + } + + // 닉네임을 가져와 키체인에 저장하는 메서드 + private func fetchUserNickname() { + UserApi.shared.me { (user, error) in + if let error = error { + print("사용자 정보 요청 실패: \(error.localizedDescription)") + } else { + if let nickname = user?.kakaoAccount?.profile?.nickname { + do { + try self.keychain.set(nickname, key: "kakaoUserNickname") + } catch { + print("키체인에 닉네임 저장 실패: \(error.localizedDescription)") + } + } + } + } + } + + // 메인 탭 화면으로 전환하는 메서드 + private func navigateToMainTabBarController() { + let mainTabBarController = MainTabBarController() + mainTabBarController.modalPresentationStyle = .fullScreen // 전체 화면 모달 설정 + present(mainTabBarController, animated: true, completion: nil) // 모달 전환 + } + // 경고 메시지 표시 메서드 private func showAlert(title: String, message: String) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let okAction = UIAlertAction(title: "확인", style: .default, handler: nil) alert.addAction(okAction) present(alert, animated: true, completion: nil) - - let mainTabBarController = MainTabBarController() - mainTabBarController.modalPresentationStyle = .fullScreen // 전체 화면 모달 설정 - present(mainTabBarController, animated: true, completion: nil) // 모달 전환 - } } - diff --git a/Kream/ViewControllers/MainViewController.swift b/Kream/ViewControllers/MainViewController.swift index f2a688b..9fd183e 100644 --- a/Kream/ViewControllers/MainViewController.swift +++ b/Kream/ViewControllers/MainViewController.swift @@ -2,73 +2,28 @@ import UIKit import SnapKit -// UI 구성 요소 설정을 위한 헬퍼 클래스 -class UIHelper { - static func createButton(systemImageName: String, tintColor: UIColor = .black) -> UIButton { - let button = UIButton(type: .system) - button.setImage(UIImage(systemName: systemImageName), for: .normal) - button.tintColor = tintColor - return button - } - - static func createSearchBar(placeholder: String, backgroundColor: UIColor = .systemGray6) -> UISearchBar { - let searchBar = UISearchBar() - searchBar.placeholder = placeholder - searchBar.backgroundImage = UIImage() - searchBar.searchTextField.backgroundColor = backgroundColor - return searchBar - } - - static func createSegmentedControl(items: [String]) -> UISegmentedControl { - let segmentedControl = UISegmentedControl(items: items) - segmentedControl.selectedSegmentIndex = 0 - segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.gray, .font: UIFont.systemFont(ofSize: 14)], for: .normal) - segmentedControl.setTitleTextAttributes([.foregroundColor: UIColor.black, .font: UIFont.boldSystemFont(ofSize: 14)], for: .selected) - return segmentedControl - } -} - -// 데이터 로직을 관리하는 클래스 (OCP 적용) -class JustDroppedDataManager { - func fetchJustDroppedItems() -> [JustDroppedItem] { - return [ - JustDroppedItem(imageName: "dropImage1", title: "MLB", description: "청키라이너 뉴욕양키스", price: "139,000원", purchaseInfo: "즉시 구매가"), - JustDroppedItem(imageName: "dropImage2", title: "Jordan", description: "Jordan 1 Retro High OG Yellow Ochre", price: "228,000원", purchaseInfo: "즉시 구매가"), - JustDroppedItem(imageName: "dropImage3", title: "Human Made", description: "Human Made Varsity Jacket", price: "2,000,000원", purchaseInfo: "즉시 구매가") - ] - } -} - -// SOLID 원칙을 고려하여 리팩토링된 MainViewController -class MainViewController: UIViewController { - - private let scrollView = UIScrollView() - private let contentView = UIView() - private let searchBar = UIHelper.createSearchBar(placeholder: "브랜드, 상품, 프로필, 태그 등") - private let bellButton = UIHelper.createButton(systemImageName: "bell") - private let segmentedControl = UIHelper.createSegmentedControl(items: ["추천", "랭킹", "발매정보", "럭셔리", "남성", "여성"]) - private let underlineView = UIView() - private let bannerImageView = UIImageView(image: UIImage(named: "banner_image")) - private let collectionView: UICollectionView - private let justDroppedCollectionView: UICollectionView - private let dataManager = JustDroppedDataManager() // 의존성 역전 원칙 적용 - - private let menuItems = [ - class MainViewController: UIViewController { // 스크롤 뷰 추가 let scrollView = UIScrollView() let contentView = UIView() // 스크롤 뷰 안에 들어갈 콘텐츠 뷰 - // 검색창 추가 - let searchBar: UISearchBar = { - let searchBar = UISearchBar() - searchBar.placeholder = "브랜드, 상품, 프로필, 태그 등" - searchBar.backgroundImage = UIImage() - searchBar.searchTextField.backgroundColor = .systemGray6 - return searchBar + let searchBar: UIView = { + let view = UIView() + view.backgroundColor = .systemGray6 + view.layer.cornerRadius = 10 + let label = UILabel() + label.text = "브랜드, 상품, 프로필, 태그 등" + label.textColor = .gray + label.font = UIFont.systemFont(ofSize: 14) + view.addSubview(label) + label.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(8) + } + return view }() + // 알림 버튼 추가 let bellButton: UIButton = { @@ -98,12 +53,13 @@ class MainViewController: UIViewController { segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged) return segmentedControl }() - // 밑줄 뷰 추가 + // 뭐 챌린지 .. let underlineView = UIHelpers.createSeparatorLine(color: .black, height: 2) + let mainLabel = UIHelpers.createLabel(text: "본격 한파 대비! 연말 필수템 모음", font: .boldSystemFont(ofSize: 16), textColor: .black) + let hashtagLabel = UIHelpers.createLabel(text: "#해피홀리록챌린지", font: .systemFont(ofSize: 14), textColor: .gray) // 메뉴 아이템 데이터 배열 let menuItems = [ - (image: "collection1", title: "크림 드로우"), (image: "collection2", title: "실시간 차트"), (image: "collection3", title: "남성 추천"), @@ -116,35 +72,6 @@ class MainViewController: UIViewController { (image: "collection10", title: "아크네 선물") ] - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.backgroundColor = .white - - let justDroppedLayout = UICollectionViewFlowLayout() - justDroppedLayout.scrollDirection = .horizontal - justDroppedCollectionView = UICollectionView(frame: .zero, collectionViewLayout: justDroppedLayout) - justDroppedCollectionView.backgroundColor = .white - - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - setupView() - setupLayout() - setupCollectionView() - } - - private func setupView() { - view.backgroundColor = .white - // Just Dropped 데이터 배열 let justDroppedItems: [JustDroppedItem] = [ JustDroppedItem(imageName: "dropImage1", title: "MLB", description: "청키라이너 뉴욕양키스", price: "139,000원", purchaseInfo: "즉시 구매가"), @@ -179,28 +106,47 @@ class MainViewController: UIViewController { collectionView.backgroundColor = .white return collectionView }() + //밑에 구분선 하나 더 추가 + let newSeparatorLine = UIHelpers.createSeparatorLine() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white // 검색창과 알림 버튼, 세그먼트 추가 (스크롤 뷰 바깥에 위치) - view.addSubview(searchBar) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(searchBarTapped)) + searchBar.addGestureRecognizer(tapGesture) view.addSubview(bellButton) view.addSubview(segmentedControl) view.addSubview(underlineView) - - view.addSubview(scrollView) + // 스크롤 뷰와 콘텐츠 뷰 설정 + view.addSubview(scrollView) scrollView.addSubview(contentView) + + // 네비게이션 타이틀 제거 + navigationItem.title = nil + navigationController?.setNavigationBarHidden(true, animated: false) + + // UI 요소들 콘텐츠 뷰에 추가 contentView.addSubview(bannerImageView) contentView.addSubview(collectionView) + contentView.addSubview(separatorLine) + contentView.addSubview(justDroppedTitleLabel) + contentView.addSubview(justDroppedSubtitleLabel) contentView.addSubview(justDroppedCollectionView) + contentView.addSubview(newSeparatorLine) + contentView.addSubview(mainLabel) + contentView.addSubview(hashtagLabel) + + setupCollectionView() + setupLayout() } - private func setupLayout() { - + // 레이아웃 설정 + func setupLayout() { + // 검색창 레이아웃 searchBar.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(8) make.leading.equalToSuperview().offset(16) @@ -211,11 +157,7 @@ class MainViewController: UIViewController { bellButton.snp.makeConstraints { make in make.centerY.equalTo(searchBar) make.trailing.equalToSuperview().offset(-16) - - make.size.equalTo(24) - make.width.height.equalTo(24) - } segmentedControl.snp.makeConstraints { make in @@ -224,15 +166,6 @@ class MainViewController: UIViewController { make.height.equalTo(36) } - - underlineView.snp.makeConstraints { make in - make.top.equalTo(segmentedControl.snp.bottom).offset(2) - make.leading.equalTo(segmentedControl.snp.leading) - make.width.equalTo(segmentedControl.frame.width / CGFloat(segmentedControl.numberOfSegments)) - make.height.equalTo(2) - } - - // 밑줄 초기 위치 설정 underlineView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(2) @@ -242,50 +175,30 @@ class MainViewController: UIViewController { } // 스크롤 뷰 레이아웃 - scrollView.snp.makeConstraints { make in make.top.equalTo(underlineView.snp.bottom).offset(16) make.leading.trailing.bottom.equalToSuperview() } - + // 콘텐츠 뷰 레이아웃 - contentView.snp.makeConstraints { make in make.edges.equalTo(scrollView) make.width.equalTo(scrollView) } - // 배너 이미지 레이아웃 - bannerImageView.snp.makeConstraints { make in make.top.equalTo(contentView).offset(16) make.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(336) } - - collectionView.snp.makeConstraints { make in - // 컬렉션 뷰 레이아웃 (메뉴 아이템) collectionView.snp.remakeConstraints { make in - make.top.equalTo(bannerImageView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() make.height.equalTo(200) } - - - justDroppedCollectionView.snp.makeConstraints { make in - make.top.equalTo(collectionView.snp.bottom).offset(16) - make.leading.trailing.equalToSuperview() - make.height.equalTo(200) - make.bottom.equalToSuperview().offset(-20) - } - } - - private func setupCollectionView() { - // 구분선과 텍스트 레이아웃 separatorLine.snp.remakeConstraints { make in @@ -304,19 +217,35 @@ class MainViewController: UIViewController { make.leading.equalTo(justDroppedTitleLabel) } - // Just Dropped 컬렉션 뷰 레이아웃 - justDroppedCollectionView.snp.remakeConstraints { make in - make.top.equalTo(justDroppedSubtitleLabel.snp.bottom).offset(16) - make.leading.trailing.equalToSuperview() - make.height.equalTo(250) - make.bottom.equalToSuperview().offset(-30) - } - + justDroppedCollectionView.snp.makeConstraints { make in + make.top.equalTo(justDroppedSubtitleLabel.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview() + make.height.equalTo(200) + } + + // Just Dropped 섹션 아래 구분선 추가 + newSeparatorLine.snp.makeConstraints { make in + make.top.equalTo(justDroppedCollectionView.snp.bottom).offset(30) + make.leading.trailing.equalToSuperview() + make.height.equalTo(1) + } + + // 본문 레이블 설정 + mainLabel.snp.makeConstraints { make in + make.top.equalTo(newSeparatorLine.snp.bottom).offset(20) + make.leading.equalToSuperview().offset(16) + } + + // 해시태그 레이블 설정 + hashtagLabel.snp.makeConstraints { make in + make.top.equalTo(mainLabel.snp.bottom).offset(4) + make.leading.equalTo(mainLabel) + make.bottom.equalToSuperview().offset(-20) // 전체 콘텐츠 하단 간격 + } } // MARK: - UICollectionView 설정 메서드 func setupCollectionView() { - collectionView.delegate = self collectionView.dataSource = self collectionView.register(MainMenuCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell") @@ -326,35 +255,6 @@ class MainViewController: UIViewController { justDroppedCollectionView.register(JustDroppedCollectionViewCell.self, forCellWithReuseIdentifier: "JustDroppedCell") } -} - -// MARK: - UICollectionViewDelegate, UICollectionViewDataSource -extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return collectionView == self.collectionView ? menuItems.count : dataManager.fetchJustDroppedItems().count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - if collectionView == self.collectionView { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as! MainMenuCollectionViewCell - let menuItem = menuItems[indexPath.item] - cell.configure(imageName: menuItem.image, title: menuItem.title) - return cell - } else { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "JustDroppedCell", for: indexPath) as! JustDroppedCollectionViewCell - let justDroppedItem = dataManager.fetchJustDroppedItems()[indexPath.item] - cell.configure(with: justDroppedItem) - return cell - } - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return collectionView == justDroppedCollectionView ? CGSize(width: 150, height: 230) : CGSize(width: 61, height: 81) - } -} - - - // MARK: - 세그먼트 선택 변경 시 호출 @objc func segmentChanged(_ sender: UISegmentedControl) { updateUnderlinePosition() @@ -422,6 +322,7 @@ extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSour make.height.equalTo(200) make.bottom.equalToSuperview().offset(-20) } + default: let label = UILabel() label.textAlignment = .center @@ -431,7 +332,13 @@ extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSour } } } + + // MARK: - SearchBar 클릭 시 호출 + @objc func searchBarTapped() { + let searchBarTab = SearchBarTabViewController() + navigationController?.pushViewController(searchBarTab, animated: true) } +} // MARK: - UICollectionViewDelegate, UICollectionViewDataSource extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @@ -456,11 +363,10 @@ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if collectionView == self.justDroppedCollectionView { - return CGSize(width: 150, height: 230) + return CGSize(width: 150, height: 230) } return CGSize(width: 61, height: 81) } } - diff --git a/Kream/ViewControllers/MyViewController.swift b/Kream/ViewControllers/MyViewController.swift index aa3ef63..13d5c78 100644 --- a/Kream/ViewControllers/MyViewController.swift +++ b/Kream/ViewControllers/MyViewController.swift @@ -1,6 +1,6 @@ import UIKit import SnapKit - +import KeychainAccess protocol ProfileEditDelegate: AnyObject { // 프로필 이미지가 변경되었을 때 호출될 메서드 @@ -96,6 +96,8 @@ class MyViewController: UIViewController { return view }() + let keychain = Keychain(service: "com.yourapp.kakaoLogin") // Keychain 인스턴스 생성 + override func viewDidLoad() { super.viewDidLoad() @@ -139,6 +141,11 @@ class MyViewController: UIViewController { if let savedEmail = UserDefaults.standard.string(forKey: "userEmail") { usernameLabel.text = savedEmail // usernameLabel에 저장된 이메일 출력 } + + // Keychain에서 저장된 카카오 닉네임 불러오기 + if let kakaoNickname = try? keychain.get("kakaoUserNickname") { + usernameLabel.text = kakaoNickname + } } //설정버튼 @@ -215,4 +222,3 @@ extension MyViewController: ProfileEditDelegate { } } - diff --git a/Kream/ViewControllers/SerachbarTabDetailViewController.swift b/Kream/ViewControllers/SerachbarTabDetailViewController.swift new file mode 100644 index 0000000..b7fa48d --- /dev/null +++ b/Kream/ViewControllers/SerachbarTabDetailViewController.swift @@ -0,0 +1,155 @@ +import UIKit +import SnapKit +import Moya + +class SerachbarTabDetailViewController: UIViewController { + + // Moya Provider 생성 + let provider = MoyaProvider(plugins: [NetworkLoggerPlugin()]) // NetworkLoggerPlugin 추가 + + // 검색창 추가 + let searchBar: UISearchBar = { + let searchBar = UISearchBar() + searchBar.searchTextField.attributedPlaceholder = NSAttributedString( + string: "브랜드, 상품, 프로필, 태그 등", + attributes: [ + .font: UIFont.systemFont(ofSize: 14) // 원하는 크기로 설정 + ] + ) + searchBar.backgroundImage = UIImage() + searchBar.searchTextField.backgroundColor = .systemGray6 + searchBar.searchTextField.leftView = nil + + return searchBar + }() + + // 취소 버튼 추가 + let cancelButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("취소", for: .normal) + button.setTitleColor(.black, for: .normal) + button.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + return button + }() + + // 추천 검색어 테이블 뷰 + let recommendedTableView: UITableView = { + let tableView = UITableView() + tableView.backgroundColor = .white + tableView.separatorStyle = .singleLine + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SearchResultCell") + return tableView + }() + + // 추천 검색어 데이터 배열 + var recommendedKeywords: [String] = [] + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + // 검색창과 취소 버튼 추가 + view.addSubview(searchBar) + view.addSubview(cancelButton) + + // 추천 검색어 테이블 뷰 추가 + view.addSubview(recommendedTableView) + + searchBar.delegate = self // 검색바 델리게이트 설정 + + recommendedTableView.delegate = self + recommendedTableView.dataSource = self + + // 레이아웃 설정 + setupLayout() + } + + // 레이아웃 설정 메서드 + func setupLayout() { + searchBar.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(8) + make.leading.equalToSuperview().offset(16) + make.trailing.equalTo(cancelButton.snp.leading).offset(-8) + make.height.equalTo(44) + } + + cancelButton.snp.makeConstraints { make in + make.centerY.equalTo(searchBar) + make.trailing.equalToSuperview().offset(-16) + } + + recommendedTableView.snp.makeConstraints { make in + make.top.equalTo(searchBar.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview().inset(16) + make.bottom.equalToSuperview().offset(-20) + } + } + + // 취소 버튼 클릭 시 동작 + @objc func cancelButtonTapped() { + navigationController?.popViewController(animated: true) + } + + // 검색어로 제품 검색 + func searchProducts(with query: String) { + let urlString = "https://dummyjson.com/products/search?q=\(query)" + guard let url = URL(string: urlString) else { return } + + let task = URLSession.shared.dataTask(with: url) { data, response, error in + if let error = error { + print("네트워크 오류: \(error)") + return + } + + guard let data = data else { + print("데이터 없음") + return + } + + do { + let decoder = JSONDecoder() + let response = try decoder.decode(ProductSearchResponse.self, from: data) + self.recommendedKeywords = response.products.map { $0.title } + DispatchQueue.main.async { + self.recommendedTableView.reloadData() + } + } catch { + print("JSON 파싱 오류: \(error)") + } + } + task.resume() + } + +} + +// MARK: - UISearchBarDelegate +extension SerachbarTabDetailViewController: UISearchBarDelegate { + func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { + guard let query = searchBar.text, !query.isEmpty else { return } + print("검색어 입력됨: \(query)") // 검색어 확인 + searchProducts(with: query) // 검색어로 API 요청 + } + +} + +// MARK: - UITableViewDelegate, UITableViewDataSource +extension SerachbarTabDetailViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return recommendedKeywords.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) + let keyword = recommendedKeywords[indexPath.row] + cell.textLabel?.text = keyword + cell.textLabel?.font = UIFont.systemFont(ofSize: 16) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + // 셀 선택 시 추가 동작 구현 (필요할 경우) + } +} + diff --git a/Kream/ViewControllers/SerachbarTabViewController.swift b/Kream/ViewControllers/SerachbarTabViewController.swift new file mode 100644 index 0000000..010d418 --- /dev/null +++ b/Kream/ViewControllers/SerachbarTabViewController.swift @@ -0,0 +1,171 @@ +import UIKit +import SnapKit + +class SearchBarTabViewController: UIViewController { + + // 검색창 대체 UIView 추가 + let searchBarView: UIView = { + let view = UIView() + view.backgroundColor = .systemGray6 + view.layer.cornerRadius = 10 + + let label = UILabel() + label.text = "브랜드, 상품, 프로필, 태그 등" + label.textColor = .gray + label.font = UIFont.systemFont(ofSize: 14) + view.addSubview(label) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(searchBarTapped)) + view.addGestureRecognizer(tapGesture) + + label.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(16) + } + return view + }() + + // 취소 버튼 추가 + let cancelButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("취소", for: .normal) + button.setTitleColor(.black, for: .normal) + button.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + return button + }() + + // 추천 검색어 레이블 + let recommendedLabel: UILabel = { + let label = UILabel() + label.text = "추천 검색어" + label.font = UIFont.boldSystemFont(ofSize: 16) + label.textColor = .black + return label + }() + + // 추천 검색어 컬렉션 뷰 + let recommendedCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 12 + layout.minimumInteritemSpacing = 8 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .white + collectionView.showsVerticalScrollIndicator = false + collectionView.register(NewKeywordCollectionViewCell.self, forCellWithReuseIdentifier: "NewKeywordCell") + return collectionView + }() + + // 추천 검색어 데이터 배열 + var recommendedKeywords: [String] = [ + "채원 슈프림 후리스", "나이키V2K런", "뉴발란드996", "신상 나이키 콜라보", "허그 FW 패딩", "벨루어 눕시" + ] + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + // 검색창과 취소 버튼 추가 + view.addSubview(searchBarView) + view.addSubview(cancelButton) + + // 추천 검색어 레이블 추가 + view.addSubview(recommendedLabel) + + // 추천 검색어 컬렉션 뷰 추가 + view.addSubview(recommendedCollectionView) + + searchBarView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(searchBarTapped))) + + recommendedCollectionView.delegate = self + recommendedCollectionView.dataSource = self + + // 레이아웃 설정 + setupLayout() + } + + // 레이아웃 설정 메서드 + func setupLayout() { + searchBarView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(8) + make.leading.equalToSuperview().offset(16) + make.trailing.equalTo(cancelButton.snp.leading).offset(-8) + make.height.equalTo(44) + } + + cancelButton.snp.makeConstraints { make in + make.centerY.equalTo(searchBarView) + make.trailing.equalToSuperview().offset(-16) + } + + recommendedLabel.snp.makeConstraints { make in + make.top.equalTo(searchBarView.snp.bottom).offset(24) + make.leading.equalToSuperview().offset(16) + } + + recommendedCollectionView.snp.makeConstraints { make in + make.top.equalTo(recommendedLabel.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview().inset(16) + make.bottom.equalToSuperview().offset(-20) + } + } + + // 검색창 클릭 시 동작 + @objc func searchBarTapped() { + let searchBarDetailViewController = SerachbarTabDetailViewController() + navigationController?.pushViewController(searchBarDetailViewController, animated: true) + } + + // 취소 버튼 클릭 시 동작 + @objc func cancelButtonTapped() { + navigationController?.popViewController(animated: true) + } +} + +// MARK: - UICollectionViewDelegate, UICollectionViewDataSource +extension SearchBarTabViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return recommendedKeywords.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NewKeywordCell", for: indexPath) as! NewKeywordCollectionViewCell + cell.configure(with: recommendedKeywords[indexPath.item]) + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let keyword = recommendedKeywords[indexPath.item] + let width = keyword.size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)]).width + 32 + return CGSize(width: width, height: 36) + } +} + +// MARK: - NewKeywordCollectionViewCell 정의 +class NewKeywordCollectionViewCell: UICollectionViewCell { + let keywordLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 14) + label.textColor = .black + label.textAlignment = .center + label.backgroundColor = .systemGray6 // 연한 회색 배경 + label.layer.cornerRadius = 16 // 둥근 모서리 + label.clipsToBounds = true + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(keywordLabel) + keywordLabel.snp.makeConstraints { make in + make.edges.equalToSuperview() // 셀 크기에 딱 맞게 배치 + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(with keyword: String) { + keywordLabel.text = keyword + } +}