├── .gitignore ├── Example └── DemoMicro │ ├── DemoMicro.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── DemoMicro │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift ├── LICENSE.md ├── Micro.xcodeproj ├── DeepDiff_Info.plist ├── MicroTests_Info.plist ├── Micro_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── Micro-Package.xcscheme ├── Package.resolved ├── Package.swift ├── README.md ├── Screenshots └── demo.gif ├── Sources └── Micro │ ├── Cell.swift │ ├── DataSource.swift │ ├── Extensions.swift │ ├── ForEach.swift │ ├── Reloader.swift │ └── State.swift └── Tests ├── LinuxMain.swift └── MicroTests ├── MicroTests.swift └── XCTestManifests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | 29 | # CocoaPods 30 | Pods 31 | 32 | # Carthage 33 | Carthage 34 | 35 | # SPM 36 | .build/ 37 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D29CB49423F0A54D0067685A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29CB49323F0A54D0067685A /* AppDelegate.swift */; }; 11 | D29CB49623F0A54D0067685A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29CB49523F0A54D0067685A /* SceneDelegate.swift */; }; 12 | D29CB49823F0A54D0067685A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29CB49723F0A54D0067685A /* ViewController.swift */; }; 13 | D29CB49B23F0A54D0067685A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D29CB49923F0A54D0067685A /* Main.storyboard */; }; 14 | D29CB49D23F0A54E0067685A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D29CB49C23F0A54E0067685A /* Assets.xcassets */; }; 15 | D29CB4A023F0A54E0067685A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D29CB49E23F0A54E0067685A /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | D29CB4D023F0A6C40067685A /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = D29CB4C723F0A6C40067685A /* Micro.xcodeproj */; 22 | proxyType = 2; 23 | remoteGlobalIDString = "DeepDiff::DeepDiff::Product"; 24 | remoteInfo = DeepDiff; 25 | }; 26 | D29CB4D223F0A6C40067685A /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = D29CB4C723F0A6C40067685A /* Micro.xcodeproj */; 29 | proxyType = 2; 30 | remoteGlobalIDString = "Micro::Micro::Product"; 31 | remoteInfo = Micro; 32 | }; 33 | D29CB4D423F0A6C40067685A /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = D29CB4C723F0A6C40067685A /* Micro.xcodeproj */; 36 | proxyType = 2; 37 | remoteGlobalIDString = "Micro::MicroTests::Product"; 38 | remoteInfo = MicroTests; 39 | }; 40 | D29CB4D623F0A6D00067685A /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = D29CB4C723F0A6C40067685A /* Micro.xcodeproj */; 43 | proxyType = 1; 44 | remoteGlobalIDString = "Micro::Micro"; 45 | remoteInfo = Micro; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | D29CB49023F0A54D0067685A /* DemoMicro.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoMicro.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | D29CB49323F0A54D0067685A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | D29CB49523F0A54D0067685A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 53 | D29CB49723F0A54D0067685A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 54 | D29CB49A23F0A54D0067685A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | D29CB49C23F0A54E0067685A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | D29CB49F23F0A54E0067685A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | D29CB4A123F0A54E0067685A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | D29CB4C723F0A6C40067685A /* Micro.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Micro.xcodeproj; path = ../../../Micro.xcodeproj; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | D29CB48D23F0A54D0067685A /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | D29CB48723F0A54D0067685A = { 73 | isa = PBXGroup; 74 | children = ( 75 | D29CB49223F0A54D0067685A /* DemoMicro */, 76 | D29CB49123F0A54D0067685A /* Products */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | D29CB49123F0A54D0067685A /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | D29CB49023F0A54D0067685A /* DemoMicro.app */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | D29CB49223F0A54D0067685A /* DemoMicro */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | D29CB4C723F0A6C40067685A /* Micro.xcodeproj */, 92 | D29CB49323F0A54D0067685A /* AppDelegate.swift */, 93 | D29CB49523F0A54D0067685A /* SceneDelegate.swift */, 94 | D29CB49723F0A54D0067685A /* ViewController.swift */, 95 | D29CB49923F0A54D0067685A /* Main.storyboard */, 96 | D29CB49C23F0A54E0067685A /* Assets.xcassets */, 97 | D29CB49E23F0A54E0067685A /* LaunchScreen.storyboard */, 98 | D29CB4A123F0A54E0067685A /* Info.plist */, 99 | ); 100 | path = DemoMicro; 101 | sourceTree = ""; 102 | }; 103 | D29CB4C823F0A6C40067685A /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | D29CB4D123F0A6C40067685A /* DeepDiff.framework */, 107 | D29CB4D323F0A6C40067685A /* Micro.framework */, 108 | D29CB4D523F0A6C40067685A /* MicroTests.xctest */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | D29CB48F23F0A54D0067685A /* DemoMicro */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = D29CB4A423F0A54E0067685A /* Build configuration list for PBXNativeTarget "DemoMicro" */; 119 | buildPhases = ( 120 | D29CB48C23F0A54D0067685A /* Sources */, 121 | D29CB48D23F0A54D0067685A /* Frameworks */, 122 | D29CB48E23F0A54D0067685A /* Resources */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | D29CB4D723F0A6D00067685A /* PBXTargetDependency */, 128 | ); 129 | name = DemoMicro; 130 | productName = DemoMicro; 131 | productReference = D29CB49023F0A54D0067685A /* DemoMicro.app */; 132 | productType = "com.apple.product-type.application"; 133 | }; 134 | /* End PBXNativeTarget section */ 135 | 136 | /* Begin PBXProject section */ 137 | D29CB48823F0A54D0067685A /* Project object */ = { 138 | isa = PBXProject; 139 | attributes = { 140 | LastSwiftUpdateCheck = 1130; 141 | LastUpgradeCheck = 1130; 142 | ORGANIZATIONNAME = "Khoa Pham"; 143 | TargetAttributes = { 144 | D29CB48F23F0A54D0067685A = { 145 | CreatedOnToolsVersion = 11.3.1; 146 | }; 147 | }; 148 | }; 149 | buildConfigurationList = D29CB48B23F0A54D0067685A /* Build configuration list for PBXProject "DemoMicro" */; 150 | compatibilityVersion = "Xcode 9.3"; 151 | developmentRegion = en; 152 | hasScannedForEncodings = 0; 153 | knownRegions = ( 154 | en, 155 | Base, 156 | ); 157 | mainGroup = D29CB48723F0A54D0067685A; 158 | productRefGroup = D29CB49123F0A54D0067685A /* Products */; 159 | projectDirPath = ""; 160 | projectReferences = ( 161 | { 162 | ProductGroup = D29CB4C823F0A6C40067685A /* Products */; 163 | ProjectRef = D29CB4C723F0A6C40067685A /* Micro.xcodeproj */; 164 | }, 165 | ); 166 | projectRoot = ""; 167 | targets = ( 168 | D29CB48F23F0A54D0067685A /* DemoMicro */, 169 | ); 170 | }; 171 | /* End PBXProject section */ 172 | 173 | /* Begin PBXReferenceProxy section */ 174 | D29CB4D123F0A6C40067685A /* DeepDiff.framework */ = { 175 | isa = PBXReferenceProxy; 176 | fileType = wrapper.framework; 177 | path = DeepDiff.framework; 178 | remoteRef = D29CB4D023F0A6C40067685A /* PBXContainerItemProxy */; 179 | sourceTree = BUILT_PRODUCTS_DIR; 180 | }; 181 | D29CB4D323F0A6C40067685A /* Micro.framework */ = { 182 | isa = PBXReferenceProxy; 183 | fileType = wrapper.framework; 184 | path = Micro.framework; 185 | remoteRef = D29CB4D223F0A6C40067685A /* PBXContainerItemProxy */; 186 | sourceTree = BUILT_PRODUCTS_DIR; 187 | }; 188 | D29CB4D523F0A6C40067685A /* MicroTests.xctest */ = { 189 | isa = PBXReferenceProxy; 190 | fileType = file; 191 | path = MicroTests.xctest; 192 | remoteRef = D29CB4D423F0A6C40067685A /* PBXContainerItemProxy */; 193 | sourceTree = BUILT_PRODUCTS_DIR; 194 | }; 195 | /* End PBXReferenceProxy section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | D29CB48E23F0A54D0067685A /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | D29CB4A023F0A54E0067685A /* LaunchScreen.storyboard in Resources */, 203 | D29CB49D23F0A54E0067685A /* Assets.xcassets in Resources */, 204 | D29CB49B23F0A54D0067685A /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | D29CB48C23F0A54D0067685A /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | D29CB49823F0A54D0067685A /* ViewController.swift in Sources */, 216 | D29CB49423F0A54D0067685A /* AppDelegate.swift in Sources */, 217 | D29CB49623F0A54D0067685A /* SceneDelegate.swift in Sources */, 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | /* End PBXSourcesBuildPhase section */ 222 | 223 | /* Begin PBXTargetDependency section */ 224 | D29CB4D723F0A6D00067685A /* PBXTargetDependency */ = { 225 | isa = PBXTargetDependency; 226 | name = Micro; 227 | targetProxy = D29CB4D623F0A6D00067685A /* PBXContainerItemProxy */; 228 | }; 229 | /* End PBXTargetDependency section */ 230 | 231 | /* Begin PBXVariantGroup section */ 232 | D29CB49923F0A54D0067685A /* Main.storyboard */ = { 233 | isa = PBXVariantGroup; 234 | children = ( 235 | D29CB49A23F0A54D0067685A /* Base */, 236 | ); 237 | name = Main.storyboard; 238 | sourceTree = ""; 239 | }; 240 | D29CB49E23F0A54E0067685A /* LaunchScreen.storyboard */ = { 241 | isa = PBXVariantGroup; 242 | children = ( 243 | D29CB49F23F0A54E0067685A /* Base */, 244 | ); 245 | name = LaunchScreen.storyboard; 246 | sourceTree = ""; 247 | }; 248 | /* End PBXVariantGroup section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | D29CB4A223F0A54E0067685A /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_ENABLE_OBJC_WEAK = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 275 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = dwarf; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | ENABLE_TESTABILITY = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu11; 288 | GCC_DYNAMIC_NO_PIC = NO; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_OPTIMIZATION_LEVEL = 0; 291 | GCC_PREPROCESSOR_DEFINITIONS = ( 292 | "DEBUG=1", 293 | "$(inherited)", 294 | ); 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 302 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 303 | MTL_FAST_MATH = YES; 304 | ONLY_ACTIVE_ARCH = YES; 305 | SDKROOT = iphoneos; 306 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | }; 309 | name = Debug; 310 | }; 311 | D29CB4A323F0A54E0067685A /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_ANALYZER_NONNULL = YES; 316 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 317 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 318 | CLANG_CXX_LIBRARY = "libc++"; 319 | CLANG_ENABLE_MODULES = YES; 320 | CLANG_ENABLE_OBJC_ARC = YES; 321 | CLANG_ENABLE_OBJC_WEAK = YES; 322 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 323 | CLANG_WARN_BOOL_CONVERSION = YES; 324 | CLANG_WARN_COMMA = YES; 325 | CLANG_WARN_CONSTANT_CONVERSION = YES; 326 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 327 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 328 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INFINITE_RECURSION = YES; 332 | CLANG_WARN_INT_CONVERSION = YES; 333 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 335 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | COPY_PHASE_STRIP = NO; 344 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 345 | ENABLE_NS_ASSERTIONS = NO; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | GCC_C_LANGUAGE_STANDARD = gnu11; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 356 | MTL_ENABLE_DEBUG_INFO = NO; 357 | MTL_FAST_MATH = YES; 358 | SDKROOT = iphoneos; 359 | SWIFT_COMPILATION_MODE = wholemodule; 360 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 361 | VALIDATE_PRODUCT = YES; 362 | }; 363 | name = Release; 364 | }; 365 | D29CB4A523F0A54E0067685A /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 369 | CODE_SIGN_STYLE = Automatic; 370 | DEVELOPMENT_TEAM = T78DK947F2; 371 | INFOPLIST_FILE = DemoMicro/Info.plist; 372 | LD_RUNPATH_SEARCH_PATHS = ( 373 | "$(inherited)", 374 | "@executable_path/Frameworks", 375 | ); 376 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.DemoMicro; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | D29CB4A623F0A54E0067685A /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | CODE_SIGN_STYLE = Automatic; 388 | DEVELOPMENT_TEAM = T78DK947F2; 389 | INFOPLIST_FILE = DemoMicro/Info.plist; 390 | LD_RUNPATH_SEARCH_PATHS = ( 391 | "$(inherited)", 392 | "@executable_path/Frameworks", 393 | ); 394 | PRODUCT_BUNDLE_IDENTIFIER = com.onmyway133.DemoMicro; 395 | PRODUCT_NAME = "$(TARGET_NAME)"; 396 | SWIFT_VERSION = 5.0; 397 | TARGETED_DEVICE_FAMILY = "1,2"; 398 | }; 399 | name = Release; 400 | }; 401 | /* End XCBuildConfiguration section */ 402 | 403 | /* Begin XCConfigurationList section */ 404 | D29CB48B23F0A54D0067685A /* Build configuration list for PBXProject "DemoMicro" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | D29CB4A223F0A54E0067685A /* Debug */, 408 | D29CB4A323F0A54E0067685A /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | D29CB4A423F0A54E0067685A /* Build configuration list for PBXNativeTarget "DemoMicro" */ = { 414 | isa = XCConfigurationList; 415 | buildConfigurations = ( 416 | D29CB4A523F0A54E0067685A /* Debug */, 417 | D29CB4A623F0A54E0067685A /* Release */, 418 | ); 419 | defaultConfigurationIsVisible = 0; 420 | defaultConfigurationName = Release; 421 | }; 422 | /* End XCConfigurationList section */ 423 | }; 424 | rootObject = D29CB48823F0A54D0067685A /* Project object */; 425 | } 426 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DemoMicro 4 | // 5 | // Created by khoa on 09/02/2020. 6 | // Copyright © 2020 Khoa Pham. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/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 | } -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/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 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/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 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/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 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // DemoMicro 4 | // 5 | // Created by khoa on 09/02/2020. 6 | // Copyright © 2020 Khoa Pham. 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 | -------------------------------------------------------------------------------- /Example/DemoMicro/DemoMicro/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DemoMicro 4 | // 5 | // Created by khoa on 09/02/2020. 6 | // Copyright © 2020 Khoa Pham. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Micro 11 | 12 | class ViewController: UIViewController { 13 | var collectionView: UICollectionView! 14 | var dataSource: DataSource! 15 | let layout = UICollectionViewFlowLayout() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | title = "Micro" 21 | view.backgroundColor = UIColor(red: 197/255.0, green: 211/255.0, blue: 226/255.0, alpha: 1) 22 | 23 | layout.sectionInset = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0) 24 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 25 | collectionView.backgroundColor = .clear 26 | 27 | view.addSubview(collectionView) 28 | collectionView.translatesAutoresizingMaskIntoConstraints = false 29 | NSLayoutConstraint.activate([ 30 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), 31 | collectionView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), 32 | collectionView.leftAnchor.constraint(equalTo: view.layoutMarginsGuide.leftAnchor), 33 | collectionView.rightAnchor.constraint(equalTo: view.layoutMarginsGuide.rightAnchor) 34 | ]) 35 | 36 | dataSource = DataSource(collectionView: collectionView) 37 | collectionView.dataSource = dataSource 38 | collectionView.delegate = dataSource 39 | 40 | navigationItem.leftBarButtonItem = UIBarButtonItem( 41 | title: "Letters", 42 | style: .plain, 43 | target: self, 44 | action: #selector(shuffleLetters) 45 | ) 46 | 47 | navigationItem.rightBarButtonItem = UIBarButtonItem( 48 | title: "Numbers", 49 | style: .plain, 50 | target: self, 51 | action: #selector(shuffleNumbers) 52 | ) 53 | } 54 | 55 | @objc private func shuffleLetters() { 56 | let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map({ String($0) }).shuffled() 57 | dataSource.state = forEach(letters) { letter in 58 | Cell() { _, cell in 59 | cell.label.text = letter 60 | } 61 | .onSize { context in 62 | CGSize( 63 | width: context.collectionView.frame.size.width / 3 - 20, 64 | height: 60 65 | ) 66 | } 67 | .onSelect { _ in 68 | print("select letter \(letter)") 69 | } 70 | } 71 | } 72 | 73 | @objc private func shuffleNumbers() { 74 | let numbers = Array(0..<100).shuffled() 75 | dataSource.state = forEach(numbers) { number in 76 | Cell() { _, cell in 77 | cell.label.text = "\(number)" 78 | } 79 | .onSize { context in 80 | CGSize( 81 | width: context.collectionView.frame.size.width / 4 - 20, 82 | height: 60 83 | ) 84 | } 85 | .onSelect { _ in 86 | print("select number \(number)") 87 | } 88 | } 89 | } 90 | } 91 | 92 | class NumberCell: UICollectionViewCell { 93 | let label = UILabel() 94 | 95 | override init(frame: CGRect) { 96 | super.init(frame: frame) 97 | 98 | label.textColor = .white 99 | label.font = .preferredFont(forTextStyle: .title1) 100 | addSubview(label) 101 | label.translatesAutoresizingMaskIntoConstraints = false 102 | NSLayoutConstraint.activate([ 103 | label.centerXAnchor.constraint(equalTo: centerXAnchor), 104 | label.centerYAnchor.constraint(equalTo: centerYAnchor) 105 | ]) 106 | 107 | layer.cornerRadius = 6 108 | backgroundColor = UIColor(red: 0, green: 156/255.0, blue: 65/255.0, alpha: 1.0) 109 | } 110 | 111 | required init?(coder: NSCoder) { 112 | super.init(coder: coder) 113 | } 114 | } 115 | 116 | class LetterCell: UICollectionViewCell { 117 | let label = UILabel() 118 | 119 | override init(frame: CGRect) { 120 | super.init(frame: frame) 121 | 122 | label.textColor = .white 123 | label.font = .preferredFont(forTextStyle: .title1) 124 | addSubview(label) 125 | label.translatesAutoresizingMaskIntoConstraints = false 126 | NSLayoutConstraint.activate([ 127 | label.centerXAnchor.constraint(equalTo: centerXAnchor), 128 | label.centerYAnchor.constraint(equalTo: centerYAnchor) 129 | ]) 130 | 131 | layer.cornerRadius = 6 132 | backgroundColor = UIColor(red: 230/255.0, green: 125/255.0, blue: 34/255.0, alpha: 1.0) 133 | } 134 | 135 | required init?(coder: NSCoder) { 136 | super.init(coder: coder) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2020 Khoa Pham 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Micro.xcodeproj/DeepDiff_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Micro.xcodeproj/MicroTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Micro.xcodeproj/Micro_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Micro.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "DeepDiff::DeepDiff" = { 7 | isa = "PBXNativeTarget"; 8 | buildConfigurationList = "OBJ_45"; 9 | buildPhases = ( 10 | "OBJ_48", 11 | "OBJ_59" 12 | ); 13 | dependencies = ( 14 | ); 15 | name = "DeepDiff"; 16 | productName = "DeepDiff"; 17 | productReference = "DeepDiff::DeepDiff::Product"; 18 | productType = "com.apple.product-type.framework"; 19 | }; 20 | "DeepDiff::DeepDiff::Product" = { 21 | isa = "PBXFileReference"; 22 | path = "DeepDiff.framework"; 23 | sourceTree = "BUILT_PRODUCTS_DIR"; 24 | }; 25 | "DeepDiff::SwiftPMPackageDescription" = { 26 | isa = "PBXNativeTarget"; 27 | buildConfigurationList = "OBJ_61"; 28 | buildPhases = ( 29 | "OBJ_64" 30 | ); 31 | dependencies = ( 32 | ); 33 | name = "DeepDiffPackageDescription"; 34 | productName = "DeepDiffPackageDescription"; 35 | productType = "com.apple.product-type.framework"; 36 | }; 37 | "Micro::Micro" = { 38 | isa = "PBXNativeTarget"; 39 | buildConfigurationList = "OBJ_67"; 40 | buildPhases = ( 41 | "OBJ_70", 42 | "OBJ_77" 43 | ); 44 | dependencies = ( 45 | "OBJ_79" 46 | ); 47 | name = "Micro"; 48 | productName = "Micro"; 49 | productReference = "Micro::Micro::Product"; 50 | productType = "com.apple.product-type.framework"; 51 | }; 52 | "Micro::Micro::Product" = { 53 | isa = "PBXFileReference"; 54 | path = "Micro.framework"; 55 | sourceTree = "BUILT_PRODUCTS_DIR"; 56 | }; 57 | "Micro::MicroPackageTests::ProductTarget" = { 58 | isa = "PBXAggregateTarget"; 59 | buildConfigurationList = "OBJ_87"; 60 | buildPhases = ( 61 | ); 62 | dependencies = ( 63 | "OBJ_90" 64 | ); 65 | name = "MicroPackageTests"; 66 | productName = "MicroPackageTests"; 67 | }; 68 | "Micro::MicroTests" = { 69 | isa = "PBXNativeTarget"; 70 | buildConfigurationList = "OBJ_92"; 71 | buildPhases = ( 72 | "OBJ_95", 73 | "OBJ_98" 74 | ); 75 | dependencies = ( 76 | "OBJ_101", 77 | "OBJ_102" 78 | ); 79 | name = "MicroTests"; 80 | productName = "MicroTests"; 81 | productReference = "Micro::MicroTests::Product"; 82 | productType = "com.apple.product-type.bundle.unit-test"; 83 | }; 84 | "Micro::MicroTests::Product" = { 85 | isa = "PBXFileReference"; 86 | path = "MicroTests.xctest"; 87 | sourceTree = "BUILT_PRODUCTS_DIR"; 88 | }; 89 | "Micro::SwiftPMPackageDescription" = { 90 | isa = "PBXNativeTarget"; 91 | buildConfigurationList = "OBJ_81"; 92 | buildPhases = ( 93 | "OBJ_84" 94 | ); 95 | dependencies = ( 96 | ); 97 | name = "MicroPackageDescription"; 98 | productName = "MicroPackageDescription"; 99 | productType = "com.apple.product-type.framework"; 100 | }; 101 | "OBJ_1" = { 102 | isa = "PBXProject"; 103 | attributes = { 104 | LastSwiftMigration = "9999"; 105 | LastUpgradeCheck = "9999"; 106 | }; 107 | buildConfigurationList = "OBJ_2"; 108 | compatibilityVersion = "Xcode 3.2"; 109 | developmentRegion = "en"; 110 | hasScannedForEncodings = "0"; 111 | knownRegions = ( 112 | "en" 113 | ); 114 | mainGroup = "OBJ_5"; 115 | productRefGroup = "OBJ_35"; 116 | projectDirPath = "."; 117 | targets = ( 118 | "DeepDiff::DeepDiff", 119 | "DeepDiff::SwiftPMPackageDescription", 120 | "Micro::Micro", 121 | "Micro::SwiftPMPackageDescription", 122 | "Micro::MicroPackageTests::ProductTarget", 123 | "Micro::MicroTests" 124 | ); 125 | }; 126 | "OBJ_10" = { 127 | isa = "PBXFileReference"; 128 | path = "DataSource.swift"; 129 | sourceTree = ""; 130 | }; 131 | "OBJ_100" = { 132 | isa = "PBXBuildFile"; 133 | fileRef = "DeepDiff::DeepDiff::Product"; 134 | }; 135 | "OBJ_101" = { 136 | isa = "PBXTargetDependency"; 137 | target = "Micro::Micro"; 138 | }; 139 | "OBJ_102" = { 140 | isa = "PBXTargetDependency"; 141 | target = "DeepDiff::DeepDiff"; 142 | }; 143 | "OBJ_11" = { 144 | isa = "PBXFileReference"; 145 | path = "Extensions.swift"; 146 | sourceTree = ""; 147 | }; 148 | "OBJ_12" = { 149 | isa = "PBXFileReference"; 150 | path = "ForEach.swift"; 151 | sourceTree = ""; 152 | }; 153 | "OBJ_13" = { 154 | isa = "PBXFileReference"; 155 | path = "Reloader.swift"; 156 | sourceTree = ""; 157 | }; 158 | "OBJ_14" = { 159 | isa = "PBXFileReference"; 160 | path = "State.swift"; 161 | sourceTree = ""; 162 | }; 163 | "OBJ_15" = { 164 | isa = "PBXGroup"; 165 | children = ( 166 | "OBJ_16" 167 | ); 168 | name = "Tests"; 169 | path = ""; 170 | sourceTree = "SOURCE_ROOT"; 171 | }; 172 | "OBJ_16" = { 173 | isa = "PBXGroup"; 174 | children = ( 175 | "OBJ_17", 176 | "OBJ_18" 177 | ); 178 | name = "MicroTests"; 179 | path = "Tests/MicroTests"; 180 | sourceTree = "SOURCE_ROOT"; 181 | }; 182 | "OBJ_17" = { 183 | isa = "PBXFileReference"; 184 | path = "MicroTests.swift"; 185 | sourceTree = ""; 186 | }; 187 | "OBJ_18" = { 188 | isa = "PBXFileReference"; 189 | path = "XCTestManifests.swift"; 190 | sourceTree = ""; 191 | }; 192 | "OBJ_19" = { 193 | isa = "PBXGroup"; 194 | children = ( 195 | "OBJ_20" 196 | ); 197 | name = "Dependencies"; 198 | path = ""; 199 | sourceTree = ""; 200 | }; 201 | "OBJ_2" = { 202 | isa = "XCConfigurationList"; 203 | buildConfigurations = ( 204 | "OBJ_3", 205 | "OBJ_4" 206 | ); 207 | defaultConfigurationIsVisible = "0"; 208 | defaultConfigurationName = "Release"; 209 | }; 210 | "OBJ_20" = { 211 | isa = "PBXGroup"; 212 | children = ( 213 | "OBJ_21", 214 | "OBJ_22", 215 | "OBJ_31" 216 | ); 217 | name = "DeepDiff 2.3.1"; 218 | path = ".build/checkouts/DeepDiff/Sources"; 219 | sourceTree = "SOURCE_ROOT"; 220 | }; 221 | "OBJ_21" = { 222 | isa = "PBXFileReference"; 223 | explicitFileType = "sourcecode.swift"; 224 | name = "Package.swift"; 225 | path = "/Users/khoa/Downloads/Micro/.build/checkouts/DeepDiff/Package.swift"; 226 | sourceTree = ""; 227 | }; 228 | "OBJ_22" = { 229 | isa = "PBXGroup"; 230 | children = ( 231 | "OBJ_23", 232 | "OBJ_26", 233 | "OBJ_27", 234 | "OBJ_28", 235 | "OBJ_29", 236 | "OBJ_30" 237 | ); 238 | name = "Shared"; 239 | path = "Shared"; 240 | sourceTree = ""; 241 | }; 242 | "OBJ_23" = { 243 | isa = "PBXGroup"; 244 | children = ( 245 | "OBJ_24", 246 | "OBJ_25" 247 | ); 248 | name = "Algorithms"; 249 | path = "Algorithms"; 250 | sourceTree = ""; 251 | }; 252 | "OBJ_24" = { 253 | isa = "PBXFileReference"; 254 | path = "Heckel.swift"; 255 | sourceTree = ""; 256 | }; 257 | "OBJ_25" = { 258 | isa = "PBXFileReference"; 259 | path = "WagnerFischer.swift"; 260 | sourceTree = ""; 261 | }; 262 | "OBJ_26" = { 263 | isa = "PBXFileReference"; 264 | path = "Array+Extensions.swift"; 265 | sourceTree = ""; 266 | }; 267 | "OBJ_27" = { 268 | isa = "PBXFileReference"; 269 | path = "Change.swift"; 270 | sourceTree = ""; 271 | }; 272 | "OBJ_28" = { 273 | isa = "PBXFileReference"; 274 | path = "DeepDiff.swift"; 275 | sourceTree = ""; 276 | }; 277 | "OBJ_29" = { 278 | isa = "PBXFileReference"; 279 | path = "DiffAware.swift"; 280 | sourceTree = ""; 281 | }; 282 | "OBJ_3" = { 283 | isa = "XCBuildConfiguration"; 284 | buildSettings = { 285 | CLANG_ENABLE_OBJC_ARC = "YES"; 286 | COMBINE_HIDPI_IMAGES = "YES"; 287 | COPY_PHASE_STRIP = "NO"; 288 | DEBUG_INFORMATION_FORMAT = "dwarf"; 289 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 290 | ENABLE_NS_ASSERTIONS = "YES"; 291 | GCC_OPTIMIZATION_LEVEL = "0"; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "$(inherited)", 294 | "SWIFT_PACKAGE=1", 295 | "DEBUG=1" 296 | ); 297 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 298 | ONLY_ACTIVE_ARCH = "YES"; 299 | OTHER_SWIFT_FLAGS = ( 300 | "$(inherited)", 301 | "-DXcode" 302 | ); 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SDKROOT = "macosx"; 305 | SUPPORTED_PLATFORMS = ( 306 | "macosx", 307 | "iphoneos", 308 | "iphonesimulator", 309 | "appletvos", 310 | "appletvsimulator", 311 | "watchos", 312 | "watchsimulator" 313 | ); 314 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 315 | "$(inherited)", 316 | "SWIFT_PACKAGE", 317 | "DEBUG" 318 | ); 319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 320 | USE_HEADERMAP = "NO"; 321 | }; 322 | name = "Debug"; 323 | }; 324 | "OBJ_30" = { 325 | isa = "PBXFileReference"; 326 | path = "MoveReducer.swift"; 327 | sourceTree = ""; 328 | }; 329 | "OBJ_31" = { 330 | isa = "PBXGroup"; 331 | children = ( 332 | "OBJ_32", 333 | "OBJ_33", 334 | "OBJ_34" 335 | ); 336 | name = "iOS"; 337 | path = "iOS"; 338 | sourceTree = ""; 339 | }; 340 | "OBJ_32" = { 341 | isa = "PBXFileReference"; 342 | path = "IndexPathConverter.swift"; 343 | sourceTree = ""; 344 | }; 345 | "OBJ_33" = { 346 | isa = "PBXFileReference"; 347 | path = "UICollectionView+Extensions.swift"; 348 | sourceTree = ""; 349 | }; 350 | "OBJ_34" = { 351 | isa = "PBXFileReference"; 352 | path = "UITableView+Extensions.swift"; 353 | sourceTree = ""; 354 | }; 355 | "OBJ_35" = { 356 | isa = "PBXGroup"; 357 | children = ( 358 | "Micro::Micro::Product", 359 | "DeepDiff::DeepDiff::Product", 360 | "Micro::MicroTests::Product" 361 | ); 362 | name = "Products"; 363 | path = ""; 364 | sourceTree = "BUILT_PRODUCTS_DIR"; 365 | }; 366 | "OBJ_39" = { 367 | isa = "PBXFileReference"; 368 | path = "Screenshots"; 369 | sourceTree = "SOURCE_ROOT"; 370 | }; 371 | "OBJ_4" = { 372 | isa = "XCBuildConfiguration"; 373 | buildSettings = { 374 | CLANG_ENABLE_OBJC_ARC = "YES"; 375 | COMBINE_HIDPI_IMAGES = "YES"; 376 | COPY_PHASE_STRIP = "YES"; 377 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 378 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 379 | GCC_OPTIMIZATION_LEVEL = "s"; 380 | GCC_PREPROCESSOR_DEFINITIONS = ( 381 | "$(inherited)", 382 | "SWIFT_PACKAGE=1" 383 | ); 384 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 385 | OTHER_SWIFT_FLAGS = ( 386 | "$(inherited)", 387 | "-DXcode" 388 | ); 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SDKROOT = "macosx"; 391 | SUPPORTED_PLATFORMS = ( 392 | "macosx", 393 | "iphoneos", 394 | "iphonesimulator", 395 | "appletvos", 396 | "appletvsimulator", 397 | "watchos", 398 | "watchsimulator" 399 | ); 400 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 401 | "$(inherited)", 402 | "SWIFT_PACKAGE" 403 | ); 404 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 405 | USE_HEADERMAP = "NO"; 406 | }; 407 | name = "Release"; 408 | }; 409 | "OBJ_40" = { 410 | isa = "PBXFileReference"; 411 | path = "Example"; 412 | sourceTree = "SOURCE_ROOT"; 413 | }; 414 | "OBJ_41" = { 415 | isa = "PBXFileReference"; 416 | path = "DerivedData"; 417 | sourceTree = "SOURCE_ROOT"; 418 | }; 419 | "OBJ_42" = { 420 | isa = "PBXFileReference"; 421 | path = "LICENSE.md"; 422 | sourceTree = ""; 423 | }; 424 | "OBJ_43" = { 425 | isa = "PBXFileReference"; 426 | path = "README.md"; 427 | sourceTree = ""; 428 | }; 429 | "OBJ_45" = { 430 | isa = "XCConfigurationList"; 431 | buildConfigurations = ( 432 | "OBJ_46", 433 | "OBJ_47" 434 | ); 435 | defaultConfigurationIsVisible = "0"; 436 | defaultConfigurationName = "Release"; 437 | }; 438 | "OBJ_46" = { 439 | isa = "XCBuildConfiguration"; 440 | buildSettings = { 441 | ENABLE_TESTABILITY = "YES"; 442 | FRAMEWORK_SEARCH_PATHS = ( 443 | "$(inherited)", 444 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 445 | ); 446 | HEADER_SEARCH_PATHS = ( 447 | "$(inherited)" 448 | ); 449 | INFOPLIST_FILE = "Micro.xcodeproj/DeepDiff_Info.plist"; 450 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 454 | ); 455 | MACOSX_DEPLOYMENT_TARGET = "10.11"; 456 | OTHER_CFLAGS = ( 457 | "$(inherited)" 458 | ); 459 | OTHER_LDFLAGS = ( 460 | "$(inherited)" 461 | ); 462 | OTHER_SWIFT_FLAGS = ( 463 | "$(inherited)" 464 | ); 465 | PRODUCT_BUNDLE_IDENTIFIER = "DeepDiff"; 466 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 467 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 468 | SKIP_INSTALL = "YES"; 469 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 470 | "$(inherited)" 471 | ); 472 | SWIFT_VERSION = "5.0"; 473 | TARGET_NAME = "DeepDiff"; 474 | TVOS_DEPLOYMENT_TARGET = "11.0"; 475 | WATCHOS_DEPLOYMENT_TARGET = "3.0"; 476 | }; 477 | name = "Debug"; 478 | }; 479 | "OBJ_47" = { 480 | isa = "XCBuildConfiguration"; 481 | buildSettings = { 482 | ENABLE_TESTABILITY = "YES"; 483 | FRAMEWORK_SEARCH_PATHS = ( 484 | "$(inherited)", 485 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 486 | ); 487 | HEADER_SEARCH_PATHS = ( 488 | "$(inherited)" 489 | ); 490 | INFOPLIST_FILE = "Micro.xcodeproj/DeepDiff_Info.plist"; 491 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 492 | LD_RUNPATH_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 495 | ); 496 | MACOSX_DEPLOYMENT_TARGET = "10.11"; 497 | OTHER_CFLAGS = ( 498 | "$(inherited)" 499 | ); 500 | OTHER_LDFLAGS = ( 501 | "$(inherited)" 502 | ); 503 | OTHER_SWIFT_FLAGS = ( 504 | "$(inherited)" 505 | ); 506 | PRODUCT_BUNDLE_IDENTIFIER = "DeepDiff"; 507 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 508 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 509 | SKIP_INSTALL = "YES"; 510 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 511 | "$(inherited)" 512 | ); 513 | SWIFT_VERSION = "5.0"; 514 | TARGET_NAME = "DeepDiff"; 515 | TVOS_DEPLOYMENT_TARGET = "11.0"; 516 | WATCHOS_DEPLOYMENT_TARGET = "3.0"; 517 | }; 518 | name = "Release"; 519 | }; 520 | "OBJ_48" = { 521 | isa = "PBXSourcesBuildPhase"; 522 | files = ( 523 | "OBJ_49", 524 | "OBJ_50", 525 | "OBJ_51", 526 | "OBJ_52", 527 | "OBJ_53", 528 | "OBJ_54", 529 | "OBJ_55", 530 | "OBJ_56", 531 | "OBJ_57", 532 | "OBJ_58" 533 | ); 534 | }; 535 | "OBJ_49" = { 536 | isa = "PBXBuildFile"; 537 | fileRef = "OBJ_24"; 538 | }; 539 | "OBJ_5" = { 540 | isa = "PBXGroup"; 541 | children = ( 542 | "OBJ_6", 543 | "OBJ_7", 544 | "OBJ_15", 545 | "OBJ_19", 546 | "OBJ_35", 547 | "OBJ_39", 548 | "OBJ_40", 549 | "OBJ_41", 550 | "OBJ_42", 551 | "OBJ_43" 552 | ); 553 | path = ""; 554 | sourceTree = ""; 555 | }; 556 | "OBJ_50" = { 557 | isa = "PBXBuildFile"; 558 | fileRef = "OBJ_25"; 559 | }; 560 | "OBJ_51" = { 561 | isa = "PBXBuildFile"; 562 | fileRef = "OBJ_26"; 563 | }; 564 | "OBJ_52" = { 565 | isa = "PBXBuildFile"; 566 | fileRef = "OBJ_27"; 567 | }; 568 | "OBJ_53" = { 569 | isa = "PBXBuildFile"; 570 | fileRef = "OBJ_28"; 571 | }; 572 | "OBJ_54" = { 573 | isa = "PBXBuildFile"; 574 | fileRef = "OBJ_29"; 575 | }; 576 | "OBJ_55" = { 577 | isa = "PBXBuildFile"; 578 | fileRef = "OBJ_30"; 579 | }; 580 | "OBJ_56" = { 581 | isa = "PBXBuildFile"; 582 | fileRef = "OBJ_32"; 583 | }; 584 | "OBJ_57" = { 585 | isa = "PBXBuildFile"; 586 | fileRef = "OBJ_33"; 587 | }; 588 | "OBJ_58" = { 589 | isa = "PBXBuildFile"; 590 | fileRef = "OBJ_34"; 591 | }; 592 | "OBJ_59" = { 593 | isa = "PBXFrameworksBuildPhase"; 594 | files = ( 595 | ); 596 | }; 597 | "OBJ_6" = { 598 | isa = "PBXFileReference"; 599 | explicitFileType = "sourcecode.swift"; 600 | path = "Package.swift"; 601 | sourceTree = ""; 602 | }; 603 | "OBJ_61" = { 604 | isa = "XCConfigurationList"; 605 | buildConfigurations = ( 606 | "OBJ_62", 607 | "OBJ_63" 608 | ); 609 | defaultConfigurationIsVisible = "0"; 610 | defaultConfigurationName = "Release"; 611 | }; 612 | "OBJ_62" = { 613 | isa = "XCBuildConfiguration"; 614 | buildSettings = { 615 | LD = "/usr/bin/true"; 616 | OTHER_SWIFT_FLAGS = ( 617 | "-swift-version", 618 | "5", 619 | "-I", 620 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 621 | "-target", 622 | "x86_64-apple-macosx10.10", 623 | "-sdk", 624 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 625 | "-package-description-version", 626 | "5" 627 | ); 628 | SWIFT_VERSION = "5.0"; 629 | }; 630 | name = "Debug"; 631 | }; 632 | "OBJ_63" = { 633 | isa = "XCBuildConfiguration"; 634 | buildSettings = { 635 | LD = "/usr/bin/true"; 636 | OTHER_SWIFT_FLAGS = ( 637 | "-swift-version", 638 | "5", 639 | "-I", 640 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 641 | "-target", 642 | "x86_64-apple-macosx10.10", 643 | "-sdk", 644 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 645 | "-package-description-version", 646 | "5" 647 | ); 648 | SWIFT_VERSION = "5.0"; 649 | }; 650 | name = "Release"; 651 | }; 652 | "OBJ_64" = { 653 | isa = "PBXSourcesBuildPhase"; 654 | files = ( 655 | "OBJ_65" 656 | ); 657 | }; 658 | "OBJ_65" = { 659 | isa = "PBXBuildFile"; 660 | fileRef = "OBJ_21"; 661 | }; 662 | "OBJ_67" = { 663 | isa = "XCConfigurationList"; 664 | buildConfigurations = ( 665 | "OBJ_68", 666 | "OBJ_69" 667 | ); 668 | defaultConfigurationIsVisible = "0"; 669 | defaultConfigurationName = "Release"; 670 | }; 671 | "OBJ_68" = { 672 | isa = "XCBuildConfiguration"; 673 | buildSettings = { 674 | ENABLE_TESTABILITY = "YES"; 675 | FRAMEWORK_SEARCH_PATHS = ( 676 | "$(inherited)", 677 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 678 | ); 679 | HEADER_SEARCH_PATHS = ( 680 | "$(inherited)" 681 | ); 682 | INFOPLIST_FILE = "Micro.xcodeproj/Micro_Info.plist"; 683 | IPHONEOS_DEPLOYMENT_TARGET = "10.0"; 684 | LD_RUNPATH_SEARCH_PATHS = ( 685 | "$(inherited)", 686 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 687 | ); 688 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 689 | OTHER_CFLAGS = ( 690 | "$(inherited)" 691 | ); 692 | OTHER_LDFLAGS = ( 693 | "$(inherited)" 694 | ); 695 | OTHER_SWIFT_FLAGS = ( 696 | "$(inherited)" 697 | ); 698 | PRODUCT_BUNDLE_IDENTIFIER = "Micro"; 699 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 700 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 701 | SKIP_INSTALL = "YES"; 702 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 703 | "$(inherited)" 704 | ); 705 | SWIFT_VERSION = "5.0"; 706 | TARGET_NAME = "Micro"; 707 | TVOS_DEPLOYMENT_TARGET = "9.0"; 708 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 709 | }; 710 | name = "Debug"; 711 | }; 712 | "OBJ_69" = { 713 | isa = "XCBuildConfiguration"; 714 | buildSettings = { 715 | ENABLE_TESTABILITY = "YES"; 716 | FRAMEWORK_SEARCH_PATHS = ( 717 | "$(inherited)", 718 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 719 | ); 720 | HEADER_SEARCH_PATHS = ( 721 | "$(inherited)" 722 | ); 723 | INFOPLIST_FILE = "Micro.xcodeproj/Micro_Info.plist"; 724 | IPHONEOS_DEPLOYMENT_TARGET = "10.0"; 725 | LD_RUNPATH_SEARCH_PATHS = ( 726 | "$(inherited)", 727 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 728 | ); 729 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 730 | OTHER_CFLAGS = ( 731 | "$(inherited)" 732 | ); 733 | OTHER_LDFLAGS = ( 734 | "$(inherited)" 735 | ); 736 | OTHER_SWIFT_FLAGS = ( 737 | "$(inherited)" 738 | ); 739 | PRODUCT_BUNDLE_IDENTIFIER = "Micro"; 740 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 741 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 742 | SKIP_INSTALL = "YES"; 743 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 744 | "$(inherited)" 745 | ); 746 | SWIFT_VERSION = "5.0"; 747 | TARGET_NAME = "Micro"; 748 | TVOS_DEPLOYMENT_TARGET = "9.0"; 749 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 750 | }; 751 | name = "Release"; 752 | }; 753 | "OBJ_7" = { 754 | isa = "PBXGroup"; 755 | children = ( 756 | "OBJ_8" 757 | ); 758 | name = "Sources"; 759 | path = ""; 760 | sourceTree = "SOURCE_ROOT"; 761 | }; 762 | "OBJ_70" = { 763 | isa = "PBXSourcesBuildPhase"; 764 | files = ( 765 | "OBJ_71", 766 | "OBJ_72", 767 | "OBJ_73", 768 | "OBJ_74", 769 | "OBJ_75", 770 | "OBJ_76" 771 | ); 772 | }; 773 | "OBJ_71" = { 774 | isa = "PBXBuildFile"; 775 | fileRef = "OBJ_9"; 776 | }; 777 | "OBJ_72" = { 778 | isa = "PBXBuildFile"; 779 | fileRef = "OBJ_10"; 780 | }; 781 | "OBJ_73" = { 782 | isa = "PBXBuildFile"; 783 | fileRef = "OBJ_11"; 784 | }; 785 | "OBJ_74" = { 786 | isa = "PBXBuildFile"; 787 | fileRef = "OBJ_12"; 788 | }; 789 | "OBJ_75" = { 790 | isa = "PBXBuildFile"; 791 | fileRef = "OBJ_13"; 792 | }; 793 | "OBJ_76" = { 794 | isa = "PBXBuildFile"; 795 | fileRef = "OBJ_14"; 796 | }; 797 | "OBJ_77" = { 798 | isa = "PBXFrameworksBuildPhase"; 799 | files = ( 800 | "OBJ_78" 801 | ); 802 | }; 803 | "OBJ_78" = { 804 | isa = "PBXBuildFile"; 805 | fileRef = "DeepDiff::DeepDiff::Product"; 806 | }; 807 | "OBJ_79" = { 808 | isa = "PBXTargetDependency"; 809 | target = "DeepDiff::DeepDiff"; 810 | }; 811 | "OBJ_8" = { 812 | isa = "PBXGroup"; 813 | children = ( 814 | "OBJ_9", 815 | "OBJ_10", 816 | "OBJ_11", 817 | "OBJ_12", 818 | "OBJ_13", 819 | "OBJ_14" 820 | ); 821 | name = "Micro"; 822 | path = "Sources/Micro"; 823 | sourceTree = "SOURCE_ROOT"; 824 | }; 825 | "OBJ_81" = { 826 | isa = "XCConfigurationList"; 827 | buildConfigurations = ( 828 | "OBJ_82", 829 | "OBJ_83" 830 | ); 831 | defaultConfigurationIsVisible = "0"; 832 | defaultConfigurationName = "Release"; 833 | }; 834 | "OBJ_82" = { 835 | isa = "XCBuildConfiguration"; 836 | buildSettings = { 837 | LD = "/usr/bin/true"; 838 | OTHER_SWIFT_FLAGS = ( 839 | "-swift-version", 840 | "5", 841 | "-I", 842 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 843 | "-target", 844 | "x86_64-apple-macosx10.10", 845 | "-sdk", 846 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 847 | "-package-description-version", 848 | "5.1" 849 | ); 850 | SWIFT_VERSION = "5.0"; 851 | }; 852 | name = "Debug"; 853 | }; 854 | "OBJ_83" = { 855 | isa = "XCBuildConfiguration"; 856 | buildSettings = { 857 | LD = "/usr/bin/true"; 858 | OTHER_SWIFT_FLAGS = ( 859 | "-swift-version", 860 | "5", 861 | "-I", 862 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 863 | "-target", 864 | "x86_64-apple-macosx10.10", 865 | "-sdk", 866 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 867 | "-package-description-version", 868 | "5.1" 869 | ); 870 | SWIFT_VERSION = "5.0"; 871 | }; 872 | name = "Release"; 873 | }; 874 | "OBJ_84" = { 875 | isa = "PBXSourcesBuildPhase"; 876 | files = ( 877 | "OBJ_85" 878 | ); 879 | }; 880 | "OBJ_85" = { 881 | isa = "PBXBuildFile"; 882 | fileRef = "OBJ_6"; 883 | }; 884 | "OBJ_87" = { 885 | isa = "XCConfigurationList"; 886 | buildConfigurations = ( 887 | "OBJ_88", 888 | "OBJ_89" 889 | ); 890 | defaultConfigurationIsVisible = "0"; 891 | defaultConfigurationName = "Release"; 892 | }; 893 | "OBJ_88" = { 894 | isa = "XCBuildConfiguration"; 895 | buildSettings = { 896 | }; 897 | name = "Debug"; 898 | }; 899 | "OBJ_89" = { 900 | isa = "XCBuildConfiguration"; 901 | buildSettings = { 902 | }; 903 | name = "Release"; 904 | }; 905 | "OBJ_9" = { 906 | isa = "PBXFileReference"; 907 | path = "Cell.swift"; 908 | sourceTree = ""; 909 | }; 910 | "OBJ_90" = { 911 | isa = "PBXTargetDependency"; 912 | target = "Micro::MicroTests"; 913 | }; 914 | "OBJ_92" = { 915 | isa = "XCConfigurationList"; 916 | buildConfigurations = ( 917 | "OBJ_93", 918 | "OBJ_94" 919 | ); 920 | defaultConfigurationIsVisible = "0"; 921 | defaultConfigurationName = "Release"; 922 | }; 923 | "OBJ_93" = { 924 | isa = "XCBuildConfiguration"; 925 | buildSettings = { 926 | CLANG_ENABLE_MODULES = "YES"; 927 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 928 | FRAMEWORK_SEARCH_PATHS = ( 929 | "$(inherited)", 930 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 931 | ); 932 | HEADER_SEARCH_PATHS = ( 933 | "$(inherited)" 934 | ); 935 | INFOPLIST_FILE = "Micro.xcodeproj/MicroTests_Info.plist"; 936 | IPHONEOS_DEPLOYMENT_TARGET = "10.0"; 937 | LD_RUNPATH_SEARCH_PATHS = ( 938 | "$(inherited)", 939 | "@loader_path/../Frameworks", 940 | "@loader_path/Frameworks" 941 | ); 942 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 943 | OTHER_CFLAGS = ( 944 | "$(inherited)" 945 | ); 946 | OTHER_LDFLAGS = ( 947 | "$(inherited)" 948 | ); 949 | OTHER_SWIFT_FLAGS = ( 950 | "$(inherited)" 951 | ); 952 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 953 | "$(inherited)" 954 | ); 955 | SWIFT_VERSION = "5.0"; 956 | TARGET_NAME = "MicroTests"; 957 | TVOS_DEPLOYMENT_TARGET = "9.0"; 958 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 959 | }; 960 | name = "Debug"; 961 | }; 962 | "OBJ_94" = { 963 | isa = "XCBuildConfiguration"; 964 | buildSettings = { 965 | CLANG_ENABLE_MODULES = "YES"; 966 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 967 | FRAMEWORK_SEARCH_PATHS = ( 968 | "$(inherited)", 969 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 970 | ); 971 | HEADER_SEARCH_PATHS = ( 972 | "$(inherited)" 973 | ); 974 | INFOPLIST_FILE = "Micro.xcodeproj/MicroTests_Info.plist"; 975 | IPHONEOS_DEPLOYMENT_TARGET = "10.0"; 976 | LD_RUNPATH_SEARCH_PATHS = ( 977 | "$(inherited)", 978 | "@loader_path/../Frameworks", 979 | "@loader_path/Frameworks" 980 | ); 981 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 982 | OTHER_CFLAGS = ( 983 | "$(inherited)" 984 | ); 985 | OTHER_LDFLAGS = ( 986 | "$(inherited)" 987 | ); 988 | OTHER_SWIFT_FLAGS = ( 989 | "$(inherited)" 990 | ); 991 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 992 | "$(inherited)" 993 | ); 994 | SWIFT_VERSION = "5.0"; 995 | TARGET_NAME = "MicroTests"; 996 | TVOS_DEPLOYMENT_TARGET = "9.0"; 997 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 998 | }; 999 | name = "Release"; 1000 | }; 1001 | "OBJ_95" = { 1002 | isa = "PBXSourcesBuildPhase"; 1003 | files = ( 1004 | "OBJ_96", 1005 | "OBJ_97" 1006 | ); 1007 | }; 1008 | "OBJ_96" = { 1009 | isa = "PBXBuildFile"; 1010 | fileRef = "OBJ_17"; 1011 | }; 1012 | "OBJ_97" = { 1013 | isa = "PBXBuildFile"; 1014 | fileRef = "OBJ_18"; 1015 | }; 1016 | "OBJ_98" = { 1017 | isa = "PBXFrameworksBuildPhase"; 1018 | files = ( 1019 | "OBJ_99", 1020 | "OBJ_100" 1021 | ); 1022 | }; 1023 | "OBJ_99" = { 1024 | isa = "PBXBuildFile"; 1025 | fileRef = "Micro::Micro::Product"; 1026 | }; 1027 | }; 1028 | rootObject = "OBJ_1"; 1029 | } 1030 | -------------------------------------------------------------------------------- /Micro.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /Micro.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Micro.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /Micro.xcodeproj/xcshareddata/xcschemes/Micro-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "DeepDiff", 6 | "repositoryURL": "https://github.com/onmyway133/DeepDiff.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "010ebbe1ce5ebeaee37a3d2f4a8c03f0e75b8045", 10 | "version": "2.3.1" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 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: "Micro", 8 | platforms: [ 9 | .iOS(.v8), 10 | ], 11 | products: [ 12 | .library( 13 | name: "Micro", 14 | targets: ["Micro"] 15 | ), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/onmyway133/DeepDiff.git", from: "2.3.1"), 19 | ], 20 | targets: [ 21 | .target( 22 | name: "Micro", 23 | dependencies: [ 24 | "DeepDiff" 25 | ] 26 | ), 27 | .testTarget( 28 | name: "MicroTests", 29 | dependencies: ["Micro"] 30 | ), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Micro 2 | 3 | ❤️ Support my apps ❤️ 4 | 5 | - [Push Hero - pure Swift native macOS application to test push notifications](https://onmyway133.com/pushhero) 6 | - [PastePal - Pasteboard, note and shortcut manager](https://onmyway133.com/pastepal) 7 | - [Quick Check - smart todo manager](https://onmyway133.com/quickcheck) 8 | - [Alias - App and file shortcut manager](https://onmyway133.com/alias) 9 | - [My other apps](https://onmyway133.com/apps/) 10 | 11 | ❤️❤️😇😍🤘❤️❤️ 12 | 13 |
14 | 15 |
16 | 17 | For demo, check [DemoMicro](https://github.com/onmyway133/Micro/tree/master/Example/DemoMicro) 18 | 19 | Read more 20 | 21 | - [How to build SwiftUI style UICollectionView data source in Swift](https://onmyway133.github.io/blog/How-to-build-SwiftUI-style-UICollectionView-data-source-in-Swift/) 22 | - [A better way to update UICollectionView data in Swift with diff framework](https://medium.com/flawless-app-stories/a-better-way-to-update-uicollectionview-data-in-swift-with-diff-framework-924db158db86) 23 | 24 | ## Description 25 | 26 | Most of the time, we want to apply model data to cell with smart diffing. 27 | Micro provides type safe SwiftUI style data source for UICollectionView, with super fast diffing powered by [DeepDiff](https://github.com/onmyway133/DeepDiff). 28 | Just declare a `State` with SwiftUI style `forEach` and Micro will reload with animated diffing 29 | 30 | ```swift 31 | struct Blog: DiffAware {} 32 | class BlogCell: UICollectionViewCell {} 33 | 34 | let dataSource = DataSource(collectionView: collectionView) 35 | dataSource.state = State { 36 | ForEach(blogs) { blog in 37 | Cell() { context, cell in 38 | cell.nameLabel.text = blog.name 39 | } 40 | .onSelect { context in 41 | print("cell at index \(context.indexPath.item) is selected") 42 | } 43 | .onSize { context in 44 | CGSize( 45 | width: context.collectionView.frame.size.width, 46 | height: 40 47 | ) 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | The above uses Swift 5.1 function builder syntax, which uses `forEach` method under the hood. You can also do like below with `forEach` method. 54 | 55 | ```swift 56 | dataSource.state = forEach(blogs) { blog in 57 | Cell() { context, cell in 58 | cell.nameLabel.text = blog.name 59 | } 60 | .onSelect { context in 61 | print("cell at index \(context.indexPath.item) is selected") 62 | } 63 | .onSize { context in 64 | CGSize( 65 | width: context.collectionView.frame.size.width, 66 | height: 40 67 | ) 68 | } 69 | } 70 | ``` 71 | 72 | Features 73 | 74 | - Supports iOS 8+ 75 | - Declare in type safe manner with `forEach` 76 | - `context` provides additional information, like `UICollectionView` and `IndexPath` 77 | - Automatic reload with smart diffing whenever `state` is set 78 | - By default, diffing is animated, you can use `dataSource.reload(newState:isAnimated:completion)` to specify animated and completion 79 | 80 | ## Advanced 81 | 82 | ### Animate reloading 83 | 84 | By default, when you set `state` on `DataSource`, animated diffing is performed. If you want to set animated property and to listen to completion event, you can use `reload` method 85 | 86 | ```swift 87 | dataSource.reload( 88 | newState: newState, 89 | isAnimated: false, 90 | completion: { finished in 91 | print("reloade is finished") 92 | } 93 | ) 94 | ``` 95 | 96 | ### Complex model with multiple cell types 97 | 98 | You can declare different `Cell` in `forEach` with different kinds of cell. 99 | 100 | ```swift 101 | struct Movie: DiffAware { 102 | enum Kind: Equatable { 103 | case show(String) 104 | case loading 105 | case ad 106 | } 107 | 108 | let diffId = UUID() 109 | let kind: Kind 110 | 111 | static func compareContent(_ a: Movie, _ b: Movie) -> Bool { 112 | return a.kind == b.kind 113 | } 114 | } 115 | 116 | class MovieCell: UICollectionViewCell { 117 | let nameLabel: UILabel = .init() 118 | } 119 | 120 | class LoadingCell: UICollectionViewCell {} 121 | 122 | class AdCell: UICollectionViewCell {} 123 | 124 | let layout = UICollectionViewFlowLayout() 125 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 126 | let dataSource = DataSource(collectionView: collectionView) 127 | collectionView.dataSource = dataSource 128 | collectionView.delegate = dataSource 129 | 130 | let movies: [Movie] = [ 131 | Movie(kind: .show("Titanic")), 132 | Movie(kind: .show("Batman")), 133 | Movie(kind: .loading), 134 | Movie(kind: .ad) 135 | ] 136 | 137 | dataSource.state = forEach(movies) { movie in 138 | switch movie.kind { 139 | case .show(let name): 140 | return Cell() { context, cell in 141 | cell.nameLabel.text = name 142 | } 143 | .onSelect { _ in 144 | 145 | } 146 | .onDeselect { _ in 147 | 148 | } 149 | .onWillDisplay { _, _ in 150 | 151 | } 152 | .onDidEndDisplay { _, _ in 153 | 154 | } 155 | .onSize { context in 156 | CGSize(width: context.collectionView.frame.size.width, height: 40) 157 | } 158 | case .loading: 159 | return Cell() 160 | case .ad: 161 | return Cell() 162 | } 163 | } 164 | ``` 165 | 166 | ### Customize with subclass 167 | 168 | `DataSource` is completely overridable, if you want to customize any methods, just subclass `DataSource`, override methods and access its `state.models` 169 | 170 | ```swift 171 | class CustomDataSource: DataSource { 172 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 173 | let blog = state.models[indexPath.item] as? Blog 174 | print(blog) 175 | } 176 | } 177 | ``` 178 | 179 | ## Installation 180 | 181 | **Micro** is also available through [Swift Package Manager](https://swift.org/package-manager/) 182 | 183 | ```swift 184 | .package(url: "https://github.com/onmyway133/Micro", from: "1.2.0") 185 | ``` 186 | 187 | ## Author 188 | 189 | Khoa Pham, onmyway133@gmail.com 190 | 191 | ## License 192 | 193 | **Micro** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/Micro/blob/master/LICENSE.md) file for more info. 194 | -------------------------------------------------------------------------------- /Screenshots/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/Micro/e6f654c29710ac4511980941483f4b05237eb799/Screenshots/demo.gif -------------------------------------------------------------------------------- /Sources/Micro/Cell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cell.swift 3 | // DeepDiff 4 | // 5 | // Created by khoa on 08/02/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol ObserverOwner { 11 | var observer: Observer { get } 12 | } 13 | 14 | public struct Cell: ObserverOwner { 15 | public let observer = Observer() 16 | 17 | public init(_ closure: @escaping (Context, C) -> Void = { _, _ in }) { 18 | observer.onConfigure = { context, dataSource in 19 | dataSource.registerIfNeeded(collectionView: context.collectionView, cellType: C.self) 20 | if let cell: C = context.collectionView.dequeue(for: context.indexPath) { 21 | closure(context, cell) 22 | return cell 23 | } else { 24 | return nil 25 | } 26 | } 27 | } 28 | 29 | public func onShouldSelect(_ closure: @escaping (Context) -> Bool) -> Self { 30 | observer.onShouldSelect = closure 31 | return self 32 | } 33 | 34 | public func onSelect(_ closure: @escaping (Context) -> Void) -> Self { 35 | observer.onSelect = closure 36 | return self 37 | } 38 | 39 | public func onDeselect(_ closure: @escaping (Context) -> Void) -> Self { 40 | observer.onDeselect = closure 41 | return self 42 | } 43 | 44 | public func onSize(_ closure: @escaping (Context) -> CGSize) -> Self { 45 | observer.onSize = closure 46 | return self 47 | } 48 | 49 | public func onShouldHighlight(_ closure: @escaping (Context) -> Bool) -> Self { 50 | observer.onShouldHighlight = closure 51 | return self 52 | } 53 | 54 | public func onWillDisplay(_ closure: @escaping (Context, C) -> Void) -> Self { 55 | observer.onWillDisplay = { context, cell in 56 | if let cell = cell as? C { 57 | closure(context, cell) 58 | } 59 | } 60 | return self 61 | } 62 | 63 | public func onDidEndDisplay(_ closure: @escaping (Context, C) -> Void) -> Self { 64 | observer.onDidEndDisplay = { context, cell in 65 | if let cell = cell as? C { 66 | closure(context, cell) 67 | } 68 | } 69 | return self 70 | } 71 | } 72 | 73 | public struct Context { 74 | public let collectionView: UICollectionView 75 | public let indexPath: IndexPath 76 | } 77 | 78 | public class Observer { 79 | public var onConfigure: (Context, DataSource) -> UICollectionViewCell? = { _, _ in nil } 80 | public var onShouldSelect: (Context) -> Bool = { _ in true } 81 | public var onSelect: (Context) -> Void = { _ in } 82 | public var onDeselect: (Context) -> Void = { _ in } 83 | public var onSize: (Context) -> CGSize = { _ in .zero } 84 | public var onShouldHighlight: (Context) -> Bool = { _ in true } 85 | public var onWillDisplay: (Context, UICollectionViewCell) -> Void = { _, _ in } 86 | public var onDidEndDisplay: (Context, UICollectionViewCell) -> Void = { _, _ in } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/Micro/DataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSource.swift 3 | // Micro 4 | // 5 | // Created by khoa on 08/02/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | open class DataSource: NSObject { 11 | private var cellRegister: Set = Set() 12 | internal var trueState: State = .init() 13 | public weak var collectionView: UICollectionView? 14 | 15 | public init(collectionView: UICollectionView) { 16 | self.collectionView = collectionView 17 | } 18 | 19 | public var state: State { 20 | get { 21 | trueState 22 | } 23 | set { 24 | reload(newState: newValue) 25 | } 26 | } 27 | 28 | public func reload( 29 | newState: State, 30 | isAnimated: Bool = true, 31 | completion: Reloader.Completion? = nil 32 | ) { 33 | guard let collectionView = collectionView else { return } 34 | let request = Reloader.Request( 35 | dataSource: self, 36 | collectionView: collectionView, 37 | newState: newState, 38 | isAnimated: isAnimated, 39 | completion: completion ?? { _ in } 40 | ) 41 | newState.reloader.onReload(request) 42 | } 43 | 44 | public func registerIfNeeded( 45 | collectionView: UICollectionView, 46 | cellType: Cell.Type 47 | ) { 48 | if !cellRegister.contains(String(describing: cellIdentifier(cellType))) { 49 | collectionView.register(cellType: cellType) 50 | cellRegister.insert( cellIdentifier(cellType)) 51 | } 52 | } 53 | } 54 | 55 | extension DataSource: UICollectionViewDataSource { 56 | open func numberOfSections(in collectionView: UICollectionView) -> Int { 57 | return 1 58 | } 59 | 60 | open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 61 | return state.models.count 62 | } 63 | 64 | open func collectionView( 65 | _ collectionView: UICollectionView, 66 | cellForItemAt indexPath: IndexPath 67 | ) -> UICollectionViewCell { 68 | let observer = state.observers[safe: indexPath.item] 69 | let context = Context(collectionView: collectionView, indexPath: indexPath) 70 | return observer?.onConfigure(context, self) ?? UICollectionViewCell() 71 | } 72 | } 73 | 74 | extension DataSource: UICollectionViewDelegate { 75 | open func collectionView( 76 | _ collectionView: UICollectionView, 77 | willDisplay cell: UICollectionViewCell, 78 | forItemAt indexPath: IndexPath 79 | ) { 80 | let observer = state.observers[safe: indexPath.item] 81 | let context = Context(collectionView: collectionView, indexPath: indexPath) 82 | observer?.onWillDisplay(context, cell) 83 | } 84 | 85 | open func collectionView( 86 | _ collectionView: UICollectionView, 87 | didEndDisplaying cell: UICollectionViewCell, 88 | forItemAt indexPath: IndexPath 89 | ) { 90 | let observer = state.observers[safe: indexPath.item] 91 | let context = Context(collectionView: collectionView, indexPath: indexPath) 92 | observer?.onDidEndDisplay(context, cell) 93 | } 94 | 95 | public func collectionView( 96 | _ collectionView: UICollectionView, 97 | shouldSelectItemAt indexPath: IndexPath 98 | ) -> Bool { 99 | let observer = state.observers[safe: indexPath.item] 100 | let context = Context(collectionView: collectionView, indexPath: indexPath) 101 | return observer?.onShouldSelect(context) ?? false 102 | } 103 | 104 | open func collectionView( 105 | _ collectionView: UICollectionView, 106 | didSelectItemAt indexPath: IndexPath 107 | ) { 108 | let observer = state.observers[safe: indexPath.item] 109 | let context = Context(collectionView: collectionView, indexPath: indexPath) 110 | observer?.onSelect(context) 111 | } 112 | 113 | open func collectionView( 114 | _ collectionView: UICollectionView, 115 | didDeselectItemAt indexPath: IndexPath 116 | ) { 117 | let observer = state.observers[safe: indexPath.item] 118 | let context = Context(collectionView: collectionView, indexPath: indexPath) 119 | observer?.onDeselect(context) 120 | } 121 | 122 | open func collectionView( 123 | _ collectionView: UICollectionView, 124 | shouldHighlightItemAt indexPath: IndexPath 125 | ) -> Bool { 126 | let observer = state.observers[safe: indexPath.item] 127 | let context = Context(collectionView: collectionView, indexPath: indexPath) 128 | return observer?.onShouldHighlight(context) ?? false 129 | } 130 | } 131 | 132 | extension DataSource: UICollectionViewDelegateFlowLayout { 133 | public func collectionView( 134 | _ collectionView: UICollectionView, 135 | layout collectionViewLayout: UICollectionViewLayout, 136 | sizeForItemAt indexPath: IndexPath 137 | ) -> CGSize { 138 | let observer = state.observers[safe: indexPath.item] 139 | let context = Context(collectionView: collectionView, indexPath: indexPath) 140 | return observer?.onSize(context) ?? .zero 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/Micro/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // Micro 4 | // 5 | // Created by khoa on 08/02/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | func cellIdentifier(_ cellType: Cell.Type) -> String { 11 | return String(describing: Cell.self) 12 | } 13 | 14 | public extension UICollectionView { 15 | func register(cellType: T.Type) { 16 | register(T.self, forCellWithReuseIdentifier: cellIdentifier(cellType)) 17 | } 18 | 19 | func dequeue(for indexPath: IndexPath) -> T? { 20 | return dequeueReusableCell( 21 | withReuseIdentifier:cellIdentifier(T.self), 22 | for: indexPath 23 | ) as? T 24 | } 25 | } 26 | 27 | extension Collection { 28 | public subscript(safe index: Index) -> Element? { 29 | indices.contains(index) ? self[index] : nil 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Micro/ForEach.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForEach.swift 3 | // Micro 4 | // 5 | // Created by khoa on 08/02/2020. 6 | // 7 | 8 | import UIKit 9 | import DeepDiff 10 | 11 | public struct ForEach { 12 | public let state: State 13 | 14 | public init( 15 | _ models: [Model], 16 | transform: (Model) -> ObserverOwner 17 | ) { 18 | self.state = forEach(models, transform: transform) 19 | } 20 | } 21 | 22 | public func forEach( 23 | _ models: [Model], 24 | transform: (Model) -> ObserverOwner 25 | ) -> State { 26 | let onReload: Reloader.Reload = { request in 27 | let reload: () -> Void = { 28 | let oldModels: [Model] = request.dataSource.trueState.models.compactMap({ $0 as? Model }) 29 | 30 | if oldModels.isEmpty { 31 | request.dataSource.trueState = request.newState 32 | request.collectionView.reloadData() 33 | request.completion(true) 34 | return 35 | } 36 | 37 | let changes: [Change] = diff(old: oldModels, new: models) 38 | request.collectionView.reload( 39 | changes: changes, 40 | section: 0, 41 | updateData: { 42 | request.dataSource.trueState = request.newState 43 | }, 44 | completion: request.completion 45 | ) 46 | } 47 | 48 | if request.isAnimated { 49 | reload() 50 | } else { 51 | UIView.performWithoutAnimation { 52 | reload() 53 | } 54 | } 55 | } 56 | 57 | let owners: [ObserverOwner] = models.map(transform) 58 | let observers: [Observer] = owners.map({ $0.observer }) 59 | 60 | return State( 61 | models: models, 62 | owners: owners, 63 | observers: observers, 64 | reloader: Reloader(onReload: onReload) 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Micro/Reloader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reloader.swift 3 | // Micro 4 | // 5 | // Created by khoa on 09/02/2020. 6 | // 7 | 8 | import UIKit 9 | 10 | public struct Reloader { 11 | public struct Request { 12 | public let dataSource: DataSource 13 | public let collectionView: UICollectionView 14 | public let newState: State 15 | public let isAnimated: Bool 16 | public let completion: Completion 17 | } 18 | 19 | public typealias Completion = (Bool) -> Void 20 | public typealias Reload = (Request) -> Void 21 | public let onReload: Reload 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Micro/State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // State.swift 3 | // Micro 4 | // 5 | // Created by khoa on 08/02/2020. 6 | // 7 | 8 | import UIKit 9 | import DeepDiff 10 | 11 | public struct State { 12 | let models: [Any] 13 | let owners: [ObserverOwner] 14 | let observers: [Observer] 15 | let reloader: Reloader 16 | } 17 | 18 | extension State { 19 | public init() { 20 | self.models = [] 21 | self.owners = [] 22 | self.observers = [] 23 | self.reloader = Reloader(onReload: { _ in }) 24 | } 25 | } 26 | 27 | @_functionBuilder 28 | public struct StateBuilder { 29 | public static func buildBlock(_ forEach: ForEach) -> ForEach { 30 | forEach 31 | } 32 | } 33 | 34 | extension State { 35 | public init(@StateBuilder builder: () -> ForEach) { 36 | let forEach = builder() 37 | self = forEach.state 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import MicroTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += MicroTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/MicroTests/MicroTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import UIKit 3 | import DeepDiff 4 | @testable import Micro 5 | 6 | struct Movie: DiffAware { 7 | enum Kind: Equatable { 8 | case show(String) 9 | case loading 10 | case ad 11 | } 12 | 13 | let diffId = UUID() 14 | let kind: Kind 15 | 16 | static func compareContent(_ a: Movie, _ b: Movie) -> Bool { 17 | return a.kind == b.kind 18 | } 19 | } 20 | 21 | class MovieCell: UICollectionViewCell { 22 | let nameLabel: UILabel = .init() 23 | } 24 | 25 | class LoadingCell: UICollectionViewCell {} 26 | class AdCell: UICollectionViewCell {} 27 | 28 | 29 | struct Blog: DiffAware { 30 | let diffId = UUID() 31 | let name: String 32 | 33 | static func compareContent(_ a: Blog, _ b: Blog) -> Bool { 34 | return a.name == b.name 35 | } 36 | } 37 | 38 | class BlogCell: UICollectionViewCell { 39 | let nameLabel: UILabel = .init() 40 | } 41 | 42 | final class MicroTests: XCTestCase { 43 | var layout: UICollectionViewFlowLayout! 44 | var collectionView: UICollectionView! 45 | var dataSource: DataSource! 46 | 47 | func beforeEachTest() { 48 | layout = UICollectionViewFlowLayout() 49 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 50 | dataSource = DataSource(collectionView: collectionView) 51 | 52 | collectionView.dataSource = dataSource 53 | collectionView.delegate = dataSource 54 | } 55 | 56 | func testDrive() { 57 | beforeEachTest() 58 | 59 | let movies: [Movie] = [ 60 | Movie(kind: .show("Titanic")), 61 | Movie(kind: .show("Batman")), 62 | Movie(kind: .loading), 63 | Movie(kind: .ad) 64 | ] 65 | 66 | dataSource.state = forEach(movies) { movie in 67 | switch movie.kind { 68 | case .show(let name): 69 | return Cell() { context, cell in 70 | cell.nameLabel.text = name 71 | } 72 | .onSelect { _ in 73 | 74 | } 75 | .onDeselect { _ in 76 | 77 | } 78 | .onWillDisplay { _, _ in 79 | 80 | } 81 | .onDidEndDisplay { _, _ in 82 | 83 | } 84 | .onSize { context in 85 | CGSize(width: context.collectionView.frame.size.width, height: 40) 86 | } 87 | case .loading: 88 | return Cell() { _, _ in } 89 | case .ad: 90 | return Cell() { _, _ in } 91 | } 92 | } 93 | } 94 | 95 | func testFunctionBuilder() { 96 | beforeEachTest() 97 | 98 | let blogs: [Blog] = [ 99 | Blog(name: "iOS"), 100 | Blog(name: "Android"), 101 | ] 102 | 103 | dataSource.state = State { 104 | ForEach(blogs) { blog in 105 | Cell() { context, cell in 106 | cell.nameLabel.text = blog.name 107 | } 108 | } 109 | } 110 | } 111 | 112 | func testSubclass() { 113 | class CustomDataSource: DataSource { 114 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 115 | let blog = state.models[indexPath.item] as? Blog 116 | print(blog) 117 | } 118 | } 119 | } 120 | 121 | static var allTests = [ 122 | ("testDrive", testDrive), 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /Tests/MicroTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(MicroTests.allTests), 7 | ] 8 | } 9 | #endif 10 | --------------------------------------------------------------------------------