├── .gitignore
├── .swiftlint.yml
├── CleanArchitecture.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── WorkspaceSettings.xcsettings
│ └── swiftpm
│ └── Package.resolved
├── CleanArchitecture
├── .gitignore
├── Package.swift
└── Sources
│ └── CleanArchitecture
│ ├── ActivityIndicator.swift
│ ├── Bindable.swift
│ ├── CancelBag.swift
│ ├── ErrorTracker.swift
│ ├── GenericSubscriber.swift
│ ├── LoadingState.swift
│ ├── PagingInfo.swift
│ ├── ViewModel+FetchItem.swift
│ ├── ViewModel+FetchList.swift
│ ├── ViewModel+FetchPage.swift
│ └── ViewModel.swift
├── CleanArchitectureExample.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── WorkspaceSettings.xcsettings
│ └── swiftpm
│ └── Package.resolved
├── CleanArchitectureExample
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── CleanArchitecture-Bridging-Header.h
├── Config
│ └── ColorCompatibility.swift
├── ContentView.swift
├── Data
│ └── Gateways
│ │ ├── AuthGateway.swift
│ │ ├── GitEndpoint.swift
│ │ ├── ProductGateway.swift
│ │ └── RepoGateway.swift
├── Domain
│ ├── Entities
│ │ ├── AppError.swift
│ │ ├── Product.swift
│ │ └── Repo.swift
│ └── UseCases
│ │ ├── Auth
│ │ └── LogIn.swift
│ │ ├── Product
│ │ └── GetProducts.swift
│ │ ├── Repo
│ │ └── GetRepoList.swift
│ │ └── Validations
│ │ ├── ValidationError.swift
│ │ └── ValidationResult.swift
├── Info.plist
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Scene
│ ├── Common
│ │ ├── ActivityIndicatorView.swift
│ │ ├── AlertMessage.swift
│ │ ├── BaseViewController.swift
│ │ ├── LoadingView.swift
│ │ └── ViewDidLoadModifier.swift
│ ├── Login
│ │ ├── LoginView.swift
│ │ └── LoginViewModel.swift
│ ├── Main
│ │ ├── MainViewController.swift
│ │ ├── MainViewModel.swift
│ │ ├── MenuCell.swift
│ │ ├── MenuCell.xib
│ │ └── Storyboards+Main.swift
│ ├── Navigators
│ │ ├── ShowLogin.swift
│ │ ├── ShowProductDetail.swift
│ │ ├── ShowProductList.swift
│ │ ├── ShowRepoCollection.swift
│ │ ├── ShowRepoDetail.swift
│ │ └── ShowRepoList.swift
│ ├── ProductDetail
│ │ ├── ProductDetailView.swift
│ │ └── ProductDetailViewModel.swift
│ ├── Products
│ │ ├── ProductItemViewModel.swift
│ │ ├── ProductRow.swift
│ │ ├── ProductsView.swift
│ │ └── ProductsViewModel.swift
│ ├── RepoCollection
│ │ ├── RepoCollectionCell.swift
│ │ ├── RepoCollectionCell.xib
│ │ └── RepoCollectionViewController.swift
│ ├── Repos
│ │ ├── Repo.storyboard
│ │ ├── RepoCell.swift
│ │ ├── RepoCell.xib
│ │ ├── RepoItemViewModel.swift
│ │ ├── ReposViewController.swift
│ │ ├── ReposViewModel.swift
│ │ └── Storyboards+Repo.swift
│ └── Storyboards
│ │ ├── Main.storyboard
│ │ └── Storyboards.swift
├── SceneDelegate.swift
└── Support
│ └── Extensions
│ ├── Double+.swift
│ ├── Driver.swift
│ ├── Observable.swift
│ ├── Publisher+.swift
│ ├── UIImageView+SDWebImage.swift
│ ├── UIViewController+.swift
│ ├── UIViewController+Combine.swift
│ └── UIViewController+Debug.swift
├── CleanArchitectureExampleTests
├── Data
│ └── Gateways
│ │ ├── MockAuthGateway.swift
│ │ ├── MockProductGateway.swift
│ │ └── MockRepoGateway.swift
├── Domain
│ └── UseCases
│ │ └── LogInTests.swift
├── Extensions
│ └── XCTestCase+.swift
├── Info.plist
├── Scene
│ ├── Main
│ │ ├── MainViewControllerTests.swift
│ │ └── MainViewModelTests.swift
│ ├── Products
│ │ └── ProductsViewModelTests.swift
│ └── Repos
│ │ └── ReposViewModelTests.swift
└── TestError.swift
├── CleanArchitectureExampleUITests
└── LoginUITests.swift
├── LICENSE
├── Package.swift
├── PagingTableView
├── .gitignore
├── Package.swift
└── Sources
│ └── PagingTableView
│ ├── PagingCollectionView.swift
│ ├── PagingTableView.swift
│ ├── RefreshFooterAnimator.swift
│ └── RefreshHeaderAnimator.swift
├── README.md
├── files
├── xcode_project_template.zip
└── xcode_templates.zip
├── igen.config
├── images
├── bridging_header.png
├── copy_files.png
├── create_bridging_header.png
├── data.png
├── delete_files.png
├── dependency_direction.png
├── detail_overview.png
├── domain.png
├── drag_files_folders.png
├── example.jpg
├── high_level_overview.png
├── mvvm_pattern.png
├── new_project.png
├── presentation.png
├── remove_scene_configuration.png
├── result.png
├── skeleton.png
├── swiftlint_run_script.png
├── template_scene_name.png
├── templates.png
└── xcode_templates.png
├── xcode_project_template.md
└── xcode_templates.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
70 | .DS_Store
71 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules: # rule identifiers to exclude from running
2 | - trailing_whitespace
3 | - trailing_newline
4 | - xctfail_message
5 | - identifier_name
6 | - function_body_length
7 | - type_body_length
8 | - file_length
9 | - type_name
10 | - multiple_closures_with_trailing_closure
11 |
12 | opt_in_rules: # some rules are only opt-in
13 | # - anyobject_protocol
14 | # - array_init
15 | - attributes
16 | - block_based_kvo
17 | - class_delegate_protocol
18 | - closing_brace
19 | - closure_end_indentation
20 | - closure_parameter_position
21 | - closure_spacing
22 | - colon
23 | - comma
24 | - compiler_protocol_init
25 | # - conditional_returns_on_newline
26 | - contains_over_first_not_nil
27 | - control_statement
28 | - convenience_type
29 | - custom_rules
30 | - cyclomatic_complexity
31 | - discarded_notification_center_observer
32 | - discouraged_direct_init
33 | # - discouraged_object_literal
34 | - discouraged_optional_boolean
35 | # - discouraged_optional_collection
36 | - dynamic_inline
37 | - empty_count
38 | - empty_enum_arguments
39 | - empty_parameters
40 | - empty_parentheses_with_trailing_closure
41 | - empty_string
42 | - empty_xctest_method
43 | # - explicit_acl
44 | # - explicit_enum_raw_value
45 | - explicit_init
46 | # - explicit_top_level_acl
47 | # - explicit_type_interface
48 | # - extension_access_modifier
49 | - fallthrough
50 | - fatal_error_message
51 | # - file_header
52 | # - file_length
53 | # - file_name
54 | - first_where
55 | - for_where
56 | - force_cast
57 | - force_try
58 | - force_unwrapping
59 | # - function_body_length
60 | # - function_default_parameter_at_end
61 | - function_parameter_count
62 | - generic_type_name
63 | # - identifier_name
64 | - implicit_getter
65 | # - implicit_return
66 | # - implicitly_unwrapped_optional
67 | - is_disjoint
68 | - joined_default_parameter
69 | - large_tuple
70 | - leading_whitespace
71 | - legacy_cggeometry_functions
72 | - legacy_constant
73 | - legacy_constructor
74 | - legacy_nsgeometry_functions
75 | # - let_var_whitespace
76 | - line_length
77 | - literal_expression_end_indentation
78 | - lower_acl_than_parent
79 | - mark
80 | # - missing_docs
81 | - modifier_order
82 | - multiline_arguments
83 | - multiline_function_chains
84 | - multiline_parameters
85 | # - multiple_closures_with_trailing_closure
86 | - nesting
87 | - nimble_operator
88 | # - no_extension_access_modifier
89 | - no_fallthrough_only
90 | # - no_grouping_extension
91 | - notification_center_detachment
92 | # - number_separator
93 | # - object_literal
94 | - opening_brace
95 | - operator_usage_whitespace
96 | - operator_whitespace
97 | - overridden_super_call
98 | - override_in_extension
99 | - pattern_matching_keywords
100 | - prefixed_toplevel_constant
101 | # - private_action
102 | # - private_outlet
103 | - private_over_fileprivate
104 | - private_unit_test
105 | - prohibited_super_call
106 | - protocol_property_accessors_order
107 | - quick_discouraged_call
108 | - quick_discouraged_focused_test
109 | - quick_discouraged_pending_test
110 | - redundant_discardable_let
111 | - redundant_nil_coalescing
112 | - redundant_optional_initialization
113 | - redundant_set_access_control
114 | - redundant_string_enum_value
115 | - redundant_type_annotation
116 | - redundant_void_return
117 | - required_enum_case
118 | - return_arrow_whitespace
119 | - shorthand_operator
120 | - single_test_class
121 | - sorted_first_last
122 | # - sorted_imports
123 | - statement_position
124 | # - strict_fileprivate
125 | - superfluous_disable_command
126 | - switch_case_alignment
127 | - switch_case_on_newline
128 | - syntactic_sugar
129 | - todo
130 | # - trailing_closure
131 | - trailing_comma
132 | - trailing_newline
133 | - trailing_semicolon
134 | # - trailing_whitespace
135 | # - type_body_length
136 | # - type_name
137 | - unavailable_function
138 | - unneeded_break_in_switch
139 | # - unneeded_parentheses_in_closure_argument
140 | - untyped_error_in_catch
141 | - unused_closure_parameter
142 | - unused_enumerated
143 | - unused_optional_binding
144 | - valid_ibinspectable
145 | - vertical_parameter_alignment
146 | - vertical_parameter_alignment_on_call
147 | - vertical_whitespace
148 | - void_return
149 | - weak_delegate
150 | # - xctfail_message
151 | - yoda_condition
152 | # Find all the available rules by running:
153 | # swiftlint rules
154 |
155 | included: # paths to include during linting. `--path` is ignored if present.
156 | - CleanArchitecture
157 | - CleanArchitectureTests
158 |
159 | excluded: # paths to ignore during linting. Takes precedence over `included`.
160 | - Carthage
161 | - Pods
162 |
163 | line_length:
164 | warning: 120
165 | ignores_function_declarations: false
166 | ignores_comments: true
167 | ignores_urls: true
168 |
169 | function_parameter_count:
170 | warning: 6
171 | error: 8
172 |
173 | cyclomatic_complexity:
174 | warning: 15
175 | error: 25
176 |
177 | reporter: "xcode" # reporter type (xcode, jsm
178 |
179 | custom_rules:
180 | final_class:
181 | name: "Final Class"
182 | regex: "(^[ ]*class [A-Z]+)"
183 | message: "Class should be marked as final."
184 | severity: warning
185 |
--------------------------------------------------------------------------------
/CleanArchitecture.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
9 |
10 |
13 |
16 |
18 |
19 |
21 |
22 |
24 |
25 |
27 |
28 |
30 |
31 |
33 |
34 |
36 |
37 |
39 |
40 |
42 |
43 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
54 |
55 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/CleanArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CleanArchitecture.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CleanArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "combinecocoa",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/CombineCommunity/CombineCocoa.git",
7 | "state" : {
8 | "revision" : "7300c75ff9e072aa7fd0fccefcc88f74aae9bf56",
9 | "version" : "0.4.1"
10 | }
11 | },
12 | {
13 | "identity" : "combineext",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/CombineCommunity/CombineExt.git",
16 | "state" : {
17 | "revision" : "d7b896fa9ca8b47fa7bcde6b43ef9b70bf8c1f56",
18 | "version" : "1.8.1"
19 | }
20 | },
21 | {
22 | "identity" : "factory",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/hmlongco/Factory",
25 | "state" : {
26 | "revision" : "f350e0d71ba241b392f70519a67e769d5e3858d4",
27 | "version" : "2.4.1"
28 | }
29 | },
30 | {
31 | "identity" : "mbprogresshud",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/jdg/MBProgressHUD",
34 | "state" : {
35 | "revision" : "bca42b801100b2b3a4eda0ba8dd33d858c780b0d",
36 | "version" : "1.2.0"
37 | }
38 | },
39 | {
40 | "identity" : "pull-to-refresh",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/eggswift/pull-to-refresh",
43 | "state" : {
44 | "revision" : "a858aaeb44abc6428af916e8496429d244fac053",
45 | "version" : "2.9.3"
46 | }
47 | },
48 | {
49 | "identity" : "reusable",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/AliSoftware/Reusable.git",
52 | "state" : {
53 | "revision" : "18674709421360e210c2ecd4e8e08b217d4ea61d",
54 | "version" : "4.1.2"
55 | }
56 | },
57 | {
58 | "identity" : "rxswift",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/ReactiveX/RxSwift.git",
61 | "state" : {
62 | "revision" : "c7c7d2cf50a3211fe2843f76869c698e4e417930",
63 | "version" : "6.8.0"
64 | }
65 | },
66 | {
67 | "identity" : "sdwebimage",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/SDWebImage/SDWebImage",
70 | "state" : {
71 | "revision" : "10d06f6a33bafae8c164fbfd1f03391f6d4692b3",
72 | "version" : "5.20.0"
73 | }
74 | },
75 | {
76 | "identity" : "swiftui-introspect",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/siteline/SwiftUI-Introspect.git",
79 | "state" : {
80 | "revision" : "121c146fe591b1320238d054ae35c81ffa45f45a",
81 | "version" : "0.12.0"
82 | }
83 | },
84 | {
85 | "identity" : "swiftuirefresh",
86 | "kind" : "remoteSourceControl",
87 | "location" : "https://github.com/timbersoftware/SwiftUIRefresh.git",
88 | "state" : {
89 | "revision" : "fa8fac7b5eb5c729983a8bef65f094b5e0d12014",
90 | "version" : "0.0.3"
91 | }
92 | },
93 | {
94 | "identity" : "tech-standard-ios-api",
95 | "kind" : "remoteSourceControl",
96 | "location" : "https://github.com/sun-asterisk/tech-standard-ios-api",
97 | "state" : {
98 | "revision" : "96d2a4555f94786e75685a8f8aea13b061af9f14",
99 | "version" : "0.19.0"
100 | }
101 | },
102 | {
103 | "identity" : "then",
104 | "kind" : "remoteSourceControl",
105 | "location" : "https://github.com/devxoul/Then",
106 | "state" : {
107 | "revision" : "e421a7b3440a271834337694e6050133a3958bc7",
108 | "version" : "2.7.0"
109 | }
110 | }
111 | ],
112 | "version" : 2
113 | }
114 |
--------------------------------------------------------------------------------
/CleanArchitecture/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/CleanArchitecture/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CleanArchitecture",
8 | platforms: [
9 | .iOS(.v13)
10 | ],
11 | products: [
12 | // Products define the executables and libraries a package produces, making them visible to other packages.
13 | .library(
14 | name: "CleanArchitecture",
15 | targets: ["CleanArchitecture"]),
16 | ],
17 | targets: [
18 | // Targets are the basic building blocks of a package, defining a module or a test suite.
19 | // Targets can depend on other targets in this package and products from dependencies.
20 | .target(
21 | name: "CleanArchitecture"),
22 |
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/CleanArchitecture/Sources/CleanArchitecture/ActivityIndicator.swift:
--------------------------------------------------------------------------------
1 | import Combine
2 |
3 | // typealias for ActivityIndicator, a CurrentValueSubject that tracks activity status (true/false)
4 | public typealias ActivityIndicator = CurrentValueSubject
5 |
6 | extension Publisher where Failure: Error {
7 | /// Track activity status of the publisher using an ActivityIndicator.
8 | ///
9 | /// - Parameter activityIndicator: An instance of ActivityIndicator used to track activity status of the publisher.
10 | /// - Returns: A publisher that emits the same output and failure types as the original publisher.
11 | public func trackActivity(_ activityIndicator: ActivityIndicator) -> AnyPublisher