├── CombineMarble.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── alfianlosari.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── alfianlosari.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── CombineMarble ├── AppDelegate.swift ├── CombineMarble.entitlements ├── Combines │ ├── CombineLatest │ │ └── CombineLatestSectionContainer.swift │ ├── CombineSectionContainer.swift │ ├── Filter │ │ └── CombineFilterSectionContainer.swift │ ├── Map │ │ └── CombineMapSectionContainer.swift │ ├── Merge │ │ └── CombineMergeSectionContainer.swift │ └── Zip │ │ └── CombineZipSectionContainer.swift ├── Info.plist ├── Model │ └── SectionController.swift ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard ├── SceneDelegate.swift ├── ViewController.swift └── Views │ ├── LineCell.swift │ ├── LineCell.xib │ ├── LineDecorationView.swift │ ├── LineDecorationView.xib │ ├── SectionHeaderView.swift │ └── SectionHeaderView.xib ├── README.md └── promo.png /CombineMarble.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8B184E6522CE22EF009F5EF7 /* CombineSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E6422CE22EF009F5EF7 /* CombineSectionContainer.swift */; }; 11 | 8B184E6822CE2318009F5EF7 /* CombineLatestSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E6722CE2318009F5EF7 /* CombineLatestSectionContainer.swift */; }; 12 | 8B184E6B22CE234B009F5EF7 /* CombineMapSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E6A22CE234B009F5EF7 /* CombineMapSectionContainer.swift */; }; 13 | 8B184E6E22CE25BD009F5EF7 /* CombineMergeSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E6D22CE25BD009F5EF7 /* CombineMergeSectionContainer.swift */; }; 14 | 8B184E7122CE2C89009F5EF7 /* CombineZipSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E7022CE2C89009F5EF7 /* CombineZipSectionContainer.swift */; }; 15 | 8B184E7422CE34F1009F5EF7 /* CombineFilterSectionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B184E7322CE34F1009F5EF7 /* CombineFilterSectionContainer.swift */; }; 16 | 8B23C11922CB7F49002C33D5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C11822CB7F49002C33D5 /* AppDelegate.swift */; }; 17 | 8B23C11B22CB7F49002C33D5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C11A22CB7F49002C33D5 /* SceneDelegate.swift */; }; 18 | 8B23C11D22CB7F49002C33D5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C11C22CB7F49002C33D5 /* ViewController.swift */; }; 19 | 8B23C12022CB7F49002C33D5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C11E22CB7F49002C33D5 /* Main.storyboard */; }; 20 | 8B23C12222CB7F4B002C33D5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C12122CB7F4B002C33D5 /* Assets.xcassets */; }; 21 | 8B23C12522CB7F4B002C33D5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C12322CB7F4B002C33D5 /* LaunchScreen.storyboard */; }; 22 | 8B23C12E22CB9857002C33D5 /* LineCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C12C22CB9857002C33D5 /* LineCell.swift */; }; 23 | 8B23C12F22CB9857002C33D5 /* LineCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C12D22CB9857002C33D5 /* LineCell.xib */; }; 24 | 8B23C13222CBA2A1002C33D5 /* LineDecorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C13022CBA2A1002C33D5 /* LineDecorationView.swift */; }; 25 | 8B23C13322CBA2A1002C33D5 /* LineDecorationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C13122CBA2A1002C33D5 /* LineDecorationView.xib */; }; 26 | 8B23C13522CBAD50002C33D5 /* SectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C13422CBAD50002C33D5 /* SectionController.swift */; }; 27 | 8B23C13A22CBB52E002C33D5 /* SectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B23C13822CBB52E002C33D5 /* SectionHeaderView.swift */; }; 28 | 8B23C13B22CBB52E002C33D5 /* SectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8B23C13922CBB52E002C33D5 /* SectionHeaderView.xib */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 8B184E6422CE22EF009F5EF7 /* CombineSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineSectionContainer.swift; sourceTree = ""; }; 33 | 8B184E6722CE2318009F5EF7 /* CombineLatestSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineLatestSectionContainer.swift; sourceTree = ""; }; 34 | 8B184E6A22CE234B009F5EF7 /* CombineMapSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineMapSectionContainer.swift; sourceTree = ""; }; 35 | 8B184E6D22CE25BD009F5EF7 /* CombineMergeSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineMergeSectionContainer.swift; sourceTree = ""; }; 36 | 8B184E7022CE2C89009F5EF7 /* CombineZipSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineZipSectionContainer.swift; sourceTree = ""; }; 37 | 8B184E7322CE34F1009F5EF7 /* CombineFilterSectionContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineFilterSectionContainer.swift; sourceTree = ""; }; 38 | 8B184E7922CE50DD009F5EF7 /* CombineMarble.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CombineMarble.entitlements; sourceTree = ""; }; 39 | 8B23C11522CB7F49002C33D5 /*  Combine Visualizer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = " Combine Visualizer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 8B23C11822CB7F49002C33D5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 8B23C11A22CB7F49002C33D5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 42 | 8B23C11C22CB7F49002C33D5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 43 | 8B23C11F22CB7F49002C33D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 44 | 8B23C12122CB7F4B002C33D5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 8B23C12422CB7F4B002C33D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | 8B23C12622CB7F4B002C33D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 8B23C12C22CB9857002C33D5 /* LineCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineCell.swift; sourceTree = ""; }; 48 | 8B23C12D22CB9857002C33D5 /* LineCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LineCell.xib; sourceTree = ""; }; 49 | 8B23C13022CBA2A1002C33D5 /* LineDecorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineDecorationView.swift; sourceTree = ""; }; 50 | 8B23C13122CBA2A1002C33D5 /* LineDecorationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LineDecorationView.xib; sourceTree = ""; }; 51 | 8B23C13422CBAD50002C33D5 /* SectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionController.swift; sourceTree = ""; }; 52 | 8B23C13822CBB52E002C33D5 /* SectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderView.swift; sourceTree = ""; }; 53 | 8B23C13922CBB52E002C33D5 /* SectionHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SectionHeaderView.xib; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 8B23C11222CB7F49002C33D5 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 8B184E6322CE22CC009F5EF7 /* Combines */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 8B184E6422CE22EF009F5EF7 /* CombineSectionContainer.swift */, 71 | 8B184E7222CE34E4009F5EF7 /* Filter */, 72 | 8B184E6F22CE2C4F009F5EF7 /* Zip */, 73 | 8B184E6C22CE25A9009F5EF7 /* Merge */, 74 | 8B184E6922CE2331009F5EF7 /* Map */, 75 | 8B184E6622CE230C009F5EF7 /* CombineLatest */, 76 | ); 77 | path = Combines; 78 | sourceTree = ""; 79 | }; 80 | 8B184E6622CE230C009F5EF7 /* CombineLatest */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 8B184E6722CE2318009F5EF7 /* CombineLatestSectionContainer.swift */, 84 | ); 85 | path = CombineLatest; 86 | sourceTree = ""; 87 | }; 88 | 8B184E6922CE2331009F5EF7 /* Map */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 8B184E6A22CE234B009F5EF7 /* CombineMapSectionContainer.swift */, 92 | ); 93 | path = Map; 94 | sourceTree = ""; 95 | }; 96 | 8B184E6C22CE25A9009F5EF7 /* Merge */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 8B184E6D22CE25BD009F5EF7 /* CombineMergeSectionContainer.swift */, 100 | ); 101 | path = Merge; 102 | sourceTree = ""; 103 | }; 104 | 8B184E6F22CE2C4F009F5EF7 /* Zip */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 8B184E7022CE2C89009F5EF7 /* CombineZipSectionContainer.swift */, 108 | ); 109 | path = Zip; 110 | sourceTree = ""; 111 | }; 112 | 8B184E7222CE34E4009F5EF7 /* Filter */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 8B184E7322CE34F1009F5EF7 /* CombineFilterSectionContainer.swift */, 116 | ); 117 | path = Filter; 118 | sourceTree = ""; 119 | }; 120 | 8B184E7622CE4F88009F5EF7 /* Model */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 8B23C13422CBAD50002C33D5 /* SectionController.swift */, 124 | ); 125 | path = Model; 126 | sourceTree = ""; 127 | }; 128 | 8B184E7722CE4F97009F5EF7 /* Views */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 8B23C13022CBA2A1002C33D5 /* LineDecorationView.swift */, 132 | 8B23C13122CBA2A1002C33D5 /* LineDecorationView.xib */, 133 | 8B23C12C22CB9857002C33D5 /* LineCell.swift */, 134 | 8B23C12D22CB9857002C33D5 /* LineCell.xib */, 135 | 8B23C13822CBB52E002C33D5 /* SectionHeaderView.swift */, 136 | 8B23C13922CBB52E002C33D5 /* SectionHeaderView.xib */, 137 | ); 138 | path = Views; 139 | sourceTree = ""; 140 | }; 141 | 8B184E7822CE4FBD009F5EF7 /* Resources */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 8B23C11E22CB7F49002C33D5 /* Main.storyboard */, 145 | 8B23C12122CB7F4B002C33D5 /* Assets.xcassets */, 146 | 8B23C12322CB7F4B002C33D5 /* LaunchScreen.storyboard */, 147 | ); 148 | path = Resources; 149 | sourceTree = ""; 150 | }; 151 | 8B23C10C22CB7F49002C33D5 = { 152 | isa = PBXGroup; 153 | children = ( 154 | 8B23C11722CB7F49002C33D5 /* CombineMarble */, 155 | 8B23C11622CB7F49002C33D5 /* Products */, 156 | ); 157 | sourceTree = ""; 158 | }; 159 | 8B23C11622CB7F49002C33D5 /* Products */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 8B23C11522CB7F49002C33D5 /*  Combine Visualizer.app */, 163 | ); 164 | name = Products; 165 | sourceTree = ""; 166 | }; 167 | 8B23C11722CB7F49002C33D5 /* CombineMarble */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 8B184E7922CE50DD009F5EF7 /* CombineMarble.entitlements */, 171 | 8B23C11822CB7F49002C33D5 /* AppDelegate.swift */, 172 | 8B23C11A22CB7F49002C33D5 /* SceneDelegate.swift */, 173 | 8B23C11C22CB7F49002C33D5 /* ViewController.swift */, 174 | 8B23C12622CB7F4B002C33D5 /* Info.plist */, 175 | 8B184E7822CE4FBD009F5EF7 /* Resources */, 176 | 8B184E7722CE4F97009F5EF7 /* Views */, 177 | 8B184E7622CE4F88009F5EF7 /* Model */, 178 | 8B184E6322CE22CC009F5EF7 /* Combines */, 179 | ); 180 | path = CombineMarble; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXNativeTarget section */ 186 | 8B23C11422CB7F49002C33D5 /* CombineMarble */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 8B23C12922CB7F4B002C33D5 /* Build configuration list for PBXNativeTarget "CombineMarble" */; 189 | buildPhases = ( 190 | 8B23C11122CB7F49002C33D5 /* Sources */, 191 | 8B23C11222CB7F49002C33D5 /* Frameworks */, 192 | 8B23C11322CB7F49002C33D5 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = CombineMarble; 199 | productName = CombineMarble; 200 | productReference = 8B23C11522CB7F49002C33D5 /*  Combine Visualizer.app */; 201 | productType = "com.apple.product-type.application"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | 8B23C10D22CB7F49002C33D5 /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 1100; 210 | LastUpgradeCheck = 1100; 211 | ORGANIZATIONNAME = "Alfian Losari"; 212 | TargetAttributes = { 213 | 8B23C11422CB7F49002C33D5 = { 214 | CreatedOnToolsVersion = 11.0; 215 | }; 216 | }; 217 | }; 218 | buildConfigurationList = 8B23C11022CB7F49002C33D5 /* Build configuration list for PBXProject "CombineMarble" */; 219 | compatibilityVersion = "Xcode 9.3"; 220 | developmentRegion = en; 221 | hasScannedForEncodings = 0; 222 | knownRegions = ( 223 | en, 224 | Base, 225 | ); 226 | mainGroup = 8B23C10C22CB7F49002C33D5; 227 | productRefGroup = 8B23C11622CB7F49002C33D5 /* Products */; 228 | projectDirPath = ""; 229 | projectRoot = ""; 230 | targets = ( 231 | 8B23C11422CB7F49002C33D5 /* CombineMarble */, 232 | ); 233 | }; 234 | /* End PBXProject section */ 235 | 236 | /* Begin PBXResourcesBuildPhase section */ 237 | 8B23C11322CB7F49002C33D5 /* Resources */ = { 238 | isa = PBXResourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 8B23C13322CBA2A1002C33D5 /* LineDecorationView.xib in Resources */, 242 | 8B23C13B22CBB52E002C33D5 /* SectionHeaderView.xib in Resources */, 243 | 8B23C12522CB7F4B002C33D5 /* LaunchScreen.storyboard in Resources */, 244 | 8B23C12F22CB9857002C33D5 /* LineCell.xib in Resources */, 245 | 8B23C12222CB7F4B002C33D5 /* Assets.xcassets in Resources */, 246 | 8B23C12022CB7F49002C33D5 /* Main.storyboard in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | /* End PBXResourcesBuildPhase section */ 251 | 252 | /* Begin PBXSourcesBuildPhase section */ 253 | 8B23C11122CB7F49002C33D5 /* Sources */ = { 254 | isa = PBXSourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | 8B23C11D22CB7F49002C33D5 /* ViewController.swift in Sources */, 258 | 8B184E6E22CE25BD009F5EF7 /* CombineMergeSectionContainer.swift in Sources */, 259 | 8B23C12E22CB9857002C33D5 /* LineCell.swift in Sources */, 260 | 8B23C11922CB7F49002C33D5 /* AppDelegate.swift in Sources */, 261 | 8B184E6822CE2318009F5EF7 /* CombineLatestSectionContainer.swift in Sources */, 262 | 8B184E6B22CE234B009F5EF7 /* CombineMapSectionContainer.swift in Sources */, 263 | 8B184E7122CE2C89009F5EF7 /* CombineZipSectionContainer.swift in Sources */, 264 | 8B23C13222CBA2A1002C33D5 /* LineDecorationView.swift in Sources */, 265 | 8B184E7422CE34F1009F5EF7 /* CombineFilterSectionContainer.swift in Sources */, 266 | 8B23C13A22CBB52E002C33D5 /* SectionHeaderView.swift in Sources */, 267 | 8B184E6522CE22EF009F5EF7 /* CombineSectionContainer.swift in Sources */, 268 | 8B23C11B22CB7F49002C33D5 /* SceneDelegate.swift in Sources */, 269 | 8B23C13522CBAD50002C33D5 /* SectionController.swift in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXSourcesBuildPhase section */ 274 | 275 | /* Begin PBXVariantGroup section */ 276 | 8B23C11E22CB7F49002C33D5 /* Main.storyboard */ = { 277 | isa = PBXVariantGroup; 278 | children = ( 279 | 8B23C11F22CB7F49002C33D5 /* Base */, 280 | ); 281 | name = Main.storyboard; 282 | sourceTree = ""; 283 | }; 284 | 8B23C12322CB7F4B002C33D5 /* LaunchScreen.storyboard */ = { 285 | isa = PBXVariantGroup; 286 | children = ( 287 | 8B23C12422CB7F4B002C33D5 /* Base */, 288 | ); 289 | name = LaunchScreen.storyboard; 290 | sourceTree = ""; 291 | }; 292 | /* End PBXVariantGroup section */ 293 | 294 | /* Begin XCBuildConfiguration section */ 295 | 8B23C12722CB7F4B002C33D5 /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_ANALYZER_NONNULL = YES; 300 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 301 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 302 | CLANG_CXX_LIBRARY = "libc++"; 303 | CLANG_ENABLE_MODULES = YES; 304 | CLANG_ENABLE_OBJC_ARC = YES; 305 | CLANG_ENABLE_OBJC_WEAK = YES; 306 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 307 | CLANG_WARN_BOOL_CONVERSION = YES; 308 | CLANG_WARN_COMMA = YES; 309 | CLANG_WARN_CONSTANT_CONVERSION = YES; 310 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 311 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 312 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INFINITE_RECURSION = YES; 316 | CLANG_WARN_INT_CONVERSION = YES; 317 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 319 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | COPY_PHASE_STRIP = NO; 328 | DEBUG_INFORMATION_FORMAT = dwarf; 329 | ENABLE_STRICT_OBJC_MSGSEND = YES; 330 | ENABLE_TESTABILITY = YES; 331 | GCC_C_LANGUAGE_STANDARD = gnu11; 332 | GCC_DYNAMIC_NO_PIC = NO; 333 | GCC_NO_COMMON_BLOCKS = YES; 334 | GCC_OPTIMIZATION_LEVEL = 0; 335 | GCC_PREPROCESSOR_DEFINITIONS = ( 336 | "DEBUG=1", 337 | "$(inherited)", 338 | ); 339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 341 | GCC_WARN_UNDECLARED_SELECTOR = YES; 342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 343 | GCC_WARN_UNUSED_FUNCTION = YES; 344 | GCC_WARN_UNUSED_VARIABLE = YES; 345 | INFOPLIST_FILE = ""; 346 | "INFOPLIST_FILE[sdk=*]" = ""; 347 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 348 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 349 | MTL_FAST_MATH = YES; 350 | ONLY_ACTIVE_ARCH = YES; 351 | SDKROOT = iphoneos; 352 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 353 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 354 | }; 355 | name = Debug; 356 | }; 357 | 8B23C12822CB7F4B002C33D5 /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 363 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 364 | CLANG_CXX_LIBRARY = "libc++"; 365 | CLANG_ENABLE_MODULES = YES; 366 | CLANG_ENABLE_OBJC_ARC = YES; 367 | CLANG_ENABLE_OBJC_WEAK = YES; 368 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_COMMA = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu11; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | INFOPLIST_FILE = ""; 402 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 403 | MTL_ENABLE_DEBUG_INFO = NO; 404 | MTL_FAST_MATH = YES; 405 | SDKROOT = iphoneos; 406 | SWIFT_COMPILATION_MODE = wholemodule; 407 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 8B23C12A22CB7F4B002C33D5 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | CODE_SIGN_ENTITLEMENTS = CombineMarble/CombineMarble.entitlements; 417 | CODE_SIGN_STYLE = Automatic; 418 | DERIVE_UIKITFORMAC_PRODUCT_BUNDLE_IDENTIFIER = YES; 419 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 420 | INFOPLIST_FILE = CombineMarble/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.CombineMarble; 426 | PRODUCT_NAME = " Combine Visualizer"; 427 | SUPPORTS_UIKITFORMAC = YES; 428 | SWIFT_VERSION = 5.0; 429 | TARGETED_DEVICE_FAMILY = "1,2"; 430 | }; 431 | name = Debug; 432 | }; 433 | 8B23C12B22CB7F4B002C33D5 /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | CODE_SIGN_ENTITLEMENTS = CombineMarble/CombineMarble.entitlements; 438 | CODE_SIGN_STYLE = Automatic; 439 | DERIVE_UIKITFORMAC_PRODUCT_BUNDLE_IDENTIFIER = YES; 440 | DEVELOPMENT_TEAM = 5C2XD9H2JS; 441 | INFOPLIST_FILE = CombineMarble/Info.plist; 442 | LD_RUNPATH_SEARCH_PATHS = ( 443 | "$(inherited)", 444 | "@executable_path/Frameworks", 445 | ); 446 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.CombineMarble; 447 | PRODUCT_NAME = " Combine Visualizer"; 448 | SUPPORTS_UIKITFORMAC = YES; 449 | SWIFT_VERSION = 5.0; 450 | TARGETED_DEVICE_FAMILY = "1,2"; 451 | }; 452 | name = Release; 453 | }; 454 | /* End XCBuildConfiguration section */ 455 | 456 | /* Begin XCConfigurationList section */ 457 | 8B23C11022CB7F49002C33D5 /* Build configuration list for PBXProject "CombineMarble" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | 8B23C12722CB7F4B002C33D5 /* Debug */, 461 | 8B23C12822CB7F4B002C33D5 /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | 8B23C12922CB7F4B002C33D5 /* Build configuration list for PBXNativeTarget "CombineMarble" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | 8B23C12A22CB7F4B002C33D5 /* Debug */, 470 | 8B23C12B22CB7F4B002C33D5 /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | /* End XCConfigurationList section */ 476 | }; 477 | rootObject = 8B23C10D22CB7F49002C33D5 /* Project object */; 478 | } 479 | -------------------------------------------------------------------------------- /CombineMarble.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CombineMarble.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CombineMarble.xcodeproj/project.xcworkspace/xcuserdata/alfianlosari.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfianlosari/AppleCombineMarbleVisualizer/d5070b554c15166b7634290990a2444e7dbef612/CombineMarble.xcodeproj/project.xcworkspace/xcuserdata/alfianlosari.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CombineMarble.xcodeproj/xcuserdata/alfianlosari.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /CombineMarble.xcodeproj/xcuserdata/alfianlosari.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CombineMarble.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CombineMarble/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | func applicationWillTerminate(_ application: UIApplication) { 20 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 21 | } 22 | 23 | // MARK: UISceneSession Lifecycle 24 | 25 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 26 | // Called when a new scene session is being created. 27 | // Use this method to select a configuration to create the new scene with. 28 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 29 | } 30 | 31 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 32 | // Called when the user discards a scene session. 33 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 34 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /CombineMarble/CombineMarble.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CombineMarble/Combines/CombineLatest/CombineLatestSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineLatestSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | 12 | class CombineLatestSectionContainer: CombineSectionContainer { 13 | 14 | var isCombined = false 15 | var sections: [SectionController.CombineCollection] { 16 | return [ 17 | firstLine, 18 | secondLine, 19 | combinedLine 20 | ] 21 | } 22 | var numbers1 = PassthroughSubject() 23 | var numbers2 = PassthroughSubject() 24 | 25 | private lazy var firstLine: SectionController.CombineCollection = { 26 | var first = SectionController.CombineCollection(title: "Combine Latest", items: []) 27 | first.container = self 28 | return first 29 | }() 30 | 31 | private lazy var secondLine: SectionController.CombineCollection = { 32 | var second = SectionController.CombineCollection(title: " ", items: []) 33 | second.container = self 34 | return second 35 | }() 36 | 37 | private lazy var combinedLine: SectionController.CombineCollection = { 38 | var second = SectionController.CombineCollection(title: " ", items: []) 39 | second.container = self 40 | return second 41 | }() 42 | 43 | var currentFirstLine: String? 44 | var currentSecondLine: String? 45 | 46 | var isLineCombined: Bool { 47 | return currentFirstLine != nil && currentSecondLine != nil 48 | } 49 | 50 | private var randomValues: [String] { 51 | return [ 52 | "1", 53 | "D", 54 | "3", 55 | "7", 56 | "B", 57 | "C", 58 | "9", 59 | "F" 60 | ].shuffled() 61 | } 62 | 63 | private lazy var sendValues: [String] = { 64 | return self.randomValues 65 | }() 66 | 67 | private func setupSubscription() { 68 | _ = numbers1.sink(receiveValue: { (value) in 69 | self.firstLine.items.append(SectionController.CombineItem(text: value)) 70 | self.secondLine.items.append(SectionController.CombineItem(text: nil)) 71 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 72 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 73 | if !self.isLineCombined { 74 | self.combinedLine.items.append(SectionController.CombineItem(text: nil)) 75 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 76 | } 77 | }) 78 | 79 | _ = numbers2.sink(receiveValue: { (value) in 80 | self.firstLine.items.append(SectionController.CombineItem(text: nil)) 81 | self.secondLine.items.append(SectionController.CombineItem(text: value)) 82 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 83 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 84 | if !self.isLineCombined { 85 | self.combinedLine.items.append(SectionController.CombineItem(text: nil)) 86 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 87 | } 88 | }) 89 | 90 | _ = numbers1.combineLatest(numbers2) { "\($0 ?? "")\($1 ?? "")" } 91 | .sink { combined in 92 | 93 | self.combinedLine.items.append(SectionController.CombineItem(text: combined)) 94 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 95 | } 96 | } 97 | 98 | init() { 99 | setupSubscription() 100 | } 101 | 102 | func reset() { 103 | sendValues = self.randomValues 104 | 105 | self.isCombined = false 106 | currentFirstLine = nil 107 | currentSecondLine = nil 108 | 109 | self.firstLine.items.removeAll() 110 | self.secondLine.items.removeAll() 111 | self.combinedLine.items.removeAll() 112 | 113 | numbers1 = PassthroughSubject() 114 | numbers2 = PassthroughSubject() 115 | 116 | setupSubscription() 117 | } 118 | 119 | func send() { 120 | guard let value = sendValues.popLast() else { 121 | self.isCombined = true 122 | return 123 | } 124 | 125 | if let _ = Int(value) { 126 | currentFirstLine = value 127 | numbers1.send(value) 128 | 129 | } else { 130 | currentSecondLine = value 131 | numbers2.send(value) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /CombineMarble/Combines/CombineSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Combine 10 | 11 | protocol CombineSectionContainer { 12 | var sections: [SectionController.CombineCollection] { get } 13 | var isCombined: Bool { get } 14 | func reset() 15 | func send() 16 | } 17 | -------------------------------------------------------------------------------- /CombineMarble/Combines/Filter/CombineFilterSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineFilterSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | class CombineFilterSectionContainer: CombineSectionContainer { 13 | 14 | var isCombined: Bool = false 15 | let numbers = PassthroughSubject() 16 | 17 | var sections: [SectionController.CombineCollection] { 18 | return [ 19 | firstLine, 20 | mappedLine 21 | ] 22 | } 23 | 24 | private lazy var firstLine: SectionController.CombineCollection = { 25 | var first = SectionController.CombineCollection(title: "Filter (is even number)", items: []) 26 | first.container = self 27 | 28 | return first 29 | }() 30 | 31 | private lazy var mappedLine: SectionController.CombineCollection = { 32 | var second = SectionController.CombineCollection(title: " ", items: []) 33 | second.container = self 34 | return second 35 | }() 36 | 37 | private var randomValues: [Int] { 38 | return Array(0..<10).shuffled() 39 | } 40 | 41 | private lazy var sendValues: [Int] = { 42 | return self.randomValues 43 | }() 44 | 45 | init() { 46 | _ = numbers.sink(receiveValue: { (value) in 47 | self.firstLine.items.append(SectionController.CombineItem(text: "\(value)")) 48 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 49 | }) 50 | 51 | _ = numbers 52 | .filter { $0 % 2 != 0} 53 | .sink(receiveValue: { (value) in 54 | self.mappedLine.items.append(SectionController.CombineItem(text: nil)) 55 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.mappedLine) 56 | }) 57 | 58 | _ = numbers 59 | .filter { $0 % 2 == 0} 60 | .sink(receiveValue: { (value) in 61 | self.mappedLine.items.append(SectionController.CombineItem(text: "\(value)")) 62 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.mappedLine) 63 | }) 64 | } 65 | 66 | func send() { 67 | guard let value = sendValues.popLast() else { 68 | self.isCombined = true 69 | return 70 | } 71 | numbers.send(value) 72 | } 73 | 74 | func reset() { 75 | sendValues = self.randomValues 76 | self.isCombined = false 77 | self.firstLine.items.removeAll() 78 | self.mappedLine.items.removeAll() 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /CombineMarble/Combines/Map/CombineMapSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineMapSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | 12 | class CombineMapSectionContainer: CombineSectionContainer { 13 | 14 | var isCombined: Bool = false 15 | let numbers = PassthroughSubject() 16 | 17 | var sections: [SectionController.CombineCollection] { 18 | return [ 19 | firstLine, 20 | mappedLine 21 | ] 22 | } 23 | 24 | private lazy var firstLine: SectionController.CombineCollection = { 25 | var first = SectionController.CombineCollection(title: "Map", items: []) 26 | first.container = self 27 | 28 | return first 29 | }() 30 | 31 | private lazy var mappedLine: SectionController.CombineCollection = { 32 | var second = SectionController.CombineCollection(title: " ", items: []) 33 | second.container = self 34 | return second 35 | }() 36 | 37 | 38 | private lazy var emojis: [String] = { 39 | return self.randomEmojis 40 | }() 41 | 42 | private var randomValues: [Int] { 43 | return Array(0..<8).shuffled() 44 | } 45 | 46 | private lazy var sendValues: [Int] = { 47 | return self.randomValues 48 | }() 49 | 50 | private lazy var randomEmojis: [String] = { 51 | return ["🥰", "😍", "😜", "🤪", "😉", "😄", "😀", "🤓"].shuffled() 52 | }() 53 | 54 | init() { 55 | _ = numbers.sink(receiveValue: { (value) in 56 | self.firstLine.items.append(SectionController.CombineItem(text: "\(value)")) 57 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 58 | }) 59 | 60 | _ = numbers.map { 61 | self.emojis[$0] 62 | }.sink(receiveValue: { (emoji) in 63 | self.mappedLine.items.append(SectionController.CombineItem(text: "\(emoji)")) 64 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.mappedLine) 65 | }) 66 | } 67 | 68 | func send() { 69 | guard let value = sendValues.popLast() else { 70 | self.isCombined = true 71 | return 72 | } 73 | numbers.send(value) 74 | } 75 | 76 | func reset() { 77 | sendValues = self.randomValues 78 | emojis = self.randomEmojis 79 | self.isCombined = false 80 | self.firstLine.items.removeAll() 81 | self.mappedLine.items.removeAll() 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CombineMarble/Combines/Merge/CombineMergeSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineMergeSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | class CombineMergeSectionContainer: CombineSectionContainer { 13 | 14 | var numbers1 = PassthroughSubject() 15 | var numbers2 = PassthroughSubject() 16 | 17 | var sections: [SectionController.CombineCollection] { 18 | return [ 19 | firstLine, 20 | secondLine, 21 | combinedLine 22 | ] 23 | } 24 | 25 | private lazy var firstLine: SectionController.CombineCollection = { 26 | var first = SectionController.CombineCollection(title: "Merge", items: []) 27 | first.container = self 28 | return first 29 | }() 30 | 31 | private lazy var secondLine: SectionController.CombineCollection = { 32 | var second = SectionController.CombineCollection(title: " ", items: []) 33 | second.container = self 34 | return second 35 | }() 36 | 37 | private lazy var combinedLine: SectionController.CombineCollection = { 38 | var second = SectionController.CombineCollection(title: " ", items: []) 39 | second.container = self 40 | return second 41 | }() 42 | 43 | private var randomValues: [String] { 44 | return [ 45 | "1", 46 | "D", 47 | "3", 48 | "7", 49 | "B", 50 | "C", 51 | "9", 52 | "F" 53 | ].shuffled() 54 | 55 | } 56 | 57 | private lazy var sendValues: [String] = { 58 | return self.randomValues 59 | }() 60 | 61 | var isCombined = false 62 | 63 | init() { 64 | setupSubscription() 65 | } 66 | 67 | private func setupSubscription() { 68 | _ = numbers1.sink(receiveValue: { (value) in 69 | self.firstLine.items.append(SectionController.CombineItem(text: value)) 70 | self.secondLine.items.append(SectionController.CombineItem(text: nil)) 71 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 72 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 73 | }) 74 | 75 | _ = numbers2.sink(receiveValue: { (value) in 76 | self.firstLine.items.append(SectionController.CombineItem(text: nil)) 77 | self.secondLine.items.append(SectionController.CombineItem(text: value)) 78 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 79 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 80 | }) 81 | 82 | _ = numbers1.merge(with: numbers2) 83 | .sink(receiveValue: { (merged) in 84 | self.combinedLine.items.append(SectionController.CombineItem(text: merged)) 85 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 86 | }) 87 | } 88 | 89 | func reset() { 90 | sendValues = self.randomValues 91 | 92 | self.isCombined = false 93 | 94 | self.firstLine.items.removeAll() 95 | self.secondLine.items.removeAll() 96 | self.combinedLine.items.removeAll() 97 | 98 | numbers1 = PassthroughSubject() 99 | numbers2 = PassthroughSubject() 100 | 101 | setupSubscription() 102 | } 103 | 104 | func send() { 105 | guard let value = sendValues.popLast() else { 106 | self.isCombined = true 107 | return 108 | } 109 | 110 | if let _ = Int(value) { 111 | numbers1.send(value) 112 | 113 | } else { 114 | numbers2.send(value) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /CombineMarble/Combines/Zip/CombineZipSectionContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CombineZipSectionContainer.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 04/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import Foundation 11 | 12 | class CombineZipSectionContainer: CombineSectionContainer { 13 | 14 | var isCombined = false 15 | var numbers1 = PassthroughSubject() 16 | var numbers2 = PassthroughSubject() 17 | var currentFirstLine: [String] = [] 18 | var currentSecondLine: [String] = [] 19 | 20 | var isLineCombined: Bool { 21 | return !currentFirstLine.isEmpty && !currentSecondLine.isEmpty 22 | } 23 | 24 | var sections: [SectionController.CombineCollection] { 25 | return [ 26 | firstLine, 27 | secondLine, 28 | combinedLine 29 | ] 30 | } 31 | 32 | private lazy var firstLine: SectionController.CombineCollection = { 33 | var first = SectionController.CombineCollection(title: "Zip", items: []) 34 | first.container = self 35 | return first 36 | }() 37 | 38 | private lazy var secondLine: SectionController.CombineCollection = { 39 | var second = SectionController.CombineCollection(title: " ", items: []) 40 | second.container = self 41 | return second 42 | }() 43 | 44 | private lazy var combinedLine: SectionController.CombineCollection = { 45 | var second = SectionController.CombineCollection(title: " ", items: []) 46 | second.container = self 47 | return second 48 | }() 49 | 50 | private var randomValues: [String] { 51 | return [ 52 | "1", 53 | "D", 54 | "3", 55 | "7", 56 | "B", 57 | "C", 58 | "9", 59 | "F" 60 | ].shuffled() 61 | } 62 | 63 | private lazy var sendValues: [String] = { 64 | return self.randomValues 65 | }() 66 | 67 | private func setupSubscription() { 68 | _ = numbers1.sink(receiveValue: { (value) in 69 | self.firstLine.items.append(SectionController.CombineItem(text: value)) 70 | self.secondLine.items.append(SectionController.CombineItem(text: nil)) 71 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 72 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 73 | if !self.isLineCombined { 74 | self.combinedLine.items.append(SectionController.CombineItem(text: nil)) 75 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 76 | } 77 | }) 78 | 79 | _ = numbers2.sink(receiveValue: { (value) in 80 | self.firstLine.items.append(SectionController.CombineItem(text: nil)) 81 | self.secondLine.items.append(SectionController.CombineItem(text: value)) 82 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.firstLine) 83 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.secondLine) 84 | if !self.isLineCombined { 85 | self.combinedLine.items.append(SectionController.CombineItem(text: nil)) 86 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 87 | } 88 | }) 89 | 90 | _ = numbers1.zip(numbers2) { "\($0 ?? "")\($1 ?? "")" } 91 | .sink { combined in 92 | _ = self.currentFirstLine.popLast() 93 | _ = self.currentSecondLine.popLast() 94 | self.combinedLine.items.append(SectionController.CombineItem(text: combined)) 95 | NotificationCenter.default.post(name: combineDidChangeNotification, object: self.combinedLine) 96 | } 97 | } 98 | 99 | init() { 100 | setupSubscription() 101 | } 102 | 103 | func reset() { 104 | sendValues = self.randomValues 105 | 106 | self.isCombined = false 107 | 108 | currentFirstLine = [] 109 | currentSecondLine = [] 110 | 111 | self.firstLine.items.removeAll() 112 | self.secondLine.items.removeAll() 113 | self.combinedLine.items.removeAll() 114 | 115 | numbers1 = PassthroughSubject() 116 | numbers2 = PassthroughSubject() 117 | 118 | setupSubscription() 119 | } 120 | 121 | func send() { 122 | guard let value = sendValues.popLast() else { 123 | self.isCombined = true 124 | return 125 | } 126 | 127 | if let _ = Int(value) { 128 | currentFirstLine.append(value) 129 | numbers1.send(value) 130 | 131 | } else { 132 | currentSecondLine.append(value) 133 | numbers2.send(value) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /CombineMarble/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | UISceneStoryboardFile 39 | Main 40 | 41 | 42 | 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /CombineMarble/Model/SectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionController.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Combine 11 | 12 | let combineDidChangeNotification = Notification.Name(rawValue: "CombineDidChangeNotification") 13 | 14 | class SectionController { 15 | 16 | fileprivate var _collections = [CombineSectionContainer]() 17 | var collections: [CombineSectionContainer] { 18 | return _collections 19 | } 20 | 21 | struct CombineItem: Hashable { 22 | let text: String? 23 | 24 | let identifier = UUID() 25 | func hash(into hasher: inout Hasher) { 26 | hasher.combine(identifier) 27 | } 28 | } 29 | 30 | struct CombineCollection: Hashable { 31 | 32 | let title: String? 33 | var items: [CombineItem] 34 | var container: CombineSectionContainer? = nil 35 | 36 | var isCombined = false 37 | 38 | let identifier = UUID() 39 | func hash(into hasher: inout Hasher) { 40 | hasher.combine(identifier) 41 | } 42 | 43 | static func == (lhs: SectionController.CombineCollection, rhs: SectionController.CombineCollection) -> Bool { 44 | return lhs.identifier == rhs.identifier 45 | } 46 | } 47 | 48 | init() { 49 | generateCollections() 50 | } 51 | } 52 | 53 | 54 | extension SectionController { 55 | 56 | func generateCollections() { 57 | 58 | let map = CombineMapSectionContainer() 59 | let combineLatest = CombineLatestSectionContainer() 60 | let merge = CombineMergeSectionContainer() 61 | let zip = CombineZipSectionContainer() 62 | let filter = CombineFilterSectionContainer() 63 | 64 | _collections = [ 65 | zip, 66 | combineLatest, 67 | merge, 68 | map, 69 | filter 70 | ] 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /CombineMarble/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CombineMarble/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CombineMarble/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CombineMarble/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CombineMarble/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /CombineMarble/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Combine 11 | 12 | class ViewController: UIViewController { 13 | 14 | var colors: [UIColor] { 15 | return [ 16 | UIColor.systemRed, 17 | UIColor.systemBlue, 18 | UIColor.systemPink, 19 | UIColor.systemTeal, 20 | UIColor.systemGreen, 21 | UIColor.systemIndigo, 22 | UIColor.systemYellow, 23 | UIColor.systemPurple, 24 | UIColor.systemOrange, 25 | UIColor.systemRed 26 | ].shuffled() 27 | } 28 | 29 | var isCombining = false 30 | 31 | let sectionController = SectionController() 32 | static let lineDecorationElementKind = "line-decoration-element-kind" 33 | static let sectionHeaderElementKind = "sectionHeaderElementKind" 34 | 35 | var collectionView: UICollectionView! = nil 36 | var dataSource: UICollectionViewDiffableDataSource! = nil 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | navigationItem.title = " Combine Visualizer" 41 | 42 | configureHierarchy() 43 | configureDataSource() 44 | configureCombineNotificationUpdateSubscription() 45 | 46 | configureNavItem() 47 | toggleCombine() 48 | } 49 | 50 | private func configureCombineNotificationUpdateSubscription() { 51 | _ = NotificationCenter.default.publisher(for: combineDidChangeNotification) 52 | .sink { (note) in 53 | guard let section = note.object as? SectionController.CombineCollection else { 54 | return 55 | } 56 | 57 | let snapshot = self.dataSource.snapshot() 58 | guard let _ = snapshot.indexOfSection(section) else { 59 | return 60 | } 61 | let items = section.items 62 | snapshot.deleteItems(items) 63 | snapshot.appendItems(items, toSection: section) 64 | 65 | self.dataSource.apply(snapshot, animatingDifferences: true) 66 | } 67 | } 68 | 69 | private func configureHierarchy() { 70 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) 71 | collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 72 | collectionView.backgroundColor = .systemBackground 73 | collectionView.register(UINib(nibName: "LineCell", bundle: nil), forCellWithReuseIdentifier: "LineCell") 74 | collectionView.register( 75 | UINib(nibName: "SectionHeaderView", bundle: nil), 76 | forSupplementaryViewOfKind: ViewController.sectionHeaderElementKind, 77 | withReuseIdentifier: "SectionHeaderView") 78 | view.addSubview(collectionView) 79 | } 80 | 81 | private func configureDataSource() { 82 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { 83 | (collectionView: UICollectionView, indexPath: IndexPath, item: SectionController.CombineItem) -> UICollectionViewCell? in 84 | guard let cell = collectionView.dequeueReusableCell( 85 | withReuseIdentifier: "LineCell", 86 | for: indexPath) as? LineCell else { fatalError("Cannot create new cell") } 87 | let sections = self.sectionController.collections.flatMap { $0.sections } 88 | let text = sections[indexPath.section].items[indexPath.item].text 89 | 90 | cell.label.text = text 91 | cell.view.isHidden = text == nil 92 | cell.view.backgroundColor = self.colors[indexPath.item % 10] 93 | 94 | return cell 95 | } 96 | 97 | dataSource.supplementaryViewProvider = { ( 98 | collectionView: UICollectionView, 99 | kind: String, 100 | indexPath: IndexPath) -> UICollectionReusableView? in 101 | 102 | guard let supplementaryView = collectionView.dequeueReusableSupplementaryView( 103 | ofKind: kind, 104 | withReuseIdentifier: "SectionHeaderView", 105 | for: indexPath) as? SectionHeaderView else { fatalError("Cannot create new supplementary") } 106 | 107 | let sections = self.sectionController.collections.flatMap { $0.sections } 108 | 109 | supplementaryView.label.text = sections[indexPath.section].title 110 | return supplementaryView 111 | } 112 | dataSource.apply(getSnapshot(), animatingDifferences: false) 113 | } 114 | 115 | func getSnapshot() -> NSDiffableDataSourceSnapshot { 116 | let snapshot = NSDiffableDataSourceSnapshot() 117 | sectionController.collections.forEach { 118 | for section in $0.sections { 119 | snapshot.appendSections([section]) 120 | snapshot.appendItems(section.items) 121 | } 122 | } 123 | return snapshot 124 | } 125 | 126 | func performCombine(section: CombineSectionContainer) { 127 | if !isCombining { 128 | return 129 | } 130 | 131 | section.send() 132 | let delay = 1000 133 | DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay)) { 134 | if section.isCombined { 135 | let snapshot = self.dataSource.snapshot() 136 | section.sections.forEach { (section) in 137 | snapshot.deleteItems(section.items) 138 | } 139 | self.dataSource.apply(snapshot, animatingDifferences: true) 140 | section.reset() 141 | } 142 | self.performCombine(section: section) 143 | } 144 | } 145 | 146 | func performCombine() { 147 | self.sectionController.collections.forEach { performCombine(section: $0) } 148 | } 149 | 150 | private func createLayout() -> UICollectionViewLayout { 151 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.125), 152 | heightDimension: .fractionalHeight(1.0)) 153 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 154 | 155 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 156 | heightDimension: .absolute(44)) 157 | 158 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, 159 | subitems: [item]) 160 | 161 | let section = NSCollectionLayoutSection(group: group) 162 | section.interGroupSpacing = 0 163 | section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0) 164 | section.orthogonalScrollingBehavior = .continuous 165 | 166 | let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background( 167 | elementKind: ViewController.lineDecorationElementKind) 168 | sectionBackgroundDecoration.contentInsets = NSDirectionalEdgeInsets(top: 44, leading: 0, bottom: 0, trailing: 0) 169 | section.decorationItems = [sectionBackgroundDecoration] 170 | let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 171 | heightDimension: .estimated(44)) 172 | 173 | let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( 174 | layoutSize: headerFooterSize, 175 | elementKind: ViewController.sectionHeaderElementKind, alignment: .top) 176 | 177 | section.boundarySupplementaryItems = [sectionHeader] 178 | 179 | let layout = UICollectionViewCompositionalLayout(section: section) 180 | layout.register( 181 | UINib(nibName: "LineDecorationView", bundle: nil), 182 | forDecorationViewOfKind: ViewController.lineDecorationElementKind) 183 | return layout 184 | } 185 | 186 | func configureNavItem() { 187 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: isCombining ? "Pause" : "Combine", 188 | style: .plain, target: self, 189 | action: #selector(toggleCombine)) 190 | } 191 | 192 | @objc 193 | func toggleCombine() { 194 | isCombining.toggle() 195 | if isCombining { 196 | performCombine() 197 | } 198 | configureNavItem() 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /CombineMarble/Views/LineCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineCell.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LineCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var view: UIView! 14 | @IBOutlet weak var viewHeightConstraint: NSLayoutConstraint! 15 | @IBOutlet weak var label: UILabel! 16 | 17 | override func layoutSubviews() { 18 | super.layoutSubviews() 19 | let length = min(self.bounds.height, self.bounds.width) - 8.0 20 | view.layer.cornerRadius = length / 2.0 21 | viewHeightConstraint.constant = length 22 | } 23 | 24 | func setup() { 25 | view.layer.cornerRadius = view.frame.width / 2.0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CombineMarble/Views/LineCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /CombineMarble/Views/LineDecorationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineDecorationView.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LineDecorationView: UICollectionReusableView { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /CombineMarble/Views/LineDecorationView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /CombineMarble/Views/SectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderView.swift 3 | // CombineMarble 4 | // 5 | // Created by Alfian Losari on 02/07/19. 6 | // Copyright © 2019 Alfian Losari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SectionHeaderView: UICollectionReusableView { 12 | 13 | @IBOutlet weak var label: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /CombineMarble/Views/SectionHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Combine Framework Marble Diagram Visualizer App 2 | 3 | ![Alt text](./promo.png?raw=true "Marble Diagram Visualizer") 4 | 5 | Apple Combine Framework Marble Diagram Visualizer App 6 | 7 | ## Requirements 8 | - macOS 10.15 Catalina 9 | - Xcode 11 Beta 10 | - iOS 13 11 | 12 | ## Getting Started 13 | - Clone the Repository 14 | - Build and Run the project -------------------------------------------------------------------------------- /promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfianlosari/AppleCombineMarbleVisualizer/d5070b554c15166b7634290990a2444e7dbef612/promo.png --------------------------------------------------------------------------------