diff --git a/Kream.xcodeproj/project.pbxproj b/Kream.xcodeproj/project.pbxproj index 888bca9..31a2ae5 100644 --- a/Kream.xcodeproj/project.pbxproj +++ b/Kream.xcodeproj/project.pbxproj @@ -8,13 +8,30 @@ /* 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 */; }; + 52A7C2712CF5EEA300F668E3 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 52A7C2702CF5EEA300F668E3 /* Kingfisher */; }; + 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 +48,6 @@ exceptions = ( 52C34D032CA47B4000DB8986 /* Exceptions for "Kream" folder in "Kream" target */, ); - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 52C34CF32CA47B3E00DB8986 /* Kream */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = Kream; sourceTree = ""; }; @@ -47,6 +59,24 @@ 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 */, + 52A7C2712CF5EEA300F668E3 /* Kingfisher in Frameworks */, + 52C6308F2CE1FF740098C775 /* KakaoSDKCert in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -90,6 +120,24 @@ 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 */, + 52A7C2702CF5EEA300F668E3 /* Kingfisher */, ); productName = Kream; productReference = 52C34CF12CA47B3E00DB8986 /* Kream.app */; @@ -121,6 +169,10 @@ minimizedProjectReferenceProxies = 1; packageReferences = ( 523995592CAEF016005A358A /* XCRemoteSwiftPackageReference "SnapKit" */, + 52C630892CE1FF740098C775 /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */, + 52C630DE2CE4320C0098C775 /* XCRemoteSwiftPackageReference "KeychainAccess" */, + 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */, + 52A7C26F2CF5EEA300F668E3 /* XCRemoteSwiftPackageReference "Kingfisher" */, ); preferredProjectObjectVersion = 77; productRefGroup = 52C34CF22CA47B3E00DB8986 /* Products */; @@ -356,6 +408,38 @@ minimumVersion = 5.7.1; }; }; + 52A7C0F62CEB6F5400F668E3 /* XCRemoteSwiftPackageReference "Moya" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Moya/Moya"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 15.0.3; + }; + }; + 52A7C26F2CF5EEA300F668E3 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.1.1; + }; + }; + 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 +448,96 @@ 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; + }; + 52A7C2702CF5EEA300F668E3 /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 52A7C26F2CF5EEA300F668E3 /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; + 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/challengeImage1.imageset/Contents.json b/Kream/Assets.xcassets/challengeImage1.imageset/Contents.json new file mode 100644 index 0000000..699272a --- /dev/null +++ b/Kream/Assets.xcassets/challengeImage1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "challengeImage1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/challengeImage1.imageset/challengeImage1.png b/Kream/Assets.xcassets/challengeImage1.imageset/challengeImage1.png new file mode 100644 index 0000000..a2b9c73 Binary files /dev/null and b/Kream/Assets.xcassets/challengeImage1.imageset/challengeImage1.png differ diff --git a/Kream/Assets.xcassets/challengeImage2.imageset/Contents.json b/Kream/Assets.xcassets/challengeImage2.imageset/Contents.json new file mode 100644 index 0000000..759971e --- /dev/null +++ b/Kream/Assets.xcassets/challengeImage2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "challengeImage2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/challengeImage2.imageset/challengeImage2.png b/Kream/Assets.xcassets/challengeImage2.imageset/challengeImage2.png new file mode 100644 index 0000000..10e7fb6 Binary files /dev/null and b/Kream/Assets.xcassets/challengeImage2.imageset/challengeImage2.png differ diff --git a/Kream/Assets.xcassets/challengeImage3.imageset/Contents.json b/Kream/Assets.xcassets/challengeImage3.imageset/Contents.json new file mode 100644 index 0000000..d11f8a5 --- /dev/null +++ b/Kream/Assets.xcassets/challengeImage3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "challengeImage3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/challengeImage3.imageset/challengeImage3.png b/Kream/Assets.xcassets/challengeImage3.imageset/challengeImage3.png new file mode 100644 index 0000000..10eb1f7 Binary files /dev/null and b/Kream/Assets.xcassets/challengeImage3.imageset/challengeImage3.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage10.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage10.imageset/Contents.json new file mode 100644 index 0000000..8298fd0 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image-4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage10.imageset/image-4.png b/Kream/Assets.xcassets/drop/dropImage10.imageset/image-4.png new file mode 100644 index 0000000..6fb69e1 Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage10.imageset/image-4.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage11.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage11.imageset/Contents.json new file mode 100644 index 0000000..4c8ebe7 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage11.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image-5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage11.imageset/image-5.png b/Kream/Assets.xcassets/drop/dropImage11.imageset/image-5.png new file mode 100644 index 0000000..aa9eac9 Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage11.imageset/image-5.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage4.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage4.imageset/Contents.json new file mode 100644 index 0000000..b1eb7bf --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image 13.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage4.imageset/image 13.png b/Kream/Assets.xcassets/drop/dropImage4.imageset/image 13.png new file mode 100644 index 0000000..27a1d5e Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage4.imageset/image 13.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage5.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage5.imageset/Contents.json new file mode 100644 index 0000000..cdaf065 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image 17.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage5.imageset/image 17.png b/Kream/Assets.xcassets/drop/dropImage5.imageset/image 17.png new file mode 100644 index 0000000..0988cec Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage5.imageset/image 17.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage6.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage6.imageset/Contents.json new file mode 100644 index 0000000..5eee7c2 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image 18.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage6.imageset/image 18.png b/Kream/Assets.xcassets/drop/dropImage6.imageset/image 18.png new file mode 100644 index 0000000..2ca67f4 Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage6.imageset/image 18.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage7.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage7.imageset/Contents.json new file mode 100644 index 0000000..3d2bf01 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image-1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage7.imageset/image-1.png b/Kream/Assets.xcassets/drop/dropImage7.imageset/image-1.png new file mode 100644 index 0000000..006a2a5 Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage7.imageset/image-1.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage8.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage8.imageset/Contents.json new file mode 100644 index 0000000..d83b842 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage8.imageset/image-2.png b/Kream/Assets.xcassets/drop/dropImage8.imageset/image-2.png new file mode 100644 index 0000000..78187f6 Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage8.imageset/image-2.png differ diff --git a/Kream/Assets.xcassets/drop/dropImage9.imageset/Contents.json b/Kream/Assets.xcassets/drop/dropImage9.imageset/Contents.json new file mode 100644 index 0000000..719d1c9 --- /dev/null +++ b/Kream/Assets.xcassets/drop/dropImage9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "image-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Kream/Assets.xcassets/drop/dropImage9.imageset/image-3.png b/Kream/Assets.xcassets/drop/dropImage9.imageset/image-3.png new file mode 100644 index 0000000..2d8205f Binary files /dev/null and b/Kream/Assets.xcassets/drop/dropImage9.imageset/image-3.png differ 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/JustDroppedItem.swift b/Kream/Models/JustDroppedItem.swift index 554e046..d501532 100644 --- a/Kream/Models/JustDroppedItem.swift +++ b/Kream/Models/JustDroppedItem.swift @@ -1,14 +1,10 @@ import UIKit struct JustDroppedItem { - let imageName: String + let imageUrl: String // URL로 이미지 로드 let title: String let description: String let price: String let purchaseInfo: String - - var image: UIImage? { - return UIImage(named: imageName) - } } 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/ChallengeCollectionViewCell.swift b/Kream/VIews/Cell/ChallengeCollectionViewCell.swift new file mode 100644 index 0000000..f895431 --- /dev/null +++ b/Kream/VIews/Cell/ChallengeCollectionViewCell.swift @@ -0,0 +1,50 @@ +import UIKit +import SnapKit + +class ChallengeCollectionViewCell: UICollectionViewCell { + static let identifier = "ChallengeCollectionViewCell" + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill // 이미지가 꽉 차게 + imageView.clipsToBounds = true // 셀의 경계를 넘지 않도록 + return imageView + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 12, weight: .bold) + label.textColor = .black + label.textAlignment = .left // 왼쪽 정렬 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(imageView) + contentView.addSubview(titleLabel) + + // 오토레이아웃 설정 + imageView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() // 셀 크기에 맞게 이미지 확장 + make.height.equalTo(165) // 이미지 높이 설정 + } + + titleLabel.snp.makeConstraints { make in + make.top.equalTo(imageView.snp.bottom).offset(8) // 이미지 아래에 배치 + make.leading.trailing.equalToSuperview().inset(4) // 텍스트 좌우 여백 + make.height.equalTo(16) // 텍스트 높이 설정 + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // 데이터 설정 메서드 + func configure(imageName: String, title: String) { + imageView.image = UIImage(named: imageName) // 이미지 설정 + titleLabel.text = title // 텍스트 설정 + } +} + diff --git a/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift b/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift index d27665f..5276ef1 100644 --- a/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift +++ b/Kream/VIews/Cell/JustDroppedCollectionViewCell.swift @@ -1,9 +1,11 @@ import UIKit import SnapKit +import Kingfisher class JustDroppedCollectionViewCell: UICollectionViewCell { - + static let identifier = "JustDroppedCell" // 셀 식별자 추가 + let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill @@ -11,10 +13,7 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { imageView.layer.cornerRadius = 10 return imageView }() - - - let bookmarkButton: UIButton = { var configuration = UIButton.Configuration.plain() configuration.image = UIImage(systemName: "bookmark") @@ -36,8 +35,6 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { return button }() - - let titleLabel: UILabel = { let label = UILabel() label.font = UIFont.boldSystemFont(ofSize: 12) @@ -47,8 +44,9 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { let descriptionLabel: UILabel = { let label = UILabel() - label.font = UIFont.systemFont(ofSize: 10) + label.font = UIFont.systemFont(ofSize: 12) label.textColor = .darkGray + label.numberOfLines = 2 // 최대 2줄로 설정 return label }() @@ -66,22 +64,17 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { return label }() - override init(frame: CGRect) { super.init(frame: frame) setupLayout() - 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,34 +83,22 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { imageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() - make.height.equalTo(142) } bookmarkButton.snp.makeConstraints { make in make.bottom.equalTo(imageView.snp.bottom).offset(-8) make.trailing.equalTo(imageView.snp.trailing).offset(-8) - make.width.height.equalTo(24) + make.width.height.equalTo(20) } - 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,23 +113,21 @@ class JustDroppedCollectionViewCell: UICollectionViewCell { make.bottom.equalToSuperview().offset(-8) } } - @objc func bookmarkButtonTapped() { - bookmarkButton.isSelected.toggle() - let imageName = bookmarkButton.isSelected ? "bookmark.fill" : "bookmark" - let icon = UIImage(systemName: imageName)?.withTintColor(.black, renderingMode: .alwaysOriginal) - bookmarkButton.setImage(icon, for: .normal) // 아이콘 전체를 검은색으로 설정 - } - - @objc func toggleBookmark() { - bookmarkButton.isSelected.toggle() - } - - + bookmarkButton.isSelected.toggle() + let imageName = bookmarkButton.isSelected ? "bookmark.fill" : "bookmark" + let icon = UIImage(systemName: imageName)?.withTintColor(.black, renderingMode: .alwaysOriginal) + bookmarkButton.setImage(icon, for: .normal) + } func configure(with item: JustDroppedItem) { - imageView.image = item.image + // Kingfisher를 사용해 URL로 이미지 로드 + if let url = URL(string: item.imageUrl) { + imageView.kf.setImage(with: url) + } else { + imageView.image = nil // URL이 유효하지 않으면 기본 이미지 처리 + } titleLabel.text = item.title descriptionLabel.text = item.description priceLabel.text = item.price 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..a34477d 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 = { @@ -82,7 +37,8 @@ class MainViewController: UIViewController { let bannerImageView: UIImageView = { let imageView = UIImageView() imageView.image = UIImage(named: "banner_image") - imageView.contentMode = .scaleAspectFit + imageView.contentMode = .scaleAspectFill // 이미지가 꽉 차도록 설정 + imageView.clipsToBounds = true // 이미지가 영역을 넘어가지 않도록 설정 return imageView }() // SegmentedControl 추가 @@ -98,12 +54,8 @@ class MainViewController: UIViewController { segmentedControl.addTarget(self, action: #selector(segmentChanged), for: .valueChanged) return segmentedControl }() - // 밑줄 뷰 추가 - let underlineView = UIHelpers.createSeparatorLine(color: .black, height: 2) - // 메뉴 아이템 데이터 배열 let menuItems = [ - (image: "collection1", title: "크림 드로우"), (image: "collection2", title: "실시간 차트"), (image: "collection3", title: "남성 추천"), @@ -116,51 +68,63 @@ 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() - } + // 뭐 챌린지 .. + 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 challengeItems: [String] = ["challengeImage1", "challengeImage2", "challengeImage3" ] - private func setupView() { - view.backgroundColor = .white - // Just Dropped 데이터 배열 let justDroppedItems: [JustDroppedItem] = [ - 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: "즉시 구매가") + JustDroppedItem( + imageUrl: "https://static.nike.com/a/images/c_limit,w_592,f_auto/t_product_v1/v6vr4e25eoq1ncjxqcjq/NIKE+P-6000.png", + title: "Nike", + description: "Black shoes", + price: "139,000원", + purchaseInfo: "즉시 구매가" + ), + JustDroppedItem( + imageUrl: "https://static.nike.com/a/images/c_limit,w_592,f_auto/t_product_v1/mcdrf7uxgwnem8l0sv0q/NIKE+P-6000.png", + title: "Nike", + description: "sliver shoes", + price: "228,000원", + purchaseInfo: "즉시 구매가" + ), + JustDroppedItem( + imageUrl: "https://static.nike.com/a/images/c_limit,w_592,f_auto/t_product_v1/5a96e95f-90a8-4215-9692-c93eb3fe0e37/WMNS+AIR+FORCE+1+%2707.png", + title: "Nike", + description: "brown shoes", + price: "2,000,000원", + purchaseInfo: "즉시 구매가" + ), + JustDroppedItem( + imageUrl: "https://static.nike.com/a/images/c_limit,w_592,f_auto/t_product_v1/u_126ab356-44d8-4a06-89b4-fcdcc8df0245,c_scale,fl_relative,w_1.0,h_1.0,fl_layer_apply/32b0f17a-38ba-40fa-9de7-31c5bb1661e3/AIR+JORDAN+1+LOW.png", + title: "Nike", + description: "red and black shoes", + price: "2,000,000원", + purchaseInfo: "즉시 구매가" + ), + JustDroppedItem( + imageUrl: "https://static.nike.com/a/images/c_limit,w_592,f_auto/t_product_v1/b4d8abd2-187c-438d-bf06-522813827ebc/NIKE+DYNAMO+2+EASYON+LIL+%28PS%29.png", + title: "Nike", + description: "baby shoes", + price: "2,000,000원", + purchaseInfo: "즉시 구매가" + ) + ] - + + // 컬렉션 뷰 초기화 let collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical - layout.minimumLineSpacing = 9 - layout.minimumInteritemSpacing = 9 - layout.sectionInset = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16) + layout.minimumLineSpacing = 16 + layout.minimumInteritemSpacing = 8 + layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .white + collectionView.isScrollEnabled = false // 스크롤 비활성화 return collectionView }() @@ -179,144 +143,166 @@ class MainViewController: UIViewController { collectionView.backgroundColor = .white return collectionView }() + //밑에 구분선 하나 더 추가 + let newSeparatorLine = UIHelpers.createSeparatorLine() + + //챌린지어쩌고 + let challengeCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 10 + layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .white + return collectionView + }() 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(challengeCollectionView) + // 스크롤 뷰와 콘텐츠 뷰 설정 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) + contentView.addSubview(challengeCollectionView) + + + setupCollectionView() + setupLayout() } - private func setupLayout() { - + + // 레이아웃 설정 + func setupLayout() { + // 검색창 레이아웃 searchBar.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(8) make.leading.equalToSuperview().offset(16) make.trailing.equalTo(bellButton.snp.leading).offset(-8) - make.height.equalTo(44) + make.height.equalTo(36) } 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 - make.top.equalTo(searchBar.snp.bottom).offset(16) + make.top.equalTo(searchBar.snp.bottom).offset(12) make.leading.trailing.equalToSuperview().inset(16) - 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) + make.height.equalTo(32) } - - // 밑줄 초기 위치 설정 underlineView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(2) make.height.equalTo(2) - make.width.equalTo(segmentedControl.frame.width / CGFloat(segmentedControl.numberOfSegments)) + make.width.equalTo(UIScreen.main.bounds.width / CGFloat(segmentedControl.numberOfSegments)) make.leading.equalTo(segmentedControl.snp.leading) } - // 스크롤 뷰 레이아웃 - 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) + make.bottom.equalTo(challengeCollectionView.snp.bottom).offset(16) } - - // 배너 이미지 레이아웃 - + // 배너 이미지가 양옆으로 꽉 차게 설정 bannerImageView.snp.makeConstraints { make in make.top.equalTo(contentView).offset(16) - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(336) + make.leading.trailing.equalToSuperview() + make.height.equalTo(374) // 배너 높이 } - + // 메뉴 아이템 컬렉션 뷰 레이아웃 + let totalWidth = UIScreen.main.bounds.width - 32 // 좌우 여백 16씩 제거 + let spacing: CGFloat = 8 // 셀 간 간격 + let cellWidth = (totalWidth - spacing * 4) / 5 + collectionView.snp.makeConstraints { make in + make.top.equalTo(bannerImageView.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview() + make.height.equalTo((cellWidth + 20) * 2 + 16) // 두 줄의 높이 계산 + } - // 컬렉션 뷰 레이아웃 (메뉴 아이템) - collectionView.snp.remakeConstraints { make in - - make.top.equalTo(bannerImageView.snp.bottom).offset(16) + + separatorLine.snp.makeConstraints { make in + make.top.equalTo(collectionView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview() - make.height.equalTo(200) + make.height.equalTo(1) + } + + justDroppedTitleLabel.snp.makeConstraints { make in + make.top.equalTo(separatorLine.snp.bottom).offset(16) + make.leading.equalToSuperview().offset(16) + } + + justDroppedSubtitleLabel.snp.makeConstraints { make in + make.top.equalTo(justDroppedTitleLabel.snp.bottom).offset(4) + make.leading.equalTo(justDroppedTitleLabel) } - justDroppedCollectionView.snp.makeConstraints { make in - make.top.equalTo(collectionView.snp.bottom).offset(16) + make.top.equalTo(justDroppedSubtitleLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() - make.height.equalTo(200) - make.bottom.equalToSuperview().offset(-20) + make.height.equalTo(220) // Just Dropped 높이 } - } - - private func setupCollectionView() { - - - // 구분선과 텍스트 레이아웃 - separatorLine.snp.remakeConstraints { make in - make.top.equalTo(collectionView.snp.bottom).offset(30) + + newSeparatorLine.snp.makeConstraints { make in + make.top.equalTo(justDroppedCollectionView.snp.bottom).offset(24) make.leading.trailing.equalToSuperview() make.height.equalTo(1) } - - justDroppedTitleLabel.snp.remakeConstraints { make in - make.top.equalTo(separatorLine.snp.bottom).offset(20) + + mainLabel.snp.makeConstraints { make in + make.top.equalTo(newSeparatorLine.snp.bottom).offset(16) make.leading.equalToSuperview().offset(16) } - - justDroppedSubtitleLabel.snp.remakeConstraints { make in - make.top.equalTo(justDroppedTitleLabel.snp.bottom).offset(4) - make.leading.equalTo(justDroppedTitleLabel) + + hashtagLabel.snp.makeConstraints { make in + make.top.equalTo(mainLabel.snp.bottom).offset(8) + make.leading.equalTo(mainLabel) } - - // Just Dropped 컬렉션 뷰 레이아웃 - justDroppedCollectionView.snp.remakeConstraints { make in - make.top.equalTo(justDroppedSubtitleLabel.snp.bottom).offset(16) + + challengeCollectionView.snp.makeConstraints { make in + make.top.equalTo(hashtagLabel.snp.bottom).offset(16) make.leading.trailing.equalToSuperview() - make.height.equalTo(250) - make.bottom.equalToSuperview().offset(-30) + make.height.equalTo(120) // 챌린지 컬렉션 뷰 높이 + make.bottom.equalToSuperview().offset(-16) } - } - + + // MARK: - UICollectionView 설정 메서드 func setupCollectionView() { - collectionView.delegate = self collectionView.dataSource = self collectionView.register(MainMenuCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell") @@ -325,34 +311,16 @@ class MainViewController: UIViewController { justDroppedCollectionView.dataSource = self 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 setupJustDroppedCollectionView() { + justDroppedCollectionView.delegate = self + justDroppedCollectionView.dataSource = self + justDroppedCollectionView.register(JustDroppedCollectionViewCell.self, forCellWithReuseIdentifier: "JustDroppedCell") } + func setupChallengeCollectionView() { + challengeCollectionView.delegate = self + challengeCollectionView.dataSource = self + challengeCollectionView.register(ChallengeCollectionViewCell.self, forCellWithReuseIdentifier: ChallengeCollectionViewCell.identifier) } - - 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: - 세그먼트 선택 변경 시 호출 @@ -422,6 +390,7 @@ extension MainViewController: UICollectionViewDelegate, UICollectionViewDataSour make.height.equalTo(200) make.bottom.equalToSuperview().offset(-20) } + default: let label = UILabel() label.textAlignment = .center @@ -431,36 +400,71 @@ 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 { -func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return collectionView == self.collectionView ? menuItems.count : justDroppedItems.count -} + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if collectionView == self.collectionView { + return menuItems.count + } else if collectionView == self.justDroppedCollectionView { + return justDroppedItems.count + } else if collectionView == self.challengeCollectionView { + return challengeItems.count + } + return 0 + } -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 = justDroppedItems[indexPath.item] - cell.configure(with: justDroppedItem) - return cell + 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 if collectionView == self.justDroppedCollectionView { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "JustDroppedCell", for: indexPath) as! JustDroppedCollectionViewCell + let justDroppedItem = justDroppedItems[indexPath.item] + cell.configure(with: justDroppedItem) + return cell + } else if collectionView == self.challengeCollectionView { + // 여기에서 셀을 설정합니다. + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChallengeCollectionViewCell.identifier, for: indexPath) as! ChallengeCollectionViewCell + let imageName = challengeItems[indexPath.item] + let title = "챌린지 \(indexPath.item + 1)" // 적절한 제목 설정 + cell.configure(imageName: imageName, title: title) // 이미지와 제목 전달 + return cell + } + return UICollectionViewCell() } -} + // 셀 크기 설정 메서드 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - if collectionView == self.justDroppedCollectionView { - return CGSize(width: 150, height: 230) + if collectionView == self.collectionView { + // 한 줄에 5개씩 표시 + let totalWidth = UIScreen.main.bounds.width - 32 // 좌우 여백 16씩 제거 + let spacing: CGFloat = 8 // 셀 간 간격 + let cellWidth = (totalWidth - spacing * 4) / 5 // 셀 5개, 간격 4개 + return CGSize(width: cellWidth, height: cellWidth + 20) // 높이는 셀 너비에 추가 공간을 더함 + } else if collectionView == self.justDroppedCollectionView { + return CGSize(width: 150, height: 230) // Just Dropped 컬렉션 뷰 + } else if collectionView == self.challengeCollectionView { + // 챌린지 셀 크기 설정 + let cellWidth: CGFloat = 124 + let imageHeight: CGFloat = 165 + let titleHeight: CGFloat = 16 + let spacing: CGFloat = 8 + let cellHeight = imageHeight + titleHeight + spacing // 이미지 높이 + 텍스트 높이 + 간격 + return CGSize(width: cellWidth, height: cellHeight) } 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 + } +}