diff --git a/Helfy.xcodeproj/project.pbxproj b/Helfy.xcodeproj/project.pbxproj index bdc7f06..badb720 100644 --- a/Helfy.xcodeproj/project.pbxproj +++ b/Helfy.xcodeproj/project.pbxproj @@ -7,8 +7,22 @@ objects = { /* Begin PBXBuildFile section */ - 26EA7E4A2AD153C800FFE7ED /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EA7E492AD153C800FFE7ED /* ReportView.swift */; }; + 26094C022AD87577000AAD3B /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26094C012AD87577000AAD3B /* NavigationController.swift */; }; + 26094C042AD87943000AAD3B /* WriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26094C032AD87943000AAD3B /* WriteView.swift */; }; + 26094C072AD87971000AAD3B /* WriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26094C062AD87971000AAD3B /* WriteViewController.swift */; }; + 26094C0A2ADC3E9E000AAD3B /* WriteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26094C092ADC3E9E000AAD3B /* WriteModel.swift */; }; + 263A950C2B301B460027E668 /* CommunityViewControllter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263A950B2B301B460027E668 /* CommunityViewControllter.swift */; }; + 263A950E2B301B5A0027E668 /* CommunityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263A950D2B301B5A0027E668 /* CommunityView.swift */; }; + 263A95192B301E330027E668 /* CommunityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263A95182B301E330027E668 /* CommunityModel.swift */; }; + 268D13DD2B70E7CA00DBE998 /* CommunityApiHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268D13DC2B70E7CA00DBE998 /* CommunityApiHandler.swift */; }; + 268D13DF2B70E88200DBE998 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268D13DE2B70E88200DBE998 /* ImageLoader.swift */; }; 26EA7E4C2AD1540300FFE7ED /* ReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EA7E4B2AD1540300FFE7ED /* ReportViewController.swift */; }; + 26EFAF642B469C9B00D3A781 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF632B469C9B00D3A781 /* FirebaseAnalytics */; }; + 26EFAF662B469C9B00D3A781 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF652B469C9B00D3A781 /* FirebaseAnalyticsOnDeviceConversion */; }; + 26EFAF682B469C9B00D3A781 /* FirebaseAnalyticsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF672B469C9B00D3A781 /* FirebaseAnalyticsSwift */; }; + 26EFAF6A2B469C9B00D3A781 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF692B469C9B00D3A781 /* FirebaseAnalyticsWithoutAdIdSupport */; }; + 26EFAF6C2B469C9B00D3A781 /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF6B2B469C9B00D3A781 /* FirebaseAppCheck */; }; + 26EFAF6E2B469C9B00D3A781 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 26EFAF6D2B469C9B00D3A781 /* FirebaseAuth */; }; 2A2E02E32AD54D970026C495 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2E02E22AD54D970026C495 /* LoginViewController.swift */; }; 2A2E02E62AD5510F0026C495 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2A2E02E52AD5510F0026C495 /* GoogleService-Info.plist */; }; 2A2E02E82AD5551C0026C495 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 2A2E02E72AD5551C0026C495 /* GoogleSignIn */; }; @@ -24,11 +38,6 @@ 2AFC98C52ACD4A3500AB349D /* HelfyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFC98C42ACD4A3500AB349D /* HelfyTests.swift */; }; 2AFC98CF2ACD4A3500AB349D /* HelfyUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFC98CE2ACD4A3500AB349D /* HelfyUITests.swift */; }; 2AFC98D12ACD4A3500AB349D /* HelfyUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFC98D02ACD4A3500AB349D /* HelfyUITestsLaunchTests.swift */; }; - 2AFC98E12ACD4DB700AB349D /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 2AFC98E02ACD4DB700AB349D /* FirebaseAnalytics */; }; - 2AFC98E32ACD4DB700AB349D /* FirebaseAnalyticsOnDeviceConversion in Frameworks */ = {isa = PBXBuildFile; productRef = 2AFC98E22ACD4DB700AB349D /* FirebaseAnalyticsOnDeviceConversion */; }; - 2AFC98E52ACD4DB700AB349D /* FirebaseAnalyticsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2AFC98E42ACD4DB700AB349D /* FirebaseAnalyticsSwift */; }; - 2AFC98E72ACD4DB700AB349D /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 2AFC98E62ACD4DB700AB349D /* FirebaseAnalyticsWithoutAdIdSupport */; }; - 2AFC98E92ACD4DB700AB349D /* FirebaseAppCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 2AFC98E82ACD4DB700AB349D /* FirebaseAppCheck */; }; C1735B3C2AD03F61002FFB77 /* QuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1735B3B2AD03F61002FFB77 /* QuizViewController.swift */; }; C1735B3E2AD042E5002FFB77 /* RankingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1735B3D2AD042E5002FFB77 /* RankingView.swift */; }; C1735B422AD04532002FFB77 /* RankingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1735B412AD04532002FFB77 /* RankingModel.swift */; }; @@ -52,7 +61,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 26EA7E492AD153C800FFE7ED /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = ""; }; + 26094C012AD87577000AAD3B /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; + 26094C032AD87943000AAD3B /* WriteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteView.swift; sourceTree = ""; }; + 26094C062AD87971000AAD3B /* WriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteViewController.swift; sourceTree = ""; }; + 26094C092ADC3E9E000AAD3B /* WriteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteModel.swift; sourceTree = ""; }; + 263A950B2B301B460027E668 /* CommunityViewControllter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunityViewControllter.swift; sourceTree = ""; }; + 263A950D2B301B5A0027E668 /* CommunityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommunityView.swift; sourceTree = ""; }; + 263A95182B301E330027E668 /* CommunityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityModel.swift; sourceTree = ""; }; + 268D13DC2B70E7CA00DBE998 /* CommunityApiHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityApiHandler.swift; sourceTree = ""; }; + 268D13DE2B70E88200DBE998 /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; 26EA7E4B2AD1540300FFE7ED /* ReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewController.swift; sourceTree = ""; }; 2A2E02E22AD54D970026C495 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 2A2E02E52AD5510F0026C495 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -81,13 +98,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2AFC98E52ACD4DB700AB349D /* FirebaseAnalyticsSwift in Frameworks */, - 2AFC98E12ACD4DB700AB349D /* FirebaseAnalytics in Frameworks */, 2A2E02EA2AD5551C0026C495 /* GoogleSignInSwift in Frameworks */, - 2AFC98E32ACD4DB700AB349D /* FirebaseAnalyticsOnDeviceConversion in Frameworks */, - 2AFC98E92ACD4DB700AB349D /* FirebaseAppCheck in Frameworks */, - 2AFC98E72ACD4DB700AB349D /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, + 26EFAF6A2B469C9B00D3A781 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, 2A2E02E82AD5551C0026C495 /* GoogleSignIn in Frameworks */, + 26EFAF642B469C9B00D3A781 /* FirebaseAnalytics in Frameworks */, + 26EFAF6C2B469C9B00D3A781 /* FirebaseAppCheck in Frameworks */, + 26EFAF662B469C9B00D3A781 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */, + 26EFAF682B469C9B00D3A781 /* FirebaseAnalyticsSwift in Frameworks */, + 26EFAF6E2B469C9B00D3A781 /* FirebaseAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,10 +126,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 268D13DB2B70E6AA00DBE998 /* API */ = { + isa = PBXGroup; + children = ( + 268D13DC2B70E7CA00DBE998 /* CommunityApiHandler.swift */, + 268D13DE2B70E88200DBE998 /* ImageLoader.swift */, + ); + path = API; + sourceTree = ""; + }; 2A99A2632AD681820090155C /* Recovered References */ = { isa = PBXGroup; children = ( - 26EA7E492AD153C800FFE7ED /* ReportView.swift */, ); name = "Recovered References"; sourceTree = ""; @@ -119,9 +145,12 @@ 2AB9D1422ACFF42B00686F83 /* Controller */ = { isa = PBXGroup; children = ( + 263A950B2B301B460027E668 /* CommunityViewControllter.swift */, C1735B3B2AD03F61002FFB77 /* QuizViewController.swift */, 26EA7E4B2AD1540300FFE7ED /* ReportViewController.swift */, 2A2E02E22AD54D970026C495 /* LoginViewController.swift */, + 26094C012AD87577000AAD3B /* NavigationController.swift */, + 26094C062AD87971000AAD3B /* WriteViewController.swift */, ); path = Controller; sourceTree = ""; @@ -150,6 +179,7 @@ 2AFC98AC2ACD4A2100AB349D /* Helfy */ = { isa = PBXGroup; children = ( + 268D13DB2B70E6AA00DBE998 /* API */, C1735B342ACFFB57002FFB77 /* View */, C1735B332ACFFB46002FFB77 /* Model */, 2AB9D1422ACFF42B00686F83 /* Controller */, @@ -185,6 +215,8 @@ children = ( C1735B412AD04532002FFB77 /* RankingModel.swift */, 2A99A2662AD682050090155C /* QuizeModel.swift */, + 26094C092ADC3E9E000AAD3B /* WriteModel.swift */, + 263A95182B301E330027E668 /* CommunityModel.swift */, ); path = Model; sourceTree = ""; @@ -196,6 +228,8 @@ C1735B3D2AD042E5002FFB77 /* RankingView.swift */, 2A99A2682AD6882F0090155C /* ReportView.swift */, 2A99A26A2AD688660090155C /* TextView.swift */, + 26094C032AD87943000AAD3B /* WriteView.swift */, + 263A950D2B301B5A0027E668 /* CommunityView.swift */, ); path = View; sourceTree = ""; @@ -217,13 +251,14 @@ ); name = Helfy; packageProductDependencies = ( - 2AFC98E02ACD4DB700AB349D /* FirebaseAnalytics */, - 2AFC98E22ACD4DB700AB349D /* FirebaseAnalyticsOnDeviceConversion */, - 2AFC98E42ACD4DB700AB349D /* FirebaseAnalyticsSwift */, - 2AFC98E62ACD4DB700AB349D /* FirebaseAnalyticsWithoutAdIdSupport */, - 2AFC98E82ACD4DB700AB349D /* FirebaseAppCheck */, 2A2E02E72AD5551C0026C495 /* GoogleSignIn */, 2A2E02E92AD5551C0026C495 /* GoogleSignInSwift */, + 26EFAF632B469C9B00D3A781 /* FirebaseAnalytics */, + 26EFAF652B469C9B00D3A781 /* FirebaseAnalyticsOnDeviceConversion */, + 26EFAF672B469C9B00D3A781 /* FirebaseAnalyticsSwift */, + 26EFAF692B469C9B00D3A781 /* FirebaseAnalyticsWithoutAdIdSupport */, + 26EFAF6B2B469C9B00D3A781 /* FirebaseAppCheck */, + 26EFAF6D2B469C9B00D3A781 /* FirebaseAuth */, ); productName = Helfy; productReference = 2AFC98AA2ACD4A2100AB349D /* Helfy.app */; @@ -298,8 +333,8 @@ ); mainGroup = 2AFC98A12ACD4A2100AB349D; packageReferences = ( - 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, + 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 2AFC98AB2ACD4A2100AB349D /* Products */; projectDirPath = ""; @@ -344,17 +379,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 263A950C2B301B460027E668 /* CommunityViewControllter.swift in Sources */, C1735B422AD04532002FFB77 /* RankingModel.swift in Sources */, + 268D13DF2B70E88200DBE998 /* ImageLoader.swift in Sources */, 2A99A2652AD681DB0090155C /* QuizView.swift in Sources */, + 26094C072AD87971000AAD3B /* WriteViewController.swift in Sources */, 2A99A2672AD682050090155C /* QuizeModel.swift in Sources */, C1735B3E2AD042E5002FFB77 /* RankingView.swift in Sources */, 2A2E02E32AD54D970026C495 /* LoginViewController.swift in Sources */, + 26094C0A2ADC3E9E000AAD3B /* WriteModel.swift in Sources */, + 26094C042AD87943000AAD3B /* WriteView.swift in Sources */, 2A99A2692AD6882F0090155C /* ReportView.swift in Sources */, 2AFC98AE2ACD4A2100AB349D /* AppDelegate.swift in Sources */, - 26EA7E4A2AD153C800FFE7ED /* ReportView.swift in Sources */, + 26094C022AD87577000AAD3B /* NavigationController.swift in Sources */, 26EA7E4C2AD1540300FFE7ED /* ReportViewController.swift in Sources */, + 263A950E2B301B5A0027E668 /* CommunityView.swift in Sources */, 2A99A26B2AD688660090155C /* TextView.swift in Sources */, + 268D13DD2B70E7CA00DBE998 /* CommunityApiHandler.swift in Sources */, C1735B3C2AD03F61002FFB77 /* QuizViewController.swift in Sources */, + 263A95192B301E330027E668 /* CommunityModel.swift in Sources */, 2AFC98B02ACD4A2100AB349D /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -531,7 +574,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = M8XKVU2476; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Helfy/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -558,7 +601,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = M8XKVU2476; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Helfy/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -696,60 +739,65 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { + 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.0.0; + minimumVersion = 10.19.1; }; }; - 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; + repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 10.16.0; + minimumVersion = 7.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 2A2E02E72AD5551C0026C495 /* GoogleSignIn */ = { - isa = XCSwiftPackageProductDependency; - package = 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; - productName = GoogleSignIn; - }; - 2A2E02E92AD5551C0026C495 /* GoogleSignInSwift */ = { + 26EFAF632B469C9B00D3A781 /* FirebaseAnalytics */ = { isa = XCSwiftPackageProductDependency; - package = 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; - productName = GoogleSignInSwift; - }; - 2AFC98E02ACD4DB700AB349D /* FirebaseAnalytics */ = { - isa = XCSwiftPackageProductDependency; - package = 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalytics; }; - 2AFC98E22ACD4DB700AB349D /* FirebaseAnalyticsOnDeviceConversion */ = { + 26EFAF652B469C9B00D3A781 /* FirebaseAnalyticsOnDeviceConversion */ = { isa = XCSwiftPackageProductDependency; - package = 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalyticsOnDeviceConversion; }; - 2AFC98E42ACD4DB700AB349D /* FirebaseAnalyticsSwift */ = { + 26EFAF672B469C9B00D3A781 /* FirebaseAnalyticsSwift */ = { isa = XCSwiftPackageProductDependency; - package = 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalyticsSwift; }; - 2AFC98E62ACD4DB700AB349D /* FirebaseAnalyticsWithoutAdIdSupport */ = { + 26EFAF692B469C9B00D3A781 /* FirebaseAnalyticsWithoutAdIdSupport */ = { isa = XCSwiftPackageProductDependency; - package = 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAnalyticsWithoutAdIdSupport; }; - 2AFC98E82ACD4DB700AB349D /* FirebaseAppCheck */ = { + 26EFAF6B2B469C9B00D3A781 /* FirebaseAppCheck */ = { isa = XCSwiftPackageProductDependency; - package = 2AFC98DF2ACD4DB700AB349D /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseAppCheck; }; + 26EFAF6D2B469C9B00D3A781 /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 26EFAF622B469C9B00D3A781 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; + }; + 2A2E02E72AD5551C0026C495 /* GoogleSignIn */ = { + isa = XCSwiftPackageProductDependency; + package = 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignIn; + }; + 2A2E02E92AD5551C0026C495 /* GoogleSignInSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 2A2E02E42AD5505E0026C495 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; + productName = GoogleSignInSwift; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2AFC98A22ACD4A2100AB349D /* Project object */; diff --git a/Helfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Helfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 56e318c..acb1927 100644 --- a/Helfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Helfy.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "1.2022062300.0" } }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "3e464dad87dad2d29bb29a97836789bf0f8f67d2", + "version" : "10.18.1" + } + }, { "identity" : "appauth-ios", "kind" : "remoteSourceControl", @@ -23,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "837d4af6ead57cec1fc38007892500d3139c7556", - "version" : "10.16.0" + "revision" : "b880ec8ec927a838c51c12862c6222c30d7097d7", + "version" : "10.20.0" } }, { @@ -32,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "56f681586ff006a7982b53dc94082eea31971acf", - "version" : "10.16.0" + "revision" : "ceec9f28dea12b7cf3dabf18b5ed7621c88fd4aa", + "version" : "10.20.0" } }, { @@ -41,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { - "revision" : "aae45a320fd0d11811820335b1eabc8753902a40", - "version" : "9.2.5" + "revision" : "a732a4b47f59e4f725a2ea10f0c77e93a7131117", + "version" : "9.3.0" } }, { @@ -59,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "c38ce365d77b04a9a300c31061c5227589e5597b", - "version" : "7.11.5" + "revision" : "bc27fad73504f3d4af235de451f02ee22586ebd3", + "version" : "7.12.1" } }, { diff --git a/Helfy/API/CommunityApiHandler.swift b/Helfy/API/CommunityApiHandler.swift new file mode 100644 index 0000000..f1b6e01 --- /dev/null +++ b/Helfy/API/CommunityApiHandler.swift @@ -0,0 +1,173 @@ +// +// CommunityApiHandler.swift +// Helfy +// +// Created by YEOMI on 1/23/24. +// + + +import Foundation +/* +let token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjY5NjI5NzU5NmJiNWQ4N2NjOTc2Y2E2YmY0Mzc3NGE3YWE5OTMxMjkiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoi7KCE7ZmN7JiBIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FDZzhvY0tEZVRjNVNWR1JsdlRPbjRkdFVHb0N4Qzk3Y01KSmR3UlhoUUV6Skpkej1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9oZWxmeS1mYmUwNSIsImF1ZCI6ImhlbGZ5LWZiZTA1IiwiYXV0aF90aW1lIjoxNzA2NjA0ODE0LCJ1c2VyX2lkIjoiQUZ1OEhGemlPMFJMQnRsbXhmSjRDZlViS0VIMyIsInN1YiI6IkFGdThIRnppTzBSTEJ0bG14Zko0Q2ZVYktFSDMiLCJpYXQiOjE3MDY2MDQ4MTQsImV4cCI6MTcwNjYwODQxNCwiZW1haWwiOiJqdW5ob25neW91bmc5OTA5MTZAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTA0OTU0NDYyNzUxNjA0MzY5MjAiXSwiZW1haWwiOlsianVuaG9uZ3lvdW5nOTkwOTE2QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.aPe-ypSrnEmajYms2TllUPqxOoE1uwGFwMURjrG9w4FI_HnXhGJDAVkBYRWiir2C3JpytAE0tnfE9PdqBu-7INHrDljaA-wun3SNCm1rIfts6eLzbCB4DiJpfrQ0SoDyDmTaEiqALM2dT1VisFHXlQUoG2WpZA6M0E0cm1a60nl0cIuUnC__ItPOb—27-_y6BTemoXLKa_MwIlLEOz3pOt05DFEs1NCwUATlYAdRK0fVL5XWdELUa5kau68yIsQHCwHm4F88UxWeS-qsPhwgWGwFfXgWvvvGQ1kPCbSSrVv49moZErJKeeyDRiUypfNtxArMmT2NEnBdsHqBd4bjA" +*/ + +class APIHandler { + let token = UserDefaults.standard.string(forKey: "GoogleToken") ?? "" + func getPost(page: Int, size: Int, sort: [String], completion: @escaping (Result) -> Void) { + + print("getpost") + // API 요청을 보내고 응답을 처리하는 코드 + guard let url = URL(string: "https://helfy-server.duckdns.org/api/v1/posts") else { + print("유효하지 않은 URL입니다.") + return + } + print("token 값 1: \(self.token)") + + + var requestURL = URLRequest(url: url) + requestURL.httpMethod = "GET" + requestURL.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + URLSession.shared.dataTask(with: requestURL) { data, response, error in + guard error == nil else { + print("네트워크 오류: \(error!.localizedDescription)") + completion(.failure(error!)) + return + } + + guard let data = data, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { + print("데이터를 받아오는 데 문제가 발생했습니다.") + completion(.failure(NSError(domain: "Network Error", code: 0, userInfo: nil))) + return + } + + // 데이터 출력 + if let responseDataString = String(data: data, encoding: .utf8) { + print("Response Data: \(responseDataString)") + } + + do { + let decodedData = try JSONDecoder().decode(GetPostResponse.self, from: data) + completion(.success(decodedData)) + } catch { + print("데이터 디코딩 오류: \(error.localizedDescription)") + completion(.failure(error)) + } + }.resume() + } + + + func createPost(postData: CreatePost, completion: @escaping (Result) -> Void) { + print("createpost") + guard let url = URL(string: "https://helfy-server.duckdns.org/api/v1/posts") else { + print("Invalid URL") + return + } + print("token 값 2: \(self.token)") + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + + do { + let encoder = JSONEncoder() + request.httpBody = try encoder.encode(postData) + } catch { + print("Error encoding post data: \(error.localizedDescription)") + completion(.failure(error)) + return + } + + URLSession.shared.dataTask(with: request) { data, response, error in + guard let data = data, let response = response as? HTTPURLResponse, error == nil else { + print("Error: \(error?.localizedDescription ?? "Unknown error")") + completion(.failure(error ?? NSError(domain: "Unknown error", code: 0, userInfo: nil))) + return + } + + guard response.statusCode == 200 else { + print("HTTP Status Code: \(response.statusCode)") + completion(.failure(NSError(domain: "Server Error", code: response.statusCode, userInfo: nil))) + return + } + + // 데이터 출력 + if let responseDataString = String(data: data, encoding: .utf8) { + print("Response Data: \(responseDataString)") + } + + do { + let decoder = JSONDecoder() + let createdPost = try decoder.decode(CreatePostResponse.self, from: data) + completion(.success(createdPost)) + } catch { + print("Error decoding response data: \(error.localizedDescription)") + completion(.failure(error)) + } + }.resume() + } + func updatePost(postID: Int, updatedData: [String: Any]) { + guard let url = URL(string: "https://yourapi.com/posts/\(postID)") else { return } + + var request = URLRequest(url: url) + request.httpMethod = "PUT" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: updatedData, options: []) + } catch { + print("Error encoding updated data: \(error.localizedDescription)") + return + } + + URLSession.shared.dataTask(with: request) { data, response, error in + // Handle response from the server + }.resume() + } + + func deletePost(postID: Int) { + guard let url = URL(string: "https://yourapi.com/posts/\(postID)") else { return } + + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + + URLSession.shared.dataTask(with: request) { data, response, error in + // Handle response from the server + }.resume() + } + func ImageUploadFunction(_ imageData: Data, completion: @escaping (Result) -> Void) { + print("imageupload") + guard let url = URL(string: "https://yourapi.com/uploadImage") else { + completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil))) + return + } + + var request = URLRequest(url: url) + + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + // 이미지 데이터를 HTTP body에 추가 + request.httpBody = imageData + + URLSession.shared.dataTask(with: request) { data, response, error in + guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil else { + completion(.failure(error ?? NSError(domain: "Unknown Error", code: 0, userInfo: nil))) + return + } + + guard (200...299).contains(httpResponse.statusCode) else { + completion(.failure(NSError(domain: "Server Error", code: httpResponse.statusCode, userInfo: nil))) + return + } + + if let imageURL = String(data: data, encoding: .utf8) { + completion(.success(imageURL)) + } else { + completion(.failure(NSError(domain: "Image URL Error", code: 0, userInfo: nil))) + } + }.resume() + } + +} diff --git a/Helfy/API/ImageLoader.swift b/Helfy/API/ImageLoader.swift new file mode 100644 index 0000000..0ad9bf0 --- /dev/null +++ b/Helfy/API/ImageLoader.swift @@ -0,0 +1,56 @@ +// +// ImageLoader.swift +// Helfy +// +// Created by YEOMI on 1/23/24. +// + +import Foundation +import UIKit + +class ImageLoader { + private static let imageCache = NSCache() + + static func loadImage(url: String, completion: @escaping (UIImage?) -> Void) { + // url이 비어있다면 nil처리 + if url.isEmpty { + completion(nil) + return + } + + // URL 형식으로 변환 + let realURL = URL(string: url)! + + // 캐시에 있다면 바로 반환 + if let image = imageCache.object(forKey: realURL.lastPathComponent as NSString) { + print("캐시에 존재 😎") + // UI는 메인 쓰레드에서 진행 + DispatchQueue.main.async { + completion(image) + } + return + } + + // 캐시에 없다면 + DispatchQueue.global(qos: .background).async { + print("캐시에 없음 🥲") + // 데이터 타입 변환 + if let data = try? Data(contentsOf: realURL) { + // 이미지 변환 + let image = UIImage(data: data)! + // cache에 추가 + self.imageCache.setObject(image, forKey: realURL.lastPathComponent as NSString) + // UI는 메인 쓰레드에서 진행 + DispatchQueue.main.async { + completion(image) + } + } else { + DispatchQueue.main.async { + completion(nil) + } + } + } + + } +} + diff --git a/Helfy/AppDelegate.swift b/Helfy/AppDelegate.swift index 3fda5de..730e952 100644 --- a/Helfy/AppDelegate.swift +++ b/Helfy/AppDelegate.swift @@ -51,7 +51,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + } diff --git a/Helfy/Assets.xcassets/img1.imageset/Contents.json b/Helfy/Assets.xcassets/img1.imageset/Contents.json new file mode 100644 index 0000000..b75f72a --- /dev/null +++ b/Helfy/Assets.xcassets/img1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "img1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Helfy/Assets.xcassets/img1.imageset/img1.png b/Helfy/Assets.xcassets/img1.imageset/img1.png new file mode 100644 index 0000000..33e8e82 Binary files /dev/null and b/Helfy/Assets.xcassets/img1.imageset/img1.png differ diff --git a/Helfy/Assets.xcassets/img2.imageset/Contents.json b/Helfy/Assets.xcassets/img2.imageset/Contents.json new file mode 100644 index 0000000..e83c564 --- /dev/null +++ b/Helfy/Assets.xcassets/img2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "img2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Helfy/Assets.xcassets/img2.imageset/img2.png b/Helfy/Assets.xcassets/img2.imageset/img2.png new file mode 100644 index 0000000..be1985d Binary files /dev/null and b/Helfy/Assets.xcassets/img2.imageset/img2.png differ diff --git a/Helfy/Assets.xcassets/img3.imageset/Contents.json b/Helfy/Assets.xcassets/img3.imageset/Contents.json new file mode 100644 index 0000000..0a9a17f --- /dev/null +++ b/Helfy/Assets.xcassets/img3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "img3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Helfy/Assets.xcassets/img3.imageset/img3.png b/Helfy/Assets.xcassets/img3.imageset/img3.png new file mode 100644 index 0000000..9c2a6e0 Binary files /dev/null and b/Helfy/Assets.xcassets/img3.imageset/img3.png differ diff --git a/Helfy/Controller/BannerViewController.swift b/Helfy/Controller/BannerViewController.swift new file mode 100644 index 0000000..317dc58 --- /dev/null +++ b/Helfy/Controller/BannerViewController.swift @@ -0,0 +1,8 @@ +// +// BannerViewController.swift +// Helfy +// +// Created by YEOMI on 11/18/23. +// + +import Foundation diff --git a/Helfy/Controller/CommunityViewControllter.swift b/Helfy/Controller/CommunityViewControllter.swift new file mode 100644 index 0000000..7780de7 --- /dev/null +++ b/Helfy/Controller/CommunityViewControllter.swift @@ -0,0 +1,166 @@ +// +// CommunityViewControllter.swift +// Helfy +// +// Created by YEOMI on 11/13/23. +// + +import UIKit + +class CommunityViewController: UIViewController { + var communityView: CommunityView! + let apiHandler = APIHandler() + + var communityPosts: [PostContent] = [] // 게시글 데이터를 저장할 배열 + + //데이터 모델 객체 + var postData: CreatePost?{ + didSet { + print("data fetch completed") + } + } + var CreateResponseData: CreatePostResponse?{ + didSet { + print("data fetch completed") + } + } + var ImageData: Image?{ + didSet { + print("data fetch completed") + } + } + var getData: GetPost?{ + didSet { + print("data fetch completed") + } + } + var postResponseData: GetPostResponse?{ + didSet { + print("data fetch completed") + } + } + var postContentData: PostContent?{ + didSet { + print("data fetch completed") + } + } + var postImageData: PostImage?{ + didSet { + print("data fetch completed") + } + } + var postPageableData: PostPageable?{ + didSet { + print("data fetch completed") + } + } + var postSortData: PostSort?{ + didSet { + print("data fetch completed") + } + } + + + + + override func viewDidLoad() { + super.viewDidLoad() + + + //setLayout() + + // 뷰 생성 및 추가 + communityView = CommunityView(frame: self.view.bounds) + self.view.addSubview(communityView) + + // 네비게이션 바 설정 + setupNavigationBar() + + + // 모든 버튼에 대해 이벤트 핸들러 설정 + for i in 0.. UIBarButtonItem { + let button = UIButton(type:.system) + button.setImage(UIImage(systemName:imageName), for:.normal) + button.addTarget(self, action:action, for:.touchUpInside) + button.tintColor = .black + button.imageView?.contentMode = .scaleAspectFit + + button.widthAnchor.constraint(equalToConstant: 30).isActive = true + button.heightAnchor.constraint(equalToConstant: 30).isActive = true + + + + let label = UILabel() + label.text=text + label.font=UIFont(name:"Helvetica-Bold", size :8.5) + + + let stackView=UIStackView(arrangedSubviews:[button,label]) + stackView.spacing = 5 + stackView.axis = .vertical + + return UIBarButtonItem(customView:stackView) + } + + + @objc private func firstButtonTapped() { + let writeViewController = WriteViewController() + navigationController?.pushViewController(writeViewController, animated: true) + } + + @objc private func secondButtonTapped() { + let reportViewController = ReportViewController() + navigationController?.pushViewController(reportViewController, animated: true) + } + + + +} + diff --git a/Helfy/Controller/NavigationController.swift b/Helfy/Controller/NavigationController.swift new file mode 100644 index 0000000..61deae9 --- /dev/null +++ b/Helfy/Controller/NavigationController.swift @@ -0,0 +1,81 @@ + +// +// NavigationController.swift +// Helfy +// +// Created by YEOMI on 10/13/23. +// +import UIKit + +class NavigationController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // View의 배경색 설정 + view.backgroundColor = .white + + // Navigation Bar 설정 + setupNavigationBar() + } + + private func setupNavigationBar() { + + // 첫 번째 버튼 생성 (WriteView로 이동) + let firstButton = createCustomBarButton(imageName: "highlighter", text: " 글쓰기", action: #selector(firstButtonTapped)) + + + // 두 번째 버튼 생성 (ReportView로 이동) + let secondButton = createCustomBarButton(imageName: "lightbulb.min.badge.exclamationmark", text: "제보하기", action: #selector(secondButtonTapped)) + + // 오른쪽 아이템으로 추가 + navigationItem.rightBarButtonItems = [secondButton ,firstButton] + + //네비게이션바 community + let titleLabel = UILabel() + titleLabel.text = "Community" + + titleLabel.font = UIFont.systemFont(ofSize: 30, weight:.semibold) // 폰트 크기와 굵기 조정 + + titleLabel.textAlignment = .left // 왼쪽 정렬 + navigationItem.leftBarButtonItem=UIBarButtonItem(customView:titleLabel) + + } + + private func createCustomBarButton(imageName: String, text:String, action: Selector) -> UIBarButtonItem { + let button = UIButton(type:.system) + button.setImage(UIImage(systemName:imageName), for:.normal) + button.addTarget(self, action:action, for:.touchUpInside) + button.tintColor = .black + button.imageView?.contentMode = .scaleAspectFit + + button.widthAnchor.constraint(equalToConstant: 30).isActive = true + button.heightAnchor.constraint(equalToConstant: 30).isActive = true + + + + let label = UILabel() + label.text=text + label.font=UIFont(name:"Helvetica-Bold", size :8.5) + + + let stackView=UIStackView(arrangedSubviews:[button,label]) + stackView.spacing = 5 + stackView.axis = .vertical + + return UIBarButtonItem(customView:stackView) + } + + + @objc private func firstButtonTapped() { + let writeViewController = WriteViewController() + navigationController?.pushViewController(writeViewController, animated: true) + } + + @objc private func secondButtonTapped() { + let reportViewController = ReportViewController() + navigationController?.pushViewController(reportViewController, animated: true) + } + + + } diff --git a/Helfy/Controller/SearchViewController.swift b/Helfy/Controller/SearchViewController.swift new file mode 100644 index 0000000..ece6c8a --- /dev/null +++ b/Helfy/Controller/SearchViewController.swift @@ -0,0 +1,8 @@ +// +// SearchViewController.swift +// Helfy +// +// Created by YEOMI on 11/21/23. +// + +import Foundation diff --git a/Helfy/Controller/ViewController.swift b/Helfy/Controller/ViewController.swift deleted file mode 100644 index 484b004..0000000 --- a/Helfy/Controller/ViewController.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// ViewController.swift -// Helfy -// -// Created by 윤성은 on 2023/10/06. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } - -} diff --git a/Helfy/Controller/WriteViewController.swift b/Helfy/Controller/WriteViewController.swift new file mode 100644 index 0000000..ebe8a4a --- /dev/null +++ b/Helfy/Controller/WriteViewController.swift @@ -0,0 +1,323 @@ +// +// WriteViewController.swift +// Helfy +// +// Created by YEOMI on 10/13/23. +// +import UIKit +//데이터 모델 객체 +var postData: CreatePost?{ + didSet { + print("data fetch completed") + } +} +var CreateResponseData: CreatePostResponse?{ + didSet { + print("data fetch completed") + } +} +var ImageData: Image?{ + didSet { + print("data fetch completed") + } +} +var getData: GetPost?{ + didSet { + print("data fetch completed") + } +} +var postResponseData: GetPostResponse?{ + didSet { + print("data fetch completed") + } +} +var postContentData: PostContent?{ + didSet { + print("data fetch completed") + } +} +var postImageData: PostImage?{ + didSet { + print("data fetch completed") + } +} +var postPageableData: PostPageable?{ + didSet { + print("data fetch completed") + } +} +var postSortData: PostSort?{ + didSet { + print("data fetch completed") + } +} + +// 게시물에 대한 정보를 저장하는 클래스 +class Post { + var title: String = "" // 게시물의 제목 + var image: UIImage? = nil // 게시물의 이미지를 저장. 이미지가 없을 수도 있으므로 옵셔널로 선언 + var hashtags: [String] = [] // 게시물의 해시태그들을 저장 +} + +// 게시물을 작성하는 뷰 컨트롤러 클래스 +class WriteViewController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate,UITextViewDelegate { + + let post = Post() // 게시물 객체 생성 + + // 제목을 입력받는 텍스트 필드 생성 + private let titleField: UITextField = { + let field = UITextField() + field.placeholder = "제목" + field.borderStyle = .roundedRect + return field + }() + // 게시물 내용을 입력받는 텍스트 뷰 생성 + private let contentTextView: UITextView = { + let textView = UITextView() + textView.isScrollEnabled = true + textView.isEditable = true + textView.translatesAutoresizingMaskIntoConstraints = false + textView.layer.borderWidth = 1.0 + textView.layer.borderColor = UIColor.systemGray5.cgColor + textView.text = "내용" // 기본 텍스트 설정 + textView.textColor = UIColor.lightGray // 기본 텍스트의 색상 설정 + textView.font = UIFont.systemFont(ofSize: 14.0) // 기본 텍스트의 글꼴 설정 + textView.layer.cornerRadius = 8.0 // 모서리 둥글게 설정 + textView.clipsToBounds = true // 모서리를 둥글게 설정할 때, 텍스트 뷰가 넘치는 것을 방지합니다. + + return textView + }() + + //기본 텍스트 삭제 + func textViewDidBeginEditing(_ textView: UITextView) { + if textView.textColor == UIColor.lightGray { + textView.text = nil + textView.textColor = UIColor.black + } + } + + // 이미지를 보여주는 이미지 뷰 생성 + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.backgroundColor = .lightGray + return imageView + }() + + // 이미지를 선택하는 버튼 생성 + private let imagePickerButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("사진 선택", for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + picker.dismiss(animated: true, completion: nil) + + // 이미지 피커에서 선택한 이미지를 가져옵니다. + guard let selectedImage = info[.originalImage] as? UIImage else { + print("Error: No image found") + return + } + + // 선택한 이미지를 이미지뷰에 설정합니다. + imageView.image = selectedImage + } + + // 해시태그를 입력받는 텍스트 필드 생성 + private let hashtagField: UITextField = { + let field = UITextField() + field.placeholder = "#해시태그" + field.borderStyle = .roundedRect + return field + }() + + override func viewDidLoad() { + + super.viewDidLoad() + + view.backgroundColor = UIColor(red: 249/255, green: 223/255, blue: 86/255, alpha: 1.0) + + // 위에서 생성한 각 요소들을 테이블 뷰에 추가 + tableView.addSubview(titleField) + tableView.addSubview(contentTextView) + tableView.addSubview(imageView) + tableView.addSubview(imagePickerButton) + tableView.addSubview(hashtagField) + + setupUIConstraints() // UI 요소들의 위치와 크기를 설정하는 메서드를 호출 + + titleField.delegate = self + hashtagField.delegate = self + contentTextView.delegate = self + + // 이미지 선택 버튼이 클릭되었을 때의 동작을 설정 + imagePickerButton.addTarget(self, action: #selector(selectImageButtonTapped), for: .touchUpInside) + // 저장 버튼을 추가하고 레이아웃을 설정 + let saveButton = UIButton(type: .system) + saveButton.setTitle("저장", for: .normal) + saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside) + + // 뷰에 저장 버튼을 추가 + view.addSubview(saveButton) + + // 저장 버튼의 레이아웃을 설정 + saveButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + saveButton.topAnchor.constraint(equalTo: hashtagField.bottomAnchor, constant: 20), + saveButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + saveButton.widthAnchor.constraint(equalToConstant: 100), + saveButton.heightAnchor.constraint(equalToConstant: 40) + ]) + } + + // UI 요소들의 위치와 크기를 설정하는 메서드 + private func setupUIConstraints() { + titleField.translatesAutoresizingMaskIntoConstraints = false + imageView.translatesAutoresizingMaskIntoConstraints = false + hashtagField.translatesAutoresizingMaskIntoConstraints = false + contentTextView.translatesAutoresizingMaskIntoConstraints = false + + // 각 UI 요소들의 위치와 크기를 제약 조건을 통해 정의 + NSLayoutConstraint.activate([ + titleField.topAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.topAnchor, constant: 20), + titleField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + titleField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + titleField.heightAnchor.constraint(equalToConstant: 50), + + contentTextView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 20), + contentTextView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + contentTextView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + contentTextView.heightAnchor.constraint(equalToConstant: 75), + + imageView.topAnchor.constraint(equalTo: contentTextView.bottomAnchor, constant: 20), + imageView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor), // 이미지 높이와 너비 동일 + + imagePickerButton.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 20), + imagePickerButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + + hashtagField.topAnchor.constraint(equalTo: imagePickerButton.bottomAnchor, constant: 10), + hashtagField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + hashtagField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + hashtagField.heightAnchor.constraint(equalToConstant: 50) + ]) + tableView.contentSize = CGSize(width: view.frame.width, height: contentTextView.frame.maxY + 20) + } + + // 이미지 선택 버튼이 클릭되었을 때의 동작을 정의하는 메서드 + @objc func selectImageButtonTapped() { + let picker = UIImagePickerController() // 이미지를 선택할 수 있는 이미지 피커 + picker.sourceType = .photoLibrary // 이미지 피커의 소스 타입을 포토 라이브러리로 설정 + picker.delegate = self // 이미지 피커의 델리게이트를 self로 설정 + present(picker, animated: true) // 이미지 피커를 화면에 표시 + } + + // 텍스트 필드에 문자를 입력할 때마다 호출되는 메서드 + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if textField == hashtagField, string == " " { + textField.text = (textField.text ?? "") + " #" // 해시태그 필드에 공백 문자가 입력되면 자동으로 '#' 문자를 추가 + return false + } + return true + } + + // 텍스트 필드의 편집이 끝났을 때 호출되는 메서드 + func textFieldDidEndEditing(_ textField: UITextField) { + if textField == hashtagField { + post.hashtags.removeAll() // 해시태그 배열 초기화 + if let text = textField.text { + var uniqueHashtags = Set() // 중복된 해시태그를 제거하기 위해 Set을 사용 + let hashtags = text.split(separator: " ") // 텍스트를 공백 문자를 기준으로 분리 + for hashtag in hashtags where !hashtag.isEmpty { + // 해시태그가 '#' 문자로 시작하면 Set에 추가합니다. + if hashtag.hasPrefix("#") { + uniqueHashtags.insert(String(hashtag)) + } + } + post.hashtags.append(contentsOf: uniqueHashtags) // 해시태그 배열에 추가 + print("Current Hashtags (final): \(post.hashtags)") // 해시태그 배열을 출력 + textField.text = uniqueHashtags.joined(separator: " ") // 중복이 제거된 해시태그들을 다시 텍스트 필드에 표시 + } + } + } + // 글을 저장하는 함수 + func savePost() { + // 제목과 내용을 가져옵니다. + guard let title = titleField.text, + let content = contentTextView.text else { + print("Error: 제목 또는 내용을 입력해주세요.") + return + } + + // 이미지를 업로드하고, 성공하면 이미지 URL을 받아옵니다. + if let imageData = imageView.image?.jpegData(compressionQuality: 0.5) { + // 이미지가 선택된 경우 + APIHandler().ImageUploadFunction(imageData) { result in + switch result { + case .success(let imageURL): + // 이미지 업로드 성공한 경우, 게시글 정보를 서버에 전송합니다. + let postData = CreatePost(title: title, content: content, hashTag: "", imageName: imageURL) + self.callCreatePostAPI(with: postData) + case .failure(let error): + // 이미지 업로드 실패 + print("이미지 업로드에 실패했습니다: \(error.localizedDescription)") + // 이미지 업로드가 실패해도 createPost를 호출할 수 있도록 원하는 동작을 수행합니다. + let postData = CreatePost(title: title, content: content, hashTag: "", imageName: "") + self.callCreatePostAPI(with: postData) + } + } + } else { + // 이미지를 선택하지 않은 경우 + let postData = CreatePost(title: title, content: content, hashTag: "", imageName: "") + self.callCreatePostAPI(with: postData) + } + } + + // createPost 호출하는 함수 + func callCreatePostAPI(with postData: CreatePost) { + APIHandler().createPost(postData: postData) { result in + switch result { + case .success(_): + // 게시글 작성 성공 + print("게시글이 성공적으로 작성되었습니다.") + // 여기에 필요한 추가 작업 수행 + case .failure(let error): + // 게시글 작성 실패 + print("게시글 작성에 실패했습니다: \(error.localizedDescription)") + } + + } + + } + + + // 서버에 이미지를 업로드하는 함수 + func yourImageUploadFunction(_ imageData: Data, completion: @escaping (Result) -> Void) { + // 이미지를 서버에 업로드하고, 업로드된 이미지의 URL을 반환합니다. + // 네트워크 요청 등의 비동기 작업을 수행합니다. + // 성공 시 imageURL을 반환하고, 실패 시 에러를 반환합니다. + } + + // 서버에 게시글을 생성하는 함수 + func yourPostCreateFunction(_ post: CreatePost, completion: @escaping (Result) -> Void) { + // 게시글 정보를 서버에 전송하여 데이터베이스에 저장합니다. + // 네트워크 요청 등의 비동기 작업을 수행합니다. + // 성공 시에는 success를 반환하고, 실패 시에는 에러를 반환합니다. + } + + + // 글 저장 버튼이 클릭되었을 때의 동작을 정의하는 메서드 + @objc func saveButtonTapped() { + savePost() // 글을 저장하는 함수 호출 + print("Save button tapped") + + // 저장 완료 후 필요한 후속 동작을 수행할 수 있습니다. + // 예를 들어, 저장 완료 알림을 표시하거나 이전 화면으로 돌아가는 등의 동작을 구현할 수 있습니다. + } + + + +} + diff --git a/Helfy/Model/CommunityModel.swift b/Helfy/Model/CommunityModel.swift new file mode 100644 index 0000000..efaa8ee --- /dev/null +++ b/Helfy/Model/CommunityModel.swift @@ -0,0 +1,117 @@ +// +// CommunityModel.swift +// Helfy +// +// Created by YEOMI on 12/18/23. +// + +import Foundation + +//GET +struct GetPost: Codable { + let page, size: Int + let sort: [String] +} + +struct GetPostResponse: Codable { + let totalPages, totalElements, size: Int + let content: [PostContent] + let number: Int + let sort: PostSort + let pageable: PostPageable + let numberOfElements: Int + let first, last, empty: Bool +} + +struct PostContent: Codable { + let id: Int + let content, writerNickname: String + let image: PostImage + let likeCount: Int + let likeStatus: Bool + let createdTime, modifiedTime: String +} + +struct PostImage: Codable { + let id: Int + let imageURL: String + + enum CodingKeys: String, CodingKey { + case id + case imageURL = "imageUrl" + } +} + +struct PostPageable: Codable { + let offset: Int + let sort: PostSort + let unpaged: Bool + let pageNumber: Int + let paged: Bool + let pageSize: Int +} + +struct PostSort: Codable { + let empty, unsorted, sorted: Bool +} + +//POST +struct CreatePost: Codable { + let title, content, hashTag: String + let imageName: String + + enum CodingKeys: String, CodingKey { + case title, content, hashTag + case imageName + } +} + +struct CreatePostResponse: Codable { + let id: Int + let content, writerNickname: String + let image: Image + let likeCount: Int + let likeStatus: Bool + let createdTime, modifiedTime: String +} + +struct Image: Codable { + let id: Int + let imageURL: String + + enum CodingKeys: String, CodingKey { + case id + case imageURL = "imageUrl" + } +} + +//DELETE +struct DeletePost: Codable { + let id: Int +} + +//PATCH +struct UpdatePost: Codable { + let content: String + let imageID: Int + + enum CodingKeys: String, CodingKey { + case content + case imageID = "imageId" + } +} + +struct UpdatedPost: Codable { + let id: Int + let content, writerNickname: String + let image: Image + let likeCount: Int + let likeStatus: Bool + let createdTime, modifiedTime: String +} + +//LIKE +struct LikePosts: Codable { + let id: Int +} + diff --git a/Helfy/Model/WriteModel.swift b/Helfy/Model/WriteModel.swift new file mode 100644 index 0000000..b332a1b --- /dev/null +++ b/Helfy/Model/WriteModel.swift @@ -0,0 +1,10 @@ +// +// WriteModel.swift +// Helfy +// +// Created by YEOMI on 10/16/23. +// + +struct MyModel { + let text: String +} diff --git a/Helfy/SceneDelegate.swift b/Helfy/SceneDelegate.swift index f29448b..06a87ea 100644 --- a/Helfy/SceneDelegate.swift +++ b/Helfy/SceneDelegate.swift @@ -12,12 +12,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } - - window = UIWindow(windowScene: windowScene) - window?.rootViewController = LoginViewController() - window?.makeKeyAndVisible() - } + guard let windowScene = (scene as? UIWindowScene) else { return } + let window = UIWindow(windowScene: windowScene) + window.rootViewController = LoginViewController() + self.window = window + window.makeKeyAndVisible() + } func sceneDidDisconnect(_ scene: UIScene) { // Called as the scene is being released by the system. diff --git a/Helfy/View/BannerView.swift b/Helfy/View/BannerView.swift new file mode 100644 index 0000000..98a7cdd --- /dev/null +++ b/Helfy/View/BannerView.swift @@ -0,0 +1,130 @@ +// +// BannerView.swift +// Helfy +// +// Created by YEOMI on 11/18/23. +// +import UIKit + +class BannerViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + let collectionView = UICollectionView(frame: CGRect(x: 50, y: 100, width: 300, height: 100), collectionViewLayout: UICollectionViewFlowLayout()) + let pageControl = UIPageControl() + let bannerDuration = 3.0 + var bannerTimer: Timer? + var currentIndex: Int = 0 + let colors: [UIColor] = [UIColor.red, UIColor.green, UIColor.blue] + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.white + + setupCollectionView() + setupPageControl() + startBannerTimer() + } + + func setupCollectionView() { + collectionView.dataSource = self + collectionView.delegate = self + collectionView.register(BannerCell.self, forCellWithReuseIdentifier: "BannerCell") + collectionView.isPagingEnabled = true + collectionView.showsHorizontalScrollIndicator = false + view.addSubview(collectionView) + + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + collectionView.collectionViewLayout = layout + + collectionView.reloadData() + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBannerTap)) + collectionView.addGestureRecognizer(tapGesture) + } + + func setupPageControl() { + pageControl.frame = CGRect(x: 50, y: 170, width: 300, height: 100) // 배너 아래에 위치 + pageControl.numberOfPages = colors.count + pageControl.currentPage = currentIndex + pageControl.isUserInteractionEnabled = false + pageControl.currentPageIndicatorTintColor = UIColor(red: 249/255, green: 164/255, blue: 86/255, alpha: 1.0) // 현재 페이지 색상 설정 + pageControl.pageIndicatorTintColor = UIColor(red: 249/255, green: 223/255, blue: 86/255, alpha: 1.0) + + view.addSubview(pageControl) + } + + func startBannerTimer() { + bannerTimer = Timer.scheduledTimer(timeInterval: bannerDuration, target: self, selector: #selector(updateBanner), userInfo: nil, repeats: true) + } + + @objc func updateBanner() { + currentIndex = (currentIndex + 1) % colors.count + collectionView.scrollToItem(at: IndexPath(item: currentIndex, section: 0), at: .centeredHorizontally, animated: true) + } + + @objc func handleBannerTap() { + let currentColor = colors[currentIndex] + print("Current Color:", currentColor) + } + + // MARK: - UICollectionViewDataSource + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return colors.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerCell", for: indexPath) as! BannerCell + let color = colors[indexPath.item] // 해당 셀의 배너 색상 + cell.configure(with: color) // 색상 파라미터 추가 + return cell + } + + // MARK: - UICollectionViewDelegateFlowLayout + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: collectionView.frame.width, height: collectionView.frame.height) + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + updateCurrentIndex() + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + updateCurrentIndex() + } + + private func updateCurrentIndex() { + let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) + let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY) + + if let indexPath = collectionView.indexPathForItem(at: visiblePoint) { + currentIndex = indexPath.item + pageControl.currentPage = currentIndex + } + } +} + +class BannerCell: UICollectionViewCell { + var bannerView: UIView = UIView() + + override init(frame: CGRect) { + super.init(frame: frame) + setupBannerView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupBannerView() + } + + private func setupBannerView() { + bannerView.frame = contentView.bounds + bannerView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + contentView.addSubview(bannerView) + } + + func configure(with color: UIColor) { + bannerView.backgroundColor = color + } +} diff --git a/Helfy/View/CommunityView.swift b/Helfy/View/CommunityView.swift new file mode 100644 index 0000000..c31cc2c --- /dev/null +++ b/Helfy/View/CommunityView.swift @@ -0,0 +1,318 @@ +// +// CommunityView.swift +// Helfy +// +// Created by YEOMI on 11/13/23. +// +// CommunityView.swift +// Helfy +// Created by YEOMI on 11/13/23. + +import UIKit + + +class CommunityView: UIView { + let apiHandler = APIHandler() + + //데이터 모델 객체 + var postData: CreatePost?{ + didSet { + print("data fetch completed") + } + } + var CreateResponseData: CreatePostResponse?{ + didSet { + print("data fetch completed") + } + } + var ImageData: Image?{ + didSet { + print("data fetch completed") + } + } + var getData: GetPost?{ + didSet { + print("data fetch completed") + } + } + var postResponseData: GetPostResponse?{ + didSet { + print("data fetch completed") + } + } + var postContentData: PostContent?{ + didSet { + print("data fetch completed") + } + } + var postImageData: PostImage?{ + didSet { + print("data fetch completed") + } + } + var postPageableData: PostPageable?{ + didSet { + print("data fetch completed") + } + } + var postSortData: PostSort?{ + didSet { + print("data fetch completed") + } + } + + let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.translatesAutoresizingMaskIntoConstraints = false + return scrollView + }() + + let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 10 + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + var communityPosts: [CreatePost] = [ + // 커뮤니티 게시물을 여기에 추가 + CreatePost(title: "안전사고", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "img2"), + CreatePost(title: "안전사고", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "img2"), + ] + /* + CommunityModel(title: "안전사고", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "img2"), + CommunityModel(title: "안전사고", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "imageName"), + CommunityModel(title: "안전사고", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "imageName"), + CommunityModel(title: "안전사고 발생했으니 조심하세요", content: "게시글 내용", hashTag: "#ㅇㅇ동 #안전사고 #조심", imageName: "imageName") + ] + */ + // 게시글별로 버튼이 눌렸는지 여부를 저장하는 배열 + var isButtonPressed: [Bool] = [] + + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .white + + isButtonPressed = Array(repeating: false, count: communityPosts.count) + // UIScrollView 추가 + self.addSubview(scrollView) + + // UIStackView 추가 + scrollView.addSubview(stackView) + + + // UIScrollView의 제약조건 설정 + NSLayoutConstraint.activate([ + scrollView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 20), + scrollView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor) + ]) + + // UIStackView의 제약조건 설정 + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 20 ), + stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -20 ), + stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -40 ) + ]) + + // 게시글 뷰 + for (index, post) in communityPosts.enumerated() { + let postView = UIView() + + postView.layer.cornerRadius = 20 // 모서리 둥글게 설정 + postView.translatesAutoresizingMaskIntoConstraints = false + postView.heightAnchor.constraint(equalToConstant: 100).isActive = true + postView.backgroundColor = UIColor(red: 249/255, green: 223/255, blue: 86/255, alpha: 1.0) // F9DF56으로 색 설정 + + let button = UIButton(type: .system) // 버튼 생성 + button.setImage(UIImage(systemName: "megaphone"), for: .normal) // 아이콘 설정 + button.tintColor = .black // 아이콘 색상 설정. 원하는 색상으로 변경 가능 + button.translatesAutoresizingMaskIntoConstraints = false + button.tag = index + postView.addSubview(button) + + NSLayoutConstraint.activate([ + button.bottomAnchor.constraint(equalTo: postView.bottomAnchor, constant: -10), // postView 아래쪽에 10포인트 여백을 두고 배치 + button.leadingAnchor.constraint(equalTo: postView.leadingAnchor, constant: 20), // postView 왼쪽에 10포인트 여백을 두고 배치 + button.widthAnchor.constraint(equalToConstant: 23), // 버튼의 너비 설정 + button.heightAnchor.constraint(equalToConstant: 23) // 버튼의 높이 설정 + ]) + + // 게시글 뷰 내부의 스택뷰 생성 + let postStackView = UIStackView() + postStackView.axis = .horizontal + postStackView.spacing = 0 + postStackView.alignment = .center + postStackView.translatesAutoresizingMaskIntoConstraints = false + postView.addSubview(postStackView) + + // 게시글 뷰 내부의 스택뷰 제약조건 설정 + NSLayoutConstraint.activate([ + postStackView.topAnchor.constraint(equalTo: postView.topAnchor), + postStackView.bottomAnchor.constraint(equalTo: postView.bottomAnchor), + postStackView.leadingAnchor.constraint(equalTo: postView.leadingAnchor,constant: 20), + postStackView.trailingAnchor.constraint(equalTo: postView.trailingAnchor, constant: -20) + ]) + + let imageView = UIImageView() // 이미지 뷰 생성 + imageView.image = UIImage(named: post.imageName) + imageView.backgroundColor = .clear // 배경색 설정 + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.widthAnchor.constraint(equalToConstant: 70).isActive = true // 이미지 뷰 너비 설정 + imageView.heightAnchor.constraint(equalToConstant: 70).isActive = true // 이미지 뷰 높이 설정 + + imageView.isUserInteractionEnabled = true // 이미지 뷰를 터치 가능하도록 + // 터치 제스처 추가 + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:))) + imageView.addGestureRecognizer(tapGesture) + + + + + let titleLabel = UILabel() // 타이틀 라벨 생성 + titleLabel.text = post.content // 텍스트 설정 + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.numberOfLines = 0 // 줄 수를 자동으로 조절하도록 설정 + titleLabel.lineBreakMode = .byWordWrapping // 단어 단위로 줄 바꿈 처리 + + let hashtagLabel = UILabel() // 해시태그 라벨 생성 + hashtagLabel.text = post.hashTag // 텍스트 설정 + hashtagLabel.translatesAutoresizingMaskIntoConstraints = false + hashtagLabel.textAlignment = .right // 오른쪽 정렬 + + // 타이틀 라벨과 해시태그 라벨을 위아래로 배치하는 또 다른 스택뷰 생성 + let labelStackView = UIStackView() + labelStackView.axis = .vertical // 위 아래로 배치 + labelStackView.spacing = 15 + labelStackView.translatesAutoresizingMaskIntoConstraints = false + labelStackView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10) // 오른쪽에 마진 추가 + labelStackView.isLayoutMarginsRelativeArrangement = true // 마진 설정 적용 + labelStackView.addArrangedSubview(titleLabel) + labelStackView.addArrangedSubview(hashtagLabel) + postStackView.addArrangedSubview(labelStackView) + postStackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(postView) + + + // 버튼과 레이블을 함께 담을 수 있는 스택뷰 생성 + let buttonAndLabelStackView = UIStackView() + buttonAndLabelStackView.axis = .horizontal // 가로 방향으로 요소 배치 + buttonAndLabelStackView.spacing = 10 // 요소 사이 간격 설정 + buttonAndLabelStackView.translatesAutoresizingMaskIntoConstraints = false + + let countLabel = UILabel() + countLabel.text = "0" + countLabel.tag = index + 1000 + countLabel.translatesAutoresizingMaskIntoConstraints = false + + // 버튼과 레이블을 스택뷰에 추가 + buttonAndLabelStackView.addArrangedSubview(button) + buttonAndLabelStackView.addArrangedSubview(countLabel) + + // 스택뷰를 postView에 추가 + postView.addSubview(buttonAndLabelStackView) + + // 스택뷰에 대한 레이아웃 제약조건 추가 + NSLayoutConstraint.activate([ + buttonAndLabelStackView.bottomAnchor.constraint(equalTo: postView.bottomAnchor, constant: -10), // postView 아래쪽에 10포인트 여백을 두고 배치 + buttonAndLabelStackView.leadingAnchor.constraint(equalTo: postView.leadingAnchor, constant: 20) // postView 왼쪽에 20포인트 여백을 두고 배치 + ]) + + // 스택뷰를 postView에 추가 + postView.addSubview(buttonAndLabelStackView) + + // 스택뷰에 대한 레이아웃 제약조건 추가 + NSLayoutConstraint.activate([ + buttonAndLabelStackView.bottomAnchor.constraint(equalTo: postView.bottomAnchor, constant: -10), // postView 아래쪽에 10포인트 여백을 두고 배치 + buttonAndLabelStackView.leadingAnchor.constraint(equalTo: postView.leadingAnchor, constant: 20) // postView 왼쪽에 20포인트 여백을 두고 배치 + ]) + + stackView.addArrangedSubview(postView) + } + + } + var tappedImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + @objc func imageTapped(_ sender: UITapGestureRecognizer) { + guard let tappedImageView = sender.view as? UIImageView else { return } + + // 이미지가 터치되었을 때의 동작을 정의 + + // 확대된 이미지를 담을 이미지 뷰 생성 + let zoomedImageView = UIImageView(image: tappedImageView.image) + zoomedImageView.contentMode = .scaleAspectFit + zoomedImageView.frame = UIScreen.main.bounds + + // 확대된 이미지를 담을 뷰 생성 + let zoomedView = UIView(frame: UIScreen.main.bounds) + zoomedView.backgroundColor = .black + zoomedView.addSubview(zoomedImageView) + + // 확대된 이미지 뷰를 탭했을 때 원래 크기로 돌아가는 동작을 위한 탭 제스처 생성 + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissZoomedView(_:))) + zoomedView.addGestureRecognizer(tapGesture) + + // 현재 윈도우에 확대된 이미지 뷰를 추가 + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first { + window.addSubview(zoomedView) + } + } + + @objc func dismissZoomedView(_ sender: UITapGestureRecognizer) { + // 확대된 이미지 뷰를 탭했을 때 해당 뷰를 제거 + sender.view?.removeFromSuperview() + } + + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + func setData() { + DispatchQueue.global(qos: .userInteractive).async { + // API를 통해 데이터 불러오기 + self.apiHandler.getPost(page: 1, size: 10, sort: ["createdAt"]) { result in + switch result { + case .success(let responseData): + // 정의해둔 모델 객체에 할당 + self.postResponseData = responseData + + // 데이터를 제대로 잘 받아왔다면 + guard let responseData = self.postResponseData else { + return + } + + // 이미지 URL을 가져오고 처리하는 로직 + for postContent in responseData.content { + guard let url = URL(string: postContent.image.imageURL) else { + print("Invalid URL") + continue + } + + // 이미지를 로드하고 UI를 업데이트 + ImageLoader.loadImage(url: url.absoluteString) { [weak self] image in + DispatchQueue.main.async { + self?.tappedImageView.image = image + } + } + } + + case .failure(let error): + // 데이터를 제대로 받지 못한 경우 에러 처리 + print("Failed to fetch data: \(error.localizedDescription)") + } + } + } + } +} + diff --git a/Helfy/View/NavigationView.swift b/Helfy/View/NavigationView.swift new file mode 100644 index 0000000..429393f --- /dev/null +++ b/Helfy/View/NavigationView.swift @@ -0,0 +1,8 @@ +// +// NavigationView.swift +// Helfy +// +// Created by YEOMI on 11/13/23. +// + +import Foundation diff --git a/Helfy/View/SearchView.swift b/Helfy/View/SearchView.swift new file mode 100644 index 0000000..157a743 --- /dev/null +++ b/Helfy/View/SearchView.swift @@ -0,0 +1,96 @@ +// +// SearchView.swift +// Helfy +// +// Created by YEOMI on 11/21/23. +// +import UIKit + +class SearchViewController: UIViewController { + + let searchContainerView: UIView = { + let view = UIView() + view.backgroundColor = .clear + view.layer.cornerRadius = 15.0 + view.layer.backgroundColor = CGColor(red: 249/255, green: 223/255, blue: 86/255, alpha: 1.0) + view.clipsToBounds = true + return view + }() + + let searchTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "검색어를 입력하세요" + textField.textColor = .black + textField.borderStyle = .none + return textField + }() + + let searchButtonContainer: UIView = { + let container = UIView() + container.backgroundColor = UIColor(red: 249/255, green: 164/255, blue: 86/255, alpha: 1.0) + container.layer.cornerRadius = 22.5 // 반지름을 버튼 높이의 절반으로 설정 + return container + }() + + let searchButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "magnifyingglass"), for: .normal) + button.tintColor = .black + button.addTarget(SearchViewController.self, action: #selector(searchButtonTapped), for: .touchUpInside) + return button + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + setupUI() + } + + func setupUI() { + view.addSubview(searchContainerView) + searchContainerView.addSubview(searchTextField) + view.addSubview(searchButtonContainer) + searchButtonContainer.addSubview(searchButton) + + searchContainerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + searchContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + searchContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + searchContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -80), + searchContainerView.heightAnchor.constraint(equalToConstant: 45) + ]) + + searchTextField.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + searchTextField.topAnchor.constraint(equalTo: searchContainerView.topAnchor), + searchTextField.leadingAnchor.constraint(equalTo: searchContainerView.leadingAnchor, constant: 10), + searchTextField.trailingAnchor.constraint(equalTo: searchContainerView.trailingAnchor, constant: -10), + searchTextField.bottomAnchor.constraint(equalTo: searchContainerView.bottomAnchor) + ]) + + searchButtonContainer.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + searchButtonContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + searchButtonContainer.leadingAnchor.constraint(equalTo: searchContainerView.trailingAnchor, constant: 10), + searchButtonContainer.widthAnchor.constraint(equalToConstant: 45), + searchButtonContainer.heightAnchor.constraint(equalToConstant: 45) + ]) + + searchButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + searchButton.centerXAnchor.constraint(equalTo: searchButtonContainer.centerXAnchor), + searchButton.centerYAnchor.constraint(equalTo: searchButtonContainer.centerYAnchor), + searchButton.widthAnchor.constraint(equalTo: searchButtonContainer.widthAnchor), + searchButton.heightAnchor.constraint(equalTo: searchButtonContainer.heightAnchor) + ]) + } + + @objc func searchButtonTapped() { + if let searchTerm = searchTextField.text, !searchTerm.isEmpty { + print("검색어: \(searchTerm)") + // 여기에 실제 검색 동작을 수행하는 코드를 추가할 수 있습니다. + } else { + print("검색어를 입력하세요.") + } + } +} diff --git a/Helfy/View/WriteView.swift b/Helfy/View/WriteView.swift new file mode 100644 index 0000000..9f2bb5a --- /dev/null +++ b/Helfy/View/WriteView.swift @@ -0,0 +1,20 @@ +// +// WriteView.swift +// Helfy +// +// Created by YEOMI on 10/13/23. +// + +import UIKit + +class WriteView: UIView { + override init(frame: CGRect) { + super.init(frame: frame) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} +