├── .gitignore ├── CHANGELOG.md ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── DiffyTables WatchKit App │ ├── Base.lproj │ │ └── Interface.storyboard │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── checkbox-completed.imageset │ │ │ ├── Contents.json │ │ │ └── check-tick.png │ │ └── checkbox.imageset │ │ │ ├── Contents.json │ │ │ └── check.png │ └── Info.plist ├── DiffyTables WatchKit Extension │ ├── Info.plist │ ├── ShoppingItem.swift │ ├── ShoppingListApp.swift │ └── Utilities.swift └── DiffyTables │ ├── AppDelegate.swift │ ├── Info.plist │ └── Main.storyboard ├── DiffyTables.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── DiffyTables.xcscheme ├── LICENSE ├── README.md ├── Source ├── Diffing.swift ├── DiffyTables.swift ├── Supporting Files │ ├── DiffyTables.h │ └── Info.plist └── WatchKitUpdatable.swift └── Tests ├── DiffyTables_Tests.swift └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *~.nib 4 | 5 | /build/ 6 | .project 7 | *.mode1 8 | *.mode1v3 9 | *.mode2v3 10 | *.perspective 11 | *.perspectivev3 12 | *.pbxuser 13 | xcuserdata/ 14 | *.xcuserstate 15 | *.xccheckout 16 | /Build/ 17 | /DerivedData/ 18 | 19 | Carthage/Build 20 | *.dSYM 21 | *.dSYM.zip 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.0.0 (2016-09-19) 2 | 3 | - Swift 3 release (no functional change) 4 | 5 | * * * 6 | 7 | ### 1.0.1 (2016-08-03) 8 | 9 | - Add support for Xcode 8 (Swift 2.3) for Carthage users 10 | 11 | ### 1.0.0 (2016-07-24) 12 | 13 | - First tagged release 14 | - Swift 2.2 & 2.3 compatible 15 | - Carthage support -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6E9E5A771B1A32D200C902ED /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5A761B1A32D200C902ED /* AppDelegate.swift */; }; 11 | 6E9E5A791B1A32EC00C902ED /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9E5A781B1A32EC00C902ED /* Main.storyboard */; }; 12 | 6E9E5A931B1A335000C902ED /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E9E5A921B1A335000C902ED /* Images.xcassets */; }; 13 | 6E9E5AA11B1A33BC00C902ED /* ShoppingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5A9E1B1A33BC00C902ED /* ShoppingItem.swift */; }; 14 | 6E9E5AA21B1A33BC00C902ED /* ShoppingListApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5A9F1B1A33BC00C902ED /* ShoppingListApp.swift */; }; 15 | 6E9E5AA31B1A33BC00C902ED /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5AA01B1A33BC00C902ED /* Utilities.swift */; }; 16 | 6E9E5AA51B1A33D200C902ED /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9E5AA41B1A33D200C902ED /* Interface.storyboard */; }; 17 | 6EEB21241D44A21300ECA84E /* Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5AA71B1A342F00C902ED /* Diffing.swift */; }; 18 | 6EEB21251D44A21300ECA84E /* DiffyTables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5AA81B1A342F00C902ED /* DiffyTables.swift */; }; 19 | 6EEB21261D44A21300ECA84E /* WatchKitUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9E5AA91B1A342F00C902ED /* WatchKitUpdatable.swift */; }; 20 | 6EEB21291D44A3EC00ECA84E /* Demo.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6E9E5A7E1B1A335000C902ED /* Demo.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 21 | 6EEB212C1D44A3EC00ECA84E /* Demo.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 6E9E5A881B1A335000C902ED /* Demo.app */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 6EEB21271D44A3EC00ECA84E /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 6E9E5A491B1A329D00C902ED /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 6E9E5A7D1B1A335000C902ED; 30 | remoteInfo = "Demo WatchKit Extension"; 31 | }; 32 | 6EEB212A1D44A3EC00ECA84E /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 6E9E5A491B1A329D00C902ED /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 6E9E5A871B1A335000C902ED; 37 | remoteInfo = "Demo WatchKit App"; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXCopyFilesBuildPhase section */ 42 | 6EEB212D1D44A3EC00ECA84E /* Embed App Extensions */ = { 43 | isa = PBXCopyFilesBuildPhase; 44 | buildActionMask = 2147483647; 45 | dstPath = ""; 46 | dstSubfolderSpec = 13; 47 | files = ( 48 | 6EEB21291D44A3EC00ECA84E /* Demo.appex in Embed App Extensions */, 49 | ); 50 | name = "Embed App Extensions"; 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | 6EEB212E1D44A3EC00ECA84E /* Embed Watch Content */ = { 54 | isa = PBXCopyFilesBuildPhase; 55 | buildActionMask = 2147483647; 56 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; 57 | dstSubfolderSpec = 16; 58 | files = ( 59 | 6EEB212C1D44A3EC00ECA84E /* Demo.app in Embed Watch Content */, 60 | ); 61 | name = "Embed Watch Content"; 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXCopyFilesBuildPhase section */ 65 | 66 | /* Begin PBXFileReference section */ 67 | 6E9E5A511B1A329D00C902ED /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 6E9E5A551B1A329D00C902ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 6E9E5A761B1A32D200C902ED /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 70 | 6E9E5A781B1A32EC00C902ED /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 71 | 6E9E5A7E1B1A335000C902ED /* Demo.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Demo.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 6E9E5A811B1A335000C902ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 6E9E5A881B1A335000C902ED /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | 6E9E5A8E1B1A335000C902ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../DiffyTables WatchKit App/Info.plist"; sourceTree = ""; }; 75 | 6E9E5A921B1A335000C902ED /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = "../DiffyTables WatchKit App/Images.xcassets"; sourceTree = ""; }; 76 | 6E9E5A9E1B1A33BC00C902ED /* ShoppingItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShoppingItem.swift; sourceTree = ""; }; 77 | 6E9E5A9F1B1A33BC00C902ED /* ShoppingListApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShoppingListApp.swift; sourceTree = ""; }; 78 | 6E9E5AA01B1A33BC00C902ED /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; 79 | 6E9E5AA41B1A33D200C902ED /* Interface.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Interface.storyboard; path = "../DiffyTables WatchKit App/Base.lproj/Interface.storyboard"; sourceTree = ""; }; 80 | 6E9E5AA71B1A342F00C902ED /* Diffing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Diffing.swift; path = ../Source/Diffing.swift; sourceTree = ""; }; 81 | 6E9E5AA81B1A342F00C902ED /* DiffyTables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiffyTables.swift; path = ../Source/DiffyTables.swift; sourceTree = ""; }; 82 | 6E9E5AA91B1A342F00C902ED /* WatchKitUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WatchKitUpdatable.swift; path = ../Source/WatchKitUpdatable.swift; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 6E9E5A4E1B1A329D00C902ED /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | 6E9E5A7B1B1A335000C902ED /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | 6E9E5A481B1A329D00C902ED = { 104 | isa = PBXGroup; 105 | children = ( 106 | 6E9E5A7F1B1A335000C902ED /* Demo */, 107 | 6E9E5A531B1A329D00C902ED /* iOS app */, 108 | 6E9E5AA61B1A341E00C902ED /* DiffyTables */, 109 | 6E9E5A521B1A329D00C902ED /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | 6E9E5A521B1A329D00C902ED /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 6E9E5A511B1A329D00C902ED /* Demo.app */, 117 | 6E9E5A7E1B1A335000C902ED /* Demo.appex */, 118 | 6E9E5A881B1A335000C902ED /* Demo.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 6E9E5A531B1A329D00C902ED /* iOS app */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 6E9E5A761B1A32D200C902ED /* AppDelegate.swift */, 127 | 6E9E5A781B1A32EC00C902ED /* Main.storyboard */, 128 | 6E9E5A551B1A329D00C902ED /* Info.plist */, 129 | ); 130 | name = "iOS app"; 131 | path = DiffyTables; 132 | sourceTree = ""; 133 | }; 134 | 6E9E5A7F1B1A335000C902ED /* Demo */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 6E9E5A9E1B1A33BC00C902ED /* ShoppingItem.swift */, 138 | 6E9E5A9F1B1A33BC00C902ED /* ShoppingListApp.swift */, 139 | 6E9E5AA41B1A33D200C902ED /* Interface.storyboard */, 140 | 6E9E5AA01B1A33BC00C902ED /* Utilities.swift */, 141 | 6E9E5A921B1A335000C902ED /* Images.xcassets */, 142 | 6E9E5A811B1A335000C902ED /* Info.plist */, 143 | 6E9E5A8E1B1A335000C902ED /* Info.plist */, 144 | ); 145 | name = Demo; 146 | path = "DiffyTables WatchKit Extension"; 147 | sourceTree = ""; 148 | }; 149 | 6E9E5AA61B1A341E00C902ED /* DiffyTables */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 6E9E5AA71B1A342F00C902ED /* Diffing.swift */, 153 | 6E9E5AA81B1A342F00C902ED /* DiffyTables.swift */, 154 | 6E9E5AA91B1A342F00C902ED /* WatchKitUpdatable.swift */, 155 | ); 156 | name = DiffyTables; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXNativeTarget section */ 162 | 6E9E5A501B1A329D00C902ED /* Demo */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = 6E9E5A701B1A329D00C902ED /* Build configuration list for PBXNativeTarget "Demo" */; 165 | buildPhases = ( 166 | 6E9E5A4D1B1A329D00C902ED /* Sources */, 167 | 6E9E5A4E1B1A329D00C902ED /* Frameworks */, 168 | 6E9E5A4F1B1A329D00C902ED /* Resources */, 169 | 6EEB212E1D44A3EC00ECA84E /* Embed Watch Content */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | 6EEB212B1D44A3EC00ECA84E /* PBXTargetDependency */, 175 | ); 176 | name = Demo; 177 | productName = DiffyTables; 178 | productReference = 6E9E5A511B1A329D00C902ED /* Demo.app */; 179 | productType = "com.apple.product-type.application"; 180 | }; 181 | 6E9E5A7D1B1A335000C902ED /* Demo WatchKit Extension */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 6E9E5A9A1B1A335000C902ED /* Build configuration list for PBXNativeTarget "Demo WatchKit Extension" */; 184 | buildPhases = ( 185 | 6E9E5A7A1B1A335000C902ED /* Sources */, 186 | 6E9E5A7B1B1A335000C902ED /* Frameworks */, 187 | 6E9E5A7C1B1A335000C902ED /* Resources */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = "Demo WatchKit Extension"; 194 | productName = "DiffyTables WatchKit Extension"; 195 | productReference = 6E9E5A7E1B1A335000C902ED /* Demo.appex */; 196 | productType = "com.apple.product-type.watchkit2-extension"; 197 | }; 198 | 6E9E5A871B1A335000C902ED /* Demo WatchKit App */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = 6E9E5A971B1A335000C902ED /* Build configuration list for PBXNativeTarget "Demo WatchKit App" */; 201 | buildPhases = ( 202 | 6E9E5A861B1A335000C902ED /* Resources */, 203 | 6EEB212D1D44A3EC00ECA84E /* Embed App Extensions */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | 6EEB21281D44A3EC00ECA84E /* PBXTargetDependency */, 209 | ); 210 | name = "Demo WatchKit App"; 211 | productName = "DiffyTables WatchKit App"; 212 | productReference = 6E9E5A881B1A335000C902ED /* Demo.app */; 213 | productType = "com.apple.product-type.application.watchapp2"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | 6E9E5A491B1A329D00C902ED /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | LastSwiftUpdateCheck = 0720; 222 | LastUpgradeCheck = 0800; 223 | ORGANIZATIONNAME = "Radosław Pietruszewski"; 224 | TargetAttributes = { 225 | 6E9E5A501B1A329D00C902ED = { 226 | CreatedOnToolsVersion = 6.3.1; 227 | LastSwiftMigration = 0800; 228 | }; 229 | 6E9E5A7D1B1A335000C902ED = { 230 | CreatedOnToolsVersion = 6.3.1; 231 | LastSwiftMigration = 0800; 232 | }; 233 | 6E9E5A871B1A335000C902ED = { 234 | CreatedOnToolsVersion = 6.3.1; 235 | LastSwiftMigration = 0800; 236 | }; 237 | }; 238 | }; 239 | buildConfigurationList = 6E9E5A4C1B1A329D00C902ED /* Build configuration list for PBXProject "Demo" */; 240 | compatibilityVersion = "Xcode 3.2"; 241 | developmentRegion = English; 242 | hasScannedForEncodings = 0; 243 | knownRegions = ( 244 | en, 245 | Base, 246 | ); 247 | mainGroup = 6E9E5A481B1A329D00C902ED; 248 | productRefGroup = 6E9E5A521B1A329D00C902ED /* Products */; 249 | projectDirPath = ""; 250 | projectRoot = ""; 251 | targets = ( 252 | 6E9E5A501B1A329D00C902ED /* Demo */, 253 | 6E9E5A7D1B1A335000C902ED /* Demo WatchKit Extension */, 254 | 6E9E5A871B1A335000C902ED /* Demo WatchKit App */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | 6E9E5A4F1B1A329D00C902ED /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | 6E9E5A791B1A32EC00C902ED /* Main.storyboard in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | 6E9E5A7C1B1A335000C902ED /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | 6E9E5A861B1A335000C902ED /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 6E9E5AA51B1A33D200C902ED /* Interface.storyboard in Resources */, 280 | 6E9E5A931B1A335000C902ED /* Images.xcassets in Resources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXResourcesBuildPhase section */ 285 | 286 | /* Begin PBXSourcesBuildPhase section */ 287 | 6E9E5A4D1B1A329D00C902ED /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | 6E9E5A771B1A32D200C902ED /* AppDelegate.swift in Sources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | 6E9E5A7A1B1A335000C902ED /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | 6E9E5AA11B1A33BC00C902ED /* ShoppingItem.swift in Sources */, 300 | 6E9E5AA31B1A33BC00C902ED /* Utilities.swift in Sources */, 301 | 6EEB21251D44A21300ECA84E /* DiffyTables.swift in Sources */, 302 | 6E9E5AA21B1A33BC00C902ED /* ShoppingListApp.swift in Sources */, 303 | 6EEB21241D44A21300ECA84E /* Diffing.swift in Sources */, 304 | 6EEB21261D44A21300ECA84E /* WatchKitUpdatable.swift in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXTargetDependency section */ 311 | 6EEB21281D44A3EC00ECA84E /* PBXTargetDependency */ = { 312 | isa = PBXTargetDependency; 313 | target = 6E9E5A7D1B1A335000C902ED /* Demo WatchKit Extension */; 314 | targetProxy = 6EEB21271D44A3EC00ECA84E /* PBXContainerItemProxy */; 315 | }; 316 | 6EEB212B1D44A3EC00ECA84E /* PBXTargetDependency */ = { 317 | isa = PBXTargetDependency; 318 | target = 6E9E5A871B1A335000C902ED /* Demo WatchKit App */; 319 | targetProxy = 6EEB212A1D44A3EC00ECA84E /* PBXContainerItemProxy */; 320 | }; 321 | /* End PBXTargetDependency section */ 322 | 323 | /* Begin XCBuildConfiguration section */ 324 | 6E9E5A6E1B1A329D00C902ED /* Debug */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | ENABLE_TESTABILITY = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu99; 347 | GCC_DYNAMIC_NO_PIC = NO; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 362 | MTL_ENABLE_DEBUG_INFO = YES; 363 | ONLY_ACTIVE_ARCH = YES; 364 | SDKROOT = iphoneos; 365 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 366 | SWIFT_VERSION = 2.3; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Debug; 370 | }; 371 | 6E9E5A6F1B1A329D00C902ED /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ALWAYS_SEARCH_USER_PATHS = NO; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 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 = gnu99; 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 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SWIFT_VERSION = 2.3; 405 | TARGETED_DEVICE_FAMILY = "1,2"; 406 | VALIDATE_PRODUCT = YES; 407 | }; 408 | name = Release; 409 | }; 410 | 6E9E5A711B1A329D00C902ED /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 414 | CLANG_ENABLE_MODULES = YES; 415 | INFOPLIST_FILE = DiffyTables/Info.plist; 416 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 417 | PRODUCT_BUNDLE_IDENTIFIER = "io.radex.$(PRODUCT_NAME:rfc1034identifier)"; 418 | PRODUCT_NAME = Demo; 419 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 420 | SWIFT_VERSION = 3.0; 421 | }; 422 | name = Debug; 423 | }; 424 | 6E9E5A721B1A329D00C902ED /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 428 | CLANG_ENABLE_MODULES = YES; 429 | INFOPLIST_FILE = DiffyTables/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = "io.radex.$(PRODUCT_NAME:rfc1034identifier)"; 432 | PRODUCT_NAME = Demo; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | SWIFT_VERSION = 3.0; 435 | }; 436 | name = Release; 437 | }; 438 | 6E9E5A981B1A335000C902ED /* Debug */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 443 | GCC_PREPROCESSOR_DEFINITIONS = ( 444 | "DEBUG=1", 445 | "$(inherited)", 446 | ); 447 | IBSC_MODULE = DiffyTables_WatchKit_Extension; 448 | INFOPLIST_FILE = "DiffyTables WatchKit App/Info.plist"; 449 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.Demo.watchkitapp; 450 | PRODUCT_NAME = Demo; 451 | SDKROOT = watchos; 452 | SKIP_INSTALL = YES; 453 | SWIFT_VERSION = 3.0; 454 | TARGETED_DEVICE_FAMILY = 4; 455 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 456 | }; 457 | name = Debug; 458 | }; 459 | 6E9E5A991B1A335000C902ED /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 463 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 464 | IBSC_MODULE = DiffyTables_WatchKit_Extension; 465 | INFOPLIST_FILE = "DiffyTables WatchKit App/Info.plist"; 466 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.Demo.watchkitapp; 467 | PRODUCT_NAME = Demo; 468 | SDKROOT = watchos; 469 | SKIP_INSTALL = YES; 470 | SWIFT_VERSION = 3.0; 471 | TARGETED_DEVICE_FAMILY = 4; 472 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 473 | }; 474 | name = Release; 475 | }; 476 | 6E9E5A9B1B1A335000C902ED /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | GCC_PREPROCESSOR_DEFINITIONS = ( 480 | "DEBUG=1", 481 | "$(inherited)", 482 | ); 483 | INFOPLIST_FILE = "DiffyTables WatchKit Extension/Info.plist"; 484 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 485 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.Demo.watchkitapp.watchkitextension; 486 | PRODUCT_NAME = Demo; 487 | SDKROOT = watchos; 488 | SKIP_INSTALL = YES; 489 | SWIFT_VERSION = 3.0; 490 | TARGETED_DEVICE_FAMILY = 4; 491 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 492 | }; 493 | name = Debug; 494 | }; 495 | 6E9E5A9C1B1A335000C902ED /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | INFOPLIST_FILE = "DiffyTables WatchKit Extension/Info.plist"; 499 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 500 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.Demo.watchkitapp.watchkitextension; 501 | PRODUCT_NAME = Demo; 502 | SDKROOT = watchos; 503 | SKIP_INSTALL = YES; 504 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 505 | SWIFT_VERSION = 3.0; 506 | TARGETED_DEVICE_FAMILY = 4; 507 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 508 | }; 509 | name = Release; 510 | }; 511 | /* End XCBuildConfiguration section */ 512 | 513 | /* Begin XCConfigurationList section */ 514 | 6E9E5A4C1B1A329D00C902ED /* Build configuration list for PBXProject "Demo" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 6E9E5A6E1B1A329D00C902ED /* Debug */, 518 | 6E9E5A6F1B1A329D00C902ED /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 6E9E5A701B1A329D00C902ED /* Build configuration list for PBXNativeTarget "Demo" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 6E9E5A711B1A329D00C902ED /* Debug */, 527 | 6E9E5A721B1A329D00C902ED /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | 6E9E5A971B1A335000C902ED /* Build configuration list for PBXNativeTarget "Demo WatchKit App" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 6E9E5A981B1A335000C902ED /* Debug */, 536 | 6E9E5A991B1A335000C902ED /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | 6E9E5A9A1B1A335000C902ED /* Build configuration list for PBXNativeTarget "Demo WatchKit Extension" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 6E9E5A9B1B1A335000C902ED /* Debug */, 545 | 6E9E5A9C1B1A335000C902ED /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | /* End XCConfigurationList section */ 551 | }; 552 | rootObject = 6E9E5A491B1A329D00C902ED /* Project object */; 553 | } 554 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 54 |
55 | 56 | 57 | 58 | 59 |
60 |
61 | 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "24x24", 5 | "idiom" : "watch", 6 | "scale" : "2x", 7 | "role" : "notificationCenter", 8 | "subtype" : "38mm" 9 | }, 10 | { 11 | "size" : "27.5x27.5", 12 | "idiom" : "watch", 13 | "scale" : "2x", 14 | "role" : "notificationCenter", 15 | "subtype" : "42mm" 16 | }, 17 | { 18 | "size" : "29x29", 19 | "idiom" : "watch", 20 | "role" : "companionSettings", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "size" : "29x29", 25 | "idiom" : "watch", 26 | "role" : "companionSettings", 27 | "scale" : "3x" 28 | }, 29 | { 30 | "size" : "40x40", 31 | "idiom" : "watch", 32 | "scale" : "2x", 33 | "role" : "appLauncher", 34 | "subtype" : "38mm" 35 | }, 36 | { 37 | "size" : "44x44", 38 | "idiom" : "watch", 39 | "scale" : "2x", 40 | "role" : "longLook", 41 | "subtype" : "42mm" 42 | }, 43 | { 44 | "size" : "86x86", 45 | "idiom" : "watch", 46 | "scale" : "2x", 47 | "role" : "quickLook", 48 | "subtype" : "38mm" 49 | }, 50 | { 51 | "size" : "98x98", 52 | "idiom" : "watch", 53 | "scale" : "2x", 54 | "role" : "quickLook", 55 | "subtype" : "42mm" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Images.xcassets/checkbox-completed.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "filename" : "check-tick.png" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "screenWidth" : "{130,145}", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "screenWidth" : "{146,165}", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Images.xcassets/checkbox-completed.imageset/check-tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radex/DiffyTables/97f315b7562cb1b729b503395782c99c8fd89352/Demo/DiffyTables WatchKit App/Images.xcassets/checkbox-completed.imageset/check-tick.png -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Images.xcassets/checkbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "filename" : "check.png" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "screenWidth" : "{130,145}", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "screenWidth" : "{146,165}", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Images.xcassets/checkbox.imageset/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radex/DiffyTables/97f315b7562cb1b729b503395782c99c8fd89352/Demo/DiffyTables WatchKit App/Images.xcassets/checkbox.imageset/check.png -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | DiffyTables 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | UISupportedInterfaceOrientations 26 | 27 | UIInterfaceOrientationPortrait 28 | UIInterfaceOrientationPortraitUpsideDown 29 | 30 | WKCompanionAppBundleIdentifier 31 | io.radex.Demo 32 | WKWatchKitApp 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | DiffyTables WatchKit Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | NSExtension 26 | 27 | NSExtensionAttributes 28 | 29 | WKAppBundleIdentifier 30 | io.radex.Demo.watchkitapp 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.watchkit 34 | 35 | RemoteInterfacePrincipalClass 36 | $(PRODUCT_MODULE_NAME).InterfaceController 37 | 38 | 39 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit Extension/ShoppingItem.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // 4 | // Data model & related utilities 5 | // 6 | 7 | struct ShoppingItem { 8 | let id: String 9 | var name: String 10 | var completed: Bool 11 | } 12 | 13 | extension ShoppingItem: CustomStringConvertible { 14 | var description: String { 15 | let completion = completed ? "Done: " : "" 16 | return "ShoppingItem(\(completion)\(name))" 17 | } 18 | } 19 | 20 | func sampleShoppingList() -> [ShoppingItem] { 21 | return [ 22 | ShoppingItem(id: "1", name: "Milk", completed: false), 23 | ShoppingItem(id: "2", name: "Bread", completed: false), 24 | ShoppingItem(id: "3", name: "Chocolate", completed: false), 25 | ShoppingItem(id: "4", name: "Ice cream", completed: true), 26 | ShoppingItem(id: "5", name: "Cookies", completed: false), 27 | ] 28 | } 29 | 30 | func shoppingListVariation(_ list: [ShoppingItem]) -> [ShoppingItem] { 31 | var list = list 32 | let rand = { random(list.count) } 33 | 34 | // reorder a random element 35 | var i = rand() 36 | var el = list[i] 37 | list.remove(at: i) 38 | list.insert(el, at: rand()) 39 | 40 | // toggle `completed` for a random element 41 | i = rand() 42 | el = list[i] 43 | el.completed = !el.completed 44 | list[i] = el 45 | 46 | // add a variation to random element's name 47 | list[i].name += "!" 48 | 49 | return list 50 | } 51 | 52 | func sampleShoppingItem() -> ShoppingItem { 53 | let names = ["Veggies", "Apples", "Mayo", "Ketchup", "Cheese", "Coffee"] 54 | 55 | return ShoppingItem( 56 | id: "\(random(10000))", 57 | name: names[random(names.count)], 58 | completed: random(3) == 0) 59 | } 60 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit Extension/ShoppingListApp.swift: -------------------------------------------------------------------------------- 1 | import WatchKit 2 | 3 | // 4 | // Our main interface controller 5 | // 6 | 7 | class ShoppingListApp: WKInterfaceController { 8 | @IBOutlet weak var table: WKInterfaceTable! 9 | var items = sampleShoppingList() 10 | 11 | // MARK: Initialization 12 | 13 | override func awake(withContext context: Any?) { 14 | updateView() 15 | } 16 | 17 | var firstActivation = true 18 | 19 | override func willActivate() { 20 | if firstActivation { 21 | DispatchQueue.main.async { 22 | self.loadMore() 23 | } 24 | } 25 | 26 | firstActivation = false 27 | } 28 | 29 | // MARK: Data manipulation 30 | 31 | @IBAction func add() { 32 | items.insert(sampleShoppingItem(), at: random(items.count)) 33 | rowLimit += 1 34 | updateView() 35 | } 36 | 37 | @IBAction func changeUp() { 38 | items = shoppingListVariation(items) 39 | updateView() 40 | } 41 | 42 | // MARK: Rendering 43 | 44 | var displayedRows: [ShoppingItemRowModel] = [] 45 | 46 | func updateView() { 47 | let newRows = items.limit(rowLimit).map { ShoppingItemRowModel($0) } 48 | table.updateViewModels(from: displayedRows, to: newRows) 49 | displayedRows = newRows 50 | 51 | updateLoadMoreButton() 52 | } 53 | 54 | // MARK: Lazy loading 55 | 56 | @IBOutlet weak var _loadMoreButton: WKInterfaceButton! 57 | lazy var loadMoreButton: WKUpdatableButton = WKUpdatableButton(self._loadMoreButton, defaultHidden: false) 58 | 59 | // This is a tiny number for demonstration only. You'd probably want the initial row limit 60 | // to be ~4 (enough to fit one screen), and in loadMore() — double that number. 61 | var rowLimit = 1 62 | 63 | @IBAction func loadMore() { 64 | rowLimit += 1 65 | updateView() 66 | } 67 | 68 | func updateLoadMoreButton() { 69 | let moreToLoad = items.count > rowLimit 70 | loadMoreButton.updateHidden(!moreToLoad) 71 | } 72 | } 73 | 74 | // 75 | // The view model that describes table row 76 | // 77 | 78 | struct ShoppingItemRowModel: TableRowModel { 79 | typealias RowController = ShoppingItemRow 80 | static let tableRowType = "item" 81 | 82 | let objectId: String 83 | var name: String 84 | var completed: Bool 85 | 86 | init(_ item: ShoppingItem) { 87 | objectId = item.id 88 | name = item.name 89 | completed = item.completed 90 | } 91 | } 92 | 93 | // 94 | // The table row controller 95 | // 96 | 97 | class ShoppingItemRow: NSObject, UpdatableRowController { 98 | @IBOutlet weak var checkbox: WKInterfaceImage! 99 | @IBOutlet weak var name: WKInterfaceLabel! 100 | 101 | func update(from old: ShoppingItemRowModel?, to new: ShoppingItemRowModel) { 102 | checkbox.updateImageName(from: (old?.completed).map(checkboxImage) ?? "checkbox", 103 | to: checkboxImage(new.completed)) 104 | name.updateText(from: old?.name, to: new.name) 105 | } 106 | 107 | func checkboxImage(_ completed: Bool) -> String { 108 | return completed ? "checkbox-completed" : "checkbox" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Demo/DiffyTables WatchKit Extension/Utilities.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func random(_ n: Int) -> Int { 4 | return Int(arc4random_uniform(UInt32(n))) 5 | } 6 | 7 | extension Array { 8 | /// Returns first `n` elements of the array 9 | /// (Less if the array doesn't have that many) 10 | 11 | func limit(_ n: Int) -> [Element] { 12 | precondition(n >= 0) 13 | if self.count <= n { 14 | return self 15 | } else { 16 | return Array(self[0.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo/DiffyTables/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /DiffyTables.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B62C15F81D43BBD1003DCEE7 /* Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67481791D43AF27003F6357 /* Diffing.swift */; }; 11 | B67481741D43AF0D003F6357 /* DiffyTables.h in Headers */ = {isa = PBXBuildFile; fileRef = B67481731D43AF0D003F6357 /* DiffyTables.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | B674817C1D43AF27003F6357 /* Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67481791D43AF27003F6357 /* Diffing.swift */; }; 13 | B674817D1D43AF27003F6357 /* DiffyTables.swift in Sources */ = {isa = PBXBuildFile; fileRef = B674817A1D43AF27003F6357 /* DiffyTables.swift */; }; 14 | B674817E1D43AF27003F6357 /* WatchKitUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B674817B1D43AF27003F6357 /* WatchKitUpdatable.swift */; }; 15 | B6A153D01D43B5B100CAC8CC /* DiffyTables_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A153CF1D43B5B100CAC8CC /* DiffyTables_Tests.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | B611AED11D43B8E400ADFC54 /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = B684C4621D43B75C00DF341D /* Demo.xcodeproj */; 22 | proxyType = 2; 23 | remoteGlobalIDString = 6E9E5A511B1A329D00C902ED; 24 | remoteInfo = Demo; 25 | }; 26 | B611AED31D43B8E400ADFC54 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = B684C4621D43B75C00DF341D /* Demo.xcodeproj */; 29 | proxyType = 2; 30 | remoteGlobalIDString = 6E9E5A7E1B1A335000C902ED; 31 | remoteInfo = "Demo WatchKit Extension"; 32 | }; 33 | B611AED51D43B8E400ADFC54 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = B684C4621D43B75C00DF341D /* Demo.xcodeproj */; 36 | proxyType = 2; 37 | remoteGlobalIDString = 6E9E5A881B1A335000C902ED; 38 | remoteInfo = "Demo WatchKit App"; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | B67481701D43AF0D003F6357 /* DiffyTables.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DiffyTables.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | B67481731D43AF0D003F6357 /* DiffyTables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DiffyTables.h; sourceTree = ""; }; 45 | B67481751D43AF0D003F6357 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | B67481791D43AF27003F6357 /* Diffing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diffing.swift; sourceTree = ""; }; 47 | B674817A1D43AF27003F6357 /* DiffyTables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffyTables.swift; sourceTree = ""; }; 48 | B674817B1D43AF27003F6357 /* WatchKitUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchKitUpdatable.swift; sourceTree = ""; }; 49 | B684C4621D43B75C00DF341D /* Demo.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Demo.xcodeproj; path = Demo/Demo.xcodeproj; sourceTree = ""; }; 50 | B6A153CD1D43B5B100CAC8CC /* DiffyTables Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DiffyTables Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | B6A153CF1D43B5B100CAC8CC /* DiffyTables_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffyTables_Tests.swift; sourceTree = ""; }; 52 | B6A153D11D43B5B100CAC8CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | B674816C1D43AF0D003F6357 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | B6A153CA1D43B5B100CAC8CC /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | B611AECC1D43B8E400ADFC54 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | B611AED21D43B8E400ADFC54 /* Demo.app */, 77 | B611AED41D43B8E400ADFC54 /* Demo.appex */, 78 | B611AED61D43B8E400ADFC54 /* Demo.app */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | B67481641D43AEA4003F6357 = { 84 | isa = PBXGroup; 85 | children = ( 86 | B684C4621D43B75C00DF341D /* Demo.xcodeproj */, 87 | B67481721D43AF0D003F6357 /* Source */, 88 | B67481711D43AF0D003F6357 /* Products */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | B67481711D43AF0D003F6357 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | B67481701D43AF0D003F6357 /* DiffyTables.framework */, 96 | B6A153CD1D43B5B100CAC8CC /* DiffyTables Tests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | B67481721D43AF0D003F6357 /* Source */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | B67481791D43AF27003F6357 /* Diffing.swift */, 105 | B674817A1D43AF27003F6357 /* DiffyTables.swift */, 106 | B674817B1D43AF27003F6357 /* WatchKitUpdatable.swift */, 107 | B6A153CE1D43B5B100CAC8CC /* Tests */, 108 | B6A153D61D43B6DF00CAC8CC /* Supporting Files */, 109 | ); 110 | path = Source; 111 | sourceTree = ""; 112 | }; 113 | B6A153CE1D43B5B100CAC8CC /* Tests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | B6A153CF1D43B5B100CAC8CC /* DiffyTables_Tests.swift */, 117 | B6A153D11D43B5B100CAC8CC /* Info.plist */, 118 | ); 119 | path = Tests; 120 | sourceTree = SOURCE_ROOT; 121 | }; 122 | B6A153D61D43B6DF00CAC8CC /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | B67481751D43AF0D003F6357 /* Info.plist */, 126 | B67481731D43AF0D003F6357 /* DiffyTables.h */, 127 | ); 128 | path = "Supporting Files"; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXHeadersBuildPhase section */ 134 | B674816D1D43AF0D003F6357 /* Headers */ = { 135 | isa = PBXHeadersBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | B67481741D43AF0D003F6357 /* DiffyTables.h in Headers */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXHeadersBuildPhase section */ 143 | 144 | /* Begin PBXNativeTarget section */ 145 | B674816F1D43AF0D003F6357 /* DiffyTables */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = B67481761D43AF0D003F6357 /* Build configuration list for PBXNativeTarget "DiffyTables" */; 148 | buildPhases = ( 149 | B674816B1D43AF0D003F6357 /* Sources */, 150 | B674816C1D43AF0D003F6357 /* Frameworks */, 151 | B674816D1D43AF0D003F6357 /* Headers */, 152 | B674816E1D43AF0D003F6357 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = DiffyTables; 159 | productName = DiffyTables; 160 | productReference = B67481701D43AF0D003F6357 /* DiffyTables.framework */; 161 | productType = "com.apple.product-type.framework"; 162 | }; 163 | B6A153CC1D43B5B100CAC8CC /* DiffyTables Tests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = B6A153D21D43B5B100CAC8CC /* Build configuration list for PBXNativeTarget "DiffyTables Tests" */; 166 | buildPhases = ( 167 | B6A153C91D43B5B100CAC8CC /* Sources */, 168 | B6A153CA1D43B5B100CAC8CC /* Frameworks */, 169 | B6A153CB1D43B5B100CAC8CC /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = "DiffyTables Tests"; 176 | productName = "DiffyTables Tests"; 177 | productReference = B6A153CD1D43B5B100CAC8CC /* DiffyTables Tests.xctest */; 178 | productType = "com.apple.product-type.bundle.unit-test"; 179 | }; 180 | /* End PBXNativeTarget section */ 181 | 182 | /* Begin PBXProject section */ 183 | B67481651D43AEA4003F6357 /* Project object */ = { 184 | isa = PBXProject; 185 | attributes = { 186 | LastSwiftUpdateCheck = 0730; 187 | LastUpgradeCheck = 0800; 188 | ORGANIZATIONNAME = "Radosław Pietruszewski"; 189 | TargetAttributes = { 190 | B674816F1D43AF0D003F6357 = { 191 | CreatedOnToolsVersion = 7.3.1; 192 | }; 193 | B6A153CC1D43B5B100CAC8CC = { 194 | CreatedOnToolsVersion = 7.3.1; 195 | LastSwiftMigration = 0800; 196 | }; 197 | }; 198 | }; 199 | buildConfigurationList = B67481681D43AEA4003F6357 /* Build configuration list for PBXProject "DiffyTables" */; 200 | compatibilityVersion = "Xcode 3.2"; 201 | developmentRegion = English; 202 | hasScannedForEncodings = 0; 203 | knownRegions = ( 204 | en, 205 | ); 206 | mainGroup = B67481641D43AEA4003F6357; 207 | productRefGroup = B67481711D43AF0D003F6357 /* Products */; 208 | projectDirPath = ""; 209 | projectReferences = ( 210 | { 211 | ProductGroup = B611AECC1D43B8E400ADFC54 /* Products */; 212 | ProjectRef = B684C4621D43B75C00DF341D /* Demo.xcodeproj */; 213 | }, 214 | ); 215 | projectRoot = ""; 216 | targets = ( 217 | B674816F1D43AF0D003F6357 /* DiffyTables */, 218 | B6A153CC1D43B5B100CAC8CC /* DiffyTables Tests */, 219 | ); 220 | }; 221 | /* End PBXProject section */ 222 | 223 | /* Begin PBXReferenceProxy section */ 224 | B611AED21D43B8E400ADFC54 /* Demo.app */ = { 225 | isa = PBXReferenceProxy; 226 | fileType = wrapper.application; 227 | path = Demo.app; 228 | remoteRef = B611AED11D43B8E400ADFC54 /* PBXContainerItemProxy */; 229 | sourceTree = BUILT_PRODUCTS_DIR; 230 | }; 231 | B611AED41D43B8E400ADFC54 /* Demo.appex */ = { 232 | isa = PBXReferenceProxy; 233 | fileType = "wrapper.app-extension"; 234 | path = Demo.appex; 235 | remoteRef = B611AED31D43B8E400ADFC54 /* PBXContainerItemProxy */; 236 | sourceTree = BUILT_PRODUCTS_DIR; 237 | }; 238 | B611AED61D43B8E400ADFC54 /* Demo.app */ = { 239 | isa = PBXReferenceProxy; 240 | fileType = wrapper.application; 241 | path = Demo.app; 242 | remoteRef = B611AED51D43B8E400ADFC54 /* PBXContainerItemProxy */; 243 | sourceTree = BUILT_PRODUCTS_DIR; 244 | }; 245 | /* End PBXReferenceProxy section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | B674816E1D43AF0D003F6357 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | B6A153CB1D43B5B100CAC8CC /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXSourcesBuildPhase section */ 265 | B674816B1D43AF0D003F6357 /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | B674817C1D43AF27003F6357 /* Diffing.swift in Sources */, 270 | B674817D1D43AF27003F6357 /* DiffyTables.swift in Sources */, 271 | B674817E1D43AF27003F6357 /* WatchKitUpdatable.swift in Sources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | B6A153C91D43B5B100CAC8CC /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | B6A153D01D43B5B100CAC8CC /* DiffyTables_Tests.swift in Sources */, 280 | B62C15F81D43BBD1003DCEE7 /* Diffing.swift in Sources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXSourcesBuildPhase section */ 285 | 286 | /* Begin XCBuildConfiguration section */ 287 | B67481691D43AEA4003F6357 /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_EMPTY_BODY = YES; 293 | CLANG_WARN_ENUM_CONVERSION = YES; 294 | CLANG_WARN_INT_CONVERSION = YES; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_TESTABILITY = YES; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | ONLY_ACTIVE_ARCH = YES; 307 | SWIFT_VERSION = 2.3; 308 | }; 309 | name = Debug; 310 | }; 311 | B674816A1D43AEA4003F6357 /* Release */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | CLANG_WARN_BOOL_CONVERSION = YES; 315 | CLANG_WARN_CONSTANT_CONVERSION = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_UNREACHABLE_CODE = YES; 320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 321 | ENABLE_STRICT_OBJC_MSGSEND = YES; 322 | GCC_NO_COMMON_BLOCKS = YES; 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | SWIFT_VERSION = 2.3; 330 | }; 331 | name = Release; 332 | }; 333 | B67481771D43AF0D003F6357 /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | APPLICATION_EXTENSION_API_ONLY = YES; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BOOL_CONVERSION = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | CODE_SIGN_IDENTITY = ""; 353 | COPY_PHASE_STRIP = NO; 354 | CURRENT_PROJECT_VERSION = 1; 355 | DEBUG_INFORMATION_FORMAT = dwarf; 356 | DEFINES_MODULE = YES; 357 | DYLIB_COMPATIBILITY_VERSION = 1; 358 | DYLIB_CURRENT_VERSION = 1; 359 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 360 | ENABLE_STRICT_OBJC_MSGSEND = YES; 361 | ENABLE_TESTABILITY = YES; 362 | GCC_C_LANGUAGE_STANDARD = gnu99; 363 | GCC_DYNAMIC_NO_PIC = NO; 364 | GCC_NO_COMMON_BLOCKS = YES; 365 | GCC_OPTIMIZATION_LEVEL = 0; 366 | GCC_PREPROCESSOR_DEFINITIONS = ( 367 | "DEBUG=1", 368 | "$(inherited)", 369 | ); 370 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 371 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 372 | GCC_WARN_UNDECLARED_SELECTOR = YES; 373 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 374 | GCC_WARN_UNUSED_FUNCTION = YES; 375 | GCC_WARN_UNUSED_VARIABLE = YES; 376 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 377 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 379 | MTL_ENABLE_DEBUG_INFO = YES; 380 | ONLY_ACTIVE_ARCH = YES; 381 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.DiffyTables; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | SDKROOT = watchos; 384 | SKIP_INSTALL = YES; 385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 386 | SWIFT_VERSION = 3.0; 387 | TARGETED_DEVICE_FAMILY = 4; 388 | VERSIONING_SYSTEM = "apple-generic"; 389 | VERSION_INFO_PREFIX = ""; 390 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 391 | }; 392 | name = Debug; 393 | }; 394 | B67481781D43AF0D003F6357 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | APPLICATION_EXTENSION_API_ONLY = YES; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_EMPTY_BODY = YES; 408 | CLANG_WARN_ENUM_CONVERSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN_UNREACHABLE_CODE = YES; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | CODE_SIGN_IDENTITY = ""; 414 | COPY_PHASE_STRIP = NO; 415 | CURRENT_PROJECT_VERSION = 1; 416 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 417 | DEFINES_MODULE = YES; 418 | DYLIB_COMPATIBILITY_VERSION = 1; 419 | DYLIB_CURRENT_VERSION = 1; 420 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 421 | ENABLE_NS_ASSERTIONS = NO; 422 | ENABLE_STRICT_OBJC_MSGSEND = YES; 423 | GCC_C_LANGUAGE_STANDARD = gnu99; 424 | GCC_NO_COMMON_BLOCKS = YES; 425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 427 | GCC_WARN_UNDECLARED_SELECTOR = YES; 428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 429 | GCC_WARN_UNUSED_FUNCTION = YES; 430 | GCC_WARN_UNUSED_VARIABLE = YES; 431 | INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; 432 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 434 | MTL_ENABLE_DEBUG_INFO = NO; 435 | PRODUCT_BUNDLE_IDENTIFIER = io.radex.DiffyTables; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SDKROOT = watchos; 438 | SKIP_INSTALL = YES; 439 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 440 | SWIFT_VERSION = 3.0; 441 | TARGETED_DEVICE_FAMILY = 4; 442 | VALIDATE_PRODUCT = YES; 443 | VERSIONING_SYSTEM = "apple-generic"; 444 | VERSION_INFO_PREFIX = ""; 445 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 446 | }; 447 | name = Release; 448 | }; 449 | B6A153D31D43B5B100CAC8CC /* Debug */ = { 450 | isa = XCBuildConfiguration; 451 | buildSettings = { 452 | ALWAYS_SEARCH_USER_PATHS = NO; 453 | CLANG_ANALYZER_NONNULL = YES; 454 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 455 | CLANG_CXX_LIBRARY = "libc++"; 456 | CLANG_ENABLE_MODULES = YES; 457 | CLANG_ENABLE_OBJC_ARC = YES; 458 | CLANG_WARN_BOOL_CONVERSION = YES; 459 | CLANG_WARN_CONSTANT_CONVERSION = YES; 460 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 461 | CLANG_WARN_EMPTY_BODY = YES; 462 | CLANG_WARN_ENUM_CONVERSION = YES; 463 | CLANG_WARN_INT_CONVERSION = YES; 464 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 465 | CLANG_WARN_UNREACHABLE_CODE = YES; 466 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 467 | CODE_SIGN_IDENTITY = "-"; 468 | COMBINE_HIDPI_IMAGES = YES; 469 | COPY_PHASE_STRIP = NO; 470 | DEBUG_INFORMATION_FORMAT = dwarf; 471 | ENABLE_STRICT_OBJC_MSGSEND = YES; 472 | ENABLE_TESTABILITY = YES; 473 | GCC_C_LANGUAGE_STANDARD = gnu99; 474 | GCC_DYNAMIC_NO_PIC = NO; 475 | GCC_NO_COMMON_BLOCKS = YES; 476 | GCC_OPTIMIZATION_LEVEL = 0; 477 | GCC_PREPROCESSOR_DEFINITIONS = ( 478 | "DEBUG=1", 479 | "$(inherited)", 480 | ); 481 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 482 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 483 | GCC_WARN_UNDECLARED_SELECTOR = YES; 484 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 485 | GCC_WARN_UNUSED_FUNCTION = YES; 486 | GCC_WARN_UNUSED_VARIABLE = YES; 487 | INFOPLIST_FILE = Tests/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 489 | MACOSX_DEPLOYMENT_TARGET = 10.11; 490 | MTL_ENABLE_DEBUG_INFO = YES; 491 | ONLY_ACTIVE_ARCH = YES; 492 | PRODUCT_BUNDLE_IDENTIFIER = "io.radex.DiffyTables-Tests"; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SDKROOT = macosx; 495 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 496 | SWIFT_VERSION = 3.0; 497 | }; 498 | name = Debug; 499 | }; 500 | B6A153D41D43B5B100CAC8CC /* Release */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | ALWAYS_SEARCH_USER_PATHS = NO; 504 | CLANG_ANALYZER_NONNULL = YES; 505 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 506 | CLANG_CXX_LIBRARY = "libc++"; 507 | CLANG_ENABLE_MODULES = YES; 508 | CLANG_ENABLE_OBJC_ARC = YES; 509 | CLANG_WARN_BOOL_CONVERSION = YES; 510 | CLANG_WARN_CONSTANT_CONVERSION = YES; 511 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 512 | CLANG_WARN_EMPTY_BODY = YES; 513 | CLANG_WARN_ENUM_CONVERSION = YES; 514 | CLANG_WARN_INT_CONVERSION = YES; 515 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 516 | CLANG_WARN_UNREACHABLE_CODE = YES; 517 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 518 | CODE_SIGN_IDENTITY = "-"; 519 | COMBINE_HIDPI_IMAGES = YES; 520 | COPY_PHASE_STRIP = NO; 521 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 522 | ENABLE_NS_ASSERTIONS = NO; 523 | ENABLE_STRICT_OBJC_MSGSEND = YES; 524 | GCC_C_LANGUAGE_STANDARD = gnu99; 525 | GCC_NO_COMMON_BLOCKS = YES; 526 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 527 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 528 | GCC_WARN_UNDECLARED_SELECTOR = YES; 529 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 530 | GCC_WARN_UNUSED_FUNCTION = YES; 531 | GCC_WARN_UNUSED_VARIABLE = YES; 532 | INFOPLIST_FILE = Tests/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 534 | MACOSX_DEPLOYMENT_TARGET = 10.11; 535 | MTL_ENABLE_DEBUG_INFO = NO; 536 | PRODUCT_BUNDLE_IDENTIFIER = "io.radex.DiffyTables-Tests"; 537 | PRODUCT_NAME = "$(TARGET_NAME)"; 538 | SDKROOT = macosx; 539 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 540 | SWIFT_VERSION = 3.0; 541 | }; 542 | name = Release; 543 | }; 544 | /* End XCBuildConfiguration section */ 545 | 546 | /* Begin XCConfigurationList section */ 547 | B67481681D43AEA4003F6357 /* Build configuration list for PBXProject "DiffyTables" */ = { 548 | isa = XCConfigurationList; 549 | buildConfigurations = ( 550 | B67481691D43AEA4003F6357 /* Debug */, 551 | B674816A1D43AEA4003F6357 /* Release */, 552 | ); 553 | defaultConfigurationIsVisible = 0; 554 | defaultConfigurationName = Release; 555 | }; 556 | B67481761D43AF0D003F6357 /* Build configuration list for PBXNativeTarget "DiffyTables" */ = { 557 | isa = XCConfigurationList; 558 | buildConfigurations = ( 559 | B67481771D43AF0D003F6357 /* Debug */, 560 | B67481781D43AF0D003F6357 /* Release */, 561 | ); 562 | defaultConfigurationIsVisible = 0; 563 | defaultConfigurationName = Release; 564 | }; 565 | B6A153D21D43B5B100CAC8CC /* Build configuration list for PBXNativeTarget "DiffyTables Tests" */ = { 566 | isa = XCConfigurationList; 567 | buildConfigurations = ( 568 | B6A153D31D43B5B100CAC8CC /* Debug */, 569 | B6A153D41D43B5B100CAC8CC /* Release */, 570 | ); 571 | defaultConfigurationIsVisible = 0; 572 | defaultConfigurationName = Release; 573 | }; 574 | /* End XCConfigurationList section */ 575 | }; 576 | rootObject = B67481651D43AEA4003F6357 /* Project object */; 577 | } 578 | -------------------------------------------------------------------------------- /DiffyTables.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DiffyTables.xcodeproj/xcshareddata/xcschemes/DiffyTables.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Radosław Pietruszewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DiffyTables 2 | 3 | DiffyTables is a practical and efficient way to use tables in WatchKit apps. 4 | 5 | Instead of manipulating the table directly from an interface controller, you can now simply generate a representation of the table. DiffyTables then uses a diffing algorithm to compare new table row data with what's currently on screen and intelligently sends only the truly necessary updates to the watch. 6 | 7 | (Yep, kind of like React.) 8 | 9 | ### How do I use this 10 | 11 | 1. Read my article: [Practical and efficient WatchKit tables with view model diffing](http://radex.io/watch/diffing/). It explains how to structure your application so you can use DiffyTables. Seriously, go read it, I can wait. 12 | 2. Check out the demo project in the `Demo/` folder 13 | 3. Copy `Diffing.swift`, `WatchKitUpdatable.swift`, and `DiffyTables.swift` from `Source/` to your project. 14 | 4. Now you can create your view models (conforming to `TableRowModel`) and make your row controllers conform to `UpdatableRowController` 15 | 5. Boom, you can now use `table.updateViewModels()` to do all the updating for you 😊 16 | 6. **Bonus:** You can make your app even faster by adopting lazy loading. Read [Lazy WatchKit tables](http://radex.io/watch/lazy/) for more info 17 | 18 | **Note:** if you're on Swift 2, please use the the [`swift2` branch](https://github.com/radex/DiffyTables/tree/swift2). 19 | 20 | #### Via Carthage 21 | 22 | Add this to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile): 23 | 24 | github "radex/DiffyTables" 25 | 26 | and follow the [usual steps for watchOS frameworks](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos) 27 | 28 | ### But… native apps? 29 | 30 | > Since I published this post, Apple has announced a native SDK for the Watch to be released in the fall. You might conclude that this makes this post obsolete and there’s no reason to implement the view model-based architecture in your app. I believe this conclusion to be false. 31 | > 32 | > First of all, even though the native SDK is available for us to play with, your users will still have to use the WatchKit 1 app until the fall. This is a pretty long time to live with a frustratingly slow app. You can improve their experience with limited effort by using the techniques presented above. 33 | > 34 | > Second of all, I believe that the architectural benefits of this approach still apply on watchOS 2. Decoupling your business logic from interface logic makes your code cleaner and easier to reason about. So does treating the UI as a function of application state. 35 | > 36 | > Some of the optimizations, like the `update(...)` helpers won’t be needed anymore. But the diffing algorithm is actually still useful. Although conceived as a performance trick, it has a side effect of nicely animating row insertions and deletions without you having to do anything. 37 | 38 | ### Contributing 39 | 40 | If you have comments, complaints or ideas for improvements, feel free to open an issue or a pull request. 41 | 42 | ### Author and license 43 | 44 | Radek Pietruszewski 45 | 46 | * [github.com/radex](http://github.com/radex) 47 | * [twitter.com/radexp](http://twitter.com/radexp) 48 | * [radex.io](http://radex.io) 49 | * this.is@radex.io 50 | 51 | DiffyTables is available under the MIT license. See the LICENSE file for more info. 52 | -------------------------------------------------------------------------------- /Source/Diffing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffyTables 3 | // 4 | // Copyright (c) 2015 Radosław Pietruszewski 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | // 28 | // Diffing code 29 | // 30 | 31 | /// Calculates the longest common subsequence of two arrays 32 | /// Based on http://rosettacode.org/wiki/Longest_common_subsequence 33 | 34 | func longestCommonSubsequence(_ s1: [T], _ s2: [T]) -> [T] { 35 | var x = s1.count 36 | var y = s2.count 37 | var lens = Array(repeating: Array(repeating: 0, count: y + 1), count: x + 1) 38 | var result: [T] = [] 39 | 40 | for i in 0..(_ left: [T], _ right: [T]) -> [AlignmentDiffChange] { 79 | let lcs = longestCommonSubsequence(left, right) 80 | 81 | var left_i = 0 82 | var right_i = 0 83 | 84 | var totalOffset = 0 85 | 86 | var changes: [AlignmentDiffChange] = [] 87 | 88 | for commonElement in lcs { 89 | var leftOffset = 0 90 | var rightOffset = 0 91 | 92 | // find index of el in left 93 | while true { 94 | if left[left_i] == commonElement { 95 | break 96 | } else { 97 | left_i += 1 98 | leftOffset += 1 99 | } 100 | } 101 | 102 | // find index of el in right 103 | while true { 104 | if right[right_i] == commonElement { 105 | break 106 | } else { 107 | right_i += 1 108 | rightOffset += 1 109 | } 110 | } 111 | 112 | // if offsets differ, add to list of changes 113 | if rightOffset > leftOffset { 114 | let insertions = rightOffset - leftOffset 115 | let pos = left_i + totalOffset 116 | changes.append(.insertion(pos: pos, len: insertions)) 117 | totalOffset += insertions 118 | } else if leftOffset > rightOffset { 119 | let deletions = leftOffset - rightOffset 120 | let pos = left_i - deletions + totalOffset 121 | changes.append(.deletion(pos: pos, len: deletions)) 122 | totalOffset -= deletions 123 | } 124 | 125 | // start search with next element 126 | left_i += 1 127 | right_i += 1 128 | } 129 | 130 | // elements after last common element 131 | let afterLastInLeft = left.count - left_i 132 | let afterLastInRight = right.count - right_i 133 | 134 | if afterLastInRight > afterLastInLeft { 135 | let insertions = afterLastInRight - afterLastInLeft 136 | let pos = left_i + totalOffset 137 | changes.append(.insertion(pos: pos, len: insertions)) 138 | } else if afterLastInLeft > afterLastInRight { 139 | let deletions = afterLastInLeft - afterLastInRight 140 | let pos = left_i + totalOffset 141 | changes.append(.deletion(pos: pos, len: deletions)) 142 | } 143 | 144 | return changes 145 | } 146 | -------------------------------------------------------------------------------- /Source/DiffyTables.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffyTables 3 | // 4 | // Copyright (c) 2015 Radosław Pietruszewski 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | 25 | import WatchKit 26 | 27 | // 28 | // WatchKit extensions for diffing-based table updates 29 | // 30 | 31 | 32 | // _TableRowModel is workaround to Swift crashing on circular typealiases. 33 | // https://github.com/practicalswift/swift-compiler-crashes/blob/master/crashes/23409-circular-typealias.swift 34 | 35 | public protocol _TableRowModel { } 36 | 37 | public protocol TableRowModel: _TableRowModel { 38 | associatedtype RowController: UpdatableRowController 39 | static var tableRowType: String { get } 40 | var objectId: String { get } 41 | } 42 | 43 | public protocol UpdatableRowController { 44 | associatedtype RowModel: _TableRowModel 45 | 46 | func update(from old: RowModel?, to new: RowModel) 47 | } 48 | 49 | public extension WKInterfaceTable { 50 | public func updateViewModels(from old: [T], to new: [T]) where T.RowController.RowModel == T { 51 | // Represents view models displayed on screen. 52 | // Same as `old`, but corrected with rows 53 | // inserted/deleted on screen 54 | var displayed: [T?] 55 | 56 | // Remove and insert rows on screen to align currently 57 | // displayed data with `new` data as closely as possible 58 | // (Or just set up new rows if table is empty) 59 | if old.count == 0 { 60 | displayed = Array(repeating: nil, count: new.count) 61 | setNumberOfRows(new.count, withRowType: T.tableRowType) 62 | } else { 63 | let changes = diffToAlign(old.map { $0.objectId }, new.map { $0.objectId }) 64 | displayed = applyChangesToArray(changes, array: old) 65 | applyChanges(changes, insertedRowType: T.tableRowType) 66 | } 67 | 68 | // Update rows with fresh data 69 | 70 | for (i, (displayedModel, newModel)) in zip(displayed, new).enumerated() { 71 | let row = rowController(at: i) as! T.RowController 72 | row.update(from: displayedModel, to: newModel) 73 | } 74 | } 75 | 76 | private func applyChanges(_ changes: [AlignmentDiffChange], insertedRowType: String) { 77 | for change in changes { 78 | switch change { 79 | case .insertion(let pos, let len): 80 | let rows = IndexSet(integersIn: pos..<(pos + len)) 81 | insertRows(at: rows, withRowType: insertedRowType) 82 | case .deletion(let pos, let len): 83 | let rows = IndexSet(integersIn: pos..<(pos + len)) 84 | removeRows(at: rows) 85 | } 86 | } 87 | } 88 | 89 | private func applyChangesToArray(_ changes: [AlignmentDiffChange], array: [T]) -> [T?] { 90 | var working: [T?] = array.map { $0 } 91 | 92 | for change in changes { 93 | switch change { 94 | case .insertion(let pos, let len): 95 | for _ in (0.. 10 | 11 | //! Project version number for DiffyTables. 12 | FOUNDATION_EXPORT double DiffyTablesVersionNumber; 13 | 14 | //! Project version string for DiffyTables. 15 | FOUNDATION_EXPORT const unsigned char DiffyTablesVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/WatchKitUpdatable.swift: -------------------------------------------------------------------------------- 1 | import WatchKit 2 | 3 | // 4 | // Updatable WatchKit interface objects 5 | // 6 | 7 | extension WKInterfaceImage { 8 | func updateImageName(from old: String?, to new: String) { 9 | if old != new { 10 | setImageNamed(new) 11 | } 12 | } 13 | } 14 | 15 | extension WKInterfaceLabel { 16 | func updateText(from old: String?, to new: String) { 17 | if old != new { 18 | setText(new) 19 | } 20 | } 21 | } 22 | 23 | class WKUpdatableButton { 24 | private(set) var button: WKInterfaceButton 25 | private(set) var hidden: Bool 26 | 27 | init(_ button: WKInterfaceButton, defaultHidden: Bool) { 28 | self.button = button 29 | self.hidden = defaultHidden 30 | } 31 | 32 | func updateHidden(_ hidden: Bool) { 33 | if hidden != self.hidden { 34 | button.setHidden(hidden) 35 | self.hidden = hidden 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/DiffyTables_Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | extension AlignmentDiffChange: Equatable {} 4 | 5 | // This is really disappointing, Swift… 6 | 7 | public func == (left: AlignmentDiffChange, right: AlignmentDiffChange) -> Bool { 8 | switch left { 9 | case .insertion(pos: let a, len: let b): 10 | switch right { 11 | case .insertion(pos: let aa, len: let bb): return a == aa && b == bb 12 | default: return false 13 | } 14 | case .deletion(pos: let a, len: let b): 15 | switch right { 16 | case .deletion(pos: let aa, len: let bb): return a == aa && b == bb 17 | default: return false 18 | } 19 | } 20 | } 21 | 22 | extension AlignmentDiffChange: CustomStringConvertible { 23 | public var description: String { 24 | switch self { 25 | case .insertion(let pos, let len): return "Insertion(\(pos), \(len))" 26 | case .deletion (let pos, let len): return "Deletion(\(pos), \(len))" 27 | } 28 | } 29 | } 30 | 31 | class DiffyTables_Tests: XCTestCase { 32 | func testLongestCommonSubsequence() { 33 | var a: [Int] 34 | var b: [Int] 35 | 36 | a = [1, 2, 3, 4] 37 | b = [1, 2, 3, 4] 38 | 39 | XCTAssertEqual(longestCommonSubsequence(a, b), [1, 2, 3, 4]) 40 | 41 | a = [] 42 | b = [] 43 | 44 | XCTAssertEqual(longestCommonSubsequence(a, b), []) 45 | 46 | a = [1, 2, 3, 4] 47 | b = [] 48 | 49 | XCTAssertEqual(longestCommonSubsequence(a, b), []) 50 | 51 | a = [] 52 | b = [1, 2, 3, 4] 53 | 54 | XCTAssertEqual(longestCommonSubsequence(a, b), []) 55 | 56 | a = [1] 57 | b = [1, 2, 3, 4] 58 | 59 | XCTAssertEqual(longestCommonSubsequence(a, b), [1]) 60 | 61 | a = [2] 62 | b = [1, 2, 3, 4] 63 | 64 | XCTAssertEqual(longestCommonSubsequence(a, b), [2]) 65 | 66 | a = [1, 2, 3, 4] 67 | b = [4] 68 | 69 | XCTAssertEqual(longestCommonSubsequence(a, b), [4]) 70 | 71 | a = [1, 2, 3, 4] 72 | b = [1, 3] 73 | 74 | XCTAssertEqual(longestCommonSubsequence(a, b), [1, 3]) 75 | 76 | a = [1, 2, 3, 4] 77 | b = [0, 1, 2, 3, 4] 78 | 79 | XCTAssertEqual(longestCommonSubsequence(a, b), [1, 2, 3, 4]) 80 | 81 | a = [5, 3, 2, 1, 8, 6, 4, 3, 2, 1] 82 | b = [0, 8, 3, 0, 4, 5, 1, 0] 83 | 84 | XCTAssertEqual(longestCommonSubsequence(a, b), [3, 4, 1]) 85 | } 86 | 87 | func testLongestCommonSubsequence2() { 88 | var a: [Character] 89 | var b: [Character] 90 | 91 | a = Array("banana".characters) 92 | b = Array("atana".characters) 93 | 94 | XCTAssertEqual(longestCommonSubsequence(a, b), Array("aana".characters)) 95 | 96 | a = Array("XMJYAUZ".characters) 97 | b = Array("MZJAWXU".characters) 98 | 99 | XCTAssertEqual(longestCommonSubsequence(a, b), Array("MJAU".characters)) 100 | 101 | a = Array("nematode knowledge".characters) 102 | b = Array("empty bottle".characters) 103 | 104 | XCTAssertEqual(longestCommonSubsequence(a, b), Array("emt ole".characters)) 105 | } 106 | 107 | func testDiff() { 108 | var a: [Character] 109 | var b: [Character] 110 | var c: [AlignmentDiffChange] 111 | 112 | a = Array("abcdef".characters) 113 | b = Array("abcdef".characters) 114 | c = [] 115 | 116 | XCTAssertEqual(diffToAlign(a, b), c) 117 | 118 | a = Array("abcdef".characters) 119 | b = Array("xabcdef".characters) 120 | c = [.insertion(pos: 0, len: 1)] 121 | 122 | XCTAssertEqual(diffToAlign(a, b), c) 123 | 124 | a = Array("abcdef".characters) 125 | b = Array("xabcde".characters) 126 | c = [.insertion(pos: 0, len: 1), .deletion(pos: 6, len: 1)] 127 | 128 | XCTAssertEqual(diffToAlign(a, b), c) 129 | 130 | a = Array("abcdef".characters) 131 | b = Array("adef".characters) 132 | c = [.deletion(pos: 1, len: 2)] 133 | 134 | XCTAssertEqual(diffToAlign(a, b), c) 135 | 136 | a = Array("abcdef".characters) 137 | b = Array("acbdef".characters) 138 | c = [.insertion(pos: 1, len: 1), .deletion(pos: 3, len: 1)] 139 | 140 | XCTAssertEqual(diffToAlign(a, b), c) 141 | 142 | a = Array("a".characters) 143 | b = Array("".characters) 144 | c = [.deletion(pos: 0, len: 1)] 145 | 146 | XCTAssertEqual(diffToAlign(a, b), c) 147 | 148 | a = Array("ab".characters) 149 | b = Array("".characters) 150 | c = [.deletion(pos: 0, len: 2)] 151 | 152 | XCTAssertEqual(diffToAlign(a, b), c) 153 | 154 | a = Array("".characters) 155 | b = Array("ab".characters) 156 | c = [.insertion(pos: 0, len: 2)] 157 | 158 | XCTAssertEqual(diffToAlign(a, b), c) 159 | 160 | a = Array("banana".characters) 161 | b = Array("atana".characters) 162 | c = [.deletion(pos: 0, len: 1)] 163 | 164 | XCTAssertEqual(diffToAlign(a, b), c) 165 | 166 | a = Array("XMJYAUZ".characters) 167 | b = Array("MZJAWXU".characters) 168 | c = [.deletion(pos: 0, len: 1), .insertion(pos: 1, len: 1), .deletion(pos: 3, len: 1), 169 | .insertion(pos: 4, len: 2), .deletion(pos: 7, len: 1)] 170 | 171 | XCTAssertEqual(diffToAlign(a, b), c) 172 | 173 | a = Array("nematode knowledge".characters) 174 | b = Array("empty bottle".characters) 175 | c = [.deletion(pos: 0, len: 1), .deletion(pos: 5, len: 2), .deletion(pos: 7, len: 1), 176 | .insertion(pos: 9, len: 1), .deletion(pos: 12, len: 3)] 177 | 178 | XCTAssertEqual(diffToAlign(a, b), c) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | --------------------------------------------------------------------------------