diff --git a/Projects/App/Tests/.gitkeep b/Configurations/Base/Projects/Project-Development.xcconfig
similarity index 100%
rename from Projects/App/Tests/.gitkeep
rename to Configurations/Base/Projects/Project-Development.xcconfig
diff --git a/Projects/Core/Tests/.gitkeep b/Configurations/Base/Projects/Project-PROD.xcconfig
similarity index 100%
rename from Projects/Core/Tests/.gitkeep
rename to Configurations/Base/Projects/Project-PROD.xcconfig
diff --git a/Projects/Domain/Tests/.gitkeep b/Configurations/Base/Projects/Project-QA.xcconfig
similarity index 100%
rename from Projects/Domain/Tests/.gitkeep
rename to Configurations/Base/Projects/Project-QA.xcconfig
diff --git a/Projects/Features/BaseFeatureDependency/Tests/.gitkeep b/Configurations/Base/Projects/Project-Test.xcconfig
similarity index 100%
rename from Projects/Features/BaseFeatureDependency/Tests/.gitkeep
rename to Configurations/Base/Projects/Project-Test.xcconfig
diff --git a/Configurations/Targets/iOS-Demo.xcconfig b/Configurations/Targets/iOS-Demo.xcconfig
new file mode 100644
index 0000000..124fa75
--- /dev/null
+++ b/Configurations/Targets/iOS-Demo.xcconfig
@@ -0,0 +1,9 @@
+//
+// iOS-Demo.xcconfig
+// Manifests
+//
+// Created by 류희재 on 6/28/24.
+//
+
+// Configuration settings file format documentation can be found at:
+// https://help.apple.com/xcode/#/dev745c5c974
diff --git a/Configurations/Targets/iOS-Framework.xcconfig b/Configurations/Targets/iOS-Framework.xcconfig
new file mode 100644
index 0000000..d758176
--- /dev/null
+++ b/Configurations/Targets/iOS-Framework.xcconfig
@@ -0,0 +1,9 @@
+//
+// iOS-Framework.xcconfig
+// Manifests
+//
+// Created by 류희재 on 6/28/24.
+//
+
+// Configuration settings file format documentation can be found at:
+// https://help.apple.com/xcode/#/dev745c5c974
diff --git a/Configurations/Targets/iOS-Tests.xcconfig b/Configurations/Targets/iOS-Tests.xcconfig
new file mode 100644
index 0000000..e526977
--- /dev/null
+++ b/Configurations/Targets/iOS-Tests.xcconfig
@@ -0,0 +1,9 @@
+//
+// iOS-Tests.xcconfig
+// Manifests
+//
+// Created by 류희재 on 6/28/24.
+//
+
+// Configuration settings file format documentation can be found at:
+// https://help.apple.com/xcode/#/dev745c5c974
diff --git a/Plugins/ConfigPlugin/Plugin.swift b/Plugins/ConfigPlugin/Plugin.swift
new file mode 100644
index 0000000..9e5826f
--- /dev/null
+++ b/Plugins/ConfigPlugin/Plugin.swift
@@ -0,0 +1,3 @@
+import ProjectDescription
+
+let configPlugin = Plugin(name: "ConfigPlugin")
diff --git a/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift b/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift
new file mode 100644
index 0000000..02ee565
--- /dev/null
+++ b/Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift
@@ -0,0 +1,48 @@
+import Foundation
+import ProjectDescription
+
+/// 빌드할 환경에 대한 설정
+
+/// Target 분리 (The Modular Architecture 기반으로 분리했습니다)
+
+
+/// DEV : 실제 프로덕트 BaseURL을 사용하는 debug scheme
+/// TEST : 테스트 BaseURL을 사용하는 debug scheme
+/// QA : 테스트 BaseURL을 사용하는 release scheme
+/// RELEASE : 실제 프로덕트 BaseURL을 사용하는 release scheme
+
+
+public struct XCConfig {
+ private struct Path {
+ static var framework: ProjectDescription.Path { .relativeToRoot("Configurations/Targets/iOS-Framework.xcconfig") }
+ static var demo: ProjectDescription.Path { .relativeToRoot("Configurations/Targets/iOS-Demo.xcconfig") }
+ static var tests: ProjectDescription.Path { .relativeToRoot("Configurations/Targets/iOS-Tests.xcconfig") }
+ static func project(_ config: String) -> ProjectDescription.Path { .relativeToRoot("Configurations/Base/Projects/Project-\(config).xcconfig") }
+ }
+
+ public static let framework: [Configuration] = [
+ .debug(name: "Development", xcconfig: Path.framework),
+ .debug(name: "Test", xcconfig: Path.framework),
+ .release(name: "QA", xcconfig: Path.framework),
+ .release(name: "PROD", xcconfig: Path.framework),
+ ]
+
+ public static let tests: [Configuration] = [
+ .debug(name: "Development", xcconfig: Path.tests),
+ .debug(name: "Test", xcconfig: Path.tests),
+ .release(name: "QA", xcconfig: Path.tests),
+ .release(name: "PROD", xcconfig: Path.tests),
+ ]
+ public static let demo: [Configuration] = [
+ .debug(name: "Development", xcconfig: Path.demo),
+ .debug(name: "Test", xcconfig: Path.demo),
+ .release(name: "QA", xcconfig: Path.demo),
+ .release(name: "PROD", xcconfig: Path.demo),
+ ]
+ public static let project: [Configuration] = [
+ .debug(name: "Development", xcconfig: Path.project("Development")),
+ .debug(name: "Test", xcconfig: Path.project("Test")),
+ .release(name: "QA", xcconfig: Path.project("QA")),
+ .release(name: "PROD", xcconfig: Path.project("PROD")),
+ ]
+}
diff --git a/Plugins/DependencyPlugin/Plugin.swift b/Plugins/DependencyPlugin/Plugin.swift
index 001e9bf..16e44d2 100644
--- a/Plugins/DependencyPlugin/Plugin.swift
+++ b/Plugins/DependencyPlugin/Plugin.swift
@@ -1,3 +1,3 @@
import ProjectDescription
- let dependencyPlugin = Plugin(name: "DependencyPlugin")
+let dependencyPlugin = Plugin(name: "DependencyPlugin")
diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift
index ef71062..42f0c76 100644
--- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift
+++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift
@@ -7,6 +7,9 @@
import ProjectDescription
+/// 프로젝트 내 모듈 및 기능별 종속성을 체계적으로 관리하기 위한 유틸리티를 제공
+/// 새로운 모듈이나 기능이 추가되더라도 해당 파일만 업데이트하면 되어 유지보수가 용이합니다.
+
public extension Dep {
struct Features {
public struct Main {}
@@ -17,36 +20,58 @@ public extension Dep {
struct Modules {}
}
-// MARK: - Root
+// MARK: - Root: 프로젝트의 핵심 모듈에 대한 종속성을 정의
public extension Dep {
static let data = Dep.project(target: "Data", path: .data)
-
+
static let domain = Dep.project(target: "Domain", path: .domain)
static let core = Dep.project(target: "Core", path: .core)
}
-// MARK: - Modules
+// MARK: - Modules: 프로젝트 내 모듈 단위 종속성을 정의
public extension Dep.Modules {
- static let dsKit = Dep.project(target: "DSKit", path: .relativeToModules("DSKit"))
+ static let dsKit = Dep.project(
+ target: "DSKit",
+ path: .relativeToModules("DSKit")
+ )
- static let networks = Dep.project(target: "Networks", path: .relativeToModules("Networks"))
+ static let networks = Dep.project(
+ target: "Networks",
+ path: .relativeToModules("Networks")
+ )
- static let thirdPartyLibs = Dep.project(target: "ThirdPartyLibs", path: .relativeToModules("ThirdPartyLibs"))
+ static let thirdPartyLibs = Dep.project(
+ target: "ThirdPartyLibs",
+ path: .relativeToModules("ThirdPartyLibs")
+ )
}
// MARK: - Features
public extension Dep.Features {
- static func project(name: String, group: String) -> Dep { .project(target: "\(group)\(name)", path: .relativeToFeature("\(group)\(name)")) }
+ static func project(name: String, group: String) -> Dep {
+ .project(
+ target: "\(group)\(name)",
+ path: .relativeToFeature("\(group)\(name)")
+ )
+ }
- static let BaseFeatureDependency = TargetDependency.project(target: "BaseFeatureDependency", path: .relativeToFeature("BaseFeatureDependency"))
+ static let BaseFeatureDependency = Dep.project(
+ target: "BaseFeatureDependency",
+ path: .relativeToFeature("BaseFeatureDependency")
+ )
- static let RootFeature = TargetDependency.project(target: "RootFeature", path: .relativeToFeature("RootFeature"))
+ static let RootFeature = Dep.project(
+ target: "RootFeature",
+ path: .relativeToFeature("RootFeature")
+ )
}
+//TODO: 폴더별로 분기처리하기 위해서 이런식으로 했다면 하나의 그룹이름만 주입해서 만드는게 좋지않나?
+
public extension Dep.Features.Main {
static let group = "Main"
diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift
index a2102d2..72b5e21 100644
--- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift
+++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+SPM.swift
@@ -7,6 +7,9 @@
import ProjectDescription
+/// TargetDependency의 확장을 통해
+/// 프로젝트 내 외부 라이브러리 종속성을 보다 체계적으로 관리하기 위한 유틸리티를 제공하는 파일
+
public extension TargetDependency {
enum SPM {}
enum Carthage {}
@@ -21,4 +24,5 @@ public extension TargetDependency.SPM {
static let RxSwift = TargetDependency.external(name: "RxSwift")
static let RxCocoa = TargetDependency.external(name: "RxCocoa")
static let RxRelay = TargetDependency.external(name: "RxRelay")
+ static let ReactorKit = TargetDependency.external(name: "ReactorKit")
}
diff --git a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/PathExtension.swift b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/PathExtension.swift
index 0e6715b..11f0b59 100644
--- a/Plugins/DependencyPlugin/ProjectDescriptionHelpers/PathExtension.swift
+++ b/Plugins/DependencyPlugin/ProjectDescriptionHelpers/PathExtension.swift
@@ -7,27 +7,36 @@
import ProjectDescription
+/// ProjectDescription.Path의 확장을 통해
+/// 프로젝트 내 경로를 보다 간결하고 직관적으로 관리하기 위한 유틸리티를 제공하는 파일
+
public extension ProjectDescription.Path {
+ /// 기능 폴더에 대한 상대 경로를 생성
static func relativeToFeature(_ path: String) -> Self {
return .relativeToRoot("Projects/Features/\(path)")
}
+ /// 모듈 폴더에 대한 상대 경로를 생성
static func relativeToModules(_ path: String) -> Self {
return .relativeToRoot("Projects/Modules/\(path)")
}
+ /// 각각 앱 폴더에 대한 경로를 반환하는 속성
static var app: Self {
return .relativeToRoot("Projects/App")
}
+ /// 각각 데이터 폴더에 대한 경로를 반환하는 속성
static var data: Self {
return .relativeToRoot("Projects/Data")
}
+ /// 각각 도메인 폴더에 대한 경로를 반환하는 속성
static var domain: Self {
return .relativeToRoot("Projects/Domain")
}
+ /// 각각 코어 폴더에 대한 경로를 반환하는 속성
static var core: Self {
return .relativeToRoot("Projects/Core")
}
diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Enviroment.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Enviroment.swift
index cac2151..cd48090 100644
--- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/Enviroment.swift
+++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/Enviroment.swift
@@ -7,15 +7,18 @@
import ProjectDescription
-public enum Environment {
- public static let workspaceName = "Weather-iOS"
-}
+/// 프로젝트 환경 관련 파일입니다
-public extension Project {
- enum Environment {
- public static let workspaceName = "Weather-iOS"
- public static let deploymentTarget = DeploymentTarget.iOS(targetVersion: "16.0", devices: [.iphone])
- public static let platform = Platform.iOS
- public static let bundlePrefix = "com.Weather-iOS"
- }
+public struct ProjectEnvironment {
+ public let workspaceName: String
+ public let deploymentTarget: DeploymentTarget
+ public let platform: Platform
+ public let bundlePrefix: String
}
+
+public let env = ProjectEnvironment(
+ workspaceName: "Weather-iOS",
+ deploymentTarget: DeploymentTarget.iOS(targetVersion: "17.0", devices: [.iphone]),
+ platform: .iOS,
+ bundlePrefix: "com.Weather-iOS"
+)
diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift
index ca2ea31..6ccc473 100644
--- a/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift
+++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift
@@ -7,14 +7,16 @@
import ProjectDescription
+/// InfoPList를 정리해둔 파일이빈다
+///
public extension Project {
- static let appInfoPlist: [String: InfoPlist.Value] = [
+ static let appInfoPlist: [String: Plist.Value] = [
"BASE_URL": "https://api.openweathermap.org/data/2.5",
"API_KEY": "7618d35ff394f5dd39212928a3a4692f",
"CFBundleShortVersionString": "1.0.0",
"CFBundleDevelopmentRegion": "ko",
"CFBundleVersion": "1",
- "CFBundleIdentifier": "com.Weather-iOS.release",
+ "CFBundleIdentifier": "com.Weather-iOS.$(PRODUCT_MODULE_NAME)",
"CFBundleDisplayName": "Weather-iOS",
"UILaunchStoryboardName": "LaunchScreen",
"UIApplicationSceneManifest": [
@@ -38,18 +40,9 @@ public extension Project {
"App Transport Security Settings": ["Allow Arbitrary Loads": true],
"NSAppTransportSecurity": ["NSAllowsArbitraryLoads": true],
"ITSAppUsesNonExemptEncryption": false,
-// "UIUserInterfaceStyle": "Dark",
-// "CFBundleURLTypes": [
-// [
-// "CFBundleTypeRole": "Editor",
-// "CFBundleURLName": "sopt-makers",
-// "CFBundleURLSchemes": ["sopt-makers"]
-// ]
-// ],
-// "UIBackgroundModes": ["remote-notification"]
]
- static let demoInfoPlist: [String: InfoPlist.Value] = [
+ static let demoInfoPlist: [String: Plist.Value] = [
"CFBundleShortVersionString": "1.0.0",
"CFBundleDevelopmentRegion": "ko",
"CFBundleVersion": "1",
diff --git a/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingDictionary+.swift b/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingDictionary+.swift
new file mode 100644
index 0000000..4518e97
--- /dev/null
+++ b/Plugins/EnvPlugin/ProjectDescriptionHelpers/SettingDictionary+.swift
@@ -0,0 +1,74 @@
+//
+// SettingDictionary+.swift
+// EnvPlugin
+//
+// Created by 류희재 on 6/26/24.
+//
+
+import ProjectDescription
+
+// 아직 여기의 필요성은 못 느끼는중 무슨 역할을 하게 될지 궁금하다!
+
+public extension SettingsDictionary {
+ // allLoadSettings와 baseSettings는 빌드 설정 딕셔너리의 기본값을 정의
+ static let allLoadSettings: Self = [
+ "OTHER_LDFLAGS" : [
+ "$(inherited) -all_load",
+ "-Xlinker -interposable"
+ ]
+ ]
+
+ static let baseSettings: Self = [
+ "OTHER_LDFLAGS" : [
+ "$(inherited)",
+ "-ObjC"
+ ]
+ ]
+
+ func setProductBundleIdentifier(_ value: String = "com.iOS$(BUNDLE_ID_SUFFIX)") -> SettingsDictionary {
+ merging(["PRODUCT_BUNDLE_IDENTIFIER": SettingValue(stringLiteral: value)])
+ }
+
+ func setAssetcatalogCompilerAppIconName(_ value: String = "AppIcon$(BUNDLE_ID_SUFFIX)") -> SettingsDictionary {
+ merging(["ASSETCATALOG_COMPILER_APPICON_NAME": SettingValue(stringLiteral: value)])
+ }
+
+ func setBuildActiveArchitectureOnly(_ value: Bool) -> SettingsDictionary {
+ merging(["ONLY_ACTIVE_ARCH": SettingValue(stringLiteral: value ? "YES" : "NO")])
+ }
+
+ func setExcludedArchitectures(sdk: String = "iphonesimulator*", _ value: String = "arm64") -> SettingsDictionary {
+ merging(["EXCLUDED_ARCHS[sdk=\(sdk)]": SettingValue(stringLiteral: value)])
+ }
+
+ func setSwiftActiveComplationConditions(_ value: String) -> SettingsDictionary {
+ merging(["SWIFT_ACTIVE_COMPILATION_CONDITIONS": SettingValue(stringLiteral: value)])
+ }
+
+ func setAlwaysSearchUserPath(_ value: String = "NO") -> SettingsDictionary {
+ merging(["ALWAYS_SEARCH_USER_PATHS": SettingValue(stringLiteral: value)])
+ }
+
+ func setStripDebugSymbolsDuringCopy(_ value: String = "NO") -> SettingsDictionary {
+ merging(["COPY_PHASE_STRIP": SettingValue(stringLiteral: value)])
+ }
+
+ func setDynamicLibraryInstallNameBase(_ value: String = "@rpath") -> SettingsDictionary {
+ merging(["DYLIB_INSTALL_NAME_BASE": SettingValue(stringLiteral: value)])
+ }
+
+ func setSkipInstall(_ value: Bool = false) -> SettingsDictionary {
+ merging(["SKIP_INSTALL": SettingValue(stringLiteral: value ? "YES" : "NO")])
+ }
+
+ func setCodeSignManual() -> SettingsDictionary {
+ merging(["CODE_SIGN_STYLE": SettingValue(stringLiteral: "Manual")])
+ .merging(["DEVELOPMENT_TEAM": SettingValue(stringLiteral: "9K86FQHDLU")])
+ .merging(["CODE_SIGN_IDENTITY": SettingValue(stringLiteral: "$(CODE_SIGN_IDENTITY)")])
+ }
+
+ func setProvisioning() -> SettingsDictionary {
+ merging(["PROVISIONING_PROFILE_SPECIFIER": SettingValue(stringLiteral: "$(APP_PROVISIONING_PROFILE)")])
+ .merging(["PROVISIONING_PROFILE": SettingValue(stringLiteral: "$(APP_PROVISIONING_PROFILE)")])
+ }
+}
diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift
index 3e759e6..5602bbe 100644
--- a/Projects/App/Project.swift
+++ b/Projects/App/Project.swift
@@ -11,12 +11,10 @@ import DependencyPlugin
import EnvPlugin
let project = Project.makeModule(
- name: Environment.workspaceName,
- product: .app,
- dependencies: [
- .Features.RootFeature,
- .data
- ],
- sources: ["Sources/**"],
- resources: ["Resources/**"]
+ name: env.workspaceName,
+ targets: [.app, .unitTest],
+ internalDependencies: [
+ .data,
+ .Features.RootFeature
+ ]
)
diff --git a/Projects/App/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/App/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/App/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/App/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/App/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/App/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/App/Tests/Resources/Assets.xcassets/Contents.json b/Projects/App/Tests/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/App/Tests/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/App/Tests/Resources/LaunchScreen.storyboard b/Projects/App/Tests/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/App/Tests/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Features/RootFeature/Tests/.gitkeep b/Projects/App/Tests/Sources/UnitTest.swift
similarity index 100%
rename from Projects/Features/RootFeature/Tests/.gitkeep
rename to Projects/App/Tests/Sources/UnitTest.swift
diff --git a/Projects/App/Weather_iOSTests/Weather_iOSTests.swift b/Projects/App/Weather_iOSTests/Weather_iOSTests.swift
new file mode 100644
index 0000000..fff0400
--- /dev/null
+++ b/Projects/App/Weather_iOSTests/Weather_iOSTests.swift
@@ -0,0 +1,36 @@
+//
+// Weather_iOSTests.swift
+// Weather_iOSTests
+//
+// Created by 류희재 on 6/25/24.
+// Copyright © 2024 hellohidi. All rights reserved.
+//
+
+import XCTest
+
+final class Weather_iOSTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() throws {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ // Any test you write for XCTest can be annotated as throws and async.
+ // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
+ // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
+ }
+
+ func testPerformanceExample() throws {
+ // This is an example of a performance test case.
+ measure {
+ // Put the code you want to measure the time of here.
+ }
+ }
+
+}
diff --git a/Projects/Core/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Core/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Core/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Core/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Core/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Demo/Resources/Assets.xcassets/Contents.json b/Projects/Core/Demo/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Core/Demo/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Demo/Resources/LaunchScreen.storyboard b/Projects/Core/Demo/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Core/Demo/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Modules/ThirdPartyLibs/Tests/.gitkeep b/Projects/Core/Demo/Resources/dummy.txt
similarity index 100%
rename from Projects/Modules/ThirdPartyLibs/Tests/.gitkeep
rename to Projects/Core/Demo/Resources/dummy.txt
diff --git a/Projects/Core/Demo/Sources/Demo.swift b/Projects/Core/Demo/Sources/Demo.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Core/Project.swift b/Projects/Core/Project.swift
index 75da975..0b836b2 100644
--- a/Projects/Core/Project.swift
+++ b/Projects/Core/Project.swift
@@ -9,11 +9,14 @@ import ProjectDescription
import ProjectDescriptionHelpers
import DependencyPlugin
+import ProjectDescription
+import ProjectDescriptionHelpers
+import DependencyPlugin
+
let project = Project.makeModule(
name: "Core",
- product: .framework,
- dependencies: [
+ targets: [.unitTest, .dynamicFramework, .demo],
+ internalDependencies: [
.Modules.thirdPartyLibs
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Core/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Core/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Core/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Core/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Core/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Tests/Resources/Assets.xcassets/Contents.json b/Projects/Core/Tests/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Core/Tests/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Core/Tests/Resources/LaunchScreen.storyboard b/Projects/Core/Tests/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Core/Tests/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Core/Tests/Sources/UnitTest.swift b/Projects/Core/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Data/Project.swift b/Projects/Data/Project.swift
index a5ac5e2..fdd44a8 100644
--- a/Projects/Data/Project.swift
+++ b/Projects/Data/Project.swift
@@ -11,11 +11,10 @@ import DependencyPlugin
let project = Project.makeModule(
name: "Data",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .staticFramework],
+ internalDependencies: [
.domain,
.Modules.networks
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Data/Tests/Resources/dummy.txt b/Projects/Data/Tests/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Data/Tests/Sources/UnitTest.swift b/Projects/Data/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Domain/Project.swift b/Projects/Domain/Project.swift
index a85e3f3..42b6719 100644
--- a/Projects/Domain/Project.swift
+++ b/Projects/Domain/Project.swift
@@ -11,9 +11,8 @@ import DependencyPlugin
let project = Project.makeModule(
name: "Domain",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .dynamicFramework],
+ internalDependencies: [
.core
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Domain/Sources/Model/HourlyWeatherModel.swift b/Projects/Domain/Sources/Model/HourlyWeatherModel.swift
index 7686df4..56713b6 100644
--- a/Projects/Domain/Sources/Model/HourlyWeatherModel.swift
+++ b/Projects/Domain/Sources/Model/HourlyWeatherModel.swift
@@ -8,8 +8,6 @@
import UIKit
-import DSKit
-
public struct HourlyWeatherModel {
public let time, weatherImage: String
public let temparature: Double
diff --git a/Projects/Domain/Sources/Model/WeatherForecastModel.swift b/Projects/Domain/Sources/Model/WeatherForecastModel.swift
index 600abe4..a77ce97 100644
--- a/Projects/Domain/Sources/Model/WeatherForecastModel.swift
+++ b/Projects/Domain/Sources/Model/WeatherForecastModel.swift
@@ -8,8 +8,6 @@
import UIKit
-import DSKit
-
public struct WeatherForecastModel {
public let day: String
public let weatherImg: UIImage
diff --git a/Projects/Domain/Sources/UseCase/DefaultMainUseCase.swift b/Projects/Domain/Sources/UseCase/DefaultMainUseCase.swift
index 8e2491c..8209225 100644
--- a/Projects/Domain/Sources/UseCase/DefaultMainUseCase.swift
+++ b/Projects/Domain/Sources/UseCase/DefaultMainUseCase.swift
@@ -5,6 +5,7 @@
// Created by 류희재 on 2023/10/24.
// Copyright © 2023 hellohidi. All rights reserved.
//
+//
import RxSwift
import RxCocoa
@@ -21,28 +22,69 @@ public final class DefaultMainUseCase: MainUseCase {
self.repository = repository
}
- public func updateSearchResult(_ text: String) {
+ public func updateSearchResult(_ text: String) -> Observable<[CurrentWeatherModel]> {
let defaultWeatherList: [CurrentWeatherModel] = []
- if text.isEmpty {
- weatherList.accept(defaultWeatherList)
- } else {
- let filteredList = defaultWeatherList.filter { $0.place.contains(text) }
- weatherList.accept(filteredList)
- }
+ return Observable.just(
+ defaultWeatherList.filter { $0.place.contains(text)}
+ )
+
+
+// let defaultWeatherList: [CurrentWeatherModel] = []
+// if text.isEmpty {
+// weatherList.accept(defaultWeatherList)
+// } else {
+// let filteredList = defaultWeatherList.filter { $0.place.contains(text) }
+// weatherList.accept(filteredList)
+// }
}
- public func getCurrentWeatherData() {
+ public func getCurrentWeatherData() -> Observable<[CurrentWeatherModel]> {
let currentCityWeatherList = City.cityList.map { city in
return repository.getCityWeatherData(city: city)
}
-
- Observable.zip(currentCityWeatherList)
- .subscribe(onNext: { cityWeatherArray in
- let updateCurrentWeatherList = cityWeatherArray.compactMap { $0 }
- self.weatherList.accept(updateCurrentWeatherList)
- })
- .disposed(by: disposeBag)
-
-
+ return Observable.zip(currentCityWeatherList)
}
}
+
+
+
+//import RxSwift
+//import RxCocoa
+//
+//import Core
+//
+//public final class DefaultMainUseCase: MainUseCase {
+// public var weatherList = BehaviorRelay<[CurrentWeatherModel]>(value: [])
+//
+// public let repository: WeatherRepository
+// private let disposeBag = DisposeBag()
+//
+// public init(repository: WeatherRepository) {
+// self.repository = repository
+// }
+//
+// public func updateSearchResult(_ text: String) {
+// let defaultWeatherList: [CurrentWeatherModel] = []
+// if text.isEmpty {
+// weatherList.accept(defaultWeatherList)
+// } else {
+// let filteredList = defaultWeatherList.filter { $0.place.contains(text) }
+// weatherList.accept(filteredList)
+// }
+// }
+//
+// public func getCurrentWeatherData() {
+// let currentCityWeatherList = City.cityList.map { city in
+// return repository.getCityWeatherData(city: city)
+// }
+//
+// Observable.zip(currentCityWeatherList)
+// .subscribe(onNext: { cityWeatherArray in
+// let updateCurrentWeatherList = cityWeatherArray.compactMap { $0 }
+// self.weatherList.accept(updateCurrentWeatherList)
+// })
+// .disposed(by: disposeBag)
+//
+//
+// }
+//}
diff --git a/Projects/Domain/Sources/UseCase/Protocol/MainUseCase.swift b/Projects/Domain/Sources/UseCase/Protocol/MainUseCase.swift
index 5f4545f..96fe9ff 100644
--- a/Projects/Domain/Sources/UseCase/Protocol/MainUseCase.swift
+++ b/Projects/Domain/Sources/UseCase/Protocol/MainUseCase.swift
@@ -10,8 +10,6 @@ import RxSwift
import RxCocoa
public protocol MainUseCase {
- var weatherList: BehaviorRelay<[CurrentWeatherModel]> { get }
-
- func updateSearchResult(_ text: String)
- func getCurrentWeatherData()
+ func updateSearchResult(_ text: String) -> Observable<[CurrentWeatherModel]>
+ func getCurrentWeatherData() -> Observable<[CurrentWeatherModel]>
}
diff --git a/Projects/Domain/Tests/Resources/dummy.txt b/Projects/Domain/Tests/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Domain/Tests/Sources/Unittest.swift b/Projects/Domain/Tests/Sources/Unittest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/BaseFeatureDependency/Project.swift b/Projects/Features/BaseFeatureDependency/Project.swift
index dd121a2..cdd7a9b 100644
--- a/Projects/Features/BaseFeatureDependency/Project.swift
+++ b/Projects/Features/BaseFeatureDependency/Project.swift
@@ -11,11 +11,10 @@ import DependencyPlugin
let project = Project.makeModule(
name: "BaseFeatureDependency",
- product: .framework,
- dependencies: [
- .Modules.dsKit,
- .domain
- ],
- sources: ["Sources/**"]
+ targets: [.dynamicFramework],
+ internalDependencies: [
+ .domain,
+ .Modules.dsKit
+ ]
)
diff --git a/Projects/Features/BaseFeatureDependency/Sources/ViewControllable.swift b/Projects/Features/BaseFeatureDependency/Sources/ViewControllable.swift
new file mode 100644
index 0000000..d8b1d4a
--- /dev/null
+++ b/Projects/Features/BaseFeatureDependency/Sources/ViewControllable.swift
@@ -0,0 +1,26 @@
+//
+// ViewControllable.swift
+// BaseFeatureDependency
+//
+// Created by 류희재 on 7/4/24.
+// Copyright © 2024 Weather-iOS. All rights reserved.
+//
+
+import UIKit
+
+public protocol ViewControllable {
+ var viewController: UIViewController { get }
+ var asNavigationController: UINavigationController { get }
+}
+public extension ViewControllable where Self: UIViewController {
+ var viewController: UIViewController {
+ return self
+ }
+
+ var asNavigationController: UINavigationController {
+ return self as? UINavigationController
+ ?? UINavigationController(rootViewController: self)
+ }
+}
+
+extension UIViewController: ViewControllable {}
diff --git a/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/Contents.json b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Features/DetailFeature/Demo/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/DetailFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Features/DetailFeature/Demo/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Features/DetailFeature/Demo/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Features/DetailFeature/Demo/Resources/dummy.txt b/Projects/Features/DetailFeature/Demo/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/DetailFeature/Demo/Sources/Demo.swift b/Projects/Features/DetailFeature/Demo/Sources/Demo.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/DetailFeature/Interface/Sources/DetailPresentable.swift b/Projects/Features/DetailFeature/Interface/Sources/DetailPresentable.swift
new file mode 100644
index 0000000..7b7ebc1
--- /dev/null
+++ b/Projects/Features/DetailFeature/Interface/Sources/DetailPresentable.swift
@@ -0,0 +1,8 @@
+//
+// DetailPresentable.swift
+// DetailFeatureManifests
+//
+// Created by 류희재 on 6/28/24.
+//
+
+import Foundation
diff --git a/Projects/Features/DetailFeature/Project.swift b/Projects/Features/DetailFeature/Project.swift
index bd483b2..95b3275 100644
--- a/Projects/Features/DetailFeature/Project.swift
+++ b/Projects/Features/DetailFeature/Project.swift
@@ -11,9 +11,8 @@ import DependencyPlugin
let project = Project.makeModule(
name: "DetailFeature",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .staticFramework, .demo, .interface],
+ interfaceDependencies: [
.Features.BaseFeatureDependency
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Features/DetailFeature/Sources/View/DetailView.swift b/Projects/Features/DetailFeature/Sources/View/DetailView.swift
index b7b4e02..9e5ddc6 100644
--- a/Projects/Features/DetailFeature/Sources/View/DetailView.swift
+++ b/Projects/Features/DetailFeature/Sources/View/DetailView.swift
@@ -18,7 +18,7 @@ import Then
final class DetailView: UIView {
let scrollView = UIScrollView()
private let contentView = UIView()
- private let backgroundImageView = UIImageView.init(image: DSKitAsset.backgroundImg.image)
+ private let backgroundImageView = UIImageView.init(image: DSKitAsset.backgroundIMG.image)
let detailTopView = DetailTopView()
let detailStickyHeaderView = DetailTopHeaderView()
diff --git a/Projects/Features/DetailFeature/Tests/Resources/dummy.txt b/Projects/Features/DetailFeature/Tests/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/DetailFeature/Tests/Sources/UnitTest.swift b/Projects/Features/DetailFeature/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/Contents.json b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Features/MainFeature/Demo/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/MainFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Features/MainFeature/Demo/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Features/MainFeature/Demo/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Features/MainFeature/Demo/Resources/dummy.txt b/Projects/Features/MainFeature/Demo/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/MainFeature/Demo/Sources/Demo.Swift b/Projects/Features/MainFeature/Demo/Sources/Demo.Swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/MainFeature/Interface/Sources/MainPresentable.swift b/Projects/Features/MainFeature/Interface/Sources/MainPresentable.swift
new file mode 100644
index 0000000..d171783
--- /dev/null
+++ b/Projects/Features/MainFeature/Interface/Sources/MainPresentable.swift
@@ -0,0 +1,8 @@
+//
+// MainPresentable.swift
+// MainFeatureManifests
+//
+// Created by 류희재 on 6/28/24.
+//
+
+import Foundation
diff --git a/Projects/Features/MainFeature/MainFeatureTests/MainViewModelTests.swift b/Projects/Features/MainFeature/MainFeatureTests/MainViewModelTests.swift
new file mode 100644
index 0000000..7c508a3
--- /dev/null
+++ b/Projects/Features/MainFeature/MainFeatureTests/MainViewModelTests.swift
@@ -0,0 +1,39 @@
+//
+// MainFeatureTests.swift
+// MainFeatureTests
+//
+// Created by 류희재 on 6/25/24.
+// Copyright © 2024 hellohidi. All rights reserved.
+//
+
+import XCTest
+
+import RxRelay
+import RxSwift
+import MainFeature
+import Domain
+//import RxTest
+
+final class MainViewModelTests: XCTestCase {
+ private var viewModel: MainViewModel!
+ private var scheduler:
+ private var disposeBag: DisposeBag!
+ private var input: MainViewModel.Input!
+ private var output: MainViewModel.Output!
+ private var mainUseCase: MainUseCase!
+
+ override func setUpWithError() throws {
+ self.mainUseCase = MockMainUseCase()
+ self.viewModel = MainViewModel.init(
+ mainCoordinator: nil,
+ mainUseCase: mainUseCase
+ )
+ self.disposeBag = DisposeBag()
+// self.scheduler = TestScheduler(initialClock: 0)
+ }
+
+ override func tearDownWithError() throws {
+ self.viewModel = nil
+ self.disposeBag = nil
+ }
+}
diff --git a/Projects/Features/MainFeature/MainFeatureTests/Mock/MockMainUseCase.swift b/Projects/Features/MainFeature/MainFeatureTests/Mock/MockMainUseCase.swift
new file mode 100644
index 0000000..87b8dd9
--- /dev/null
+++ b/Projects/Features/MainFeature/MainFeatureTests/Mock/MockMainUseCase.swift
@@ -0,0 +1,25 @@
+//
+// MockLoginUseCase.swift
+// MainFeatureTests
+//
+// Created by 류희재 on 6/25/24.
+// Copyright © 2024 hellohidi. All rights reserved.
+//
+
+import RxSwift
+import RxCocoa
+
+import Domain
+import Core
+
+public final class MockMainUseCase: MainUseCase {
+ public var weatherList = BehaviorRelay<[CurrentWeatherModel]>(value: [])
+
+ public func updateSearchResult(_ text: String) -> Observable<[CurrentWeatherModel]> {
+ return Observable.just([])
+ }
+
+ public func getCurrentWeatherData() -> Observable<[CurrentWeatherModel]> {
+ return Observable.just([])
+ }
+}
diff --git a/Projects/Features/MainFeature/Project.swift b/Projects/Features/MainFeature/Project.swift
index 656b26f..3042503 100644
--- a/Projects/Features/MainFeature/Project.swift
+++ b/Projects/Features/MainFeature/Project.swift
@@ -11,10 +11,9 @@ import DependencyPlugin
let project = Project.makeModule(
name: "MainFeature",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .staticFramework, .demo, .interface],
+ interfaceDependencies: [
.Features.BaseFeatureDependency
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Features/MainFeature/Sources/Coordinator/DefaultMainCoordinator.swift b/Projects/Features/MainFeature/Sources/Coordinator/DefaultMainCoordinator.swift
index e9d1622..879281e 100644
--- a/Projects/Features/MainFeature/Sources/Coordinator/DefaultMainCoordinator.swift
+++ b/Projects/Features/MainFeature/Sources/Coordinator/DefaultMainCoordinator.swift
@@ -28,9 +28,9 @@ public class DefaultMainCoordinator: MainCoordinator {
}
public func start() {
- let viewController = MainViewController(
- viewModel: MainViewModel(
- mainCoordinator: self,
+ let viewController = MainViewController_Reactor(
+ coordinator: self,
+ reactor: MainReactor(
mainUseCase: DefaultMainUseCase(
repository: DefaultWeatherRepository(
urlSessionService: WeatherNetworkService()
diff --git a/Projects/Features/MainFeature/Sources/ViewController/MainViewController.swift b/Projects/Features/MainFeature/Sources/ViewController/MainViewController.swift
index 9de0840..75fc612 100644
--- a/Projects/Features/MainFeature/Sources/ViewController/MainViewController.swift
+++ b/Projects/Features/MainFeature/Sources/ViewController/MainViewController.swift
@@ -10,7 +10,6 @@ import UIKit
import Domain
import DSKit
-import DetailFeature
import SnapKit
import Then
diff --git a/Projects/Features/MainFeature/Sources/ViewController/MainViewController_Reactor.swift b/Projects/Features/MainFeature/Sources/ViewController/MainViewController_Reactor.swift
new file mode 100644
index 0000000..e37d07d
--- /dev/null
+++ b/Projects/Features/MainFeature/Sources/ViewController/MainViewController_Reactor.swift
@@ -0,0 +1,147 @@
+//
+// MainViewController_Reactor.swift
+// MainFeature
+//
+// Created by 류희재 on 6/6/24.
+// Copyright © 2024 hellohidi. All rights reserved.
+//
+
+import UIKit
+
+import Domain
+import DSKit
+
+import SnapKit
+import Then
+import RxSwift
+import RxGesture
+import RxCocoa
+
+public final class MainViewController_Reactor : UIViewController {
+
+ //MARK: - Properties
+
+ public let reactor: MainReactor
+ public let coordinator: MainCoordinator?
+
+ private let disposeBag = DisposeBag()
+
+ public let searchBarDidChangeSubject = PublishSubject()
+
+ //MARK: - UI Components
+
+ let rootView = MainView()
+
+ private lazy var seeMoreButton = UIBarButtonItem()
+ private lazy var weatherSearchController = UISearchController(searchResultsController: nil)
+
+ //MARK: - Life Cycle
+
+ public init(coordinator: MainCoordinator?, reactor: MainReactor) {
+ self.coordinator = coordinator
+ self.reactor = reactor
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func loadView() {
+ self.view = rootView
+ }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+
+ style()
+ setDelegate()
+
+ bindInput()
+ bindOutput()
+ }
+
+ //MARK: - Custom Method
+
+ private func style() {
+ navigationItem.do {
+ $0.title = "날씨"
+ $0.rightBarButtonItem = seeMoreButton
+ $0.searchController = weatherSearchController
+ $0.hidesSearchBarWhenScrolling = false
+ }
+
+ self.navigationController?.navigationBar.do {
+ $0.barTintColor = .black.withAlphaComponent(0.75)
+ $0.prefersLargeTitles = true
+ $0.titleTextAttributes = [.foregroundColor: UIColor.white]
+ $0.largeTitleTextAttributes = [
+ .foregroundColor: UIColor.white,
+ .font: DSKitFontFamily.SFProDisplay.bold.font(size: 36)]
+ }
+
+ seeMoreButton.do {
+ $0.image = DSKitAsset.menu.image
+ $0.tintColor = .white
+ }
+
+ weatherSearchController.searchBar.do {
+ $0.searchTextField.setPlaceholderColor(
+ text: "도시 또는 공항 검색",
+ color: .lightGray
+ )
+ $0.barTintColor = .black
+ $0.tintColor = .white
+ $0.searchTextField.backgroundColor = .darkGray
+ $0.setValue("취소", forKey: "cancelButtonText")
+ $0.setImage(DSKitAsset.search.image, for: .search, state: .normal)
+ }
+
+ weatherSearchController.searchBar.searchTextField.textColor = .white
+ }
+
+ private func setDelegate() {
+ weatherSearchController.searchResultsUpdater = self
+ }
+
+ private func bindInput() {
+ rx.viewWillAppear.subscribe(with: self, onNext: { owner, _ in
+ owner.navigationController?.navigationBar.isHidden = false
+ }).disposed(by: disposeBag)
+
+ rootView.weatherView.rx.itemSelected
+ .map { $0.item }
+ .asDriver(onErrorJustReturn: Int())
+ .drive(with: self, onNext: { owner, page in
+ owner.coordinator?.pushToDetailVC(with: page)
+ }).disposed(by: disposeBag)
+
+ rx.viewWillAppear
+ .map { MainReactor.Action.viewWillAppearEvent }
+ .bind(to: reactor.action)
+ .disposed(by: disposeBag)
+
+ searchBarDidChangeSubject
+ .map { MainReactor.Action.searchBarDidChangeEvent($0) }
+ .bind(to: reactor.action)
+ .disposed(by: disposeBag)
+ }
+
+ private func bindOutput() {
+ reactor.state.map { $0.weatherList }
+ .asDriver(onErrorJustReturn: [])
+ .drive(self.rootView.weatherView.rx.items(
+ cellIdentifier: MainWeatherCollectionViewCell.cellIdentifier,
+ cellType: MainWeatherCollectionViewCell.self)
+ ) { _, data, cell in
+ cell.dataBind(data)
+ }.disposed(by: disposeBag)
+ }
+}
+
+extension MainViewController_Reactor: UISearchResultsUpdating {
+ public func updateSearchResults(for searchController: UISearchController) {
+ searchBarDidChangeSubject.onNext(searchController.searchBar.text ?? "")
+ }
+}
+
diff --git a/Projects/Features/MainFeature/Sources/ViewModel/MainReactor.swift b/Projects/Features/MainFeature/Sources/ViewModel/MainReactor.swift
new file mode 100644
index 0000000..9e97677
--- /dev/null
+++ b/Projects/Features/MainFeature/Sources/ViewModel/MainReactor.swift
@@ -0,0 +1,65 @@
+//
+// MainReactor.swift
+// MainFeature
+//
+// Created by 류희재 on 6/6/24.
+// Copyright © 2024 hellohidi. All rights reserved.
+//
+
+import Foundation
+
+import Domain
+
+import RxSwift
+import ReactorKit
+
+public class MainReactor: Reactor {
+
+ //MARK: - Properties
+
+ public let mainUseCase: MainUseCase
+
+ public let initialState: State = State()
+
+ public enum Action {
+ case viewWillAppearEvent
+ case searchBarDidChangeEvent(String)
+ }
+
+ public enum Mutation {
+ case getCurrentWeatherData([CurrentWeatherModel])
+ case updateSearchResult([CurrentWeatherModel])
+ }
+
+ public struct State {
+ var weatherList: [CurrentWeatherModel] = []
+ }
+
+ init(mainUseCase: MainUseCase) {
+ self.mainUseCase = mainUseCase
+ }
+}
+
+extension MainReactor {
+ public func mutate(action: Action) -> Observable {
+ switch action {
+ case .viewWillAppearEvent:
+ return mainUseCase.getCurrentWeatherData()
+ .map {Mutation.getCurrentWeatherData($0)}
+ case .searchBarDidChangeEvent(let city):
+ return mainUseCase.updateSearchResult(city)
+ .map {Mutation.updateSearchResult($0)}
+ }
+ }
+}
+
+extension MainReactor {
+ public func reduce(state: State, mutation: Mutation) -> State {
+ var newstate = state
+ switch mutation {
+ case .getCurrentWeatherData(let weatherList), .updateSearchResult(let weatherList):
+ newstate.weatherList = weatherList
+ }
+ return newstate
+ }
+}
diff --git a/Projects/Features/MainFeature/Sources/ViewModel/MainViewModel.swift b/Projects/Features/MainFeature/Sources/ViewModel/MainViewModel.swift
index 9d23f93..4e4d022 100644
--- a/Projects/Features/MainFeature/Sources/ViewModel/MainViewModel.swift
+++ b/Projects/Features/MainFeature/Sources/ViewModel/MainViewModel.swift
@@ -19,18 +19,18 @@ public final class MainViewModel {
public let mainCoordinator: MainCoordinator?
public let mainUseCase: MainUseCase
- public init(mainCoordinator: MainCoordinator ,mainUseCase: MainUseCase) {
+ public init(mainCoordinator: MainCoordinator? ,mainUseCase: MainUseCase) {
self.mainCoordinator = mainCoordinator
self.mainUseCase = mainUseCase
}
- struct Input {
+ public struct Input {
let viewWillAppearEvent: Observable
let weatherListViewDidTapEvent: Observable
let searchBarDidChangeEvent: Observable
}
- struct Output {
+ public struct Output {
public var weatherList = BehaviorRelay<[CurrentWeatherModel]>(value: [])
}
@@ -54,15 +54,15 @@ public final class MainViewModel {
private func bindOutput(output: Output, disposeBag: DisposeBag) {
- mainUseCase.weatherList.subscribe(onNext: { weatherList in
- output.weatherList.accept(weatherList)
- }).disposed(by: disposeBag)
+// mainUseCase.weatherList.subscribe(onNext: { weatherList in
+// output.weatherList.accept(weatherList)
+// }).disposed(by: disposeBag)
}
}
-public extension MainViewModel {
- func getWeatherList() -> [CurrentWeatherModel] {
- return mainUseCase.weatherList.value
- }
-}
+//public extension MainViewModel {
+// func getWeatherList() -> [CurrentWeatherModel] {
+// return mainUseCase.weatherList.value
+// }
+//}
diff --git a/Projects/Features/MainFeature/Tests/Resources/dummy.txt b/Projects/Features/MainFeature/Tests/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/MainFeature/Tests/Sources/UnitTest.swift b/Projects/Features/MainFeature/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/Contents.json b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Features/RootFeature/Demo/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Features/RootFeature/Demo/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Features/RootFeature/Demo/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Features/RootFeature/Demo/Sources/Demo.swift b/Projects/Features/RootFeature/Demo/Sources/Demo.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Features/RootFeature/Project.swift b/Projects/Features/RootFeature/Project.swift
index 5e72e5e..54509aa 100644
--- a/Projects/Features/RootFeature/Project.swift
+++ b/Projects/Features/RootFeature/Project.swift
@@ -11,11 +11,9 @@ import DependencyPlugin
let project = Project.makeModule(
name: "RootFeature",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .staticFramework, .demo],
+ internalDependencies: [
.Features.Main.Feature,
.Features.Detail.Feature
- ],
- sources: ["Sources/**"]
+ ]
)
-
diff --git a/Projects/Features/RootFeature/Sources/AppCoordinator.swift b/Projects/Features/RootFeature/Sources/AppCoordinator.swift
index dabedd6..17f78ef 100644
--- a/Projects/Features/RootFeature/Sources/AppCoordinator.swift
+++ b/Projects/Features/RootFeature/Sources/AppCoordinator.swift
@@ -7,6 +7,7 @@
import UIKit
+import Core
import BaseFeatureDependency
import MainFeature
import DetailFeature
diff --git a/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000..eb87897
--- /dev/null
+++ b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..9221b9b
--- /dev/null
+++ b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/Contents.json b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/Projects/Features/RootFeature/Tests/Resources/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Projects/Features/RootFeature/Tests/Resources/LaunchScreen.storyboard b/Projects/Features/RootFeature/Tests/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Features/RootFeature/Tests/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Features/RootFeature/Tests/Sources/UnitTest.swift b/Projects/Features/RootFeature/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Modules/DSKit/Demo/ReSources/LaunchScreen.storyboard b/Projects/Modules/DSKit/Demo/ReSources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Modules/DSKit/Demo/ReSources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Modules/DSKit/Demo/Sources/Demo.swift b/Projects/Modules/DSKit/Demo/Sources/Demo.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Modules/DSKit/Project.swift b/Projects/Modules/DSKit/Project.swift
index 1f79102..c1c8228 100644
--- a/Projects/Modules/DSKit/Project.swift
+++ b/Projects/Modules/DSKit/Project.swift
@@ -11,10 +11,9 @@ import DependencyPlugin
let project = Project.makeModule(
name: "DSKit",
- product: .framework,
- dependencies: [
+ targets: [.unitTest, .demo, .dynamicFramework],
+ internalDependencies: [
.core
],
- sources: ["Sources/**"],
- resources: ["ReSources/**"]
+ hasResources: true
)
diff --git a/Projects/Modules/DSKit/Tests/Resources/LaunchScreen.storyboard b/Projects/Modules/DSKit/Tests/Resources/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/Projects/Modules/DSKit/Tests/Resources/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/Modules/DSKit/Tests/Sources/UnitTests.swift b/Projects/Modules/DSKit/Tests/Sources/UnitTests.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Modules/Networks/Project.swift b/Projects/Modules/Networks/Project.swift
index b7f5c1b..77daeae 100644
--- a/Projects/Modules/Networks/Project.swift
+++ b/Projects/Modules/Networks/Project.swift
@@ -11,9 +11,8 @@ import DependencyPlugin
let project = Project.makeModule(
name: "Networks",
- product: .staticFramework,
- dependencies: [
+ targets: [.unitTest, .staticFramework],
+ internalDependencies: [
.core
- ],
- sources: ["Sources/**"]
+ ]
)
diff --git a/Projects/Modules/Networks/Tests/Resources/dummy.txt b/Projects/Modules/Networks/Tests/Resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Modules/Networks/Tests/Sources/UnitTest.swift b/Projects/Modules/Networks/Tests/Sources/UnitTest.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Projects/Modules/ThirdPartyLibs/Project.swift b/Projects/Modules/ThirdPartyLibs/Project.swift
index fcd32ba..6484a2b 100644
--- a/Projects/Modules/ThirdPartyLibs/Project.swift
+++ b/Projects/Modules/ThirdPartyLibs/Project.swift
@@ -11,9 +11,8 @@ import DependencyPlugin
let project = Project.makeModule(
name: "ThirdPartyLibs",
- product: .framework,
- packages: [],
- dependencies: [
+ targets: [.dynamicFramework],
+ externalDependencies: [
.SPM.SnapKit,
.SPM.Then,
.SPM.RxGesture,
@@ -21,6 +20,7 @@ let project = Project.makeModule(
.SPM.RxCocoa,
.SPM.RxDataSources,
.SPM.RxRelay,
- .SPM.RxSwift
+ .SPM.RxSwift,
+ .SPM.ReactorKit
]
)
diff --git a/Projects/Modules/ThirdPartyLibs/Sources/Example.swift b/Projects/Modules/ThirdPartyLibs/Sources/Example.swift
new file mode 100644
index 0000000..e69de29
diff --git a/Tuist/Config.swift b/Tuist/Config.swift
index cd6eded..5206f67 100644
--- a/Tuist/Config.swift
+++ b/Tuist/Config.swift
@@ -3,6 +3,7 @@ import ProjectDescription
let config = Config(
plugins: [
.local(path: .relativeToRoot("Plugins/DependencyPlugin")),
- .local(path: .relativeToRoot("Plugins/EnvPlugin"))
+ .local(path: .relativeToRoot("Plugins/EnvPlugin")),
+ .local(path: .relativeToRoot("Plugins/ConfigPlugin"))
]
)
diff --git a/Tuist/Dependencies.swift b/Tuist/Dependencies.swift
index fc2a576..b7dbc97 100644
--- a/Tuist/Dependencies.swift
+++ b/Tuist/Dependencies.swift
@@ -14,7 +14,8 @@ let spm = SwiftPackageManagerDependencies([
.remote(url: "https://github.com/onevcat/Kingfisher.git", requirement: .upToNextMajor(from: "7.6.2")),
.remote(url: "https://github.com/ReactiveX/RxSwift", requirement: .upToNextMajor(from: "6.6.0")),
.remote(url: "https://github.com/RxSwiftCommunity/RxGesture", requirement: .upToNextMajor(from: "4.0.4")),
- .remote(url: "https://github.com/RxSwiftCommunity/RxDataSources", requirement: .upToNextMajor(from: "5.0.2"))
+ .remote(url: "https://github.com/RxSwiftCommunity/RxDataSources", requirement: .upToNextMajor(from: "5.0.2")),
+ .remote(url: "https://github.com/ReactorKit/ReactorKit", requirement: .upToNextMajor(from: "3.0.0"))
], baseSettings: Settings.settings(
))
diff --git a/Tuist/ProjectDescriptionHelpers/FeatureTargets.swift b/Tuist/ProjectDescriptionHelpers/FeatureTargets.swift
new file mode 100644
index 0000000..13008ac
--- /dev/null
+++ b/Tuist/ProjectDescriptionHelpers/FeatureTargets.swift
@@ -0,0 +1,28 @@
+//
+// FeatureTargets.swift
+// ProjectDescriptionHelpers
+//
+// Created by 류희재 on 6/24/24.
+//
+
+import Foundation
+import ProjectDescription
+
+public enum FeatureTarget {
+ case app // iOSApp
+ case interface // Feature Interface
+ case dynamicFramework
+ case staticFramework
+ case unitTest // Unit Test
+ case demo // Feature Excutable Test
+
+ public var hasFramework: Bool {
+ switch self {
+ case .dynamicFramework, .staticFramework: return true
+ default: return false
+ }
+ }
+ public var hasDynamicFramework: Bool { return self == .dynamicFramework }
+}
+
+
diff --git a/Tuist/ProjectDescriptionHelpers/Project+Templates.swift b/Tuist/ProjectDescriptionHelpers/Project+Templates.swift
index 90ec246..d8f477e 100644
--- a/Tuist/ProjectDescriptionHelpers/Project+Templates.swift
+++ b/Tuist/ProjectDescriptionHelpers/Project+Templates.swift
@@ -1,78 +1,238 @@
import ProjectDescription
+import ConfigPlugin
import EnvPlugin
+
public extension Project {
+ /// Sumarry: 모듈을 만드는 함수
+ ///
+ /// Discussion/Overview
+ ///
+ /// - Parameters:
+ /// - name: 프로젝트 이름 (모듈 이름)
+ /// - targets: 빌드할 타겟의 대상
+ /// - organizationName: organization 이름
+ /// - packages:SPM의 package
+ /// - internalDependencies: 내부 의존성
+ /// - externalDependencies: 외부 의존성
+ /// - interfaceDependencies: 인터페이스 의존성
+ /// - sources: 소스 파일
+ /// - hasResourcces: 리소스 파일 포함 여부
static func makeModule(
name: String,
- product: Product,
+ targets: Set = Set([.staticFramework, .unitTest, .demo]),
organizationName: String = "hellohidi",
packages: [Package] = [],
- deploymentTarget: DeploymentTarget? = Environment.deploymentTarget,
- dependencies: [TargetDependency] = [],
+ internalDependencies: [TargetDependency] = [],
+ externalDependencies: [TargetDependency] = [],
+ interfaceDependencies: [TargetDependency] = [],
sources: SourceFilesList? = nil,
- resources: ResourceFileElements? = nil,
- infoPlist: InfoPlist = .extendingDefault(with: Project.appInfoPlist)
+ hasResources: Bool = false
) -> Project {
- let settings: Settings = .settings(
- base: [:],
- configurations: [
- .debug(name: .debug),
- .release(name: .release)
- ], defaultSettings: .recommended)
-
- let appTarget = Target(
- name: name,
- platform: Environment.platform,
- product: product,
- bundleId: "\(Environment.bundlePrefix).\(name)",
- deploymentTarget: deploymentTarget,
- infoPlist: infoPlist,
- sources: sources,
- resources: resources,
- dependencies: dependencies
- )
-
- let testTarget = Target(
- name: "\(name)Tests",
- platform: Environment.platform,
- product: .unitTests,
- bundleId: "\(Environment.bundlePrefix).\(name)Tests",
- deploymentTarget: deploymentTarget,
- infoPlist: .default,
- sources: ["Tests/**"],
- dependencies: [.target(name: name)]
- )
-
- let schemes: [Scheme] = [.makeScheme(target: .debug, name: name)]
-
- let targets: [Target] = [appTarget, testTarget]
-
+ let configurationName: ConfigurationName = "Development"
+ let hasDynamicFramework = targets.contains(.dynamicFramework)
+ let baseSetting: SettingsDictionary = [:]
+
+ var projectTargets: [Target] = []
+ var schemes: [Scheme] = []
+
+ // MARK: - APP
+
+ if targets.contains(.app) {
+ let bundleSuffix = name.contains("Demo") ? "test" : "release"
+ let infoPlist = name.contains("Demo") ? Project.demoInfoPlist : Project.appInfoPlist
+ let setting = baseSetting
+
+ let target = Target(
+ name: name,
+ platform: env.platform,
+ product: .app,
+ bundleId: "\(env.bundlePrefix).\(bundleSuffix)",
+ deploymentTarget: env.deploymentTarget,
+ //infoPlist에 .extendingDefault로 Info.plsit에 추가 내용을 넣어준 이유는 tuist에서 .default 만들어주는 Info.plist는 앱을 실행할 때 화면이 어딘가 나사가 빠진상태로 실행되기 때문입니다.
+ infoPlist: .extendingDefault(with: infoPlist),
+ sources: .sources,
+ // 터미널 명령어랑 비슷한듯? 일단 바쁘니까 나중에 정리해보도록 하자ㅏ https://www.daleseo.com/glob-patterns/#google_vignette
+ resources: [.glob(pattern: "Resources/**", excluding: [])],
+ //entitlement: 주로 iOS 애플리케이션에서 특정 기능이나 권한을 활성화하기 위해 사용하는 설정 파일
+// entitlements: "\(name).entitlements",
+ dependencies: [
+ internalDependencies,
+ externalDependencies
+ ].flatMap { $0 },
+ settings: .settings(base: setting, configurations: XCConfig.project)
+ )
+ projectTargets.append(target)
+ }
+
+ //MARK: - Feature Interface
+
+ if targets.contains(.interface) {
+ let setting = baseSetting
+ let target = Target(
+ name: "\(name)Interface",
+ platform: env.platform,
+ product: .framework,
+ bundleId: "\(env.bundlePrefix).\(name)Interface",
+ deploymentTarget: env.deploymentTarget,
+ infoPlist: .default,
+ sources: .interface,
+ dependencies: interfaceDependencies,
+ settings: .settings(base: setting,configurations: XCConfig.framework)
+ )
+
+ projectTargets.append(target)
+ }
+
+ // MARK: - Framework
+
+ if targets.contains(where: { $0.hasFramework }) {
+ let deps: [TargetDependency] = targets.contains(.interface)
+ ? [.target(name: "\(name)Interface")]
+ : []
+ let settings = baseSetting
+
+ let target = Target(
+ name: name,
+ platform: env.platform,
+ product: hasDynamicFramework ? .framework : .staticFramework,
+ bundleId: "\(env.bundlePrefix).\(name)",
+ deploymentTarget: env.deploymentTarget,
+ infoPlist: .default,
+ sources: .sources,
+ resources: hasResources ? [.glob(pattern: "Resources/**", excluding: [])] : [],
+ dependencies: deps + internalDependencies + externalDependencies,
+ settings: .settings(base: settings, configurations: XCConfig.framework)
+ )
+
+ projectTargets.append(target)
+ }
+
+ //MARK: - Feature DemoApp
+
+ if targets.contains(.demo) {
+ let deps: [TargetDependency] = [.target(name:name)]
+ let setting = baseSetting
+
+ let target = Target(
+ name: "\(name)Demo",
+ platform: env.platform,
+ product: .app,
+ bundleId: "\(env.bundlePrefix).\(name)Demo",
+ deploymentTarget: env.deploymentTarget,
+ infoPlist: .extendingDefault(with: Project.demoInfoPlist),
+ sources: .demoSources,
+ resources: [.glob(pattern: "Demo/Resources/**", excluding: ["Demo/Resources/dummy.txt"])],
+ dependencies: deps,
+ settings: .settings(base: setting, configurations: XCConfig.demo)
+ )
+ projectTargets.append(target)
+ }
+
+ //MARK: - Unit Tests
+
+ if targets.contains(.unitTest) {
+ let deps: [TargetDependency] = [.target(name: name)]
+
+ let target = Target(
+ name: "\(name)Tests",
+ platform: env.platform,
+ product: .unitTests,
+ bundleId: "\(env.bundlePrefix).\(name)Tests",
+ deploymentTarget: env.deploymentTarget,
+ infoPlist: .default,
+ sources: .unitTests,
+ resources: [.glob(pattern: "Tests/Resources/**", excluding: [])],
+ dependencies: deps,
+ settings: .settings(base: SettingsDictionary().setCodeSignManual(), configurations: XCConfig.tests)
+ )
+
+ projectTargets.append(target)
+ }
+
+ let additionalSchemes = targets.contains(.demo) ?
+ [
+ Scheme.makeScheme(configs: configurationName, name: name),
+ Scheme.makeDemoScheme(configs: configurationName, name: name)
+ ]
+ : [
+ Scheme.makeScheme(configs: configurationName, name: name)
+ ]
+
+ schemes += additionalSchemes
+
+
+ var scheme = targets.contains(.app) ? appSchemes : schemes
+
+ if name.contains("Demo") {
+ let testAppScheme = Scheme.makeScheme(configs: "QA", name: name)
+ scheme.append(testAppScheme)
+ }
+
return Project(
name: name,
- organizationName: organizationName,
+ organizationName: env.workspaceName,
packages: packages,
- settings: settings,
- targets: targets,
+ settings: .settings(configurations: XCConfig.project),
+ targets: projectTargets,
schemes: schemes
)
}
}
-extension Scheme {
- static func makeScheme(target: ConfigurationName, name: String) -> Scheme {
- return Scheme(
- name: name,
+extension Project {
+ static let appSchemes: [Scheme] = [
+ // PROD API, debug scheme : 실제 프로덕트 BaseURL을 사용하는 debug scheme
+ .init(
+ name: "\(env.workspaceName)-DEV",
shared: true,
- buildAction: .buildAction(targets: ["\(name)"]),
+ buildAction: .buildAction(targets: ["\(env.workspaceName)"]),
testAction: .targets(
- ["\(name)Tests"],
- configuration: target,
- options: .options(coverage: true, codeCoverageTargets: ["\(name)"])
+ ["\(env.workspaceName)Tests", "\(env.workspaceName)UITests"],
+ configuration: "Development",
+ options: .options(coverage: true, codeCoverageTargets: ["\(env.workspaceName)"])
),
- runAction: .runAction(configuration: target),
- archiveAction: .archiveAction(configuration: target),
- profileAction: .profileAction(configuration: target),
- analyzeAction: .analyzeAction(configuration: target)
- )
- }
+ runAction: .runAction(configuration: "Development"),
+ archiveAction: .archiveAction(configuration: "Development"),
+ profileAction: .profileAction(configuration: "Development"),
+ analyzeAction: .analyzeAction(configuration: "Development")
+ ),
+ // Test API, debug scheme : 테스트 BaseURL을 사용하는 debug scheme
+ .init(
+ name: "\(env.workspaceName)-Test",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(env.workspaceName)"]),
+ testAction: .targets(
+ ["\(env.workspaceName)Tests", "\(env.workspaceName)UITests"],
+ configuration: "Test",
+ options: .options(coverage: true, codeCoverageTargets: ["\(env.workspaceName)"])
+ ),
+ runAction: .runAction(configuration: "Test"),
+ archiveAction: .archiveAction(configuration: "Test"),
+ profileAction: .profileAction(configuration: "Test"),
+ analyzeAction: .analyzeAction(configuration: "Test")
+ ),
+ // Test API, release scheme : 테스트 BaseURL을 사용하는 release scheme
+ .init(
+ name: "\(env.workspaceName)-QA",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(env.workspaceName)"]),
+ runAction: .runAction(configuration: "QA"),
+ archiveAction: .archiveAction(configuration: "QA"),
+ profileAction: .profileAction(configuration: "QA"),
+ analyzeAction: .analyzeAction(configuration: "QA")
+ ),
+ // PROD API, release scheme : 실제 프로덕트 BaseURL을 사용하는 release scheme
+ .init(
+ name: "\(env.workspaceName)-PROD",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(env.workspaceName)"]),
+ runAction: .runAction(configuration: "PROD"),
+ archiveAction: .archiveAction(configuration: "PROD"),
+ profileAction: .profileAction(configuration: "PROD"),
+ analyzeAction: .analyzeAction(configuration: "PROD")
+ ),
+ // Test API, debug scheme, Demo App Target
+ .makeDemoAppTestScheme()
+ ]
}
diff --git a/Tuist/ProjectDescriptionHelpers/Scheme+Template.swift b/Tuist/ProjectDescriptionHelpers/Scheme+Template.swift
new file mode 100644
index 0000000..d9d3f6d
--- /dev/null
+++ b/Tuist/ProjectDescriptionHelpers/Scheme+Template.swift
@@ -0,0 +1,73 @@
+//
+// Scheme+Template.swift
+// ProjectDescriptionHelpers
+//
+// Created by 류희재 on 7/1/24.
+//
+
+import ProjectDescription
+import EnvPlugin
+
+extension Scheme {
+ /// Scheme 생성하는 method
+ /// 어떤 타겟을 빌드할 것인지, 어떤 테스트를 실행할 것인지 또한 어떤 환경에서 빌드할 것인지 설정
+ ///
+ /// DEV : 실제 프로덕트 BaseURL을 사용하는 debug scheme
+ /// TEST : 테스트 BaseURL을 사용하는 debug scheme
+ /// QA : 테스트 BaseURL을 사용하는 release scheme
+ /// RELEASE : 실제 프로덕트 BaseURL을 사용하는 release scheme
+
+ static func makeScheme(configs: ConfigurationName, name: String) -> Scheme { // 일반앱
+ return Scheme(
+ name: name,
+ shared: true,
+ buildAction: .buildAction(targets: ["\(name)"]),
+ testAction: .targets(
+ ["\(name)Tests"],
+ configuration: configs,
+ options: .options(coverage: true, codeCoverageTargets: ["\(name)"])
+ ),
+ runAction: .runAction(configuration: configs),
+ archiveAction: .archiveAction(configuration: configs),
+ profileAction: .profileAction(configuration: configs),
+ analyzeAction: .analyzeAction(configuration: configs)
+ )
+ }
+
+ static func makeDemoScheme(configs: ConfigurationName, name: String) -> Scheme { // 데모앱
+ return Scheme(
+ name: "\(name)Demo",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(name)Demo"]),
+ testAction: .targets(
+ ["\(name)Tests"],
+ configuration: configs,
+ options: .options(coverage: true, codeCoverageTargets: ["\(name)Demo"])
+ ),
+ runAction: .runAction(configuration: configs),
+ archiveAction: .archiveAction(configuration: configs),
+ profileAction: .profileAction(configuration: configs),
+ analyzeAction: .analyzeAction(configuration: configs)
+ )
+ }
+
+ static func makeDemoAppTestScheme() -> Scheme { // 데모테스트앱
+ let targetName = "\(env.workspaceName)-Demo"
+ return Scheme(
+ name: "\(targetName)-Test",
+ shared: true,
+ buildAction: .buildAction(targets: ["\(targetName)"]),
+ testAction: .targets(
+ ["\(targetName)Tests"],
+ configuration: "Test",
+ options: .options(coverage: true, codeCoverageTargets: ["\(targetName)"])
+ ),
+ runAction: .runAction(configuration: "Test"),
+ archiveAction: .archiveAction(configuration: "Test"),
+ profileAction: .profileAction(configuration: "Test"),
+ analyzeAction: .analyzeAction(configuration: "Test")
+ )
+ }
+}
+
+
diff --git a/Tuist/ProjectDescriptionHelpers/SourceFiles+Template.swift b/Tuist/ProjectDescriptionHelpers/SourceFiles+Template.swift
new file mode 100644
index 0000000..3883cd8
--- /dev/null
+++ b/Tuist/ProjectDescriptionHelpers/SourceFiles+Template.swift
@@ -0,0 +1,17 @@
+//
+// SourceFiles+Template.swift
+// ProjectDescriptionHelpers
+//
+// Created by 류희재 on 7/1/24.
+//
+
+import ProjectDescription
+
+public extension SourceFilesList {
+ static let demoSources: SourceFilesList = "Demo/Sources/**/*.swift"
+ static let interface: SourceFilesList = "Interface/Sources/**/*.swift"
+ static let sources: SourceFilesList = "Sources/**/*.swift"
+ static let testing: SourceFilesList = "Testing/**"
+ static let unitTests: SourceFilesList = "Tests/Sources/**/*.swift"
+ static let uiTests: SourceFilesList = "UITests/**"
+}
diff --git a/graph.png b/graph.png
index 32de867..fd15ef2 100644
Binary files a/graph.png and b/graph.png differ