diff --git a/SOPT-Test-Code-Study.xcodeproj/project.pbxproj b/SOPT-Test-Code-Study.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3d01495 --- /dev/null +++ b/SOPT-Test-Code-Study.xcodeproj/project.pbxproj @@ -0,0 +1,519 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 6347AD062EC2EF12008C4FA5 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6347AD052EC2EF12008C4FA5 /* SnapKit */; }; + 6347AD092EC2EF1A008C4FA5 /* Then in Frameworks */ = {isa = PBXBuildFile; productRef = 6347AD082EC2EF1A008C4FA5 /* Then */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 63C525952EC591D1002440C5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 63D7DF222EC2EE2700F7C4D0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 63D7DF292EC2EE2700F7C4D0; + remoteInfo = "SOPT-Test-Code-Study"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 63C525912EC591D1002440C5 /* SOPT-Test-Code-StudyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SOPT-Test-Code-StudyTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 63D7DF2A2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SOPT-Test-Code-Study.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 63D7DF3C2EC2EE2900F7C4D0 /* Exceptions for "SOPT-Test-Code-Study" folder in "SOPT-Test-Code-Study" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 63D7DF292EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 63C525922EC591D1002440C5 /* SOPT-Test-Code-StudyTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = "SOPT-Test-Code-StudyTests"; + sourceTree = ""; + }; + 63D7DF2C2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 63D7DF3C2EC2EE2900F7C4D0 /* Exceptions for "SOPT-Test-Code-Study" folder in "SOPT-Test-Code-Study" target */, + ); + path = "SOPT-Test-Code-Study"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 63C5258E2EC591D1002440C5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 63D7DF272EC2EE2700F7C4D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6347AD062EC2EF12008C4FA5 /* SnapKit in Frameworks */, + 6347AD092EC2EF1A008C4FA5 /* Then in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 63D7DF212EC2EE2700F7C4D0 = { + isa = PBXGroup; + children = ( + 63D7DF2C2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */, + 63C525922EC591D1002440C5 /* SOPT-Test-Code-StudyTests */, + 63D7DF2B2EC2EE2700F7C4D0 /* Products */, + ); + sourceTree = ""; + }; + 63D7DF2B2EC2EE2700F7C4D0 /* Products */ = { + isa = PBXGroup; + children = ( + 63D7DF2A2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study.app */, + 63C525912EC591D1002440C5 /* SOPT-Test-Code-StudyTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 63C525902EC591D1002440C5 /* SOPT-Test-Code-StudyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 63C525992EC591D1002440C5 /* Build configuration list for PBXNativeTarget "SOPT-Test-Code-StudyTests" */; + buildPhases = ( + 63C5258D2EC591D1002440C5 /* Sources */, + 63C5258E2EC591D1002440C5 /* Frameworks */, + 63C5258F2EC591D1002440C5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 63C525962EC591D1002440C5 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 63C525922EC591D1002440C5 /* SOPT-Test-Code-StudyTests */, + ); + name = "SOPT-Test-Code-StudyTests"; + packageProductDependencies = ( + ); + productName = "SOPT-Test-Code-StudyTests"; + productReference = 63C525912EC591D1002440C5 /* SOPT-Test-Code-StudyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 63D7DF292EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */ = { + isa = PBXNativeTarget; + buildConfigurationList = 63D7DF3D2EC2EE2900F7C4D0 /* Build configuration list for PBXNativeTarget "SOPT-Test-Code-Study" */; + buildPhases = ( + 63D7DF262EC2EE2700F7C4D0 /* Sources */, + 63D7DF272EC2EE2700F7C4D0 /* Frameworks */, + 63D7DF282EC2EE2700F7C4D0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 63D7DF2C2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */, + ); + name = "SOPT-Test-Code-Study"; + packageProductDependencies = ( + 6347AD052EC2EF12008C4FA5 /* SnapKit */, + 6347AD082EC2EF1A008C4FA5 /* Then */, + ); + productName = "SOPT-Test-Code-Study"; + productReference = 63D7DF2A2EC2EE2700F7C4D0 /* SOPT-Test-Code-Study.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 63D7DF222EC2EE2700F7C4D0 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2610; + LastUpgradeCheck = 2610; + TargetAttributes = { + 63C525902EC591D1002440C5 = { + CreatedOnToolsVersion = 26.1.1; + TestTargetID = 63D7DF292EC2EE2700F7C4D0; + }; + 63D7DF292EC2EE2700F7C4D0 = { + CreatedOnToolsVersion = 26.1; + }; + }; + }; + buildConfigurationList = 63D7DF252EC2EE2700F7C4D0 /* Build configuration list for PBXProject "SOPT-Test-Code-Study" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 63D7DF212EC2EE2700F7C4D0; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 6347AD042EC2EF12008C4FA5 /* XCRemoteSwiftPackageReference "SnapKit" */, + 6347AD072EC2EF1A008C4FA5 /* XCRemoteSwiftPackageReference "Then" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 63D7DF2B2EC2EE2700F7C4D0 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 63D7DF292EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */, + 63C525902EC591D1002440C5 /* SOPT-Test-Code-StudyTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 63C5258F2EC591D1002440C5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 63D7DF282EC2EE2700F7C4D0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 63C5258D2EC591D1002440C5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 63D7DF262EC2EE2700F7C4D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 63C525962EC591D1002440C5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 63D7DF292EC2EE2700F7C4D0 /* SOPT-Test-Code-Study */; + targetProxy = 63C525952EC591D1002440C5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 63C525972EC591D1002440C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2QJX795Q3R; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "iseungjun.SOPT-Test-Code-StudyTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SOPT-Test-Code-Study.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SOPT-Test-Code-Study"; + }; + name = Debug; + }; + 63C525982EC591D1002440C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2QJX795Q3R; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "iseungjun.SOPT-Test-Code-StudyTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SOPT-Test-Code-Study.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/SOPT-Test-Code-Study"; + }; + name = Release; + }; + 63D7DF3E2EC2EE2900F7C4D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2QJX795Q3R; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SOPT-Test-Code-Study/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "iseungjun.SOPT-Test-Code-Study"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 63D7DF3F2EC2EE2900F7C4D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 2QJX795Q3R; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "SOPT-Test-Code-Study/Info.plist"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "iseungjun.SOPT-Test-Code-Study"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 63D7DF402EC2EE2900F7C4D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 2QJX795Q3R; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 63D7DF412EC2EE2900F7C4D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 2QJX795Q3R; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 63C525992EC591D1002440C5 /* Build configuration list for PBXNativeTarget "SOPT-Test-Code-StudyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63C525972EC591D1002440C5 /* Debug */, + 63C525982EC591D1002440C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 63D7DF252EC2EE2700F7C4D0 /* Build configuration list for PBXProject "SOPT-Test-Code-Study" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63D7DF402EC2EE2900F7C4D0 /* Debug */, + 63D7DF412EC2EE2900F7C4D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 63D7DF3D2EC2EE2900F7C4D0 /* Build configuration list for PBXNativeTarget "SOPT-Test-Code-Study" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63D7DF3E2EC2EE2900F7C4D0 /* Debug */, + 63D7DF3F2EC2EE2900F7C4D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 6347AD042EC2EF12008C4FA5 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SnapKit/SnapKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.7.1; + }; + }; + 6347AD072EC2EF1A008C4FA5 /* XCRemoteSwiftPackageReference "Then" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/devxoul/Then.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 6347AD052EC2EF12008C4FA5 /* SnapKit */ = { + isa = XCSwiftPackageProductDependency; + package = 6347AD042EC2EF12008C4FA5 /* XCRemoteSwiftPackageReference "SnapKit" */; + productName = SnapKit; + }; + 6347AD082EC2EF1A008C4FA5 /* Then */ = { + isa = XCSwiftPackageProductDependency; + package = 6347AD072EC2EF1A008C4FA5 /* XCRemoteSwiftPackageReference "Then" */; + productName = Then; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 63D7DF222EC2EE2700F7C4D0 /* Project object */; +} diff --git a/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..1566188 --- /dev/null +++ b/SOPT-Test-Code-Study.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "0cd11a4ea344b95c25e88fb39b351003de5b41040e76927e86f0f8b8a4edc7a3", + "pins" : [ + { + "identity" : "snapkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SnapKit/SnapKit.git", + "state" : { + "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", + "version" : "5.7.1" + } + }, + { + "identity" : "then", + "kind" : "remoteSourceControl", + "location" : "https://github.com/devxoul/Then.git", + "state" : { + "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", + "version" : "3.0.0" + } + } + ], + "version" : 3 +} diff --git a/SOPT-Test-Code-Study/App/AppDelegate.swift b/SOPT-Test-Code-Study/App/AppDelegate.swift new file mode 100644 index 0000000..fb691c1 --- /dev/null +++ b/SOPT-Test-Code-Study/App/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/25/25. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // 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/SOPT-Test-Code-Study/App/Assets.xcassets/AccentColor.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/Union.pdf b/SOPT-Test-Code-Study/App/Assets.xcassets/Union.pdf new file mode 100644 index 0000000..49770e0 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/Union.pdf differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Contents.json new file mode 100644 index 0000000..3f7af69 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_wu8lq0wu8lq0wu8l (1).png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (1).png b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (1).png new file mode 100644 index 0000000..4d9b56b Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner01.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (1).png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Contents.json new file mode 100644 index 0000000..84afa2b --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_wu8lq0wu8lq0wu8l (2).png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (2).png b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (2).png new file mode 100644 index 0000000..d3496ff Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner02.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l (2).png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Contents.json new file mode 100644 index 0000000..42223f3 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_wu8lq0wu8lq0wu8l.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l.png b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l.png new file mode 100644 index 0000000..cb10975 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/banner/banner03.imageset/Gemini_Generated_Image_wu8lq0wu8lq0wu8l.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Image.png new file mode 100644 index 0000000..8dd7fa1 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/cart.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Contents.json new file mode 100644 index 0000000..5e0cba9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_xaivh3xaivh3xaiv.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Gemini_Generated_Image_xaivh3xaivh3xaiv.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Gemini_Generated_Image_xaivh3xaivh3xaiv.png new file mode 100644 index 0000000..6abc7b8 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/cafeDesert.imageset/Gemini_Generated_Image_xaivh3xaivh3xaiv.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Contents.json new file mode 100644 index 0000000..5453a8c --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_nrm6n8nrm6n8nrm6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Gemini_Generated_Image_nrm6n8nrm6n8nrm6.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Gemini_Generated_Image_nrm6n8nrm6n8nrm6.png new file mode 100644 index 0000000..28705de Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/chicken.imageset/Gemini_Generated_Image_nrm6n8nrm6n8nrm6.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Contents.json new file mode 100644 index 0000000..e6efabb --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_twc5jxtwc5jxtwc5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Gemini_Generated_Image_twc5jxtwc5jxtwc5.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Gemini_Generated_Image_twc5jxtwc5jxtwc5.png new file mode 100644 index 0000000..67a0a80 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/delievery.imageset/Gemini_Generated_Image_twc5jxtwc5jxtwc5.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Contents.json new file mode 100644 index 0000000..ac64a0f --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_so4h9eso4h9eso4h.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Gemini_Generated_Image_so4h9eso4h9eso4h.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Gemini_Generated_Image_so4h9eso4h9eso4h.png new file mode 100644 index 0000000..aea5877 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/fastfood.imageset/Gemini_Generated_Image_so4h9eso4h9eso4h.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Contents.json new file mode 100644 index 0000000..e65ed18 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_ee9emxee9emxee9e.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Gemini_Generated_Image_ee9emxee9emxee9e.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Gemini_Generated_Image_ee9emxee9emxee9e.png new file mode 100644 index 0000000..1e36604 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/lateFood.imageset/Gemini_Generated_Image_ee9emxee9emxee9e.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Contents.json new file mode 100644 index 0000000..e0cff4b --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_8xha688xha688xha.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Gemini_Generated_Image_8xha688xha688xha.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Gemini_Generated_Image_8xha688xha688xha.png new file mode 100644 index 0000000..0920c21 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/meat.imageset/Gemini_Generated_Image_8xha688xha688xha.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Contents.json new file mode 100644 index 0000000..5015cd5 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_qkk7pbqkk7pbqkk7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Gemini_Generated_Image_qkk7pbqkk7pbqkk7.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Gemini_Generated_Image_qkk7pbqkk7pbqkk7.png new file mode 100644 index 0000000..27b9b44 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/oneBowl.imageset/Gemini_Generated_Image_qkk7pbqkk7pbqkk7.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Contents.json new file mode 100644 index 0000000..1b5773d --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_5upaqp5upaqp5upa.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Gemini_Generated_Image_5upaqp5upaqp5upa.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Gemini_Generated_Image_5upaqp5upaqp5upa.png new file mode 100644 index 0000000..991d8ea Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pickUp.imageset/Gemini_Generated_Image_5upaqp5upaqp5upa.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Contents.json new file mode 100644 index 0000000..07f1501 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_d2j9vvd2j9vvd2j9.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Gemini_Generated_Image_d2j9vvd2j9vvd2j9.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Gemini_Generated_Image_d2j9vvd2j9vvd2j9.png new file mode 100644 index 0000000..5bf4b36 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/pizza.imageset/Gemini_Generated_Image_d2j9vvd2j9vvd2j9.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Contents.json new file mode 100644 index 0000000..e9fc2b2 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_mmun0bmmun0bmmun.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Gemini_Generated_Image_mmun0bmmun0bmmun.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Gemini_Generated_Image_mmun0bmmun0bmmun.png new file mode 100644 index 0000000..8200aec Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/snackFood.imageset/Gemini_Generated_Image_mmun0bmmun0bmmun.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Contents.json new file mode 100644 index 0000000..7220eaf --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Gemini_Generated_Image_e3emlqe3emlqe3em.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Gemini_Generated_Image_e3emlqe3emlqe3em.png b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Gemini_Generated_Image_e3emlqe3emlqe3em.png new file mode 100644 index 0000000..855dcf2 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/categories/soup.imageset/Gemini_Generated_Image_e3emlqe3emlqe3em.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git "a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_\nbackground_white.colorset/Contents.json" "b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_\nbackground_white.colorset/Contents.json" new file mode 100644 index 0000000..46799c8 --- /dev/null +++ "b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_\nbackground_white.colorset/Contents.json" @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF5", + "red" : "0xF3" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF5", + "red" : "0xF3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_black.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_black.colorset/Contents.json new file mode 100644 index 0000000..666b128 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_black.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1C", + "green" : "0x1A", + "red" : "0x18" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1C", + "green" : "0x1A", + "red" : "0x18" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_500.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_500.colorset/Contents.json new file mode 100644 index 0000000..6f72524 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_500.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x76", + "red" : "0x72" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x76", + "red" : "0x72" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_700.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_700.colorset/Contents.json new file mode 100644 index 0000000..6a7c3be --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_blue_700.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x15", + "red" : "0x54" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x15", + "red" : "0x54" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_200.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_200.colorset/Contents.json new file mode 100644 index 0000000..58ee861 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_200.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD9", + "green" : "0xD3", + "red" : "0xD1" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD9", + "green" : "0xD3", + "red" : "0xD1" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_300.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_300.colorset/Contents.json new file mode 100644 index 0000000..f36902c --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_300.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB5", + "green" : "0xB3", + "red" : "0xB1" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB5", + "green" : "0xB3", + "red" : "0xB1" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_600.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_600.colorset/Contents.json new file mode 100644 index 0000000..e97f7d3 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_600.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x81", + "green" : "0x80", + "red" : "0x7E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x81", + "green" : "0x80", + "red" : "0x7E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_700.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_700.colorset/Contents.json new file mode 100644 index 0000000..0e7b168 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_700.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x66", + "green" : "0x66", + "red" : "0x66" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x66", + "green" : "0x66", + "red" : "0x66" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_800.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_800.colorset/Contents.json new file mode 100644 index 0000000..1421a35 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_gray_800.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4E", + "green" : "0x4D", + "red" : "0x4B" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4E", + "green" : "0x4D", + "red" : "0x4B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_300.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_300.colorset/Contents.json new file mode 100644 index 0000000..cfc1566 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_300.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xDB", + "green" : "0xE3", + "red" : "0x7F" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xDB", + "green" : "0xE3", + "red" : "0x7F" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_500.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_500.colorset/Contents.json new file mode 100644 index 0000000..43ddbb6 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_mint_500.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB3", + "green" : "0xB8", + "red" : "0x28" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB3", + "green" : "0xB8", + "red" : "0x28" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_red.colorset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_red.colorset/Contents.json new file mode 100644 index 0000000..9f7cd32 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/colors/baemin_red.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x30", + "green" : "0x1C", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x30", + "green" : "0x1C", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/Contents.json new file mode 100644 index 0000000..0f2ee26 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "crossGray.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/crossGray.pdf b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/crossGray.pdf new file mode 100644 index 0000000..a3d5520 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/crossGray.imageset/crossGray.pdf differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/Contents.json new file mode 100644 index 0000000..c275359 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "eye.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/eye.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/eye.png new file mode 100644 index 0000000..ca56833 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eye.imageset/eye.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/Contents.json new file mode 100644 index 0000000..3150465 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "eyeSlash.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/eyeSlash.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/eyeSlash.png new file mode 100644 index 0000000..6692704 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/eyeSlash.imageset/eyeSlash.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Image.png new file mode 100644 index 0000000..9b8c97a Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/heart.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Image.png new file mode 100644 index 0000000..e12086e Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/home_selected.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Image.png new file mode 100644 index 0000000..b4e8a67 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_black.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Image.png new file mode 100644 index 0000000..c705f4b Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/info_white.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/Contents.json new file mode 100644 index 0000000..90e56f9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "leftPointer.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/leftPointer.pdf b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/leftPointer.pdf new file mode 100644 index 0000000..49770e0 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/leftPointer.imageset/leftPointer.pdf differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Image.png new file mode 100644 index 0000000..fd7dd37 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/list.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Image.png new file mode 100644 index 0000000..e7f7cb2 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/myPage.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Image.png new file mode 100644 index 0000000..2fba773 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/notification.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Image.png new file mode 100644 index 0000000..2a079d3 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/reviewStar.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/Contents.json new file mode 100644 index 0000000..a0dc4b1 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "rightArrow.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/rightArrow.pdf b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/rightArrow.pdf new file mode 100644 index 0000000..36508fc Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/rightArrow.imageset/rightArrow.pdf differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Image.png new file mode 100644 index 0000000..c30e221 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/search.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Image.png new file mode 100644 index 0000000..86011a5 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/icons/shop.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Image.png new file mode 100644 index 0000000..3f23a1b Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/bmart.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Image.png new file mode 100644 index 0000000..c4e6f54 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/discount_badge.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Image.png new file mode 100644 index 0000000..5e93d20 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/divider.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Image.png new file mode 100644 index 0000000..47f30fc Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/free_delivery.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/Contents.json new file mode 100644 index 0000000..83de3c9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "welcome.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/welcome.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/welcome.png new file mode 100644 index 0000000..35b8055 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/welcome.imageset/welcome.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Image.png new file mode 100644 index 0000000..83215f1 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/images/won.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Image.png new file mode 100644 index 0000000..e2430e4 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/GSTheFresh.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/Contents.json new file mode 100644 index 0000000..aca8f0c --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "img.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/img.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/img.png new file mode 100644 index 0000000..849166f Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/baeminMart.imageset/img.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Image.png new file mode 100644 index 0000000..63cbffc Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/cu.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Image.png new file mode 100644 index 0000000..a1fa6f0 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Image.png new file mode 100644 index 0000000..f0d4ec6 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/emart24.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Image.png new file mode 100644 index 0000000..11d8167 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/gs25.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Image.png new file mode 100644 index 0000000..d2c9844 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplus.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Image.png new file mode 100644 index 0000000..2352c16 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/homeplusSuper.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Image.png new file mode 100644 index 0000000..f2fde23 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/jaju.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Contents.json b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Contents.json new file mode 100644 index 0000000..17f6fd9 --- /dev/null +++ b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Image.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Image.png b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Image.png new file mode 100644 index 0000000..5fbdab4 Binary files /dev/null and b/SOPT-Test-Code-Study/App/Assets.xcassets/markets/petMart.imageset/Image.png differ diff --git a/SOPT-Test-Code-Study/App/SceneDelegate.swift b/SOPT-Test-Code-Study/App/SceneDelegate.swift new file mode 100644 index 0000000..1827097 --- /dev/null +++ b/SOPT-Test-Code-Study/App/SceneDelegate.swift @@ -0,0 +1,62 @@ +// +// SceneDelegate.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/25/25. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = (scene as? UIWindowScene) else { return } + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = UINavigationController(rootViewController: LoginViewController()) + window?.makeKeyAndVisible() + } + + func changeRootViewController(_ viewController: UIViewController, animated: Bool) { + guard let window = self.window else { return } + + window.rootViewController = viewController + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/SOPT-Test-Code-Study/Base.lproj/LaunchScreen.storyboard b/SOPT-Test-Code-Study/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/SOPT-Test-Code-Study/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SOPT-Test-Code-Study/CalcOperator.swift b/SOPT-Test-Code-Study/CalcOperator.swift new file mode 100644 index 0000000..7e21b04 --- /dev/null +++ b/SOPT-Test-Code-Study/CalcOperator.swift @@ -0,0 +1,10 @@ +// +// CalcOperator.swift +// SOPT-Test-Code-Study +// +// Created by 이승준 on 12/23/25. +// + +enum CalcOperator { + case add, subtract, multiply, divide +} diff --git a/SOPT-Test-Code-Study/CalculatorService.swift b/SOPT-Test-Code-Study/CalculatorService.swift new file mode 100644 index 0000000..e04d607 --- /dev/null +++ b/SOPT-Test-Code-Study/CalculatorService.swift @@ -0,0 +1,30 @@ +// +// CalculatorService.swift +// SOPT-Test-Code-Study +// +// Created by 이승준 on 12/23/25. +// + +import Foundation + +protocol CalculatorServiceType { + func calculate(lhs: Double, rhs: Double, operator: CalcOperator) -> Double +} + +class CalculatorService: CalculatorServiceType { + + func calculate(lhs: Double, rhs: Double, operator: CalcOperator) -> Double { + switch `operator` { + case .add: + return lhs + rhs + case .subtract: + return lhs - rhs + case .multiply: + return lhs * rhs + case .divide: + // Edge Case + // nan : Not a Number를 의미, 무한도 쓸 수 있다. + return rhs == 0 ? Double.nan : lhs / rhs + } + } +} diff --git a/SOPT-Test-Code-Study/CalculatorViewController.swift b/SOPT-Test-Code-Study/CalculatorViewController.swift new file mode 100644 index 0000000..ad34cda --- /dev/null +++ b/SOPT-Test-Code-Study/CalculatorViewController.swift @@ -0,0 +1,16 @@ +// +// CalculatorViewController.swift +// SOPT-Test-Code-Study +// +// Created by 이승준 on 12/23/25. +// + +import Combine +import UIKit + +import Then +import SnapKit + +class CalculatorViewController: UIViewController { + +} diff --git a/SOPT-Test-Code-Study/CalculatorViewModel.swift b/SOPT-Test-Code-Study/CalculatorViewModel.swift new file mode 100644 index 0000000..a8a5184 --- /dev/null +++ b/SOPT-Test-Code-Study/CalculatorViewModel.swift @@ -0,0 +1,94 @@ +// +// CalculatorViewModel.swift +// SOPT-Test-Code-Study +// +// Created by 이승준 on 12/23/25. +// + +import Combine + +protocol ViewModelProtocol { + + associatedtype Input + associatedtype Output + + func transform(input: Input) -> Output + +} + +class CalculatorViewModel: ViewModelProtocol { + + struct Input { + let tapNumber = PassthroughSubject() + let tapOperator = PassthroughSubject() + let tapEqual = PassthroughSubject() + let tapClear = PassthroughSubject() + } + + struct Output { + let displayText = CurrentValueSubject("0") + } + + private let service: CalculatorServiceType // DI 적용 + private var cancellables = Set() + + private var lhs: Double? + private var currentOperator: CalcOperator? + private var rhs: Double? + + init(service: CalculatorServiceType = CalculatorService()) { + self.service = service + } + + func transform(input: Input) -> Output { + let output = Output() + + input.tapNumber + .sink { [weak self] number in + guard let self = self else { return } + if self.currentOperator == nil { + self.lhs = number + } else { + self.rhs = number + } + output.displayText.send("\(number)") + } + .store(in: &cancellables) + + input.tapOperator + .sink { [weak self] op in + self?.currentOperator = op + } + .store(in: &cancellables) + + input.tapEqual + .sink { [weak self] in + guard let self = self, + let lhs = self.lhs, + let rhs = self.rhs, + let op = self.currentOperator else { return } + + let result = self.service.calculate(lhs: lhs, rhs: rhs, operator: op) + let resultString = result.isNaN ? "Error" : "\(result)" + output.displayText.send(resultString) + + // 다음 연산을 위해 상태 업데이트 + self.lhs = result + self.rhs = nil + self.currentOperator = nil + } + .store(in: &cancellables) + + // AC + input.tapClear + .sink { [weak self] in + self?.lhs = nil + self?.rhs = nil + self?.currentOperator = nil + output.displayText.send("0") + } + .store(in: &cancellables) + + return output + } +} diff --git a/SOPT-Test-Code-Study/Components/ConfirmButton.swift b/SOPT-Test-Code-Study/Components/ConfirmButton.swift new file mode 100644 index 0000000..84ae4b2 --- /dev/null +++ b/SOPT-Test-Code-Study/Components/ConfirmButton.swift @@ -0,0 +1,77 @@ +// +// ConfirmButton.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/25/25. +// + +import UIKit +import SnapKit + +final class ConfirmButton: UIButton { + + private var toggle = false + var isAvailable: Bool { return toggle } // get-only property + + override init(frame: CGRect) { + super.init(frame: frame) + setConstraints() + } + + private lazy var title: UILabel = { + let label = UILabel() + label.textColor = .white + label.font = UIFont(name: Pretendard.Bold.name(), size: 18) + return label + }() + + private func setConstraints() { + self.addSubview(title) + + self.layer.cornerRadius = 4 + + self.snp.makeConstraints { make in + make.height.equalTo(46) + } + + title.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + func configure(title: String, isAvailable: Bool = false) { + self.title.text = title + toggle = self.isAvailable + if isAvailable { + setAvailableMode() + } else { + setUnavailableMode() + } + } + + func setUnavailableMode() { + self.backgroundColor = .baeminGray200 + toggle = false + self.isEnabled = false + } + + func setAvailableMode() { + self.backgroundColor = .baeminMint300 + toggle = true + self.isEnabled = true + } + + func toggleMode() { + toggle.toggle() + if toggle { + setAvailableMode() + } else { + setUnavailableMode() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/SOPT-Test-Code-Study/Components/CustomNavigationBar.swift b/SOPT-Test-Code-Study/Components/CustomNavigationBar.swift new file mode 100644 index 0000000..9f2e5bd --- /dev/null +++ b/SOPT-Test-Code-Study/Components/CustomNavigationBar.swift @@ -0,0 +1,80 @@ +// +// CustomNavigationBar.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/25/25. +// + +import UIKit +import SnapKit +import Then + +class CustomNavigationBar: UIView { + + var delegate: UIViewController? + + private lazy var backButton = UIButton().then { + $0.setImage(.leftPointer, for: .normal) + $0.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) + } + + private lazy var titleLabel = UILabel().then { + $0.font = UIFont(name: Pretendard.Bold.name(), size: 18) + } + + override init(frame: CGRect) { + super.init(frame: frame) + setConstraints() + } + + func configure(title: String, + isLeftButtonShown: Bool = false, + delegate: UIViewController) { + titleLabel.text = title + backButton.isHidden = isLeftButtonShown + self.delegate = delegate + } + + func setConstraints() { + self.addSubview(backButton) + self.addSubview(titleLabel) + + self.snp.makeConstraints { make in + make.height.equalTo(42) + } + + backButton.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(8) + make.centerY.equalToSuperview() + make.height.width.equalTo(42).priority(.high) + } + + backButton.imageView?.snp.makeConstraints { make in + make.width.height.equalTo(20) + } + + titleLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + @objc func backButtonTapped() { + guard let delegate = delegate else { return } + if delegate.navigationController != nil { + print("pop") + delegate.navigationController?.popViewController(animated: true) + } else { + print("dismiss") + delegate.dismiss(animated: true) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +#Preview { + LoginViewController() +} diff --git a/SOPT-Test-Code-Study/Extensions/UICollectionView+Extension.swift b/SOPT-Test-Code-Study/Extensions/UICollectionView+Extension.swift new file mode 100644 index 0000000..8a18bd6 --- /dev/null +++ b/SOPT-Test-Code-Study/Extensions/UICollectionView+Extension.swift @@ -0,0 +1,27 @@ +// +// UICollectionView.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import UIKit + +extension UICollectionView { + func fetchCellRectFor(indexPath index: IndexPath, + paddingFromLeading: CGFloat, cellHorizontalPadding: CGFloat) -> TapRect { + // 1) + guard let cellAttributes = self.layoutAttributesForItem(at: index) + else { return TapRect(index: 0, width: 0, xPosition: CGPoint()) } + // 2) + let cellOrigin = cellAttributes.frame.origin + // 3) + let cellXPosition: CGPoint = CGPoint( + x: cellOrigin.x + paddingFromLeading, + y: cellOrigin.y) + // 4) + let cellWidth: CGFloat = cellAttributes.size.width - cellHorizontalPadding + + return TapRect(index: index.item, width: cellWidth, xPosition: cellXPosition) + } +} diff --git a/SOPT-Test-Code-Study/Extensions/UICollectionViewCell+Extension.swift b/SOPT-Test-Code-Study/Extensions/UICollectionViewCell+Extension.swift new file mode 100644 index 0000000..7b71f8e --- /dev/null +++ b/SOPT-Test-Code-Study/Extensions/UICollectionViewCell+Extension.swift @@ -0,0 +1,14 @@ +// +// UICollectionView+Extension.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit + +extension UICollectionViewCell { + static func identifier() -> String { + return String(describing: type(of: self)) + } +} diff --git a/SOPT-Test-Code-Study/Extensions/UITextField+Extension.swift b/SOPT-Test-Code-Study/Extensions/UITextField+Extension.swift new file mode 100644 index 0000000..8d8fa00 --- /dev/null +++ b/SOPT-Test-Code-Study/Extensions/UITextField+Extension.swift @@ -0,0 +1,38 @@ +// +// UITextField+Extension.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/27/25. +// + +import UIKit + +extension UITextField { + func addLeftPadding(_ width: CGFloat = 10) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.height)) + self.leftView = paddingView + self.leftViewMode = ViewMode.always + } + + func addRightPadding(_ width: CGFloat = 10) { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.height)) + self.rightView = paddingView + self.rightViewMode = ViewMode.always + } + + /// 텍스트 필드에 좌우 패딩을 한 번에 추가합니다. + func addPadding(leftAmount: CGFloat = 10, rightAmount: CGFloat = 10) { + addLeftPadding(leftAmount) + addRightPadding(rightAmount) + } + + func setEdittingMode() { + self.layer.borderColor = UIColor.baeminBlack.cgColor + self.layer.borderWidth = 2 + } + + func setEdittingEndMode() { + self.layer.borderColor = UIColor.gray.cgColor + self.layer.borderWidth = 1 + } +} diff --git a/SOPT-Test-Code-Study/Extensions/UIViewController+Extension.swift b/SOPT-Test-Code-Study/Extensions/UIViewController+Extension.swift new file mode 100644 index 0000000..d8b4cb8 --- /dev/null +++ b/SOPT-Test-Code-Study/Extensions/UIViewController+Extension.swift @@ -0,0 +1,21 @@ +// +// UIViewController+Extension.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/27/25. +// + +import UIKit + +extension UIViewController { + + func hideKeyboardWhenTappedAround() { + let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard)) + tap.cancelsTouchesInView = false + view.addGestureRecognizer(tap) + } + + @objc func dismissKeyboard() { + view.endEditing(true) + } +} diff --git a/SOPT-Test-Code-Study/Fonts/Fonts.swift b/SOPT-Test-Code-Study/Fonts/Fonts.swift new file mode 100644 index 0000000..7f1d32b --- /dev/null +++ b/SOPT-Test-Code-Study/Fonts/Fonts.swift @@ -0,0 +1,16 @@ +// +// Fonts.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/27/25. +// + +import Foundation + +enum Pretendard: String { + case Regular, Pretendard, ExtraLight, Light, Medium, SemiBold, Bold, ExtraBold, Black + + func name() -> String { + return "Pretendard-" + self.rawValue + } +} diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Black.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Black.otf new file mode 100644 index 0000000..a0d849e Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Black.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Bold.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Bold.otf new file mode 100644 index 0000000..8e5e30a Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Bold.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraBold.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraBold.otf new file mode 100644 index 0000000..388f3ca Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraBold.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraLight.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraLight.otf new file mode 100644 index 0000000..40c8b69 Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-ExtraLight.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Light.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Light.otf new file mode 100644 index 0000000..228679e Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Light.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Medium.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Medium.otf new file mode 100644 index 0000000..0575069 Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Medium.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Regular.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Regular.otf new file mode 100644 index 0000000..08bf4cf Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Regular.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-SemiBold.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-SemiBold.otf new file mode 100644 index 0000000..e7e36ab Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-SemiBold.otf differ diff --git a/SOPT-Test-Code-Study/Fonts/Pretendard-Thin.otf b/SOPT-Test-Code-Study/Fonts/Pretendard-Thin.otf new file mode 100644 index 0000000..77e792d Binary files /dev/null and b/SOPT-Test-Code-Study/Fonts/Pretendard-Thin.otf differ diff --git a/SOPT-Test-Code-Study/Info.plist b/SOPT-Test-Code-Study/Info.plist new file mode 100644 index 0000000..54a3c5e --- /dev/null +++ b/SOPT-Test-Code-Study/Info.plist @@ -0,0 +1,35 @@ + + + + + UIAppFonts + + Pretendard-Black.otf + Pretendard-Bold.otf + Pretendard-ExtraBold.otf + Pretendard-ExtraLight.otf + Pretendard-Light.otf + Pretendard-Medium.otf + Pretendard-Regular.otf + Pretendard-SemiBold.otf + Pretendard-Thin.otf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/SOPT-Test-Code-Study/Models/BannerData.swift b/SOPT-Test-Code-Study/Models/BannerData.swift new file mode 100644 index 0000000..97618fb --- /dev/null +++ b/SOPT-Test-Code-Study/Models/BannerData.swift @@ -0,0 +1,18 @@ +// +// BannerData.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit + +struct BannerData { + var image: UIImage + + static let data: [BannerData] = [ + BannerData(image: .banner01), + BannerData(image: .banner02), + BannerData(image: .banner03), + ] +} diff --git a/SOPT-Test-Code-Study/Models/CategoryData.swift b/SOPT-Test-Code-Study/Models/CategoryData.swift new file mode 100644 index 0000000..eb19273 --- /dev/null +++ b/SOPT-Test-Code-Study/Models/CategoryData.swift @@ -0,0 +1,32 @@ +// +// CategoryData.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit + +struct CategoryData { + var name: String + var cells: [CategoryCellData] + + static let data = [ + CategoryData(name: "음식배달", cells: [ + CategoryCellData(image: .oneBowl, name: "한그릇"), CategoryCellData(image: .chicken, name: "치킨"), + CategoryCellData(image: .cafeDesert, name: "카페·디저트"), CategoryCellData(image: .pizza, name: "피자"), + CategoryCellData(image: .snackFood, name: "분식"), CategoryCellData(image: .meat, name: "고기"), + CategoryCellData(image: .soup, name: "찜·탕"), CategoryCellData(image: .lateFood, name: "야식"), + CategoryCellData(image: .fastfood, name: "패스트푸드"), CategoryCellData(image: .pickUp, name: "픽업"),]), + CategoryData(name: "픽업", cells: []), + CategoryData(name: "장보기·쇼핑", cells: []), + CategoryData(name: "선물하기", cells: []), + CategoryData(name: "헤택모아보기", cells: []), + CategoryData(name: "배민푸드스타", cells: []), + ] +} + +struct CategoryCellData { + var image = UIImage() + var name: String +} diff --git a/SOPT-Test-Code-Study/Models/CollectionType.swift b/SOPT-Test-Code-Study/Models/CollectionType.swift new file mode 100644 index 0000000..606ed0f --- /dev/null +++ b/SOPT-Test-Code-Study/Models/CollectionType.swift @@ -0,0 +1,12 @@ +// +// CollectionType.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import Foundation + +enum CollectionType { + case category, market, banner, rank, categoryCell +} diff --git a/SOPT-Test-Code-Study/Models/ExampleFileManager.swift b/SOPT-Test-Code-Study/Models/ExampleFileManager.swift new file mode 100644 index 0000000..870380d --- /dev/null +++ b/SOPT-Test-Code-Study/Models/ExampleFileManager.swift @@ -0,0 +1,27 @@ +// +// ExampleFileManager.swift +// SOPT-Test-Code-Study +// +// Created by 이승준 on 12/11/25. +// + +import Foundation + +class ExampleFileManager { + + var shouldSucceed: Bool = true + var mockData = "Mock file content for testing." + var mockError: Error? = nil + + func openFileAsync(with path: String, completion: @escaping (String?, Error?) -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self else { return } + if self.shouldSucceed { + completion(self.mockData, nil) + } else { + let error = NSError(domain: "ExampleFileManagerError", code: 404, userInfo: [NSLocalizedDescriptionKey: "File not found during mock test."]) + completion(nil, error) + } + } + } +} diff --git a/SOPT-Test-Code-Study/Models/MarketData.swift b/SOPT-Test-Code-Study/Models/MarketData.swift new file mode 100644 index 0000000..35ddb5d --- /dev/null +++ b/SOPT-Test-Code-Study/Models/MarketData.swift @@ -0,0 +1,27 @@ +// +// MarketData.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit + +struct MarketData { + var name: String + var image: UIImage + + static let data: [MarketData] = [ + MarketData(name: "B마트", image: .baeminMart), + MarketData(name: "CU", image: .cu), + MarketData(name: "이마트슈퍼", image: .emart), + MarketData(name: "홈플러스", image: .homeplus), + MarketData(name: "GS25", image: .gs25), + MarketData(name: "홈플슈퍼", image: .homeplusSuper), + MarketData(name: "이마트24", image: .emart24), + MarketData(name: "GS더프레시", image: .gsTheFresh), + MarketData(name: "JAJU", image: .jaju), + MarketData(name: "펫마트", image: .petMart), + ] +} + diff --git a/SOPT-Test-Code-Study/Models/RankData.swift b/SOPT-Test-Code-Study/Models/RankData.swift new file mode 100644 index 0000000..27276ed --- /dev/null +++ b/SOPT-Test-Code-Study/Models/RankData.swift @@ -0,0 +1,26 @@ +// +// RankData.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit + +struct RankData { + var menuImage: UIImage + var menuName: String + var restaurantName: String + var discountPercent: Float + var reviewAverageScore: Float + var reviewCount: Int + var originalPrice: Int + var discountPrice: Int + + static let data: [RankData] = [ + RankData(menuImage: UIImage(), menuName: "[든든한 한끼]보쌈 막국수", restaurantName: "백억보쌈제육덮밥", discountPercent: 25.0, reviewAverageScore: 5.0, reviewCount: 1973, originalPrice: 16000, discountPrice: 12000), + RankData(menuImage: UIImage(), menuName: "(1인)피자 + 사이드 set", restaurantName: "파파존스", discountPercent: 25.0, reviewAverageScore: 5.0, reviewCount: 1973, originalPrice: 16000, discountPrice: 12000), + RankData(menuImage: UIImage(), menuName: "막국수", restaurantName: "족발", discountPercent: 25.0, reviewAverageScore: 5.0, reviewCount: 1973, originalPrice: 16000, discountPrice: 12000), + ] +} + diff --git a/SOPT-Test-Code-Study/ViewControllers/BaeminFeedViewController.swift b/SOPT-Test-Code-Study/ViewControllers/BaeminFeedViewController.swift new file mode 100644 index 0000000..b173f06 --- /dev/null +++ b/SOPT-Test-Code-Study/ViewControllers/BaeminFeedViewController.swift @@ -0,0 +1,243 @@ +// +// BaeminFeedViewController.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/5/25. +// + +import UIKit +import SnapKit +import Then + +final class BaeminFeedViewController: UIViewController { + + var selectedTapIndex: Int = 0 + var isInitialLayoutDone = false + + private lazy var baeminFeedView = BaeminFeedView().then { + $0.rankCollectionView.delegate = self + $0.rankCollectionView.dataSource = self + $0.bannerCollectionView.delegate = self + $0.bannerCollectionView.dataSource = self + $0.categoryCollectionView.delegate = self + $0.categoryCollectionView.dataSource = self + $0.marketCollectionView.delegate = self + $0.marketCollectionView.dataSource = self + $0.tapViewCollectionView.delegate = self + $0.tapViewCollectionView.dataSource = self + } + + override func viewDidLoad() { + self.view = baeminFeedView + registerCells() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + guard !isInitialLayoutDone else { return } + isInitialLayoutDone = true + + let initialIndexPath = IndexPath(item: 0, section: 0) + let selectedTapRect: TapRect = baeminFeedView.tapViewCollectionView.fetchCellRectFor(indexPath: initialIndexPath, paddingFromLeading: 10, cellHorizontalPadding: 20) + + baeminFeedView.tapViewCollectionView.moveUnderlineFor(at: selectedTapRect) + + self.collectionView(baeminFeedView.tapViewCollectionView, didSelectItemAt: IndexPath(item: 0, section: 0)) + + if let startCell = baeminFeedView.tapViewCollectionView.cellForItem(at: initialIndexPath) as? TapCell { + startCell.selected() + } + } + + private func registerCells() { + baeminFeedView.rankCollectionView.register(RankCell.self, forCellWithReuseIdentifier: RankCell.identifier()) + baeminFeedView.bannerCollectionView.register(BannerCell.self, forCellWithReuseIdentifier: BannerCell.identifier()) + baeminFeedView.categoryCollectionView.register(CategoryCell.self, forCellWithReuseIdentifier: CategoryCell.identifier()) + baeminFeedView.marketCollectionView.register(PebbleCell.self, forCellWithReuseIdentifier: PebbleCell.identifier()) + baeminFeedView.tapViewCollectionView.register(TapCell.self, forCellWithReuseIdentifier: TapCell.identifier()) + } + +} + +extension BaeminFeedViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + switch collectionView { + case baeminFeedView.tapViewCollectionView: + return CategoryData.data.count + case baeminFeedView.categoryCollectionView: + return CategoryData.data.count + case baeminFeedView.marketCollectionView: + return MarketData.data.count + case baeminFeedView.bannerCollectionView: + return BannerData.data.count + case baeminFeedView.rankCollectionView: + return RankData.data.count + default: + return 0 + } + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let collection = collectionView as? BaeminUICollectionView else { + if collectionView == baeminFeedView.tapViewCollectionView { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TapCell.identifier(), for: indexPath) as? TapCell else { + return UICollectionViewCell() + } + cell.configure(CategoryData.data[indexPath.row].name) + return cell + } + return UICollectionViewCell() + } + switch collection.type { + case .rank: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RankCell.identifier(), for: indexPath) as? RankCell else { + return UICollectionViewCell() + } + cell.configure(RankData.data[indexPath.row]) + return cell + case .banner: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BannerCell.identifier(), for: indexPath) as? BannerCell else { + return UICollectionViewCell() + } + cell.configure(BannerData.data[indexPath.row]) + return cell + case .market: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PebbleCell.identifier(), for: indexPath) as? PebbleCell else { + return UICollectionViewCell() + } + cell.configure(MarketData.data[indexPath.row]) + return cell + case .category: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier(), for: indexPath) as? CategoryCell else { + return UICollectionViewCell() + } + cell.configure(data: CategoryData.data[indexPath.row].cells, destination: CategoryData.data[indexPath.row].name, delegate: self, dataSource: self) + return cell + default: + return UICollectionViewCell() + } + } + +} + +extension BaeminFeedViewController: UICollectionViewDelegateFlowLayout { + + // Cell 크기 정의 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + if collectionView == baeminFeedView.tapViewCollectionView { + let categoryName = CategoryData.data[indexPath.item].name + + let label = UILabel().then { + $0.font = UIFont(name: Pretendard.Bold.name(), size: 18) + $0.text = categoryName + $0.sizeToFit() + } + + let cellWidth = label.frame.width + 20 // cell간 간격 20 + let cellHeight: CGFloat = 48 + + return CGSize(width: cellWidth, height: cellHeight) + } + + guard let collection = collectionView as? BaeminUICollectionView else { return .zero } + + switch collection.type { + case .rank : + let width: CGFloat = 145 + let height: CGFloat = 250 + return CGSize(width: width, height: height) + case .banner : + let width: CGFloat = collectionView.frame.self.size.width + let height: CGFloat = 180 + return CGSize(width: width, height: height) + case .market, .categoryCell : + return CGSize(width: 58, height: 74) + case .category : + let width: CGFloat = collectionView.frame.self.size.width + let height: CGFloat = 235 + return CGSize(width: width, height: height) + default: + return .zero + } + } + + // 행간 + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + guard let collection = collectionView as? BaeminUICollectionView else { return 0} + + switch collection.type { + case .rank : + return 16 + case .market : + return 9 + default: + return 0 + } + } + + // mergin + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + guard let collection = collectionView as? BaeminUICollectionView else { return .zero } + + switch collection.type { + case .rank : + return .init(top: 0, left: 20, bottom: 0, right: 20) + case .market : + return .init(top: 11, left: 16, bottom: 11, right: 16) + default : + return .zero + } + } + + // cell 선택에 대한 대응 + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if collectionView == self.baeminFeedView.tapViewCollectionView { + updateTapSelection(to: indexPath) + self.baeminFeedView.categoryCollectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally) + } + } + + // 사용자가 직접 스크롤(스와이프)해서 멈췄을 때 호출됨 + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + if scrollView == baeminFeedView.categoryCollectionView { + // 현재 스크롤 위치를 기준으로 페이지 인덱스 계산 + let newIndex = Int(scrollView.contentOffset.x / scrollView.frame.width) + updateTapSelection(to: IndexPath(row: newIndex, section: 0)) + } + } + + // 탭 클릭 등으로 인해 프로그래밍 방식의 스크롤이 멈췄을 때 호출됨 + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + if scrollView == baeminFeedView.categoryCollectionView { + // 현재 스크롤 위치를 기준으로 페이지 인덱스 계산 + let newIndex = Int(scrollView.contentOffset.x / scrollView.frame.width) + updateTapSelection(to: IndexPath(row: newIndex, section: 0)) + } + } + + // IndexPath를 이용해, Tab의 Underline을 이동 + Label textColor 변화 + private func updateTapSelection(to newIndexPath: IndexPath) { + // gaurd문 위에 있는 이유, 아래에 있으면 Underline이 생성되지 않는다. + let selectedTapRect: TapRect = baeminFeedView.tapViewCollectionView.fetchCellRectFor(indexPath: newIndexPath, paddingFromLeading: 10, cellHorizontalPadding: 20) + baeminFeedView.tapViewCollectionView.moveUnderlineFor(at: selectedTapRect) + + guard newIndexPath.item != selectedTapIndex else { return } // 중복 호출 방지!! + + let oldIndexPath = IndexPath(item: selectedTapIndex, section: 0) + selectedTapIndex = newIndexPath.item + + if let selectedCell = baeminFeedView.tapViewCollectionView.cellForItem(at: newIndexPath) as? TapCell { + selectedCell.selected() + } + if let pastCell = baeminFeedView.tapViewCollectionView.cellForItem(at: oldIndexPath) as? TapCell { + pastCell.deselected() + } + } + +} + +#Preview { + BaeminFeedViewController() +} diff --git a/SOPT-Test-Code-Study/ViewControllers/LoginViewController.swift b/SOPT-Test-Code-Study/ViewControllers/LoginViewController.swift new file mode 100644 index 0000000..1f29248 --- /dev/null +++ b/SOPT-Test-Code-Study/ViewControllers/LoginViewController.swift @@ -0,0 +1,284 @@ +// +// LoginViewController.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/25/25. +// + +import UIKit +import SnapKit +import Then + +final class LoginViewControllerTestHelper { + + enum TextFieldEnum { + case emailId, password, all + } + + let loginButton: ConfirmButton + let emailIdTextField: UITextField + let passwordTextField: UITextField + + init(loginButton: ConfirmButton, emailIdTextField: UITextField, passwordTextField: UITextField) { + self.loginButton = loginButton + self.emailIdTextField = emailIdTextField + self.passwordTextField = passwordTextField + } + + // 입력 값 설정 + func input(text: String, at textField: TextFieldEnum) { + switch textField { + case .emailId: + self.emailIdTextField.text = text + case .password: + self.passwordTextField.text = text + case .all: + self.emailIdTextField.text = text + self.passwordTextField.text = text + } + } + + // 모두 삭제 + func deleteAll(at textField: TextFieldEnum) { + switch textField { + case .emailId: + self.emailIdTextField.text = "" + case .password: + self.passwordTextField.text = "" + case .all: + self.emailIdTextField.text = "" + self.passwordTextField.text = "" + } + } + + // 입력, 삭제, 붙여넣기, 일부삭제 등을 수행할 수 있음 + func textField(at textField: TextFieldEnum, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + switch textField { + case .emailId: + return self.emailIdTextField.delegate?.textField?(self.emailIdTextField, shouldChangeCharactersIn: range, replacementString: string) ?? false + case .password: + return self.passwordTextField.delegate?.textField?(self.passwordTextField, shouldChangeCharactersIn: range, replacementString: string) ?? false + case .all: + return self.emailIdTextField.delegate?.textField?(self.emailIdTextField, shouldChangeCharactersIn: range, replacementString: string) ?? false && self.passwordTextField.delegate?.textField?(self.passwordTextField, shouldChangeCharactersIn: range, replacementString: string) ?? false + } + } + + // 버튼 활성화 여부 확인 + func checkLoginButtonIsEnabled() -> Bool { + return loginButton.isEnabled + } + +} + +class LoginViewController: UIViewController, WelcomeViewControllerDelegate { + + var testHelper: LoginViewControllerTestHelper? + + private lazy var customNavigationBar = CustomNavigationBar().then { + $0.configure(title: "이메일 또는 아이디로 계속", delegate: self) + } + + private lazy var loginButton = ConfirmButton().then { + $0.configure(title: "로그인") + $0.addTarget(self, action: #selector(pushWelcomeViewController), for: .touchUpInside) + } + + private lazy var emailIdTextField = designedTextField().then { + $0.placeholder = "이메일 아이디" + } + + private lazy var passwordTextField = designedTextField().then { + $0.placeholder = "비밀번호" + $0.isSecureTextEntry = true + } + + private lazy var clearPasswordButton = UIButton().then { + $0.setImage(.crossGray, for: .normal) + $0.isHidden = true + $0.addTarget(self, action: #selector(clearTextField), for: .touchUpInside) + } + + private lazy var toggleHidePasswordButton = UIButton().then { + $0.setImage(.eye, for: .normal) + $0.isHidden = true + $0.addTarget(self, action: #selector(toggleHidePasswordButtonTapped), for: .touchUpInside) + } + + private lazy var findAccountButton = UIButton().then { + $0.setTitle("계정 찾기 >", for: .normal) + $0.setTitleColor(.baeminBlack, for: .normal) + $0.titleLabel?.font = UIFont(name: Pretendard.Regular.name(), size: 14) + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + configureUI() + hideKeyboardWhenTappedAround() + self.navigationController?.navigationBar.isHidden = true + } + + func setTestEnvironment() { + self.testHelper = LoginViewControllerTestHelper(loginButton: self.loginButton, emailIdTextField: self.emailIdTextField, passwordTextField: self.passwordTextField) + } + + func didTapGoBackButton() { + emailIdTextField.text = "" + passwordTextField.text = "" + } + + @objc func clearTextField() { + passwordTextField.text = "" + loginButton.setUnavailableMode() + } + + @objc func toggleHidePasswordButtonTapped() { + self.passwordTextField.isSecureTextEntry.toggle() + self.toggleHidePasswordButton.setImage(self.passwordTextField.isSecureTextEntry ? .eye : .eyeSlash , for: .normal) + } + + @objc func pushWelcomeViewController() { + let vc = WelcomeViewController() + vc.configure(email: emailIdTextField.text!, delegate: self) + // 강제 언래핑 : 버튼은 TextField.text가 반드시 채워져야지만 활성화되기 때문에 사용했다. + self.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension LoginViewController: UITextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + textField.setEdittingMode() + if textField == self.passwordTextField { + clearPasswordButton.isHidden = false + toggleHidePasswordButton.isHidden = false + } + } + + func textFieldDidEndEditing(_ textField: UITextField) { + textField.setEdittingEndMode() + if textField == self.passwordTextField { + clearPasswordButton.isHidden = true + toggleHidePasswordButton.isHidden = true + } + } + + // 입력, 삭제할 때마다 호출 + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + // textField.text (편집 이전의 Text) + // string : 추가된 텍스트 (긴 글을 붙여넣을 수 있으므로 String) + + // NSRange: TextField가 모두 지워질 때의 값 알아보기 + // location = 0, length = textField.text.count + + // " " 공백 입력 무시 + if string == " " { + // 추후에 경고를 띄울 수도 있다. + return false + } + + checkLoginButtonValid(textField, range: range, replacementString: string) + return true + } + + // 버튼의 활성화 여부를 결정 + func checkLoginButtonValid(_ textField: UITextField, range: NSRange, replacementString string: String) { + + print("checkLoginButtonValid started") + print("email : \(emailIdTextField.text), password: \(passwordTextField.text)") + if !string.isEmpty // 추가된 것에 대한 동작 + && ((textField == emailIdTextField && passwordTextField.text?.isEmpty == false) + || (textField == passwordTextField && emailIdTextField.text?.isEmpty == false)) + { + print("\(#function) button enabled") + loginButton.setAvailableMode() + } + else if + string.isEmpty // 삭저된 것에 대한 동작 -> 해당 TextField의 값이 모두 지워진 것인지 조사 + && range.location == 0 + && range.length == textField.text?.count + { + print("\(#function) button invalid") + loginButton.setUnavailableMode() + } + print("checkLoginButtonValid ended") + } +} + +// MARK: - UI +extension LoginViewController { + + private func configureUI() { + [customNavigationBar, emailIdTextField, passwordTextField, + clearPasswordButton, loginButton, toggleHidePasswordButton, + findAccountButton].forEach { + view.addSubview($0) + } + + [emailIdTextField, passwordTextField, loginButton].forEach { + $0.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + } + } + + customNavigationBar.snp.makeConstraints { make in + make.leading.trailing.top.equalTo(self.view.safeAreaLayoutGuide) + } + + emailIdTextField.snp.makeConstraints { make in + make.top.equalTo(customNavigationBar.snp.bottom).offset(24) + } + + passwordTextField.snp.makeConstraints { make in + make.top.equalTo(emailIdTextField.snp.bottom).offset(12) + } + + clearPasswordButton.snp.makeConstraints { make in + make.centerY.equalTo(passwordTextField) + make.width.height.equalTo(30) + make.trailing.equalTo(passwordTextField.snp.trailing).offset(-56) + } + + toggleHidePasswordButton.snp.makeConstraints { make in + make.centerY.equalTo(passwordTextField) + make.width.height.equalTo(22) + make.trailing.equalTo(passwordTextField.snp.trailing).offset(-20) + } + + loginButton.snp.makeConstraints { make in + make.top.equalTo(passwordTextField.snp.bottom).offset(20) + } + + findAccountButton.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(loginButton.snp.bottom).offset(32) + } + } + + private func designedTextField() -> UITextField { + let textField = UITextField() + textField.layer.borderWidth = 1 + textField.layer.borderColor = UIColor.gray.cgColor + textField.layer.masksToBounds = true + textField.layer.cornerRadius = 4 + textField.font = UIFont(name: Pretendard.Regular.name(), size: 14) + textField.tintColor = .baeminMint300 + + textField.delegate = self + + textField.addPadding() + textField.snp.makeConstraints { make in + make.height.equalTo(46) + } + + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + return textField + } + +} + +#Preview { + UINavigationController(rootViewController: LoginViewController()) +} diff --git a/SOPT-Test-Code-Study/ViewControllers/TabBarViewController.swift b/SOPT-Test-Code-Study/ViewControllers/TabBarViewController.swift new file mode 100644 index 0000000..7709d7e --- /dev/null +++ b/SOPT-Test-Code-Study/ViewControllers/TabBarViewController.swift @@ -0,0 +1,39 @@ +// +// TabBarViewController.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import UIKit +import Then + +final class TabBarController: UITabBarController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + let firstTabBar = BaeminFeedViewController() + firstTabBar.tabBarItem = UITabBarItem(title: "홈", image: .homeSelected, tag: 0) + + let secondTabBar = UINavigationController(rootViewController: UIViewController().then { $0.view.backgroundColor = .red} ) + secondTabBar.tabBarItem = UITabBarItem(title: "장보기·쇼핑", image: .shop, tag: 1) + + let thirdTabBar = UINavigationController(rootViewController: UIViewController().then { $0.view.backgroundColor = .orange} ) + thirdTabBar.tabBarItem = UITabBarItem(title: "찜", image: .heart, tag: 2) + + let forthTabBar = UINavigationController(rootViewController: UIViewController().then { $0.view.backgroundColor = .yellow} ) + forthTabBar.tabBarItem = UITabBarItem(title: "주문내역", image: .list, tag: 3) + + let fifthTabBar = UINavigationController(rootViewController: UIViewController().then { $0.view.backgroundColor = .green} ) + fifthTabBar.tabBarItem = UITabBarItem(title: "마이배민", image: .myPage, tag: 4) + + //탭바컨트롤러에 뷰 컨트롤러를 array형식으로 넣어주면 탭바가 완성됩니다. + self.viewControllers = [firstTabBar, secondTabBar, thirdTabBar, forthTabBar, fifthTabBar] + } +} diff --git a/SOPT-Test-Code-Study/ViewControllers/WelcomeViewController.swift b/SOPT-Test-Code-Study/ViewControllers/WelcomeViewController.swift new file mode 100644 index 0000000..dadb9bb --- /dev/null +++ b/SOPT-Test-Code-Study/ViewControllers/WelcomeViewController.swift @@ -0,0 +1,117 @@ +// +// WelcomeViewController.swift +// SOPT-Assignment +// +// Created by 이승준 on 10/27/25. +// + +import UIKit +import SnapKit +import Then + +protocol WelcomeViewControllerDelegate: AnyObject { + func didTapGoBackButton() +} + +class WelcomeViewController: UIViewController { + + private weak var delegate: WelcomeViewControllerDelegate? + // WelcomeVC는 LoginVC의 참조 카운트를 증가시키지 않는다. + // 0. LoginVC는 let WelcomeVC로 참조 카운트를 증가시킨다. + // 1. welcomeVC는 메모리에서 해제된다. + // 2. LoginVC의 메서드를 동작시킨다. + // 강한 순환 참조가 일어나는 조건 + // 2.1. 만약, 메서드가 오래 걸리는 비동기 함수이고 강한 참조인 경우 + // Login +2 / Welcome +1 + // 2.2. 메서드의 종료 이전에 VC가 화면에서 사라진 경우 + // Login +1 / Welcome +1 + // 2.3. LoginVC와 WelcomeVC 간의 강한 순환 참조가 발생하게 된다. + // Login <-> Welcome + // 3. weak 이므로 LoginVC는 메모리에서 해제된다. -> welcomeVC 또한 해제된다. + // Login +1 / Welcome +1 + // Login 0 -> deinit -> Welcome 0 -> deinit + + private lazy var navigationBar = CustomNavigationBar().then { + $0.configure(title: "대체 누가 뼈짐 시켰어??", delegate: self) + } + + private lazy var imaeView = UIImageView().then { + $0.image = .welcome + } + + private lazy var welcomeMainLabel = UILabel().then { + $0.text = "환영합니다." + $0.textAlignment = .center + $0.font = UIFont(name: Pretendard.Bold.name(), size: 24) + } + + private lazy var welcomeSubLabel = UILabel().then { + $0.text = "반가워요" + $0.textAlignment = .center + $0.font = UIFont(name: Pretendard.SemiBold.name(), size: 18) + $0.numberOfLines = 1 + } + + private lazy var goBackButton = ConfirmButton().then { + $0.configure(title: "뒤로가기", isAvailable: true) + $0.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemBackground + addSubviews() + setConstraints() + } + + @objc func backButtonTapped() { + let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate + sceneDelegate?.changeRootViewController(TabBarController(), animated: false) + } + + func configure(email: String, delegate: WelcomeViewControllerDelegate) { + welcomeSubLabel.text = "\(email)님 반가워요!" + self.delegate = delegate + } + + func addSubviews() { + [navigationBar, imaeView, welcomeMainLabel, welcomeSubLabel, goBackButton].forEach { + view.addSubview($0) + } + } + + func setConstraints() { + [navigationBar, imaeView, welcomeMainLabel, welcomeSubLabel].forEach { + $0.snp.makeConstraints{ make in + make.leading.trailing.equalToSuperview() + } + } + + navigationBar.snp.makeConstraints { make in + make.top.equalTo(self.view.safeAreaLayoutGuide) + } + + imaeView.snp.makeConstraints { make in + make.top.equalTo(navigationBar.snp.bottom) + make.height.equalTo(imaeView.snp.width).multipliedBy(211.0/375.0) + } + + welcomeMainLabel.snp.makeConstraints { make in + make.top.equalTo(imaeView.snp.bottom).offset(24) + } + + welcomeSubLabel.snp.makeConstraints { make in + make.top.equalTo(welcomeMainLabel.snp.bottom).offset(14) + } + + goBackButton.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.bottom.equalToSuperview().offset(-48) + } + } + +} + +#Preview { + WelcomeViewController() +} diff --git a/SOPT-Test-Code-Study/Views/BaeminFeedView.swift b/SOPT-Test-Code-Study/Views/BaeminFeedView.swift new file mode 100644 index 0000000..e088795 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/BaeminFeedView.swift @@ -0,0 +1,371 @@ +// +// BaeminFeedView.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/5/25. +// + +import UIKit +import SnapKit +import Then + +class BaeminFeedView: UIView { + + private lazy var stickyHeader = UIView() + + private lazy var locationButton = UIButton().then { + $0.setTitle("우리집", for: .normal) + $0.setTitleColor(.baeminBlack, for: .normal) + $0.titleLabel?.font = UIFont(name: Pretendard.Bold.name(), size: 16) + } + + private lazy var cartButton = UIButton().then { + $0.setImage(.cart, for: .normal) + } + + private lazy var notificationButton = UIButton().then { + $0.setImage(.notification, for: .normal) + } + + private lazy var discountButton = UIButton().then { + $0.setImage(.divider, for: .normal) + } + + private lazy var searchTextField = UITextField().then { + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.baeminBlack.cgColor + $0.layer.masksToBounds = true + $0.layer.cornerRadius = 20 + $0.font = UIFont(name: Pretendard.Regular.name(), size: 14) + $0.tintColor = .baeminMint300 + $0.backgroundColor = .white + + $0.addPadding() + $0.snp.makeConstraints { make in + make.height.equalTo(46) + } + + $0.autocapitalizationType = .none + $0.autocorrectionType = .no + + $0.placeholder = "찾아라! 맛있는 음식과 맛집" + } + + private lazy var searchIcon = UIImageView().then { + $0.image = .search + } + + private lazy var feedScrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.showsHorizontalScrollIndicator = false + return scrollView + }() + + private lazy var feedContentView = UIView() + + private let headGradientLayer = CAGradientLayer().then { + // 참고 블로그 : https://babbab2.tistory.com/55 + $0.colors = [ + UIColor.baeminBackgroundWhite.cgColor, + UIColor.baeminMint300.cgColor.copy(alpha: 0.5) ?? UIColor.baeminMint300.cgColor, + ] + // 중앙의 y 0에서 중앙의 y 1로 이동한다. + $0.startPoint = CGPoint(x: 0.5, y: 0.0) + $0.endPoint = CGPoint(x: 0.5, y: 1.0) + + $0.locations = [0, 1.0] + } + + private lazy var headGradientView = UIView().then { + $0.layer.addSublayer(headGradientLayer) + } + + private lazy var bmartImageView = UIImageView().then { + $0.image = .bmart + $0.contentMode = .scaleAspectFit + } + + private lazy var promotionButton = UIButton().then { + $0.setTitle("전상품 쿠폰팩 + 60%특가 >", for: .normal) + $0.setTitleColor(.baeminBlack, for: .normal) + $0.titleLabel?.font = UIFont(name: Pretendard.Bold.name(), size: 16) + } + + lazy var tapViewCollectionView: TapCollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 12 + layout.minimumInteritemSpacing = 8 + + let collection = TapCollectionView(frame: .zero, collectionViewLayout: layout) + collection.clipsToBounds = true + collection.layer.cornerRadius = 20 + collection.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] // top-left + top-right + collection.layer.shadowColor = UIColor.baeminMint500.cgColor + collection.layer.shadowOffset = CGSize(width: 0, height: 2) + + collection.backgroundColor = .white + return collection + }() + + lazy var categoryCollectionView: BaeminUICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 12 + layout.minimumInteritemSpacing = 8 + layout.sectionInset = .init(top: 12, left: 28, bottom: 21, right: 18) + + let collection = BaeminUICollectionView(frame: .zero, collectionViewLayout: layout) + collection.type = .category + collection.backgroundColor = .white + collection.showsHorizontalScrollIndicator = false + collection.isPagingEnabled = true + + return collection + }() + + lazy var marketCollectionView: BaeminUICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 9 + layout.sectionInset = .init(top: 11, left: 16, bottom: 11, right: 16) + + let collection = BaeminUICollectionView(frame: .zero, collectionViewLayout: layout) + collection.type = .market + collection.backgroundColor = .white + collection.showsHorizontalScrollIndicator = false + + return collection + }() + + lazy var bannerCollectionView: BaeminUICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + + let collection = BaeminUICollectionView(frame: .zero, collectionViewLayout: layout) + collection.type = .banner + collection.isPagingEnabled = true + collection.backgroundColor = .white + collection.showsHorizontalScrollIndicator = false + collection.showsVerticalScrollIndicator = false + + return collection + }() + + private let rankGradientLayer = CAGradientLayer().then { + $0.colors = [ + UIColor.baeminBlue500.cgColor, + UIColor.baeminBackgroundWhite.cgColor + ] + // 중앙의 y 0에서 중앙의 y 1로 이동한다. + $0.startPoint = CGPoint(x: 0.5, y: 0.0) + $0.endPoint = CGPoint(x: 0.5, y: 1.0) + + $0.locations = [0, 1.0] + } + + private lazy var rankGradientView = UIView().then { + $0.layer.addSublayer(rankGradientLayer) + } + + private lazy var rankLabel = UILabel().then { + $0.text = "우리 동네 한그릇 인기 랭킹" + $0.textColor = .white + $0.font = UIFont(name: Pretendard.SemiBold.name(), size: 18) + } + + private lazy var whiteInfoImage = UIImageView().then { + $0.image = .infoWhite + $0.contentMode = .scaleAspectFit + } + + private lazy var seeAllButton = UIButton().then { + $0.setTitle("전체보기 >", for: .normal) + $0.titleLabel?.font = UIFont(name: Pretendard.Regular.name(), size: 14) + $0.setTitleColor(.white, for: .normal) + } + + lazy var rankCollectionView: BaeminUICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + + let collection = BaeminUICollectionView(frame: .zero, collectionViewLayout: layout) + collection.type = .rank + collection.backgroundColor = .clear + collection.showsHorizontalScrollIndicator = false + + return collection + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .baeminBackgroundWhite + setSubviews([stickyHeader, + locationButton, cartButton, notificationButton, discountButton, + searchTextField, searchIcon, + feedScrollView]) + feedScrollView.addSubview(feedContentView) + [headGradientView, bmartImageView, promotionButton, + tapViewCollectionView, + categoryCollectionView, + marketCollectionView, + bannerCollectionView, + rankGradientView, rankLabel, whiteInfoImage, seeAllButton, + rankCollectionView, ].forEach { + feedContentView.addSubview($0) + } + setHeaderUIs() + setFeedUIs() + } + + override func layoutSubviews() { + super.layoutSubviews() + // 제약 조건이 모두 결정된 이후에 Gradient의 frame을 잡아주어야 한다. + headGradientLayer.frame = headGradientView.bounds + rankGradientLayer.frame = rankGradientView.bounds + } + + func setHeaderUIs() { + stickyHeader.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide) + $0.horizontalEdges.equalToSuperview().inset(16) + $0.height.equalTo(74) + } + + locationButton.snp.makeConstraints { make in + make.height.equalTo(24) + make.leading.top.equalTo(stickyHeader) + } + + cartButton.snp.makeConstraints { make in + make.size.equalTo(24) + make.trailing.top.equalTo(stickyHeader) + } + + notificationButton.snp.makeConstraints { make in + make.size.equalTo(24) + make.top.equalTo(stickyHeader) + make.trailing.equalTo(cartButton.snp.leading).offset(-12) + } + + discountButton.snp.makeConstraints { make in + make.size.equalTo(24) + make.top.equalTo(stickyHeader) + make.trailing.equalTo(notificationButton.snp.leading).offset(-12) + } + + searchTextField.snp.makeConstraints { make in + make.height.equalTo(40) + make.horizontalEdges.bottom.equalTo(stickyHeader) + } + + searchIcon.snp.makeConstraints { make in + make.trailing.equalTo(searchTextField).inset(20) + make.centerY.equalTo(searchTextField) + } + } + + func setFeedUIs() { + // Scroll이 가능하게 해주는 영역 + feedScrollView.snp.makeConstraints { make in + make.horizontalEdges.bottom.equalToSuperview() + make.top.equalTo(stickyHeader.snp.bottom) + } + + feedContentView.snp.makeConstraints { make in + make.edges.equalTo(feedScrollView) + make.width.equalTo(feedScrollView) + make.height.greaterThanOrEqualToSuperview().priority(.low) + } + + headGradientView.snp.makeConstraints { make in + make.horizontalEdges.equalTo(feedContentView) + make.top.equalTo(feedContentView).offset(20) + make.height.equalTo(100) + } + + bmartImageView.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(16) + make.top.equalToSuperview().offset(24) + make.height.equalTo(20) + make.width.equalTo(60) + } + + promotionButton.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(16) + make.top.equalTo(bmartImageView.snp.bottom).offset(5) + } + + tapViewCollectionView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(headGradientView.snp.bottom).offset(-20) + make.height.equalTo(48) + } + + categoryCollectionView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(tapViewCollectionView.snp.bottom).offset(2) + make.height.equalTo(233) // 10 + 168 + 21 + } + + marketCollectionView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(categoryCollectionView.snp.bottom).offset(10) + make.height.equalTo(96) + } + + bannerCollectionView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(marketCollectionView.snp.bottom).offset(10) + make.height.equalTo(114) + } + + rankGradientView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(bannerCollectionView.snp.bottom).offset(10) + make.height.equalTo(100) + } + + rankLabel.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(16) + make.top.equalTo(rankGradientView.snp.top).offset(24) + } + + whiteInfoImage.snp.makeConstraints { make in + make.size.equalTo(16) + make.leading.equalTo(rankLabel.snp.trailing).offset(5) + make.centerY.equalTo(rankLabel) + } + + seeAllButton.snp.makeConstraints { make in + make.trailing.equalTo(rankGradientView.snp.trailing).inset(16) + make.height.equalTo(20) + make.centerY.equalTo(rankLabel) + } + + rankCollectionView.snp.makeConstraints { make in + make.horizontalEdges.equalToSuperview() + make.top.equalTo(rankGradientView.snp.bottom).offset(-40) + make.height.equalTo(243) + make.bottom.equalToSuperview().offset(-30) + } + + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension UIView { + func setSubviews(_ views: [UIView]) { + views.forEach({ self.addSubview($0) }) + } +} + +#Preview { + BaeminFeedViewController() +} diff --git a/SOPT-Test-Code-Study/Views/BaeminUICollectionView.swift b/SOPT-Test-Code-Study/Views/BaeminUICollectionView.swift new file mode 100644 index 0000000..128a2a9 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/BaeminUICollectionView.swift @@ -0,0 +1,25 @@ +// +// BaeminUICollectionView.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import UIKit + + +final class BaeminUICollectionView: UICollectionView { + var type: CollectionType? + + override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setType(_ type: CollectionType) { + self.type = type + } +} diff --git a/SOPT-Test-Code-Study/Views/BannerCell.swift b/SOPT-Test-Code-Study/Views/BannerCell.swift new file mode 100644 index 0000000..94185ad --- /dev/null +++ b/SOPT-Test-Code-Study/Views/BannerCell.swift @@ -0,0 +1,34 @@ +// +// BannerCell.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit +import SnapKit +import Then + +final class BannerCell: UICollectionViewCell { + + private lazy var bannerImage = UIImageView().then { + $0.contentMode = .scaleAspectFill + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.addSubview(bannerImage) + bannerImage.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(_ data: BannerData) { + self.bannerImage.image = data.image + } +} + diff --git a/SOPT-Test-Code-Study/Views/CategoryCell.swift b/SOPT-Test-Code-Study/Views/CategoryCell.swift new file mode 100644 index 0000000..d9e1c48 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/CategoryCell.swift @@ -0,0 +1,135 @@ +// +// CategoryCell.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import UIKit +import SnapKit +import Then + +final class CategoryCell: UICollectionViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + var goDetail: (() -> Void)? + var data: [CategoryCellData]? + + private lazy var mainCollection: BaeminUICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + + let collection = BaeminUICollectionView(frame: .zero, collectionViewLayout: layout) + collection.type = .categoryCell + collection.isPagingEnabled = true + collection.backgroundColor = .white + collection.showsHorizontalScrollIndicator = false + collection.showsVerticalScrollIndicator = false + + return collection + }() + + private lazy var goDetailButton = UIButton().then { + $0.backgroundColor = .white + $0.addTarget(self, action: #selector(goDetailTapped), for: .touchUpInside) + } + + private lazy var moreInfoHStack = UIStackView().then { + $0.axis = .horizontal + } + + private lazy var moreInfoLabel = UILabel().then { + $0.font = UIFont(name: Pretendard.Bold.name(), size: 14) + $0.text = "음식배달" + } + + private lazy var moreInfoSubLabel = UILabel().then { + $0.font = UIFont(name: Pretendard.Regular.name(), size: 14) + $0.text = "에서 더보기 " + } + + private lazy var moreInfoArrowImageView = UIImageView().then { + $0.image = .rightArrow + $0.contentMode = .scaleAspectFit + } + + @objc func goDetailTapped() { + self.goDetail?() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setUI() + mainCollection.register(PebbleCell.self, forCellWithReuseIdentifier: PebbleCell.identifier()) + } + + func setUI() { + self.backgroundColor = .baeminBackgroundWhite + self.addSubview(mainCollection) + self.addSubview(goDetailButton) + self.addSubview(moreInfoHStack) + + mainCollection.snp.makeConstraints { make in + make.top.horizontalEdges.equalToSuperview() + make.size.equalTo(201) + } + + goDetailButton.snp.makeConstraints { make in + make.horizontalEdges.bottom.equalToSuperview() + make.top.equalTo(mainCollection.snp.bottom).offset(2) + make.height.equalTo(34) + } + + moreInfoHStack.snp.makeConstraints { make in + make.center.equalTo(goDetailButton) + make.height.equalTo(34) + } + + moreInfoHStack.addArrangedSubview(moreInfoLabel) + moreInfoHStack.addArrangedSubview(moreInfoSubLabel) + moreInfoHStack.addArrangedSubview(moreInfoArrowImageView) + + moreInfoArrowImageView.snp.makeConstraints { make in + make.size.equalTo(6) + } + } + + func configure(data: [CategoryCellData], destination: String, + delegate: UICollectionViewDelegate, dataSource: UICollectionViewDataSource) { + self.data = data + mainCollection.dataSource = self + mainCollection.delegate = self + mainCollection.reloadData() + + moreInfoLabel.text = destination + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return data?.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PebbleCell.identifier(), for: indexPath) as? PebbleCell else { + return UICollectionViewCell() + } + cell.configure(data?[indexPath.row] ?? .init(image: UIImage(), name: "")) + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: 62, height: 78) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return .init(top: 10, left: 32, bottom: 10, right: 32) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 8 + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/SOPT-Test-Code-Study/Views/PebbleCell.swift b/SOPT-Test-Code-Study/Views/PebbleCell.swift new file mode 100644 index 0000000..89f8c12 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/PebbleCell.swift @@ -0,0 +1,66 @@ +// +// PebbleCell.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit +import SnapKit +import Then + +final class PebbleCell: UICollectionViewCell { + + private lazy var title = UILabel().then { + $0.font = UIFont(name: Pretendard.Regular.name(), size: 14) + } + + private lazy var imageView = UIImageView().then { + $0.backgroundColor = .baeminBackgroundWhite + $0.clipsToBounds = true + $0.contentMode = .scaleAspectFill + $0.layer.cornerRadius = 20 + } + + override init(frame: CGRect) { + super.init(frame: frame) + setUI() + } + + func configure(_ data: MarketData) { + title.text = data.name + imageView.image = data.image + } + + func configure(_ data: CategoryCellData) { + title.text = data.name + imageView.image = data.image + } + + func setUI() { + self.addSubview(title) + self.addSubview(imageView) + + self.snp.makeConstraints { make in + make.width.equalTo(62) + make.height.equalTo(78) + } + + title.snp.makeConstraints { make in + make.bottom.centerX.equalToSuperview() + } + + imageView.snp.makeConstraints { make in + make.horizontalEdges.top.equalToSuperview() + make.size.equalTo(58) + } + + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + diff --git a/SOPT-Test-Code-Study/Views/RankCell.swift b/SOPT-Test-Code-Study/Views/RankCell.swift new file mode 100644 index 0000000..86a3217 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/RankCell.swift @@ -0,0 +1,146 @@ +// +// RankCell.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit +import SnapKit +import Then + +final class RankCell: UICollectionViewCell { + + private lazy var mainImageView = UIImageView().then { + $0.layer.cornerRadius = 8 + $0.clipsToBounds = true + $0.backgroundColor = .baeminGray200 + } + + private lazy var restaurantNameLabel = commonLabel() + + private lazy var starImageView = UIImageView().then { + $0.image = .reviewStar + $0.contentMode = .scaleAspectFit + } + + private lazy var reviewAverageScoreLabel = commonLabel() + private lazy var reviewCountLabel = commonLabel() + + private lazy var menuNameLabel = UILabel().then { + $0.text = "메뉴 이름" + $0.textColor = .baeminBlack + $0.font = UIFont(name: Pretendard.Regular.name(), size: 14) + } + private lazy var discountPercentLabel = UILabel().then { + $0.text = "25%" + $0.textColor = .baeminRed + $0.font = UIFont(name: Pretendard.Bold.name(), size: 14) + } + private lazy var discountPriceLabel = UILabel().then { + $0.text = "12,000원" + $0.textColor = .baeminBlack + $0.font = UIFont(name: Pretendard.Bold.name(), size: 14) + } + private lazy var originalPriceLabel = UILabel().then { + $0.text = "16,000원" + $0.textColor = UIColor.gray.withAlphaComponent(0.5) + $0.font = UIFont(name: Pretendard.Regular.name(), size: 12) + } + private lazy var noMinimumLabel = UILabel().then { + $0.text = "최소주문금액 없음" + $0.textColor = .baeminBlue700 + $0.font = UIFont(name: Pretendard.Bold.name(), size: 13) + } + + override init(frame: CGRect) { + super.init(frame: frame) + addSubviews() + setUI() + } + + func configure(_ data: RankData) { + mainImageView.image = data.menuImage + restaurantNameLabel.text = data.restaurantName + reviewAverageScoreLabel.text = "\(data.reviewAverageScore)" + reviewCountLabel.text = "(\(data.reviewCount))" + menuNameLabel.text = data.menuName + discountPercentLabel.text = "\(data.discountPercent)%" + discountPriceLabel.text = "\(data.discountPrice)원" + originalPriceLabel.text = "\(data.originalPrice)원" + } + + func addSubviews() { + [mainImageView, + restaurantNameLabel,starImageView, reviewAverageScoreLabel,reviewCountLabel, + menuNameLabel, + discountPercentLabel, discountPriceLabel, + originalPriceLabel,noMinimumLabel].forEach{ + self.addSubview($0) + } + } + + func setUI() { + mainImageView.snp.makeConstraints { make in + make.horizontalEdges.top.equalToSuperview() + make.size.equalTo(145) + } + reviewCountLabel.snp.makeConstraints { make in + make.trailing.equalToSuperview() + make.top.equalTo(mainImageView.snp.bottom).offset(9) + make.height.equalTo(12) + make.width.lessThanOrEqualTo(35) + } + reviewAverageScoreLabel.snp.makeConstraints { make in + make.centerY.equalTo(reviewCountLabel) + make.trailing.equalTo(reviewCountLabel.snp.leading) + make.height.equalTo(12) + make.width.equalTo(17) + } + starImageView.snp.makeConstraints { make in + make.centerY.equalTo(reviewCountLabel) + make.trailing.equalTo(reviewAverageScoreLabel.snp.leading) + make.size.equalTo(12) + } + restaurantNameLabel.snp.makeConstraints { make in + make.centerY.equalTo(reviewCountLabel) + make.leading.equalToSuperview() + make.trailing.equalTo(starImageView.snp.leading).priority(.medium) + make.height.equalTo(12) + } + menuNameLabel.snp.makeConstraints { make in + make.top.equalTo(reviewCountLabel.snp.bottom) + make.horizontalEdges.equalToSuperview() + } + discountPercentLabel.snp.makeConstraints { make in + make.leading.equalToSuperview() + make.top.equalTo(menuNameLabel.snp.bottom).offset(6) + } + discountPriceLabel.snp.makeConstraints { make in + make.leading.equalTo(discountPercentLabel.snp.trailing).offset(6) + make.centerY.equalTo(discountPercentLabel) + } + originalPriceLabel.snp.makeConstraints { make in + make.leading.equalToSuperview() + make.top.equalTo(discountPriceLabel.snp.bottom).offset(6) + } + noMinimumLabel.snp.makeConstraints { make in + make.leading.equalToSuperview() + make.top.equalTo(originalPriceLabel.snp.bottom).offset(6) + } + } + + func commonLabel() -> UILabel { + return UILabel().then { + $0.font = UIFont(name: Pretendard.Regular.name(), size: 12) + $0.textColor = UIColor.baeminGray600 + $0.numberOfLines = 0 + } + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/SOPT-Test-Code-Study/Views/TapCell.swift b/SOPT-Test-Code-Study/Views/TapCell.swift new file mode 100644 index 0000000..6d04ca2 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/TapCell.swift @@ -0,0 +1,46 @@ +// +// TapCell.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/6/25. +// + +import UIKit +import SnapKit +import Then + +final class TapCell: UICollectionViewCell { + + private lazy var label = UILabel().then { + $0.font = UIFont(name: Pretendard.Bold.name(), size: 18) + $0.textColor = .baeminGray300 + $0.textAlignment = .center + } + + override init(frame: CGRect) { + super .init(frame: frame) + self.addSubview(label) + label.snp.makeConstraints { make in + make.height.equalTo(48) + make.centerX.equalToSuperview() + make.width.greaterThanOrEqualTo(20) + } + } + + func selected() { + label.textColor = .black + } + + func deselected() { + label.textColor = .baeminGray300 + } + + func configure(_ title: String) { + label.text = title + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/SOPT-Test-Code-Study/Views/TapCollectionView.swift b/SOPT-Test-Code-Study/Views/TapCollectionView.swift new file mode 100644 index 0000000..d73eaa0 --- /dev/null +++ b/SOPT-Test-Code-Study/Views/TapCollectionView.swift @@ -0,0 +1,72 @@ +// +// TabCollectionView.swift +// SOPT-Assignment +// +// Created by 이승준 on 11/7/25. +// + +import UIKit +import SnapKit +import Then + +struct TapRect { + var index: Int + var width: CGFloat + var xPosition: CGPoint +} + +final class TapCollectionView: UICollectionView { + + private let underlineView = UIView() + + override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + basicSetup() + setStyle() + setLayout() + } + + private func basicSetup() { + self.backgroundColor = .clear + self.isScrollEnabled = true + self.showsHorizontalScrollIndicator = false + } + + private func setStyle() { + underlineView.do { + $0.backgroundColor = .black + } + } + + private func setLayout() { + self.addSubview(underlineView) + + underlineView.snp.makeConstraints { + $0.height.equalTo(3.5) + $0.centerY.equalToSuperview().offset(20) + $0.width.equalTo(14) + $0.leading.equalToSuperview().inset(15) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension TapCollectionView { + + func moveUnderlineFor(at tappedRect: TapRect, isAnimation: Bool = true) { + underlineView.snp.updateConstraints { + $0.height.equalTo(3.5) + $0.centerY.equalToSuperview().offset(20) + $0.width.equalTo(tappedRect.width) + $0.leading.equalTo(tappedRect.xPosition.x) + } + if isAnimation { + UIView.animate(withDuration: 0.3) { + self.layoutIfNeeded() + } + } + } +} diff --git a/SOPT-Test-Code-StudyTests/Asynchronous_Test.swift b/SOPT-Test-Code-StudyTests/Asynchronous_Test.swift new file mode 100644 index 0000000..ff930e6 --- /dev/null +++ b/SOPT-Test-Code-StudyTests/Asynchronous_Test.swift @@ -0,0 +1,53 @@ +// +// Asynchronous_Test.swift +// SOPT-Test-Code-StudyTests +// +// Created by 이승준 on 12/11/25. +// + +import XCTest +@testable import SOPT_Test_Code_Study + +final class Asynchronous_Test: XCTestCase { + + var fileManager: ExampleFileManager? + + override func setUpWithError() throws { + fileManager = ExampleFileManager() + } + + override func tearDownWithError() throws { + + } + + func testDownloadWebDataWithConcurrency() async throws { + // Create a URL for a webpage to download. + let url = URL(string: "https://apple.com")! + + // Use an asynchronous function to download the webpage. + let dataAndResponse: (data: Data, response: URLResponse) = try await URLSession.shared.data(from: url, delegate: nil) + + // Assert that the actual response matches the expected response. + let httpResponse = try XCTUnwrap(dataAndResponse.response as? HTTPURLResponse, "Expected an HTTPURLResponse.") + XCTAssertEqual(httpResponse.statusCode, 200, "Expected a 200 OK response.") + } + + func testFileManager() { + + let expectation = XCTestExpectation(description: "Open a file asynchronously.") + + fileManager!.openFileAsync(with: "testPath/file.txt") { file, error in + // Assert that the asynchronous task worked. + XCTAssertNotNil(file, "Expected to load a file.") + + // Assert that no errors occurred opening the file asynchronously. + XCTAssertNil(error, "Expected no errors loading a file.") + + // Fulfill the expectation. + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } + +} diff --git a/SOPT-Test-Code-StudyTests/CalculatorServiceTest.swift b/SOPT-Test-Code-StudyTests/CalculatorServiceTest.swift new file mode 100644 index 0000000..fe29338 --- /dev/null +++ b/SOPT-Test-Code-StudyTests/CalculatorServiceTest.swift @@ -0,0 +1,90 @@ +// +// CalculatorServiceTests.swift +// SOPT-Test-Code-StudyTests +// +// Created by 이승준 on 12/23/25. +// + +import XCTest +import Combine +@testable import SOPT_Test_Code_Study + +final class CalculatorServiceTests: XCTestCase { + + private var sut: CalculatorService! + + override func setUp() { + super.setUp() + sut = CalculatorService() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - 일반 케이스 (Normal Cases) + + func test_add_1And2_returns3() { + // given + let lhs = 1.0 + let rhs = 2.0 + + // when + let result = sut.calculate(lhs: lhs, rhs: rhs, operator: .add) + + // then + XCTAssertEqual(result, 3.0, "1 + 2는 3이어야 합니다.") + } + + func test_subtract_5And2_returns3() { + // given + let lhs = 5.0 + let rhs = 2.0 + + // when + let result = sut.calculate(lhs: lhs, rhs: rhs, operator: .subtract) + + // then + XCTAssertEqual(result, 3.0, "5 - 2는 3이어야 합니다.") + } + + func test_multiply_4And3_returns12() { + // given + let lhs = 4.0 + let rhs = 3.0 + + // when + let result = sut.calculate(lhs: lhs, rhs: rhs, operator: .multiply) + + // then + XCTAssertEqual(result, 12.0, "4 * 3은 12여야 합니다.") + } + + func test_divide_10By2_returns5() { + // given + let lhs = 10.0 + let rhs = 2.0 + + // when + let result = sut.calculate(lhs: lhs, rhs: rhs, operator: .divide) + + // then + XCTAssertEqual(result, 5.0, "10 / 2는 5여야 합니다.") + } + + // MARK: - 엣지 케이스 (Edge Cases) + + func test_divide_byZero_returnsNaN() { + // given + let lhs = 10.0 + let rhs = 0.0 + + // when + let result = sut.calculate(lhs: lhs, rhs: rhs, operator: .divide) + + // then + // NaN은 '==' 비교가 불가능하므로 isNaN 프로퍼티를 사용합니다. + XCTAssertTrue(result.isNaN, "0으로 나누면 NaN(Not a Number)이 반환되어야 합니다.") + } +} diff --git a/SOPT-Test-Code-StudyTests/CalculatorViewModelTests.swift b/SOPT-Test-Code-StudyTests/CalculatorViewModelTests.swift new file mode 100644 index 0000000..993ad8b --- /dev/null +++ b/SOPT-Test-Code-StudyTests/CalculatorViewModelTests.swift @@ -0,0 +1,91 @@ +// +// CalculatorViewModelTests.swift +// SOPT-Test-Code-StudyTests +// +// Created by 이승준 on 12/23/25. +// + +import XCTest +import Combine +@testable import SOPT_Test_Code_Study + +final class CalculatorViewModelTests: XCTestCase { + + private var sut: CalculatorViewModel! + private var input: CalculatorViewModel.Input! + private var output: CalculatorViewModel.Output! + private var cancellables: Set! + + override func setUp() { + super.setUp() + sut = CalculatorViewModel() + input = CalculatorViewModel.Input() + output = sut.transform(input: input) + cancellables = [] + } + + override func tearDown() { + sut = nil + input = nil + output = nil + cancellables = nil + super.tearDown() + } + + func test_initialState_showsZero() { + XCTAssertEqual(output.displayText.value, "0", "초기 화면은 0이어야 합니다.") + } + + func test_tapNumber_updatesDisplayText() { + // given + let numberToTap = 5.0 + + // when + input.tapNumber.send(numberToTap) + + // then + XCTAssertEqual(output.displayText.value, "5.0", "숫자를 누르면 해당 숫자가 화면에 표시되어야 합니다.") + } + + func test_addition_showsCorrectResult() { + // given + var receivedValues: [String] = [] + output.displayText + .sink { receivedValues.append($0) } + .store(in: &cancellables) + + // when + input.tapNumber.send(1.0) + input.tapOperator.send(.add) + input.tapNumber.send(2.0) + input.tapEqual.send(()) + + // then + XCTAssertEqual(receivedValues.last, "3.0", "1 + 2의 결과는 3이어야 합니다.") + } + + func test_divideByZero_showsError() { + // given + input.tapNumber.send(10.0) + input.tapOperator.send(.divide) + input.tapNumber.send(0.0) + + // when + input.tapEqual.send(()) + + // then + XCTAssertEqual(output.displayText.value, "Error", "0으로 나누면 Error 메시지가 표시되어야 합니다.") + } + + func test_tapClear_resetsDisplayText() { + // given + input.tapNumber.send(9.0) + input.tapOperator.send(.multiply) + + // when + input.tapClear.send(()) + + // then + XCTAssertEqual(output.displayText.value, "0", "AC를 누르면 화면이 0으로 초기화되어야 합니다.") + } +} diff --git a/SOPT-Test-Code-StudyTests/SOPT_Test_Code_StudyTests.swift b/SOPT-Test-Code-StudyTests/SOPT_Test_Code_StudyTests.swift new file mode 100644 index 0000000..b431595 --- /dev/null +++ b/SOPT-Test-Code-StudyTests/SOPT_Test_Code_StudyTests.swift @@ -0,0 +1,84 @@ +// +// SOPT_Test_Code_StudyTests.swift +// SOPT-Test-Code-StudyTests +// +// Created by 이승준 on 11/13/25. +// + +import XCTest +@testable import SOPT_Test_Code_Study + +final class SOPT_Test_Code_StudyTests: XCTestCase { + + var sut: LoginViewController! + var helper: LoginViewControllerTestHelper! + + override func setUpWithError() throws { + try super.setUpWithError() + // 1. LoginViewController 인스턴스 생성 + sut = LoginViewController() + // 2. viewDidLoad() 호출 + _ = sut.view + // 3. testHelper 환경 구축 + sut.setTestEnvironment() + guard let testHelper = sut.testHelper else { + XCTFail("LoginViewControllerTestHelper should be initialized.") + return + } + helper = testHelper + } + + override func tearDownWithError() throws { + sut = nil + helper = nil + super.tearDown() + } + + func test_checkLoginButtonValid_shouldActivateButton_whenBothFieldsAreFilled() { + // Given : 이메일 필드에 이미 텍스트가 채워져 있는 상태 + helper.input(text: "test@example.com", at: .emailId) + // 버튼이 아직 비활성화 상태임을 확인 + XCTAssertFalse(helper.checkLoginButtonIsEnabled(), "Given 상태에서 버튼은 비활성화되어야 합니다.") + // When : 비밀번호 필드에 첫 번째 문자를 입력하는 행위 시뮬레이션 + XCTAssertTrue(helper.textField(at: .password, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "password"), "Delegate 함수를 통한 비밀번호 입력이 성공해야 합니다.") + // Then : 로그인 버튼이 활성화(Available) 모드로 전환되었는지 확인 + XCTAssertTrue(helper.checkLoginButtonIsEnabled(), "이메일과 비밀번호가 모두 채워졌을 때 로그인 버튼이 활성화되어야 합니다.") + } + + func test_textFieldDelegateMethod_shouldReturnFalse_whenSpaceTextInputDetected() { + // Given : 두 필드 모두 유효한 텍스트로 채워져 있어 버튼이 활성화된 상태 + helper.input(text: "test@example.com", at: .emailId) + XCTAssertTrue(helper.textField(at: .password, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "b"), "Delegate 함수를 통한 비밀번호 입력이 성공해야 합니다.") + XCTAssertTrue(helper.checkLoginButtonIsEnabled(), "이메일과 비밀번호가 모두 채워졌을 때 로그인 버튼이 활성화되어야 합니다.") + // When : 비밀번호 필드의 모든 텍스트를 지우는 행위 시뮬레이션 + XCTAssertTrue(helper.textField(at: .password, shouldChangeCharactersIn: NSRange(location: 0, length: helper.passwordTextField.text?.count ?? 0), replacementString: ""), "비밀번호 필드 모두 지우기를 성공해야 합니다.") + // Then : 로그인 버튼이 비활성화(Unavailable) 모드로 전환되었는지 확인 + XCTAssertFalse(helper.checkLoginButtonIsEnabled(), "비밀번호가 모두 지워졌을 때 로그인 버튼이 비활성화되어야 합니다.") + } + + + func test_checkLoginButtonValid_shouldActivateButton_whenBothFeildsAreFilled() { + // Given : + + let currentText = "test" + let range = NSRange(location: currentText.count, length: 0) // 끝에 공백을 추가 + let replacementString = " " // 공백 문자 + + // 델리게이트 메서드 호출 시뮬레이션: + let shouldChange = helper.textField(at: .emailId, shouldChangeCharactersIn: range, replacementString: replacementString) + + // Then : textField(_ UITextField: NSRange: String) false 반환 + XCTAssertFalse(shouldChange, "공백 입력이 감지되었을 때 false를 반환하여 입력을 막아야 합니다.") + + // When : 공백을 입력 + let replacementStringWithSpace = " " + let shouldChangeWithSpace = helper.textField(at: .emailId, shouldChangeCharactersIn: range, replacementString: replacementStringWithSpace) + + // Then : 입력 방지 로직에 의해 false 반환을 기대 + XCTAssertFalse(shouldChangeWithSpace, "붙여넣기 등에 공백이 포함된 경우에도 false를 반환하여 입력을 막아야 합니다.") + + } + +} + +