├── .swift-version ├── Source ├── Shared │ ├── .gitkeep │ ├── Diffable.swift │ ├── ArrayEntry.swift │ ├── Counter.swift │ ├── TableEntry.swift │ ├── DiffManager.swift │ ├── Change.swift │ ├── IndexPathManager.swift │ └── Algorithm.swift ├── macOS │ ├── .gitkeep │ ├── NSCollectionView+Extensions.swift │ └── NSTableView+Extensions.swift ├── watchOS │ └── .gitkeep └── iOS+tvOS │ ├── .gitkeep │ ├── UICollectionView+Extensions.swift │ └── UITableView+Extensions.swift ├── FUNDING.yml ├── Cartfile ├── bin ├── bootstrap └── bootstrap-if-needed ├── Images └── Differific-icon.png ├── Playground-macOS.playground ├── Contents.swift ├── timeline.xctimeline └── contents.xcplayground ├── Example └── DifferificDemo │ ├── Podfile │ ├── DifferificDemo.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── DifferificDemo.xcscheme │ └── project.pbxproj │ ├── DifferificDemo │ ├── Sources │ │ ├── ViewController.swift │ │ └── AppDelegate.swift │ ├── Resources │ │ └── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── DifferificDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── Podfile.lock ├── Playground-iOS.playground ├── contents.xcplayground └── Contents.swift ├── Differific.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── Differific-watchOS.xcscheme │ │ ├── Differific-iOS.xcscheme │ │ ├── Differific-tvOS.xcscheme │ │ └── Differific-macOS.xcscheme └── project.pbxproj ├── .gitignore ├── CONTRIBUTING.md ├── codecov.yml ├── Tests ├── Info-tvOS-Tests.plist ├── Info-iOS-Tests.plist ├── Info-macOS-Tests.plist ├── Shared │ ├── SharedTests.swift │ ├── IndexPathManagerTests.swift │ └── DiffManagerTests.swift ├── macOS │ ├── NSTableViewExtensionsTests.swift │ └── NSCollectionViewExtensionTests.swift └── iOS+tvOS │ ├── UITableViewExtensionsTests.swift │ └── UICollectionViewExtensionsTests.swift ├── Differific.podspec ├── Info ├── Info-tvOS.plist ├── Info-watchOS.plist ├── Info-iOS.plist └── Info-macOS.plist ├── circle.yml ├── .travis.yml ├── LICENSE.md └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /Source/Shared/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/macOS/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/watchOS/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Source/iOS+tvOS/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [zenangst] 2 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | # https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#example-cartfile -------------------------------------------------------------------------------- /bin/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | carthage bootstrap --platform iOS,Mac 4 | cp Cartfile.resolved Carthage -------------------------------------------------------------------------------- /Images/Differific-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenangst/Differific/HEAD/Images/Differific-icon.png -------------------------------------------------------------------------------- /Source/Shared/Diffable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol Diffable { 4 | var diffValue: Int { get } 5 | } 6 | -------------------------------------------------------------------------------- /bin/bootstrap-if-needed: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! cmp -s Cartfile.resolved Carthage/Cartfile.resolved; then 4 | bin/bootstrap 5 | fi -------------------------------------------------------------------------------- /Playground-macOS.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Differific Mac Playground 2 | 3 | import Cocoa 4 | import Differific 5 | 6 | var str = "Hello, playground" 7 | -------------------------------------------------------------------------------- /Example/DifferificDemo/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '9.0' 4 | 5 | target 'DifferificDemo' do 6 | pod 'Differific', path: '../../' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /Playground-macOS.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Playground-iOS.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Playground-macOS.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Differific.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo/Sources/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Differific 3 | 4 | class ViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | view.backgroundColor = UIColor.white 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Source/Shared/ArrayEntry.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ArrayEntry: Equatable { 4 | let tableEntry: TableEntry 5 | var indexInOther: Int 6 | 7 | init(tableEntry: TableEntry, indexInOther: Int = -1) { 8 | self.tableEntry = tableEntry 9 | self.indexInOther = indexInOther 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/Shared/Counter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum Counter { 4 | case zero, one, many 5 | 6 | func increment() -> Counter { 7 | switch self { 8 | case .zero: 9 | return .one 10 | case .one: 11 | return .many 12 | case .many: 13 | return self 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Differific.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/DifferificDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Differific (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - Differific (from `../../`) 6 | 7 | EXTERNAL SOURCES: 8 | Differific: 9 | :path: "../../" 10 | 11 | SPEC CHECKSUMS: 12 | Differific: 0f03a0211fd879904ac71dce814bbd6943ae9284 13 | 14 | PODFILE CHECKSUM: 3143b825fac48b43a36a1be8861709a90f98bb2b 15 | 16 | COCOAPODS: 1.5.0 17 | -------------------------------------------------------------------------------- /Source/Shared/TableEntry.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class TableEntry: Equatable { 4 | var oldCounter: Int = 0 5 | var newCounter: Int = 0 6 | var indexesInOld: [Int] = [] 7 | var appearsInBoth: Bool { 8 | return oldCounter > 0 && newCounter > 0 9 | } 10 | 11 | static func == (lhs: TableEntry, rhs: TableEntry) -> Bool { 12 | return lhs.oldCounter == rhs.oldCounter && 13 | lhs.newCounter == rhs.newCounter && 14 | lhs.indexesInOld == rhs.indexesInOld 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Source/Shared/DiffManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class DiffManager { 4 | public init() {} 5 | public func diff(_ old: [T], _ new: [T], 6 | compare: (T,T) -> Bool = { lhs, rhs in 7 | if let lhs = lhs as? Diffable, let rhs = rhs as? Diffable { 8 | return lhs.diffValue == rhs.diffValue 9 | } else { 10 | return lhs == rhs 11 | }}) -> [Change] { 12 | return Algorithm.diff(old: old, new: new, compare: compare) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | GitHub Issues is for reporting bugs, discussing features and general feedback in **Differific**. Be sure to check our [documentation](http://cocoadocs.org/docsets/Differific), [FAQ](https://github.com/zenangst/Differific/wiki/FAQ) and [past issues](https://github.com/zenangst/Differific/issues?state=closed) before opening any new issues. 2 | 3 | If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use. 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Applications/Xcode.app/.*" 3 | - "bin" 4 | - "Example" 5 | - "Tests" 6 | - "Images" 7 | - "Info" 8 | coverage: 9 | status: 10 | project: 11 | default: off 12 | macOS: 13 | flags: macOS 14 | tvOS: 15 | flags: tvOS 16 | iOS: 17 | flags: iOS 18 | flags: 19 | macOS: 20 | paths: 21 | - Source/macOS 22 | - Source/Shared 23 | tvOS: 24 | paths: 25 | - Source/iOS+tvOS 26 | - Source/Shared 27 | iOS: 28 | paths: 29 | - Source/iOS+tvOS 30 | - Source/Shared 31 | -------------------------------------------------------------------------------- /Source/Shared/Change.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Change { 4 | public enum Kind { 5 | case insert, delete, update, move 6 | } 7 | 8 | public let kind: Kind 9 | public let item: T 10 | public let index: Int 11 | public var newIndex: Int? 12 | public var newItem: T? 13 | 14 | init(_ kind: Change.Kind, 15 | item: T, 16 | index: Int, 17 | newIndex: Int? = nil, 18 | newItem: T? = nil) { 19 | self.kind = kind 20 | self.item = item 21 | self.index = index 22 | self.newIndex = newIndex 23 | self.newItem = newItem 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Info-tvOS-Tests.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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/Info-iOS-Tests.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 | -------------------------------------------------------------------------------- /Tests/Info-macOS-Tests.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 | -------------------------------------------------------------------------------- /Tests/Shared/SharedTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class SharedTests: XCTestCase { 4 | 5 | override func setUp() { 6 | super.setUp() 7 | // Put setup code here. This method is called before the invocation of each test method in the class. 8 | } 9 | 10 | override func tearDown() { 11 | // Put teardown code here. This method is called after the invocation of each test method in the class. 12 | super.tearDown() 13 | } 14 | 15 | func testExample() { 16 | // This is an example of a functional test case. 17 | // Use XCTAssert and related functions to verify your tests produce the correct results. 18 | } 19 | 20 | func testPerformanceExample() { 21 | // This is an example of a performance test case. 22 | self.measure { 23 | // Put the code you want to measure the time of here. 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Differific 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | lazy var navigationController: UINavigationController = { [unowned self] in 10 | let controller = UINavigationController(rootViewController: self.viewController) 11 | return controller 12 | }() 13 | 14 | lazy var viewController: ViewController = { 15 | let controller = ViewController() 16 | return controller 17 | }() 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | window = UIWindow(frame: UIScreen.main.bounds) 21 | window?.rootViewController = navigationController 22 | window?.makeKeyAndVisible() 23 | 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Differific.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Differific" 3 | s.summary = "A fast and convenient diffing framework" 4 | s.version = "0.8.4" 5 | s.homepage = "https://github.com/zenangst/Differific" 6 | s.license = 'MIT' 7 | s.author = { "Christoffer Winterkvist" => "christoffer@winterkvist.com" } 8 | s.source = { 9 | :git => "https://github.com/zenangst/Differific.git", 10 | :tag => s.version.to_s 11 | } 12 | s.social_media_url = 'https://twitter.com/zenangst' 13 | 14 | s.ios.deployment_target = '9.0' 15 | s.osx.deployment_target = '10.11' 16 | s.tvos.deployment_target = '9.2' 17 | 18 | s.requires_arc = true 19 | s.ios.source_files = 'Source/{iOS+tvOS,Shared}/**/*' 20 | s.tvos.source_files = 'Source/{iOS+tvOS,Shared}/**/*' 21 | s.osx.source_files = 'Source/{macOS,Shared}/**/*' 22 | 23 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } 24 | end 25 | -------------------------------------------------------------------------------- /Info/Info-tvOS.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 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2018 Christoffer Winterkvist. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Info/Info-watchOS.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 | FMWK 17 | CFBundleShortVersionString 18 | 0.1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2018 Christoffer Winterkvist. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Info/Info-iOS.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 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2018 Christoffer Winterkvist. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Info/Info-macOS.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 | 0.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2018 Christoffer Winterkvist. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | xcode: 3 | version: "9.0" 4 | 5 | dependencies: 6 | override: 7 | - bin/bootstrap-if-needed 8 | Differific_directories: 9 | - "Carthage" 10 | 11 | test: 12 | override: 13 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-macOS" -sdk macosx clean build 14 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-macOS" -sdk macosx -enableCodeCoverage YES test 15 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-iOS" -sdk iphonesimulator clean build 16 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-iOS" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.0' -enableCodeCoverage YES test 17 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-tvOS" -destination 'platform=tvOS Simulator,name=Apple TV,OS=11.0' clean build 18 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-tvOS" -destination 'platform=tvOS Simulator,name=Apple TV,OS=11.0' -enableCodeCoverage YES test 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.2 2 | language: objective-c 3 | 4 | script: 5 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-macOS" -sdk macosx clean build | xcpretty 6 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-macOS" -sdk macosx -enableCodeCoverage YES test | xcpretty 7 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-iOS" -sdk iphonesimulator -destination name="iPhone 8" clean build | xcpretty 8 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-iOS" -sdk iphonesimulator -destination name="iPhone 8" -enableCodeCoverage YES test | xcpretty 9 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-tvOS" -destination 'platform=tvOS Simulator,name=Apple TV,OS=12.2' clean build | xcpretty 10 | - set -o pipefail && xcodebuild -project Differific.xcodeproj -scheme "Differific-tvOS" -destination 'platform=tvOS Simulator,name=Apple TV,OS=12.2' -enableCodeCoverage YES test | xcpretty 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) 14 | 15 | notifications: 16 | email: false 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2018 Christoffer Winterkvist 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 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Source/Shared/IndexPathManager.swift: -------------------------------------------------------------------------------- 1 | #if os(macOS) 2 | import Cocoa 3 | #else 4 | import UIKit 5 | #endif 6 | 7 | class IndexPathManager { 8 | func process(_ changeSet: [Change], 9 | section: Int = 0) -> (insert: [IndexPath], 10 | updates: [IndexPath], 11 | deletions: [IndexPath], 12 | moves: [(from: IndexPath, to: IndexPath)]) { 13 | let deletions = map(.delete, in: changeSet, section: section) 14 | let insertions = map(.insert, in: changeSet, section: section) 15 | let updates = map(.update, in: changeSet, section: section) 16 | let moves = changeSet.filter { $0.kind == .move }.compactMap { 17 | (from: IndexPath(item: $0.index, section: section), 18 | to: IndexPath(item: $0.newIndex!, section: section)) 19 | } 20 | 21 | return (insertions, updates, deletions, moves) 22 | } 23 | 24 | private func map(_ kind: Change.Kind, 25 | in changeSet: [Change], 26 | section: Int = 0) -> [IndexPath] { 27 | return changeSet.filter { $0.kind == kind } 28 | .compactMap { IndexPath(item: $0.index, section: section) } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/Shared/IndexPathManagerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Differific 2 | import XCTest 3 | 4 | class IndexPathManagerTests: XCTestCase { 5 | let manager = DiffManager() 6 | let indexPathManager = IndexPathManager() 7 | 8 | func testIndexPathManager() { 9 | let old = ["Eirik", "Markus", "Andreas"] 10 | let new = ["Eirik P", "Eirik N", "Markus", "Chris"] 11 | let changes = manager.diff(old, new) 12 | let result = indexPathManager.process(changes) 13 | 14 | XCTAssertEqual(result.insert, [ 15 | IndexPath(item: 0, section: 0), 16 | IndexPath(item: 1, section: 0), 17 | IndexPath(item: 3, section: 0) 18 | ]) 19 | 20 | XCTAssertTrue(result.updates.isEmpty) 21 | 22 | XCTAssertEqual(result.deletions, [ 23 | IndexPath(item: 0, section: 0), 24 | IndexPath(item: 2, section: 0) 25 | ]) 26 | 27 | XCTAssertEqual(result.moves.count, 1) 28 | XCTAssertEqual(result.moves[0].from, IndexPath(item: 1, section: 0)) 29 | XCTAssertEqual(result.moves[0].to, IndexPath(item: 2, section: 0)) 30 | } 31 | 32 | func testIndexPathGeneratingMoves() { 33 | let old = ["Foo", "Bar", "Baz"] 34 | let new = ["Baz", "Bar", "Foo"] 35 | let changes = manager.diff(old, new) 36 | let result = indexPathManager.process(changes) 37 | 38 | XCTAssertTrue(result.insert.isEmpty) 39 | XCTAssertTrue(result.updates.isEmpty) 40 | XCTAssertTrue(result.deletions.isEmpty) 41 | 42 | XCTAssertEqual(result.moves[0].from, IndexPath(item: 2, section: 0)) 43 | XCTAssertEqual(result.moves[0].to, IndexPath(item: 0, section: 0)) 44 | 45 | XCTAssertEqual(result.moves[1].from, IndexPath(item: 0, section: 0)) 46 | XCTAssertEqual(result.moves[1].to, IndexPath(item: 2, section: 0)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo/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 | 27 | 28 | -------------------------------------------------------------------------------- /Source/iOS+tvOS/UICollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UICollectionView { 4 | /// Reload collection view's data source with a collection of changes. 5 | /// 6 | /// - Parameters: 7 | /// - changes: A generic collection of changes. 8 | /// - section: The section that will be updated. 9 | /// - before: A closure that will be invoked before the updates. 10 | /// This is where you should update your data source. 11 | /// - completion: A closure that is invoked after the updates are done. 12 | public func reload(with changes: [Change], 13 | section: Int = 0, 14 | updateDataSource: (() -> Void), 15 | completion: (() -> Void)? = nil) { 16 | guard !changes.isEmpty else { 17 | completion?() 18 | return 19 | } 20 | 21 | if superview == nil { 22 | updateDataSource() 23 | reloadData() 24 | completion?() 25 | return 26 | } 27 | 28 | setNeedsLayout() 29 | layoutIfNeeded() 30 | 31 | let manager = IndexPathManager() 32 | let result = manager.process(changes, section: section) 33 | 34 | performBatchUpdates({ 35 | updateDataSource() 36 | validateUpdates(result.insert, then: insertItems) 37 | validateUpdates(result.updates, then: reloadItems) 38 | validateUpdates(result.deletions, then: deleteItems) 39 | if !result.moves.isEmpty { 40 | result.moves.forEach { 41 | moveItem(at: $0.from, to: $0.to) 42 | } 43 | } 44 | }, completion: { _ in 45 | completion?() 46 | }) 47 | } 48 | 49 | private func validateUpdates(_ collection: [IndexPath], then: ([IndexPath]) -> Void) { 50 | if !collection.isEmpty { then(collection) } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/macOS/NSCollectionView+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | public extension NSCollectionView { 4 | /// Reload collection view's data source with a collection of changes. 5 | /// 6 | /// - Parameters: 7 | /// - changes: A generic collection of changes. 8 | /// - section: The section that will be updated. 9 | /// - before: A closure that will be invoked before the updates. 10 | /// This is where you should update your data source. 11 | /// - completion: A closure that is invoked after the updates are done. 12 | func reload(with changes: [Change], 13 | animations: Bool = false, 14 | section: Int = 0, 15 | updateDataSource: (() -> Void), 16 | completion: (() -> Void)? = nil) { 17 | guard !changes.isEmpty else { 18 | completion?() 19 | return 20 | } 21 | 22 | if superview == nil { 23 | updateDataSource() 24 | reloadData() 25 | completion?() 26 | return 27 | } 28 | 29 | let manager = IndexPathManager() 30 | let result = manager.process(changes, section: section) 31 | let object = animations ? animator() : self 32 | 33 | object.performBatchUpdates({ 34 | updateDataSource() 35 | validateUpdates(result.insert, then: object.insertItems) 36 | validateUpdates(result.updates, then: object.reloadItems) 37 | validateUpdates(result.deletions, then: object.deleteItems) 38 | if !result.moves.isEmpty { 39 | result.moves.forEach { object.moveItem(at: $0.from, to: $0.to) } 40 | } 41 | }, completionHandler: { _ in 42 | completion?() 43 | }) 44 | 45 | needsLayout = true 46 | } 47 | 48 | private func validateUpdates(_ collection: [IndexPath], then: (Set) -> Void) { 49 | if !collection.isEmpty { then(Set(collection)) } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/macOS/NSTableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | public extension NSTableView { 4 | /// Reload table views data source with a collection of changes. 5 | /// 6 | /// - Parameters: 7 | /// - changes: A generic collection of changes. 8 | /// - animation: The animation that should be used when performing the update. 9 | /// - section: The section that will be updated. 10 | /// - before: A closure that will be invoked before the updates. 11 | /// This is where you should update your data source. 12 | /// - completion: A closure that is invoked after the updates are done. 13 | func reload(with changes: [Change], 14 | animation: NSTableView.AnimationOptions, 15 | section: Int = 0, 16 | updateDataSource: (() -> Void), 17 | completion: (() -> Void)? = nil) { 18 | guard !changes.isEmpty else { 19 | completion?() 20 | return 21 | } 22 | 23 | if superview == nil { 24 | updateDataSource() 25 | reloadData() 26 | completion?() 27 | return 28 | } 29 | 30 | let manager = IndexPathManager() 31 | let result = manager.process(changes, section: section) 32 | let insertions = IndexSet(result.insert.compactMap { $0.item }) 33 | let deletions = IndexSet(result.deletions.compactMap { $0.item }) 34 | let updates = IndexSet(result.updates.compactMap { $0.item }) 35 | 36 | animator().beginUpdates() 37 | updateDataSource() 38 | validateUpdates(insertions, animation: animation, then: animator().insertRows) 39 | validateUpdates(deletions, animation: animation, then: animator().removeRows) 40 | 41 | if !updates.isEmpty { 42 | animator().reloadData(forRowIndexes: updates, columnIndexes: IndexSet([section])) 43 | } 44 | 45 | if !result.moves.isEmpty { 46 | result.moves.forEach { animator().moveRow(at: $0.from.item, to: $0.to.item) } 47 | } 48 | 49 | animator().endUpdates() 50 | 51 | needsLayout = true 52 | 53 | completion?() 54 | } 55 | 56 | private func validateUpdates(_ collection: IndexSet, 57 | animation: NSTableView.AnimationOptions, 58 | then: (IndexSet, NSTableView.AnimationOptions) -> Void) { 59 | if !collection.isEmpty { then(collection, animation) } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Playground-iOS.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | // Differific iOS Playground 2 | 3 | import UIKit 4 | import Differific 5 | import PlaygroundSupport 6 | 7 | final class CollectionViewHandler: NSObject, UICollectionViewDataSource { 8 | 9 | var values = ["Foo", "Bar"] 10 | 11 | func numberOfSections(in collectionView: UICollectionView) -> Int { 12 | return 1 13 | } 14 | 15 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 16 | return values.count 17 | } 18 | 19 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 20 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath) as! MyCollectionViewCell 21 | let value = values[indexPath.row] 22 | cell.label.text = value 23 | return cell 24 | } 25 | } 26 | 27 | final class MyCollectionViewCell: UICollectionViewCell { 28 | 29 | static let identifier = "CellID" 30 | 31 | let label = UILabel() 32 | 33 | override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | label.text = "Foo" 36 | label.sizeToFit() 37 | label.textAlignment = .center 38 | label.textColor = .black 39 | addSubview(label) 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | } 46 | 47 | final class MyViewController : UIViewController { 48 | 49 | private let handler = CollectionViewHandler() 50 | 51 | private let collectionView = UICollectionView(frame: CGRect(origin: .zero, size: .init(width: 379, height: 450)), collectionViewLayout: UICollectionViewFlowLayout()) 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | view.backgroundColor = .white 56 | collectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: MyCollectionViewCell.identifier) 57 | collectionView.backgroundColor = .white 58 | collectionView.dataSource = handler 59 | view.addSubview(collectionView) 60 | } 61 | 62 | override func viewDidAppear(_ animated: Bool) { 63 | super.viewDidAppear(animated) 64 | 65 | sleep(2) 66 | 67 | let new = ["Foo", "Bar", "Baz"] 68 | let changes = DiffManager().diff(handler.values, new) 69 | collectionView.reload(with: changes, updateDataSource: { 70 | handler.values = new 71 | }) 72 | } 73 | } 74 | 75 | PlaygroundPage.current.liveView = MyViewController() 76 | -------------------------------------------------------------------------------- /Source/iOS+tvOS/UITableView+Extensions.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UITableView { 4 | /// Reload table view's data source with a collection of changes. 5 | /// 6 | /// - Parameters: 7 | /// - changes: A generic collection of changes. 8 | /// - animation: The animation that should be used when performing the update. 9 | /// - section: The section that will be updated. 10 | /// - before: A closure that will be invoked before the updates. 11 | /// This is where you should update your data source. 12 | /// - completion: A closure that is invoked after the updates are done. 13 | func reload(with changes: [Change], 14 | animation: UITableView.RowAnimation = .automatic, 15 | section: Int = 0, 16 | updateDataSource: (() -> Void), 17 | completion: (() -> Void)? = nil) { 18 | guard !changes.isEmpty else { 19 | completion?() 20 | return 21 | } 22 | 23 | if superview == nil { 24 | updateDataSource() 25 | reloadData() 26 | completion?() 27 | return 28 | } 29 | 30 | setNeedsLayout() 31 | layoutIfNeeded() 32 | 33 | let manager = IndexPathManager() 34 | let result = manager.process(changes, section: section) 35 | 36 | if #available(iOS 11, tvOS 11, *) { 37 | performBatchUpdates({ 38 | updateDataSource() 39 | validateUpdates(result.insert, animation: animation, then: insertRows) 40 | validateUpdates(result.updates, animation: animation, then: reloadRows) 41 | validateUpdates(result.deletions, animation: animation, then: deleteRows) 42 | if !result.moves.isEmpty { 43 | result.moves.forEach { moveRow(at: $0.from, to: $0.to) } 44 | } 45 | }, completion: { _ in 46 | completion?() 47 | }) 48 | } else { 49 | beginUpdates() 50 | updateDataSource() 51 | validateUpdates(result.insert, animation: animation, then: insertRows) 52 | validateUpdates(result.updates, animation: animation, then: reloadRows) 53 | validateUpdates(result.deletions, animation: animation, then: deleteRows) 54 | if !result.moves.isEmpty { 55 | result.moves.forEach { moveRow(at: $0.from, to: $0.to) } 56 | } 57 | endUpdates() 58 | completion?() 59 | } 60 | } 61 | 62 | private func validateUpdates(_ collection: [IndexPath], 63 | animation: UITableView.RowAnimation, 64 | then: ([IndexPath], UITableView.RowAnimation) -> Void) { 65 | if !collection.isEmpty { then(collection, animation) } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Differific.xcodeproj/xcshareddata/xcschemes/Differific-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Tests/macOS/NSTableViewExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Differific 3 | 4 | class NSTableViewExtensionsTests: XCTestCase { 5 | class DataSourceMock: NSObject, NSTableViewDataSource { 6 | var models = [String]() 7 | 8 | init(models: [String] = []) { 9 | self.models = models 10 | } 11 | 12 | func numberOfRows(in tableView: NSTableView) -> Int { 13 | return models.count 14 | } 15 | } 16 | 17 | func testReloadWithInsertions() { 18 | let dataSource = DataSourceMock() 19 | let tableView = NSTableView() 20 | tableView.dataSource = dataSource 21 | 22 | let old = dataSource.models 23 | let new = ["Foo", "Bar", "Baz"] 24 | let manager = DiffManager() 25 | let changes = manager.diff(old, new) 26 | 27 | var ranBefore: Bool = false 28 | var ranCompletion: Bool = false 29 | 30 | tableView.reload(with: changes, animation: .effectFade, updateDataSource: { 31 | dataSource.models = new 32 | ranBefore = true 33 | }) { 34 | ranCompletion = true 35 | } 36 | 37 | XCTAssertTrue(ranBefore) 38 | XCTAssertTrue(ranCompletion) 39 | } 40 | 41 | func testReloadWithMove() { 42 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 43 | let tableView = NSTableView() 44 | tableView.dataSource = dataSource 45 | 46 | let old = dataSource.models 47 | let new = ["Baz", "Bar", "Foo"] 48 | let manager = DiffManager() 49 | let changes = manager.diff(old, new) 50 | 51 | var ranBefore: Bool = false 52 | var ranCompletion: Bool = false 53 | 54 | tableView.reload(with: changes, animation: .effectFade, updateDataSource: { 55 | dataSource.models = new 56 | ranBefore = true 57 | }) { 58 | ranCompletion = true 59 | } 60 | 61 | XCTAssertTrue(ranBefore) 62 | XCTAssertTrue(ranCompletion) 63 | } 64 | 65 | func testReloadWithDeletions() { 66 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 67 | let tableView = NSTableView() 68 | tableView.dataSource = dataSource 69 | 70 | let old = dataSource.models 71 | let new = [String]() 72 | let manager = DiffManager() 73 | let changes = manager.diff(old, new) 74 | 75 | var ranBefore: Bool = false 76 | var ranCompletion: Bool = false 77 | 78 | tableView.reload(with: changes, animation: .effectFade, updateDataSource: { 79 | dataSource.models = new 80 | ranBefore = true 81 | }) { 82 | ranCompletion = true 83 | } 84 | 85 | XCTAssertTrue(ranBefore) 86 | XCTAssertTrue(ranCompletion) 87 | } 88 | 89 | func testReloadWithEmptyChanges() { 90 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 91 | let tableView = NSTableView() 92 | tableView.dataSource = dataSource 93 | 94 | let old = dataSource.models 95 | let new = ["Foo", "Bar", "Baz"] 96 | let manager = DiffManager() 97 | let changes = manager.diff(old, new) 98 | 99 | var ranBefore: Bool = false 100 | var ranCompletion: Bool = false 101 | 102 | tableView.reload(with: changes, animation: .effectFade, updateDataSource: { 103 | dataSource.models = new 104 | ranBefore = true 105 | }) { 106 | ranCompletion = true 107 | } 108 | 109 | XCTAssertFalse(ranBefore) 110 | XCTAssertTrue(ranCompletion) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Differific 2 | 3 |
4 | 5 | [![CI Status](https://travis-ci.org/zenangst/Differific.svg?branch=master)](https://travis-ci.org/zenangst/Differific) 6 | [![Version](https://img.shields.io/cocoapods/v/Differific.svg?style=flat)](http://cocoadocs.org/docsets/Differific) 7 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![codecov](https://codecov.io/gh/zenangst/Differific/branch/master/graph/badge.svg)](https://codecov.io/gh/zenangst/Differific) 9 | [![License](https://img.shields.io/cocoapods/l/Differific.svg?style=flat)](http://cocoadocs.org/docsets/Differific) 10 | [![Platform](https://img.shields.io/cocoapods/p/Differific.svg?style=flat)](http://cocoadocs.org/docsets/Differific) 11 | ![Swift](https://img.shields.io/badge/%20in-swift%204.2-orange.svg) 12 | 13 |
14 | 15 | ## Description 16 | 17 | Differific Icon 18 | 19 | **Differific** is a diffing tool that helps you compare Hashable objects using the Paul Heckel's diffing algorithm. Creating a changeset is seamless for all your diffing needs. The library also includes some convenience extensions to make life easier when updating data sources. 20 | 21 | The library is based and highly influenced by Matias Cudich's ([@mcudich](https://github.com/mcudich)) [HeckelDiff](https://github.com/mcudich/HeckelDiff) library that aims to solve the same issue. Versions prior to 0.3.0 was based on [DeepDiff](https://github.com/onmyway133/DeepDiff). 22 | For more information about how the algorithm works and the performance of the algorithm, head over to [DeepDiff](https://github.com/onmyway133/DeepDiff/blob/master/README.md#among-different-frameworks). For the time being, both frameworks are very similar; this is subject to change when the frameworks evolve. 23 | 24 | ## Features 25 | 26 | - [x] 🍩Built-in extensions for updating table & collection views. 27 | - [x] 🎩Customizable diffing. 28 | - [x] 🏎High performance. 29 | - [x] 📱iOS support. 30 | - [x] 💻macOS support. 31 | - [x] 📺tvOS support. 32 | 33 | ## Usage 34 | 35 | ### Diffing two collections 36 | 37 | ```swift 38 | let old = ["Foo", "Bar"] 39 | let new = ["Foo", "Bar", "Baz"] 40 | let manager = DiffManager() 41 | let changes = manager.diff(old, new) 42 | ``` 43 | 44 | ### Updating a table or collection view 45 | 46 | ```swift 47 | // uiElement is either your table view or collection view. 48 | let old = dataSource.models 49 | let new = newCollection 50 | let changes = DiffManager().diff(old, new) 51 | uiElement.reload(with: changes, before: { dataSource.models = new }) 52 | ``` 53 | 54 | 55 | ## Installation 56 | 57 | **Differific** is available through [CocoaPods](http://cocoapods.org). To install 58 | it, simply add the following line to your Podfile: 59 | 60 | ```ruby 61 | pod 'Differific' 62 | ``` 63 | 64 | **Differific** is also available through [Carthage](https://github.com/Carthage/Carthage). 65 | To install just write into your Cartfile: 66 | 67 | ```ruby 68 | github "zenangst/Differific" 69 | ``` 70 | 71 | **Differific** can also be installed manually. Just download and drop `Sources` folders in your project. 72 | 73 | ## Author 74 | 75 | - Christoffer Winterkvist, christoffer@winterkvist.com 76 | - Khoa Pham, onmyway133@gmail.com 77 | 78 | ## Contributing 79 | 80 | We would love you to contribute to **Differific**, check the [CONTRIBUTING](https://github.com/zenangst/Differific/blob/master/CONTRIBUTING.md) file for more info. 81 | 82 | ## License 83 | 84 | **Differific** is available under the MIT license. See the [LICENSE](https://github.com/zenangst/Differific/blob/master/LICENSE.md) file for more info. 85 | -------------------------------------------------------------------------------- /Tests/iOS+tvOS/UITableViewExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Differific 3 | 4 | class UITableViewExtensionsTests: XCTestCase { 5 | class DataSourceMock: NSObject, UITableViewDataSource { 6 | var models = [String]() 7 | 8 | init(models: [String] = []) { 9 | self.models = models 10 | } 11 | 12 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 13 | return models.count 14 | } 15 | 16 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 17 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 18 | return cell 19 | } 20 | } 21 | 22 | func testReloadWithInsertions() { 23 | let dataSource = DataSourceMock() 24 | let tableView = UITableView() 25 | tableView.dataSource = dataSource 26 | tableView.register(UITableViewCell.self, 27 | forCellReuseIdentifier: "cell") 28 | 29 | let old = dataSource.models 30 | let new = ["Foo", "Bar", "Baz"] 31 | let manager = DiffManager() 32 | let changes = manager.diff(old, new) 33 | 34 | var ranBefore: Bool = false 35 | var ranCompletion: Bool = false 36 | 37 | tableView.reload(with: changes, updateDataSource: { 38 | dataSource.models = new 39 | ranBefore = true 40 | }) { 41 | ranCompletion = true 42 | } 43 | 44 | XCTAssertTrue(ranBefore) 45 | XCTAssertTrue(ranCompletion) 46 | } 47 | 48 | func testReloadWithMove() { 49 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 50 | let tableView = UITableView() 51 | tableView.dataSource = dataSource 52 | tableView.register(UITableViewCell.self, 53 | forCellReuseIdentifier: "cell") 54 | 55 | let old = dataSource.models 56 | let new = ["Baz", "Bar", "Foo"] 57 | let manager = DiffManager() 58 | let changes = manager.diff(old, new) 59 | 60 | var ranBefore: Bool = false 61 | var ranCompletion: Bool = false 62 | 63 | tableView.reload(with: changes, updateDataSource: { 64 | dataSource.models = new 65 | ranBefore = true 66 | }) { 67 | ranCompletion = true 68 | } 69 | 70 | XCTAssertTrue(ranBefore) 71 | XCTAssertTrue(ranCompletion) 72 | } 73 | 74 | func testReloadWithDeletions() { 75 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 76 | let tableView = UITableView() 77 | tableView.dataSource = dataSource 78 | tableView.register(UITableViewCell.self, 79 | forCellReuseIdentifier: "cell") 80 | 81 | let old = dataSource.models 82 | let new = [String]() 83 | let manager = DiffManager() 84 | let changes = manager.diff(old, new) 85 | 86 | var ranBefore: Bool = false 87 | var ranCompletion: Bool = false 88 | 89 | tableView.reload(with: changes, updateDataSource: { 90 | dataSource.models = new 91 | ranBefore = true 92 | }) { 93 | ranCompletion = true 94 | } 95 | 96 | XCTAssertTrue(ranBefore) 97 | XCTAssertTrue(ranCompletion) 98 | } 99 | 100 | func testReloadWithEmptyChanges() { 101 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 102 | let tableView = UITableView() 103 | tableView.dataSource = dataSource 104 | tableView.register(UITableViewCell.self, 105 | forCellReuseIdentifier: "cell") 106 | 107 | let old = dataSource.models 108 | let new = ["Foo", "Bar", "Baz"] 109 | let manager = DiffManager() 110 | let changes = manager.diff(old, new) 111 | 112 | var ranBefore: Bool = false 113 | var ranCompletion: Bool = false 114 | 115 | tableView.reload(with: changes, updateDataSource: { 116 | dataSource.models = new 117 | ranBefore = true 118 | }) { 119 | ranCompletion = true 120 | } 121 | 122 | XCTAssertFalse(ranBefore) 123 | XCTAssertTrue(ranCompletion) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Example/DifferificDemo/DifferificDemo.xcodeproj/xcshareddata/xcschemes/DifferificDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Differific.xcodeproj/xcshareddata/xcschemes/Differific-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Differific.xcodeproj/xcshareddata/xcschemes/Differific-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /Differific.xcodeproj/xcshareddata/xcschemes/Differific-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /Tests/iOS+tvOS/UICollectionViewExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Differific 3 | 4 | class UICollectionViewExtensionsTests: XCTestCase { 5 | class DataSourceMock: NSObject, UICollectionViewDataSource { 6 | var models: [String] 7 | 8 | init(models: [String] = []) { 9 | self.models = models 10 | } 11 | 12 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 13 | return models.count 14 | } 15 | 16 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 17 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 18 | return cell 19 | } 20 | } 21 | 22 | func testReloadWithInsertions() { 23 | let dataSource = DataSourceMock(models: []) 24 | let layout = UICollectionViewFlowLayout() 25 | layout.itemSize = CGSize(width: 250, height: 250) 26 | let collectionView = UICollectionView(frame: .init(origin: .zero, size: layout.itemSize), 27 | collectionViewLayout: layout) 28 | collectionView.dataSource = dataSource 29 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") 30 | 31 | let old = dataSource.models 32 | let new = ["Foo", "Bar", "Baz"] 33 | let manager = DiffManager() 34 | let changes = manager.diff(old, new) 35 | var ranBefore: Bool = false 36 | var ranCompletion: Bool = false 37 | 38 | collectionView.reload(with: changes, updateDataSource: { 39 | dataSource.models = new 40 | ranBefore = true 41 | }) { 42 | ranCompletion = true 43 | } 44 | 45 | XCTAssertTrue(ranBefore) 46 | XCTAssertTrue(ranCompletion) 47 | } 48 | 49 | func testReloadWithMove() { 50 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 51 | let layout = UICollectionViewFlowLayout() 52 | layout.itemSize = CGSize(width: 250, height: 250) 53 | let collectionView = UICollectionView(frame: .init(origin: .zero, size: layout.itemSize), 54 | collectionViewLayout: layout) 55 | collectionView.dataSource = dataSource 56 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") 57 | 58 | let old = dataSource.models 59 | let new = ["Baz", "Bar", "Foo"] 60 | let manager = DiffManager() 61 | let changes = manager.diff(old, new) 62 | 63 | var ranBefore: Bool = false 64 | var ranCompletion: Bool = false 65 | 66 | collectionView.reload(with: changes, updateDataSource: { 67 | dataSource.models = new 68 | ranBefore = true 69 | }) { 70 | ranCompletion = true 71 | } 72 | 73 | XCTAssertTrue(ranBefore) 74 | XCTAssertTrue(ranCompletion) 75 | } 76 | 77 | func testReloadWithDeletions() { 78 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 79 | let layout = UICollectionViewFlowLayout() 80 | layout.itemSize = CGSize(width: 250, height: 250) 81 | let collectionView = UICollectionView(frame: .init(origin: .zero, size: layout.itemSize), 82 | collectionViewLayout: layout) 83 | collectionView.dataSource = dataSource 84 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") 85 | 86 | let old = dataSource.models 87 | let new = [String]() 88 | let manager = DiffManager() 89 | let changes = manager.diff(old, new) 90 | 91 | var ranBefore: Bool = false 92 | var ranCompletion: Bool = false 93 | 94 | collectionView.reload(with: changes, updateDataSource: { 95 | dataSource.models = new 96 | ranBefore = true 97 | }) { 98 | ranCompletion = true 99 | } 100 | 101 | XCTAssertTrue(ranBefore) 102 | XCTAssertTrue(ranCompletion) 103 | } 104 | 105 | func testReloadWithEmptyChanges() { 106 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 107 | let layout = UICollectionViewFlowLayout() 108 | layout.itemSize = CGSize(width: 250, height: 250) 109 | let collectionView = UICollectionView(frame: .init(origin: .zero, size: layout.itemSize), 110 | collectionViewLayout: layout) 111 | collectionView.dataSource = dataSource 112 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") 113 | 114 | let old = dataSource.models 115 | let new = ["Foo", "Bar", "Baz"] 116 | let manager = DiffManager() 117 | let changes = manager.diff(old, new) 118 | 119 | var ranBefore: Bool = false 120 | var ranCompletion: Bool = false 121 | 122 | collectionView.reload(with: changes, updateDataSource: { 123 | dataSource.models = new 124 | ranBefore = true 125 | }) { 126 | ranCompletion = true 127 | } 128 | 129 | XCTAssertFalse(ranBefore) 130 | XCTAssertTrue(ranCompletion) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Tests/macOS/NSCollectionViewExtensionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Differific 3 | 4 | class NSCollectionViewExtensionsTests: XCTestCase { 5 | class DataSourceMock: NSObject, NSCollectionViewDataSource { 6 | var models: [String] 7 | 8 | init(models: [String] = []) { 9 | self.models = models 10 | } 11 | 12 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { 13 | return models.count 14 | } 15 | 16 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { 17 | let cell = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier.init("cell"), for: indexPath) 18 | return cell 19 | } 20 | } 21 | 22 | func testReloadWithInsertions() { 23 | let dataSource = DataSourceMock(models: []) 24 | let layout = NSCollectionViewFlowLayout() 25 | layout.itemSize = CGSize(width: 250, height: 250) 26 | 27 | let collectionView = NSCollectionView(frame: .init(origin: .zero, size: layout.itemSize)) 28 | collectionView.collectionViewLayout = layout 29 | collectionView.dataSource = dataSource 30 | collectionView.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier.init("cell")) 31 | 32 | let old = dataSource.models 33 | let new = ["Foo", "Bar", "Baz"] 34 | let manager = DiffManager() 35 | let changes = manager.diff(old, new) 36 | var ranBefore: Bool = false 37 | var ranCompletion: Bool = false 38 | collectionView.reload(with: changes, updateDataSource: { 39 | dataSource.models = new 40 | ranBefore = true 41 | }) { 42 | ranCompletion = true 43 | } 44 | 45 | XCTAssertTrue(ranBefore) 46 | XCTAssertTrue(ranCompletion) 47 | } 48 | 49 | func testReloadWithMove() { 50 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 51 | let layout = NSCollectionViewFlowLayout() 52 | layout.itemSize = CGSize(width: 250, height: 250) 53 | let superview = NSView() 54 | let collectionView = NSCollectionView(frame: .init(origin: .zero, size: layout.itemSize)) 55 | superview.addSubview(collectionView) 56 | collectionView.collectionViewLayout = layout 57 | collectionView.dataSource = dataSource 58 | collectionView.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier.init("cell")) 59 | 60 | let old = dataSource.models 61 | let new = ["Baz", "Bar", "Foo"] 62 | let manager = DiffManager() 63 | let changes = manager.diff(old, new) 64 | 65 | var ranBefore: Bool = false 66 | var ranCompletion: Bool = false 67 | 68 | collectionView.reload(with: changes, updateDataSource: { 69 | dataSource.models = new 70 | ranBefore = true 71 | }) { 72 | ranCompletion = true 73 | } 74 | 75 | XCTAssertTrue(ranBefore) 76 | XCTAssertTrue(ranCompletion) 77 | } 78 | 79 | func testReloadWithDeletions() { 80 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 81 | let layout = NSCollectionViewFlowLayout() 82 | layout.itemSize = CGSize(width: 250, height: 250) 83 | let superview = NSView() 84 | let collectionView = NSCollectionView(frame: .init(origin: .zero, size: layout.itemSize)) 85 | superview.addSubview(collectionView) 86 | collectionView.collectionViewLayout = layout 87 | collectionView.dataSource = dataSource 88 | collectionView.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier.init("cell")) 89 | 90 | let old = dataSource.models 91 | let new = [String]() 92 | let manager = DiffManager() 93 | let changes = manager.diff(old, new) 94 | 95 | var ranBefore: Bool = false 96 | var ranCompletion: Bool = false 97 | 98 | collectionView.reload(with: changes, updateDataSource: { 99 | dataSource.models = new 100 | ranBefore = true 101 | }) { 102 | ranCompletion = true 103 | } 104 | 105 | XCTAssertTrue(ranBefore) 106 | XCTAssertTrue(ranCompletion) 107 | } 108 | 109 | func testReloadWithEmptyChanges() { 110 | let dataSource = DataSourceMock(models: ["Foo", "Bar", "Baz"]) 111 | let layout = NSCollectionViewFlowLayout() 112 | layout.itemSize = CGSize(width: 250, height: 250) 113 | let collectionView = NSCollectionView(frame: .init(origin: .zero, size: layout.itemSize)) 114 | collectionView.collectionViewLayout = layout 115 | collectionView.dataSource = dataSource 116 | collectionView.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier.init("cell")) 117 | 118 | let old = dataSource.models 119 | let new = ["Foo", "Bar", "Baz"] 120 | let manager = DiffManager() 121 | let changes = manager.diff(old, new) 122 | 123 | var ranBefore: Bool = false 124 | var ranCompletion: Bool = false 125 | 126 | collectionView.reload(with: changes, updateDataSource: { 127 | dataSource.models = new 128 | ranBefore = true 129 | }) { 130 | ranCompletion = true 131 | } 132 | 133 | XCTAssertFalse(ranBefore) 134 | XCTAssertTrue(ranCompletion) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Source/Shared/Algorithm.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class Algorithm { 4 | public static func diff(old: [T], new: [T], compare: (T,T) -> Bool) -> [Change] { 5 | if new.isEmpty { 6 | var changes = [Change]() 7 | changes.reserveCapacity(old.count) 8 | for (offset, element) in old.enumerated() { 9 | changes.append(Change(.delete, 10 | item: element, 11 | index: offset)) 12 | } 13 | return changes 14 | } else if old.isEmpty { 15 | var changes = [Change]() 16 | changes.reserveCapacity(new.count) 17 | for (offset, element) in new.enumerated() { 18 | changes.append(Change(.insert, 19 | item: element, 20 | index: offset)) 21 | } 22 | return changes 23 | } 24 | 25 | var table = [Int: TableEntry]() 26 | var (oldArray, newArray) = ([ArrayEntry](), [ArrayEntry]()) 27 | var deleteOffsets = Array(repeating: 0, count: old.count) 28 | var changes = [Change]() 29 | var (runningOffset, offset) = (0, 0) 30 | 31 | table.reserveCapacity(new.count > old.count ? new.count : old.count) 32 | changes.reserveCapacity(newArray.count + oldArray.count) 33 | newArray.reserveCapacity(new.count) 34 | oldArray.reserveCapacity(old.count) 35 | 36 | // 1 Pass 37 | for element in new[0...].lazy { 38 | let diffValue = (element as? Diffable)?.diffValue ?? element.hashValue 39 | let entry: TableEntry 40 | if let tableEntry = table[diffValue] { 41 | entry = tableEntry 42 | } else { 43 | entry = TableEntry() 44 | } 45 | 46 | table[diffValue] = entry 47 | entry.newCounter += 1 48 | newArray.append(ArrayEntry(tableEntry: entry)) 49 | } 50 | 51 | // 2 Pass 52 | for element in old[0...].lazy { 53 | let diffValue = (element as? Diffable)?.diffValue ?? element.hashValue 54 | let entry: TableEntry 55 | if let tableEntry = table[diffValue] { 56 | entry = tableEntry 57 | } else { 58 | entry = TableEntry() 59 | } 60 | 61 | table[diffValue] = entry 62 | entry.oldCounter += 1 63 | entry.indexesInOld.append(offset) 64 | oldArray.append(ArrayEntry(tableEntry: entry)) 65 | offset += 1 66 | } 67 | 68 | // 3rd Pass 69 | offset = 0 70 | 71 | for arrayEntry in newArray[0...].lazy { 72 | let entry = arrayEntry.tableEntry 73 | if entry.appearsInBoth && !entry.indexesInOld.isEmpty { 74 | let oldIndex = entry.indexesInOld.removeFirst() 75 | newArray[offset].indexInOther = oldIndex 76 | oldArray[oldIndex].indexInOther = offset 77 | } 78 | offset += 1 79 | } 80 | 81 | // 4th Pass 82 | offset = 1 83 | 84 | if offset < newArray.count - 1 { 85 | repeat { 86 | let tableEntry = newArray[offset] 87 | let otherIndex = tableEntry.indexInOther 88 | 89 | if otherIndex + 1 < oldArray.count { 90 | let newEntry = newArray[offset + 1] 91 | let oldEntry = oldArray[otherIndex + 1] 92 | 93 | if newEntry.tableEntry === oldEntry.tableEntry { 94 | newArray[offset + 1].indexInOther = otherIndex + 1 95 | oldArray[otherIndex + 1].indexInOther = offset + 1 96 | } 97 | } 98 | offset += 1 99 | } while offset < newArray.count - 1 100 | } 101 | 102 | // 5th Pass 103 | offset = newArray.count - 1 104 | 105 | if offset > newArray.count { 106 | let otherIndex = newArray[offset].indexInOther 107 | repeat { 108 | if otherIndex - 1 >= 0 && 109 | newArray[offset - 1].indexInOther == -1, 110 | oldArray[otherIndex - 1].indexInOther == -1 { 111 | newArray[offset - 1].indexInOther = otherIndex - 1 112 | oldArray[otherIndex - 1].indexInOther = offset - 1 113 | } 114 | offset -= 1 115 | } while offset > 0 116 | } 117 | 118 | // Handle deleted objects 119 | offset = 0 120 | for element in oldArray[0...].lazy { 121 | deleteOffsets[offset] = runningOffset 122 | 123 | if element.indexInOther == -1 { 124 | changes.append(Change(.delete, item: old[offset], index: offset)) 125 | runningOffset += 1 126 | } 127 | offset += 1 128 | } 129 | 130 | // Handle insert, updates and move. 131 | offset = 0 132 | for element in newArray[0...].lazy { 133 | if element.indexInOther < 0 { 134 | changes.append(Change(.insert, 135 | item: new[offset], 136 | index: offset)) 137 | runningOffset += 1 138 | } else { 139 | let oldIndex = element.indexInOther 140 | if !compare(old[oldIndex], new[offset]) { 141 | changes.append(Change(.update, 142 | item: old[oldIndex], 143 | index: oldIndex, 144 | newIndex: offset, 145 | newItem: new[offset])) 146 | } else { 147 | let deleteOffset = deleteOffsets[oldIndex] 148 | if (oldIndex - deleteOffset + runningOffset) != offset, 149 | oldIndex != offset { 150 | changes.append(Change(.move, 151 | item: new[offset], 152 | index: oldIndex, 153 | newIndex: offset)) 154 | } 155 | } 156 | 157 | } 158 | offset += 1 159 | } 160 | 161 | return changes 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Tests/Shared/DiffManagerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Differific 3 | 4 | class DiffManagerTests: XCTestCase { 5 | struct MockDiffable: Hashable, Diffable { 6 | let id: Int 7 | var diffValue: Int { 8 | return id 9 | } 10 | } 11 | 12 | struct MockObject: Hashable { 13 | let id: Int 14 | let name: String 15 | func hash(into hasher: inout Hasher) { 16 | hasher.combine(id.hashValue) 17 | } 18 | } 19 | 20 | let manager = DiffManager() 21 | 22 | func testEmptyDiff() { 23 | let old = [String]() 24 | let new = [String]() 25 | let result = manager.diff(old, new) 26 | 27 | XCTAssertTrue(result.isEmpty) 28 | } 29 | 30 | func testInsert() { 31 | let old = [String]() 32 | let new = ["Foo", "Bar", "Baz"] 33 | let result = manager.diff(old, new) 34 | 35 | XCTAssertEqual(result.count, 3) 36 | 37 | XCTAssertEqual(result[0].kind, .insert) 38 | XCTAssertEqual(result[0].item, "Foo") 39 | XCTAssertEqual(result[0].index, 0) 40 | 41 | XCTAssertEqual(result[1].kind, .insert) 42 | XCTAssertEqual(result[1].item, "Bar") 43 | XCTAssertEqual(result[1].index, 1) 44 | 45 | XCTAssertEqual(result[2].kind, .insert) 46 | XCTAssertEqual(result[2].item, "Baz") 47 | XCTAssertEqual(result[2].index, 2) 48 | } 49 | 50 | func testDelete() { 51 | let old = ["Foo", "Bar", "Baz"] 52 | let new = [String]() 53 | let result = manager.diff(old, new) 54 | 55 | XCTAssertEqual(result.count, 3) 56 | 57 | XCTAssertEqual(result[0].kind, .delete) 58 | XCTAssertEqual(result[0].item, "Foo") 59 | XCTAssertEqual(result[0].index, 0) 60 | 61 | XCTAssertEqual(result[1].kind, .delete) 62 | XCTAssertEqual(result[1].item, "Bar") 63 | XCTAssertEqual(result[1].index, 1) 64 | 65 | XCTAssertEqual(result[2].kind, .delete) 66 | XCTAssertEqual(result[2].item, "Baz") 67 | XCTAssertEqual(result[2].index, 2) 68 | } 69 | 70 | func testMove() { 71 | let old = ["Foo", "Bar", "Baz"] 72 | let new = Array(old.reversed()) 73 | let result = manager.diff(old, new) 74 | 75 | XCTAssertEqual(result.count, 2) 76 | 77 | XCTAssertEqual(result[0].kind, .move) 78 | XCTAssertEqual(result[0].item, "Baz") 79 | XCTAssertEqual(result[0].index, 2) 80 | XCTAssertEqual(result[0].newIndex, 0) 81 | 82 | XCTAssertEqual(result[1].kind, .move) 83 | XCTAssertEqual(result[1].item, "Foo") 84 | XCTAssertEqual(result[1].index, 0) 85 | XCTAssertEqual(result[1].newIndex, 2) 86 | } 87 | 88 | func testNoChange() { 89 | let old = ["Foo", "Bar", "Baz"] 90 | let new = ["Foo", "Bar", "Baz"] 91 | let result = manager.diff(old, new) 92 | 93 | XCTAssertTrue(result.isEmpty) 94 | } 95 | 96 | func testReplacing() { 97 | let old = [ 98 | MockObject(id: 0, name: "Foo"), 99 | MockObject(id: 1, name: "Bar"), 100 | MockObject(id: 2, name: "Baz") 101 | ] 102 | let new = [ 103 | MockObject(id: 0, name: "Foo0"), 104 | MockObject(id: 1, name: "Bar1"), 105 | MockObject(id: 2, name: "Baz2") 106 | ] 107 | let result = manager.diff(old, new) 108 | 109 | XCTAssertEqual(result.count, 3) 110 | 111 | XCTAssertEqual(result[0].kind, .update) 112 | XCTAssertEqual(result[0].item, MockObject(id: 0, name: "Foo")) 113 | XCTAssertEqual(result[0].newItem, MockObject(id: 0, name: "Foo0")) 114 | XCTAssertEqual(result[0].index, 0) 115 | 116 | XCTAssertEqual(result[1].kind, .update) 117 | XCTAssertEqual(result[1].item, MockObject(id: 1, name: "Bar")) 118 | XCTAssertEqual(result[1].newItem, MockObject(id: 1, name: "Bar1")) 119 | XCTAssertEqual(result[1].index, 1) 120 | 121 | XCTAssertEqual(result[2].kind, .update) 122 | XCTAssertEqual(result[2].item, MockObject(id: 2, name: "Baz")) 123 | XCTAssertEqual(result[2].newItem, MockObject(id: 2, name: "Baz2")) 124 | XCTAssertEqual(result[2].index, 2) 125 | 126 | } 127 | 128 | func testDiffing() { 129 | let old = [ 130 | MockObject(id: 0, name: "Eirik"), 131 | MockObject(id: 1, name: "Markus"), 132 | MockObject(id: 2, name: "Andreas") 133 | ] 134 | let new = [ 135 | MockObject(id: 3, name: "Eirik P"), 136 | MockObject(id: 0, name: "Eirik N"), 137 | MockObject(id: 1, name: "Markus"), 138 | MockObject(id: 4, name: "Chris") 139 | ] 140 | let result = manager.diff(old, new) 141 | 142 | XCTAssertEqual(result[0].kind, .delete) 143 | XCTAssertEqual(result[0].item, MockObject(id: 2, name: "Andreas")) 144 | 145 | XCTAssertEqual(result[1].kind, .insert) 146 | XCTAssertEqual(result[1].item, MockObject(id: 3, name: "Eirik P")) 147 | 148 | XCTAssertEqual(result[2].kind, .update) 149 | XCTAssertEqual(result[2].item, MockObject(id: 0, name: "Eirik")) 150 | 151 | XCTAssertEqual(result[3].kind, .move) 152 | XCTAssertEqual(result[3].item, MockObject(id: 1, name: "Markus")) 153 | 154 | XCTAssertEqual(result[4].kind, .insert) 155 | XCTAssertEqual(result[4].item, MockObject(id: 4, name: "Chris")) 156 | 157 | XCTAssertEqual(result.count, 5) 158 | } 159 | 160 | func testComparator() { 161 | let old = [ 162 | MockObject(id: 0, name: "Foo"), 163 | MockObject(id: 1, name: "Bar"), 164 | MockObject(id: 2, name: "Baz") 165 | ] 166 | let new = [ 167 | MockObject(id: 1, name: "Foo0"), 168 | MockObject(id: 0, name: "Bar1"), 169 | MockObject(id: 3, name: "Baz2") 170 | ] 171 | 172 | let diffManager = DiffManager() 173 | let changes = diffManager.diff(old, new, compare: { $0.id == $1.id }) 174 | 175 | XCTAssertEqual(changes[0].kind, .delete) 176 | XCTAssertEqual(changes[1].kind, .move) 177 | XCTAssertEqual(changes[2].kind, .insert) 178 | } 179 | 180 | func testDiffable() { 181 | let old = [ 182 | MockDiffable(id: 0), 183 | MockDiffable(id: 1), 184 | MockDiffable(id: 2) 185 | ] 186 | 187 | let new = [ 188 | MockDiffable(id: 1), 189 | MockDiffable(id: 0), 190 | MockDiffable(id: 3) 191 | ] 192 | 193 | let diffManager = DiffManager() 194 | let changes = diffManager.diff(old, new) 195 | 196 | XCTAssertEqual(changes[0].kind, .delete) 197 | XCTAssertEqual(changes[1].kind, .move) 198 | XCTAssertEqual(changes[2].kind, .insert) 199 | } 200 | 201 | func testPerformance() { 202 | let diffManager = DiffManager() 203 | var old = [Int]() 204 | var new = [Int]() 205 | let amount = 1_000_000 206 | 207 | new.reserveCapacity(amount) 208 | old.reserveCapacity(amount) 209 | 210 | for x in 0.. /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 196 | showEnvVarsInLog = 0; 197 | }; 198 | /* End PBXShellScriptBuildPhase section */ 199 | 200 | /* Begin PBXSourcesBuildPhase section */ 201 | D5C7F73C1C3BC9CE008CDDBA /* Sources */ = { 202 | isa = PBXSourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | D5C7F75D1C3BCA1E008CDDBA /* ViewController.swift in Sources */, 206 | D5C7F75C1C3BCA1E008CDDBA /* AppDelegate.swift in Sources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | /* End PBXSourcesBuildPhase section */ 211 | 212 | /* Begin PBXVariantGroup section */ 213 | D5C7F74C1C3BC9CE008CDDBA /* LaunchScreen.storyboard */ = { 214 | isa = PBXVariantGroup; 215 | children = ( 216 | D5C7F74D1C3BC9CE008CDDBA /* Base */, 217 | ); 218 | name = LaunchScreen.storyboard; 219 | sourceTree = ""; 220 | }; 221 | /* End PBXVariantGroup section */ 222 | 223 | /* Begin XCBuildConfiguration section */ 224 | D5C7F7501C3BC9CE008CDDBA /* Debug */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 229 | CLANG_CXX_LIBRARY = "libc++"; 230 | CLANG_ENABLE_MODULES = YES; 231 | CLANG_ENABLE_OBJC_ARC = YES; 232 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_COMMA = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 244 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 245 | CLANG_WARN_STRICT_PROTOTYPES = YES; 246 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | DEBUG_INFORMATION_FORMAT = dwarf; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | ENABLE_TESTABILITY = YES; 254 | GCC_C_LANGUAGE_STANDARD = gnu99; 255 | GCC_DYNAMIC_NO_PIC = NO; 256 | GCC_NO_COMMON_BLOCKS = YES; 257 | GCC_OPTIMIZATION_LEVEL = 0; 258 | GCC_PREPROCESSOR_DEFINITIONS = ( 259 | "DEBUG=1", 260 | "$(inherited)", 261 | ); 262 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 263 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 264 | GCC_WARN_UNDECLARED_SELECTOR = YES; 265 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 266 | GCC_WARN_UNUSED_FUNCTION = YES; 267 | GCC_WARN_UNUSED_VARIABLE = YES; 268 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 269 | MTL_ENABLE_DEBUG_INFO = YES; 270 | ONLY_ACTIVE_ARCH = YES; 271 | SDKROOT = iphoneos; 272 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 273 | SWIFT_VERSION = 4.0; 274 | }; 275 | name = Debug; 276 | }; 277 | D5C7F7511C3BC9CE008CDDBA /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | ALWAYS_SEARCH_USER_PATHS = NO; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_COMMA = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 305 | ENABLE_NS_ASSERTIONS = NO; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 316 | MTL_ENABLE_DEBUG_INFO = NO; 317 | SDKROOT = iphoneos; 318 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 319 | SWIFT_VERSION = 4.0; 320 | VALIDATE_PRODUCT = YES; 321 | }; 322 | name = Release; 323 | }; 324 | D5C7F7531C3BC9CE008CDDBA /* Debug */ = { 325 | isa = XCBuildConfiguration; 326 | baseConfigurationReference = B763EF95B0B941F40D3AC8B2 /* Pods-DifferificDemo.debug.xcconfig */; 327 | buildSettings = { 328 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 329 | INFOPLIST_FILE = DifferificDemo/Info.plist; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = com.zenangst.DifferificDemo; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | }; 334 | name = Debug; 335 | }; 336 | D5C7F7541C3BC9CE008CDDBA /* Release */ = { 337 | isa = XCBuildConfiguration; 338 | baseConfigurationReference = C196AB212A5242CACF6780D7 /* Pods-DifferificDemo.release.xcconfig */; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | INFOPLIST_FILE = DifferificDemo/Info.plist; 342 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 343 | PRODUCT_BUNDLE_IDENTIFIER = com.zenangst.DifferificDemo; 344 | PRODUCT_NAME = "$(TARGET_NAME)"; 345 | }; 346 | name = Release; 347 | }; 348 | /* End XCBuildConfiguration section */ 349 | 350 | /* Begin XCConfigurationList section */ 351 | D5C7F73B1C3BC9CE008CDDBA /* Build configuration list for PBXProject "DifferificDemo" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | D5C7F7501C3BC9CE008CDDBA /* Debug */, 355 | D5C7F7511C3BC9CE008CDDBA /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | D5C7F7521C3BC9CE008CDDBA /* Build configuration list for PBXNativeTarget "DifferificDemo" */ = { 361 | isa = XCConfigurationList; 362 | buildConfigurations = ( 363 | D5C7F7531C3BC9CE008CDDBA /* Debug */, 364 | D5C7F7541C3BC9CE008CDDBA /* Release */, 365 | ); 366 | defaultConfigurationIsVisible = 0; 367 | defaultConfigurationName = Release; 368 | }; 369 | /* End XCConfigurationList section */ 370 | }; 371 | rootObject = D5C7F7381C3BC9CE008CDDBA /* Project object */; 372 | } 373 | -------------------------------------------------------------------------------- /Differific.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BD690D4820DC13F700E56E30 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4720DC13F700E56E30 /* Counter.swift */; }; 11 | BD690D4920DC13F700E56E30 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4720DC13F700E56E30 /* Counter.swift */; }; 12 | BD690D4A20DC13F700E56E30 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4720DC13F700E56E30 /* Counter.swift */; }; 13 | BD690D4C20DC141200E56E30 /* TableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4B20DC141200E56E30 /* TableEntry.swift */; }; 14 | BD690D4D20DC141200E56E30 /* TableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4B20DC141200E56E30 /* TableEntry.swift */; }; 15 | BD690D4E20DC141200E56E30 /* TableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4B20DC141200E56E30 /* TableEntry.swift */; }; 16 | BD690D5020DC143100E56E30 /* ArrayEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4F20DC143100E56E30 /* ArrayEntry.swift */; }; 17 | BD690D5120DC143100E56E30 /* ArrayEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4F20DC143100E56E30 /* ArrayEntry.swift */; }; 18 | BD690D5220DC143100E56E30 /* ArrayEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD690D4F20DC143100E56E30 /* ArrayEntry.swift */; }; 19 | BDBF80B920923FB90002CE93 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80B720923FB90002CE93 /* UITableView+Extensions.swift */; }; 20 | BDBF80BA20923FB90002CE93 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80B720923FB90002CE93 /* UITableView+Extensions.swift */; }; 21 | BDBF80BB20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80B820923FB90002CE93 /* UICollectionView+Extensions.swift */; }; 22 | BDBF80BC20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80B820923FB90002CE93 /* UICollectionView+Extensions.swift */; }; 23 | BDBF80C720923FD00002CE93 /* DiffManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C320923FD00002CE93 /* DiffManager.swift */; }; 24 | BDBF80C820923FD00002CE93 /* DiffManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C320923FD00002CE93 /* DiffManager.swift */; }; 25 | BDBF80C920923FD00002CE93 /* DiffManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C320923FD00002CE93 /* DiffManager.swift */; }; 26 | BDBF80CA20923FD00002CE93 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C420923FD00002CE93 /* Change.swift */; }; 27 | BDBF80CB20923FD00002CE93 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C420923FD00002CE93 /* Change.swift */; }; 28 | BDBF80CC20923FD00002CE93 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C420923FD00002CE93 /* Change.swift */; }; 29 | BDBF80CD20923FD00002CE93 /* IndexPathManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C520923FD00002CE93 /* IndexPathManager.swift */; }; 30 | BDBF80CE20923FD00002CE93 /* IndexPathManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C520923FD00002CE93 /* IndexPathManager.swift */; }; 31 | BDBF80CF20923FD00002CE93 /* IndexPathManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C520923FD00002CE93 /* IndexPathManager.swift */; }; 32 | BDBF80D020923FD00002CE93 /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C620923FD00002CE93 /* Algorithm.swift */; }; 33 | BDBF80D120923FD00002CE93 /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C620923FD00002CE93 /* Algorithm.swift */; }; 34 | BDBF80D220923FD00002CE93 /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80C620923FD00002CE93 /* Algorithm.swift */; }; 35 | BDBF80D720923FFA0002CE93 /* NSCollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80BD20923FC10002CE93 /* NSCollectionView+Extensions.swift */; }; 36 | BDBF80D820923FFA0002CE93 /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80BE20923FC10002CE93 /* NSTableView+Extensions.swift */; }; 37 | BDBF80DA209240110002CE93 /* DiffManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80D9209240110002CE93 /* DiffManagerTests.swift */; }; 38 | BDBF80DB209240110002CE93 /* DiffManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80D9209240110002CE93 /* DiffManagerTests.swift */; }; 39 | BDBF80DC209240110002CE93 /* DiffManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80D9209240110002CE93 /* DiffManagerTests.swift */; }; 40 | BDBF80DE20924D880002CE93 /* IndexPathManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80DD20924D880002CE93 /* IndexPathManagerTests.swift */; }; 41 | BDBF80DF20924D880002CE93 /* IndexPathManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80DD20924D880002CE93 /* IndexPathManagerTests.swift */; }; 42 | BDBF80E020924D880002CE93 /* IndexPathManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80DD20924D880002CE93 /* IndexPathManagerTests.swift */; }; 43 | BDBF80E2209250140002CE93 /* UITableViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E1209250140002CE93 /* UITableViewExtensionsTests.swift */; }; 44 | BDBF80E4209250140002CE93 /* UITableViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E1209250140002CE93 /* UITableViewExtensionsTests.swift */; }; 45 | BDBF80E6209252440002CE93 /* UICollectionViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */; }; 46 | BDBF80E8209252440002CE93 /* UICollectionViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */; }; 47 | BDBF80EA20927B080002CE93 /* NSCollectionViewExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80E920927B080002CE93 /* NSCollectionViewExtensionTests.swift */; }; 48 | BDBF80EC20927B360002CE93 /* NSTableViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBF80EB20927B360002CE93 /* NSTableViewExtensionsTests.swift */; }; 49 | BDED0DC8222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; 50 | BDED0DC9222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; 51 | BDED0DCA222C57A800D7B46E /* Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDED0DC7222C57A800D7B46E /* Diffable.swift */; }; 52 | D284B1051F79038B00D94AF3 /* Differific.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D284B0FC1F79038B00D94AF3 /* Differific.framework */; }; 53 | D284B1381F7906DB00D94AF3 /* Info-iOS-Tests.plist in Resources */ = {isa = PBXBuildFile; fileRef = D284B12F1F7906DA00D94AF3 /* Info-iOS-Tests.plist */; }; 54 | D284B1391F7906DB00D94AF3 /* Info-tvOS-Tests.plist in Resources */ = {isa = PBXBuildFile; fileRef = D284B1301F7906DA00D94AF3 /* Info-tvOS-Tests.plist */; }; 55 | D284B13B1F7906DB00D94AF3 /* Info-macOS-Tests.plist in Resources */ = {isa = PBXBuildFile; fileRef = D284B1331F7906DA00D94AF3 /* Info-macOS-Tests.plist */; }; 56 | D5B2E8AA1C3A780C00C0327D /* Differific.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5B2E89F1C3A780C00C0327D /* Differific.framework */; }; 57 | D5C6294A1C3A7FAA007F7B7C /* Differific.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5C629401C3A7FAA007F7B7C /* Differific.framework */; }; 58 | /* End PBXBuildFile section */ 59 | 60 | /* Begin PBXContainerItemProxy section */ 61 | D284B1061F79038B00D94AF3 /* PBXContainerItemProxy */ = { 62 | isa = PBXContainerItemProxy; 63 | containerPortal = D5B2E8961C3A780C00C0327D /* Project object */; 64 | proxyType = 1; 65 | remoteGlobalIDString = D284B0FB1F79038B00D94AF3; 66 | remoteInfo = "Differific-tvOS"; 67 | }; 68 | D5B2E8AB1C3A780C00C0327D /* PBXContainerItemProxy */ = { 69 | isa = PBXContainerItemProxy; 70 | containerPortal = D5B2E8961C3A780C00C0327D /* Project object */; 71 | proxyType = 1; 72 | remoteGlobalIDString = D5B2E89E1C3A780C00C0327D; 73 | remoteInfo = Differific; 74 | }; 75 | D5C6294B1C3A7FAA007F7B7C /* PBXContainerItemProxy */ = { 76 | isa = PBXContainerItemProxy; 77 | containerPortal = D5B2E8961C3A780C00C0327D /* Project object */; 78 | proxyType = 1; 79 | remoteGlobalIDString = D5C6293F1C3A7FAA007F7B7C; 80 | remoteInfo = "Differific-Mac"; 81 | }; 82 | /* End PBXContainerItemProxy section */ 83 | 84 | /* Begin PBXFileReference section */ 85 | BD2875752092EA56002AB5C3 /* Differific.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Differific.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 86 | BD690D4720DC13F700E56E30 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = ""; }; 87 | BD690D4B20DC141200E56E30 /* TableEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableEntry.swift; sourceTree = ""; }; 88 | BD690D4F20DC143100E56E30 /* ArrayEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayEntry.swift; sourceTree = ""; }; 89 | BD7221D42094863A00D28B9C /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; }; 90 | BD7221D52094885300D28B9C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 91 | BDBF80B720923FB90002CE93 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; 92 | BDBF80B820923FB90002CE93 /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; 93 | BDBF80BD20923FC10002CE93 /* NSCollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSCollectionView+Extensions.swift"; sourceTree = ""; }; 94 | BDBF80BE20923FC10002CE93 /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 95 | BDBF80C320923FD00002CE93 /* DiffManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffManager.swift; sourceTree = ""; }; 96 | BDBF80C420923FD00002CE93 /* Change.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = ""; }; 97 | BDBF80C520923FD00002CE93 /* IndexPathManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndexPathManager.swift; sourceTree = ""; }; 98 | BDBF80C620923FD00002CE93 /* Algorithm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Algorithm.swift; sourceTree = ""; }; 99 | BDBF80D9209240110002CE93 /* DiffManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiffManagerTests.swift; sourceTree = ""; }; 100 | BDBF80DD20924D880002CE93 /* IndexPathManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexPathManagerTests.swift; sourceTree = ""; }; 101 | BDBF80E1209250140002CE93 /* UITableViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewExtensionsTests.swift; sourceTree = ""; }; 102 | BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewExtensionsTests.swift; sourceTree = ""; }; 103 | BDBF80E920927B080002CE93 /* NSCollectionViewExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSCollectionViewExtensionTests.swift; sourceTree = ""; }; 104 | BDBF80EB20927B360002CE93 /* NSTableViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTableViewExtensionsTests.swift; sourceTree = ""; }; 105 | BDED0DC7222C57A800D7B46E /* Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Diffable.swift; sourceTree = ""; }; 106 | BDED0DCB222C5FC000D7B46E /* codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = codecov.yml; sourceTree = ""; }; 107 | D284B0FC1F79038B00D94AF3 /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | D284B1041F79038B00D94AF3 /* Differific-tvOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Differific-tvOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 109 | D284B1181F79039F00D94AF3 /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | D284B12F1F7906DA00D94AF3 /* Info-iOS-Tests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS-Tests.plist"; sourceTree = ""; }; 111 | D284B1301F7906DA00D94AF3 /* Info-tvOS-Tests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS-Tests.plist"; sourceTree = ""; }; 112 | D284B1331F7906DA00D94AF3 /* Info-macOS-Tests.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-macOS-Tests.plist"; sourceTree = ""; }; 113 | D284B1401F79081F00D94AF3 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 114 | D284B1411F79081F00D94AF3 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; 115 | D284B1421F79081F00D94AF3 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 116 | D284B1431F79081F00D94AF3 /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = ""; }; 117 | D500FD111C3AABED00782D78 /* Playground-iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = "Playground-iOS.playground"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 118 | D500FD121C3AAC8E00782D78 /* Playground-macOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = "Playground-macOS.playground"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 119 | D5B2E89F1C3A780C00C0327D /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 120 | D5B2E8A91C3A780C00C0327D /* Differific-iOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Differific-iOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 121 | D5C629401C3A7FAA007F7B7C /* Differific.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Differific.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | D5C629491C3A7FAA007F7B7C /* Differific-macOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Differific-macOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 123 | /* End PBXFileReference section */ 124 | 125 | /* Begin PBXFrameworksBuildPhase section */ 126 | D284B0F81F79038B00D94AF3 /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | D284B1011F79038B00D94AF3 /* Frameworks */ = { 134 | isa = PBXFrameworksBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | D284B1051F79038B00D94AF3 /* Differific.framework in Frameworks */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | D284B1141F79039F00D94AF3 /* Frameworks */ = { 142 | isa = PBXFrameworksBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | D5B2E89B1C3A780C00C0327D /* Frameworks */ = { 149 | isa = PBXFrameworksBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | D5B2E8A61C3A780C00C0327D /* Frameworks */ = { 156 | isa = PBXFrameworksBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | D5B2E8AA1C3A780C00C0327D /* Differific.framework in Frameworks */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | D5C6293C1C3A7FAA007F7B7C /* Frameworks */ = { 164 | isa = PBXFrameworksBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | D5C629461C3A7FAA007F7B7C /* Frameworks */ = { 171 | isa = PBXFrameworksBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | D5C6294A1C3A7FAA007F7B7C /* Differific.framework in Frameworks */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXFrameworksBuildPhase section */ 179 | 180 | /* Begin PBXGroup section */ 181 | D284B0F41F79024300D94AF3 /* macOS */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | BDBF80BD20923FC10002CE93 /* NSCollectionView+Extensions.swift */, 185 | BDBF80BE20923FC10002CE93 /* NSTableView+Extensions.swift */, 186 | ); 187 | path = macOS; 188 | sourceTree = ""; 189 | }; 190 | D284B1211F79041300D94AF3 /* watchOS */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | ); 194 | path = watchOS; 195 | sourceTree = ""; 196 | }; 197 | D284B12D1F7906DA00D94AF3 /* macOS */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | BDBF80E920927B080002CE93 /* NSCollectionViewExtensionTests.swift */, 201 | BDBF80EB20927B360002CE93 /* NSTableViewExtensionsTests.swift */, 202 | ); 203 | path = macOS; 204 | sourceTree = ""; 205 | }; 206 | D284B1311F7906DA00D94AF3 /* Shared */ = { 207 | isa = PBXGroup; 208 | children = ( 209 | BDBF80D9209240110002CE93 /* DiffManagerTests.swift */, 210 | BDBF80DD20924D880002CE93 /* IndexPathManagerTests.swift */, 211 | ); 212 | path = Shared; 213 | sourceTree = ""; 214 | }; 215 | D284B1351F7906DA00D94AF3 /* iOS+tvOS */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | BDBF80E1209250140002CE93 /* UITableViewExtensionsTests.swift */, 219 | BDBF80E5209252440002CE93 /* UICollectionViewExtensionsTests.swift */, 220 | ); 221 | path = "iOS+tvOS"; 222 | sourceTree = ""; 223 | }; 224 | D284B13F1F79081F00D94AF3 /* Info */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | D284B1401F79081F00D94AF3 /* Info-iOS.plist */, 228 | D284B1411F79081F00D94AF3 /* Info-macOS.plist */, 229 | D284B1421F79081F00D94AF3 /* Info-tvOS.plist */, 230 | D284B1431F79081F00D94AF3 /* Info-watchOS.plist */, 231 | ); 232 | path = Info; 233 | sourceTree = ""; 234 | }; 235 | D5B2E8951C3A780C00C0327D = { 236 | isa = PBXGroup; 237 | children = ( 238 | BD7221D42094863A00D28B9C /* .travis.yml */, 239 | BDED0DCB222C5FC000D7B46E /* codecov.yml */, 240 | BD2875752092EA56002AB5C3 /* Differific.podspec */, 241 | D284B13F1F79081F00D94AF3 /* Info */, 242 | D500FD111C3AABED00782D78 /* Playground-iOS.playground */, 243 | D500FD121C3AAC8E00782D78 /* Playground-macOS.playground */, 244 | D5B2E8A01C3A780C00C0327D /* Products */, 245 | BD7221D52094885300D28B9C /* README.md */, 246 | D5C629691C3A809D007F7B7C /* Source */, 247 | D5C6298F1C3A8BDA007F7B7C /* Tests */, 248 | ); 249 | sourceTree = ""; 250 | }; 251 | D5B2E8A01C3A780C00C0327D /* Products */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | D5B2E89F1C3A780C00C0327D /* Differific.framework */, 255 | D5B2E8A91C3A780C00C0327D /* Differific-iOS-Tests.xctest */, 256 | D5C629401C3A7FAA007F7B7C /* Differific.framework */, 257 | D5C629491C3A7FAA007F7B7C /* Differific-macOS-Tests.xctest */, 258 | D284B0FC1F79038B00D94AF3 /* Differific.framework */, 259 | D284B1041F79038B00D94AF3 /* Differific-tvOS-Tests.xctest */, 260 | D284B1181F79039F00D94AF3 /* Differific.framework */, 261 | ); 262 | name = Products; 263 | sourceTree = ""; 264 | }; 265 | D5C629691C3A809D007F7B7C /* Source */ = { 266 | isa = PBXGroup; 267 | children = ( 268 | D5C6296A1C3A809D007F7B7C /* iOS+tvOS */, 269 | D284B0F41F79024300D94AF3 /* macOS */, 270 | D5C6296E1C3A809D007F7B7C /* Shared */, 271 | D284B1211F79041300D94AF3 /* watchOS */, 272 | ); 273 | path = Source; 274 | sourceTree = ""; 275 | }; 276 | D5C6296A1C3A809D007F7B7C /* iOS+tvOS */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | BDBF80B820923FB90002CE93 /* UICollectionView+Extensions.swift */, 280 | BDBF80B720923FB90002CE93 /* UITableView+Extensions.swift */, 281 | ); 282 | path = "iOS+tvOS"; 283 | sourceTree = ""; 284 | }; 285 | D5C6296E1C3A809D007F7B7C /* Shared */ = { 286 | isa = PBXGroup; 287 | children = ( 288 | BDBF80C620923FD00002CE93 /* Algorithm.swift */, 289 | BD690D4F20DC143100E56E30 /* ArrayEntry.swift */, 290 | BDBF80C420923FD00002CE93 /* Change.swift */, 291 | BD690D4720DC13F700E56E30 /* Counter.swift */, 292 | BDED0DC7222C57A800D7B46E /* Diffable.swift */, 293 | BDBF80C320923FD00002CE93 /* DiffManager.swift */, 294 | BDBF80C520923FD00002CE93 /* IndexPathManager.swift */, 295 | BD690D4B20DC141200E56E30 /* TableEntry.swift */, 296 | ); 297 | path = Shared; 298 | sourceTree = ""; 299 | }; 300 | D5C6298F1C3A8BDA007F7B7C /* Tests */ = { 301 | isa = PBXGroup; 302 | children = ( 303 | D284B12F1F7906DA00D94AF3 /* Info-iOS-Tests.plist */, 304 | D284B1331F7906DA00D94AF3 /* Info-macOS-Tests.plist */, 305 | D284B1301F7906DA00D94AF3 /* Info-tvOS-Tests.plist */, 306 | D284B1351F7906DA00D94AF3 /* iOS+tvOS */, 307 | D284B12D1F7906DA00D94AF3 /* macOS */, 308 | D284B1311F7906DA00D94AF3 /* Shared */, 309 | ); 310 | path = Tests; 311 | sourceTree = ""; 312 | }; 313 | /* End PBXGroup section */ 314 | 315 | /* Begin PBXHeadersBuildPhase section */ 316 | D284B0F91F79038B00D94AF3 /* Headers */ = { 317 | isa = PBXHeadersBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | D284B1151F79039F00D94AF3 /* Headers */ = { 324 | isa = PBXHeadersBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | D5B2E89C1C3A780C00C0327D /* Headers */ = { 331 | isa = PBXHeadersBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | D5C6293D1C3A7FAA007F7B7C /* Headers */ = { 338 | isa = PBXHeadersBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | /* End PBXHeadersBuildPhase section */ 345 | 346 | /* Begin PBXNativeTarget section */ 347 | D284B0FB1F79038B00D94AF3 /* Differific-tvOS */ = { 348 | isa = PBXNativeTarget; 349 | buildConfigurationList = D284B10D1F79038B00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-tvOS" */; 350 | buildPhases = ( 351 | D284B0F71F79038B00D94AF3 /* Sources */, 352 | D284B0F81F79038B00D94AF3 /* Frameworks */, 353 | D284B0F91F79038B00D94AF3 /* Headers */, 354 | D284B0FA1F79038B00D94AF3 /* Resources */, 355 | ); 356 | buildRules = ( 357 | ); 358 | dependencies = ( 359 | ); 360 | name = "Differific-tvOS"; 361 | productName = "Differific-tvOS"; 362 | productReference = D284B0FC1F79038B00D94AF3 /* Differific.framework */; 363 | productType = "com.apple.product-type.framework"; 364 | }; 365 | D284B1031F79038B00D94AF3 /* Differific-tvOS-Tests */ = { 366 | isa = PBXNativeTarget; 367 | buildConfigurationList = D284B1101F79038B00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-tvOS-Tests" */; 368 | buildPhases = ( 369 | D284B1001F79038B00D94AF3 /* Sources */, 370 | D284B1011F79038B00D94AF3 /* Frameworks */, 371 | D284B1021F79038B00D94AF3 /* Resources */, 372 | ); 373 | buildRules = ( 374 | ); 375 | dependencies = ( 376 | D284B1071F79038B00D94AF3 /* PBXTargetDependency */, 377 | ); 378 | name = "Differific-tvOS-Tests"; 379 | productName = "Differific-tvOSTests"; 380 | productReference = D284B1041F79038B00D94AF3 /* Differific-tvOS-Tests.xctest */; 381 | productType = "com.apple.product-type.bundle.unit-test"; 382 | }; 383 | D284B1171F79039F00D94AF3 /* Differific-watchOS */ = { 384 | isa = PBXNativeTarget; 385 | buildConfigurationList = D284B11D1F79039F00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-watchOS" */; 386 | buildPhases = ( 387 | D284B1131F79039F00D94AF3 /* Sources */, 388 | D284B1141F79039F00D94AF3 /* Frameworks */, 389 | D284B1151F79039F00D94AF3 /* Headers */, 390 | D284B1161F79039F00D94AF3 /* Resources */, 391 | ); 392 | buildRules = ( 393 | ); 394 | dependencies = ( 395 | ); 396 | name = "Differific-watchOS"; 397 | productName = "Differific-watchOS"; 398 | productReference = D284B1181F79039F00D94AF3 /* Differific.framework */; 399 | productType = "com.apple.product-type.framework"; 400 | }; 401 | D5B2E89E1C3A780C00C0327D /* Differific-iOS */ = { 402 | isa = PBXNativeTarget; 403 | buildConfigurationList = D5B2E8B31C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Differific-iOS" */; 404 | buildPhases = ( 405 | D5B2E89A1C3A780C00C0327D /* Sources */, 406 | D5B2E89B1C3A780C00C0327D /* Frameworks */, 407 | D5B2E89C1C3A780C00C0327D /* Headers */, 408 | D5B2E89D1C3A780C00C0327D /* Resources */, 409 | ); 410 | buildRules = ( 411 | ); 412 | dependencies = ( 413 | ); 414 | name = "Differific-iOS"; 415 | productName = Differific; 416 | productReference = D5B2E89F1C3A780C00C0327D /* Differific.framework */; 417 | productType = "com.apple.product-type.framework"; 418 | }; 419 | D5B2E8A81C3A780C00C0327D /* Differific-iOS-Tests */ = { 420 | isa = PBXNativeTarget; 421 | buildConfigurationList = D5B2E8B61C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Differific-iOS-Tests" */; 422 | buildPhases = ( 423 | D5B2E8A51C3A780C00C0327D /* Sources */, 424 | D5B2E8A61C3A780C00C0327D /* Frameworks */, 425 | D5B2E8A71C3A780C00C0327D /* Resources */, 426 | ); 427 | buildRules = ( 428 | ); 429 | dependencies = ( 430 | D5B2E8AC1C3A780C00C0327D /* PBXTargetDependency */, 431 | ); 432 | name = "Differific-iOS-Tests"; 433 | productName = DifferificTests; 434 | productReference = D5B2E8A91C3A780C00C0327D /* Differific-iOS-Tests.xctest */; 435 | productType = "com.apple.product-type.bundle.unit-test"; 436 | }; 437 | D5C6293F1C3A7FAA007F7B7C /* Differific-macOS */ = { 438 | isa = PBXNativeTarget; 439 | buildConfigurationList = D5C629511C3A7FAA007F7B7C /* Build configuration list for PBXNativeTarget "Differific-macOS" */; 440 | buildPhases = ( 441 | D5C6293B1C3A7FAA007F7B7C /* Sources */, 442 | D5C6293C1C3A7FAA007F7B7C /* Frameworks */, 443 | D5C6293D1C3A7FAA007F7B7C /* Headers */, 444 | D5C6293E1C3A7FAA007F7B7C /* Resources */, 445 | ); 446 | buildRules = ( 447 | ); 448 | dependencies = ( 449 | ); 450 | name = "Differific-macOS"; 451 | productName = "Differific-Mac"; 452 | productReference = D5C629401C3A7FAA007F7B7C /* Differific.framework */; 453 | productType = "com.apple.product-type.framework"; 454 | }; 455 | D5C629481C3A7FAA007F7B7C /* Differific-macOS-Tests */ = { 456 | isa = PBXNativeTarget; 457 | buildConfigurationList = D5C629541C3A7FAA007F7B7C /* Build configuration list for PBXNativeTarget "Differific-macOS-Tests" */; 458 | buildPhases = ( 459 | D5C629451C3A7FAA007F7B7C /* Sources */, 460 | D5C629461C3A7FAA007F7B7C /* Frameworks */, 461 | D5C629471C3A7FAA007F7B7C /* Resources */, 462 | ); 463 | buildRules = ( 464 | ); 465 | dependencies = ( 466 | D5C6294C1C3A7FAA007F7B7C /* PBXTargetDependency */, 467 | ); 468 | name = "Differific-macOS-Tests"; 469 | productName = "Differific-MacTests"; 470 | productReference = D5C629491C3A7FAA007F7B7C /* Differific-macOS-Tests.xctest */; 471 | productType = "com.apple.product-type.bundle.unit-test"; 472 | }; 473 | /* End PBXNativeTarget section */ 474 | 475 | /* Begin PBXProject section */ 476 | D5B2E8961C3A780C00C0327D /* Project object */ = { 477 | isa = PBXProject; 478 | attributes = { 479 | LastSwiftUpdateCheck = 0900; 480 | LastUpgradeCheck = 0930; 481 | ORGANIZATIONNAME = "Christoffer Winterkvist"; 482 | TargetAttributes = { 483 | D284B0FB1F79038B00D94AF3 = { 484 | CreatedOnToolsVersion = 9.0; 485 | LastSwiftMigration = 0900; 486 | ProvisioningStyle = Automatic; 487 | }; 488 | D284B1031F79038B00D94AF3 = { 489 | CreatedOnToolsVersion = 9.0; 490 | LastSwiftMigration = 0900; 491 | ProvisioningStyle = Automatic; 492 | }; 493 | D284B1171F79039F00D94AF3 = { 494 | CreatedOnToolsVersion = 9.0; 495 | LastSwiftMigration = 0900; 496 | ProvisioningStyle = Automatic; 497 | }; 498 | D5B2E89E1C3A780C00C0327D = { 499 | CreatedOnToolsVersion = 7.2; 500 | LastSwiftMigration = 1020; 501 | }; 502 | D5B2E8A81C3A780C00C0327D = { 503 | CreatedOnToolsVersion = 7.2; 504 | LastSwiftMigration = 1020; 505 | }; 506 | D5C6293F1C3A7FAA007F7B7C = { 507 | CreatedOnToolsVersion = 7.2; 508 | LastSwiftMigration = 1020; 509 | }; 510 | D5C629481C3A7FAA007F7B7C = { 511 | CreatedOnToolsVersion = 7.2; 512 | LastSwiftMigration = 1020; 513 | }; 514 | }; 515 | }; 516 | buildConfigurationList = D5B2E8991C3A780C00C0327D /* Build configuration list for PBXProject "Differific" */; 517 | compatibilityVersion = "Xcode 3.2"; 518 | developmentRegion = en; 519 | hasScannedForEncodings = 0; 520 | knownRegions = ( 521 | en, 522 | Base, 523 | ); 524 | mainGroup = D5B2E8951C3A780C00C0327D; 525 | productRefGroup = D5B2E8A01C3A780C00C0327D /* Products */; 526 | projectDirPath = ""; 527 | projectRoot = ""; 528 | targets = ( 529 | D5B2E89E1C3A780C00C0327D /* Differific-iOS */, 530 | D5B2E8A81C3A780C00C0327D /* Differific-iOS-Tests */, 531 | D5C6293F1C3A7FAA007F7B7C /* Differific-macOS */, 532 | D5C629481C3A7FAA007F7B7C /* Differific-macOS-Tests */, 533 | D284B0FB1F79038B00D94AF3 /* Differific-tvOS */, 534 | D284B1031F79038B00D94AF3 /* Differific-tvOS-Tests */, 535 | D284B1171F79039F00D94AF3 /* Differific-watchOS */, 536 | ); 537 | }; 538 | /* End PBXProject section */ 539 | 540 | /* Begin PBXResourcesBuildPhase section */ 541 | D284B0FA1F79038B00D94AF3 /* Resources */ = { 542 | isa = PBXResourcesBuildPhase; 543 | buildActionMask = 2147483647; 544 | files = ( 545 | ); 546 | runOnlyForDeploymentPostprocessing = 0; 547 | }; 548 | D284B1021F79038B00D94AF3 /* Resources */ = { 549 | isa = PBXResourcesBuildPhase; 550 | buildActionMask = 2147483647; 551 | files = ( 552 | ); 553 | runOnlyForDeploymentPostprocessing = 0; 554 | }; 555 | D284B1161F79039F00D94AF3 /* Resources */ = { 556 | isa = PBXResourcesBuildPhase; 557 | buildActionMask = 2147483647; 558 | files = ( 559 | D284B1391F7906DB00D94AF3 /* Info-tvOS-Tests.plist in Resources */, 560 | D284B1381F7906DB00D94AF3 /* Info-iOS-Tests.plist in Resources */, 561 | D284B13B1F7906DB00D94AF3 /* Info-macOS-Tests.plist in Resources */, 562 | ); 563 | runOnlyForDeploymentPostprocessing = 0; 564 | }; 565 | D5B2E89D1C3A780C00C0327D /* Resources */ = { 566 | isa = PBXResourcesBuildPhase; 567 | buildActionMask = 2147483647; 568 | files = ( 569 | ); 570 | runOnlyForDeploymentPostprocessing = 0; 571 | }; 572 | D5B2E8A71C3A780C00C0327D /* Resources */ = { 573 | isa = PBXResourcesBuildPhase; 574 | buildActionMask = 2147483647; 575 | files = ( 576 | ); 577 | runOnlyForDeploymentPostprocessing = 0; 578 | }; 579 | D5C6293E1C3A7FAA007F7B7C /* Resources */ = { 580 | isa = PBXResourcesBuildPhase; 581 | buildActionMask = 2147483647; 582 | files = ( 583 | ); 584 | runOnlyForDeploymentPostprocessing = 0; 585 | }; 586 | D5C629471C3A7FAA007F7B7C /* Resources */ = { 587 | isa = PBXResourcesBuildPhase; 588 | buildActionMask = 2147483647; 589 | files = ( 590 | ); 591 | runOnlyForDeploymentPostprocessing = 0; 592 | }; 593 | /* End PBXResourcesBuildPhase section */ 594 | 595 | /* Begin PBXSourcesBuildPhase section */ 596 | D284B0F71F79038B00D94AF3 /* Sources */ = { 597 | isa = PBXSourcesBuildPhase; 598 | buildActionMask = 2147483647; 599 | files = ( 600 | BDED0DCA222C57A800D7B46E /* Diffable.swift in Sources */, 601 | BDBF80CF20923FD00002CE93 /* IndexPathManager.swift in Sources */, 602 | BDBF80D220923FD00002CE93 /* Algorithm.swift in Sources */, 603 | BDBF80BC20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */, 604 | BDBF80C920923FD00002CE93 /* DiffManager.swift in Sources */, 605 | BDBF80BA20923FB90002CE93 /* UITableView+Extensions.swift in Sources */, 606 | BD690D4E20DC141200E56E30 /* TableEntry.swift in Sources */, 607 | BDBF80CC20923FD00002CE93 /* Change.swift in Sources */, 608 | BD690D5220DC143100E56E30 /* ArrayEntry.swift in Sources */, 609 | BD690D4A20DC13F700E56E30 /* Counter.swift in Sources */, 610 | ); 611 | runOnlyForDeploymentPostprocessing = 0; 612 | }; 613 | D284B1001F79038B00D94AF3 /* Sources */ = { 614 | isa = PBXSourcesBuildPhase; 615 | buildActionMask = 2147483647; 616 | files = ( 617 | BDBF80E8209252440002CE93 /* UICollectionViewExtensionsTests.swift in Sources */, 618 | BDBF80DC209240110002CE93 /* DiffManagerTests.swift in Sources */, 619 | BDBF80E020924D880002CE93 /* IndexPathManagerTests.swift in Sources */, 620 | BDBF80E4209250140002CE93 /* UITableViewExtensionsTests.swift in Sources */, 621 | ); 622 | runOnlyForDeploymentPostprocessing = 0; 623 | }; 624 | D284B1131F79039F00D94AF3 /* Sources */ = { 625 | isa = PBXSourcesBuildPhase; 626 | buildActionMask = 2147483647; 627 | files = ( 628 | ); 629 | runOnlyForDeploymentPostprocessing = 0; 630 | }; 631 | D5B2E89A1C3A780C00C0327D /* Sources */ = { 632 | isa = PBXSourcesBuildPhase; 633 | buildActionMask = 2147483647; 634 | files = ( 635 | BDED0DC8222C57A800D7B46E /* Diffable.swift in Sources */, 636 | BDBF80CD20923FD00002CE93 /* IndexPathManager.swift in Sources */, 637 | BDBF80D020923FD00002CE93 /* Algorithm.swift in Sources */, 638 | BDBF80BB20923FB90002CE93 /* UICollectionView+Extensions.swift in Sources */, 639 | BDBF80C720923FD00002CE93 /* DiffManager.swift in Sources */, 640 | BDBF80B920923FB90002CE93 /* UITableView+Extensions.swift in Sources */, 641 | BD690D4C20DC141200E56E30 /* TableEntry.swift in Sources */, 642 | BDBF80CA20923FD00002CE93 /* Change.swift in Sources */, 643 | BD690D5020DC143100E56E30 /* ArrayEntry.swift in Sources */, 644 | BD690D4820DC13F700E56E30 /* Counter.swift in Sources */, 645 | ); 646 | runOnlyForDeploymentPostprocessing = 0; 647 | }; 648 | D5B2E8A51C3A780C00C0327D /* Sources */ = { 649 | isa = PBXSourcesBuildPhase; 650 | buildActionMask = 2147483647; 651 | files = ( 652 | BDBF80E6209252440002CE93 /* UICollectionViewExtensionsTests.swift in Sources */, 653 | BDBF80DA209240110002CE93 /* DiffManagerTests.swift in Sources */, 654 | BDBF80DE20924D880002CE93 /* IndexPathManagerTests.swift in Sources */, 655 | BDBF80E2209250140002CE93 /* UITableViewExtensionsTests.swift in Sources */, 656 | ); 657 | runOnlyForDeploymentPostprocessing = 0; 658 | }; 659 | D5C6293B1C3A7FAA007F7B7C /* Sources */ = { 660 | isa = PBXSourcesBuildPhase; 661 | buildActionMask = 2147483647; 662 | files = ( 663 | BDED0DC9222C57A800D7B46E /* Diffable.swift in Sources */, 664 | BDBF80CE20923FD00002CE93 /* IndexPathManager.swift in Sources */, 665 | BDBF80D120923FD00002CE93 /* Algorithm.swift in Sources */, 666 | BDBF80D820923FFA0002CE93 /* NSTableView+Extensions.swift in Sources */, 667 | BDBF80C820923FD00002CE93 /* DiffManager.swift in Sources */, 668 | BDBF80CB20923FD00002CE93 /* Change.swift in Sources */, 669 | BD690D4D20DC141200E56E30 /* TableEntry.swift in Sources */, 670 | BDBF80D720923FFA0002CE93 /* NSCollectionView+Extensions.swift in Sources */, 671 | BD690D5120DC143100E56E30 /* ArrayEntry.swift in Sources */, 672 | BD690D4920DC13F700E56E30 /* Counter.swift in Sources */, 673 | ); 674 | runOnlyForDeploymentPostprocessing = 0; 675 | }; 676 | D5C629451C3A7FAA007F7B7C /* Sources */ = { 677 | isa = PBXSourcesBuildPhase; 678 | buildActionMask = 2147483647; 679 | files = ( 680 | BDBF80EC20927B360002CE93 /* NSTableViewExtensionsTests.swift in Sources */, 681 | BDBF80DB209240110002CE93 /* DiffManagerTests.swift in Sources */, 682 | BDBF80EA20927B080002CE93 /* NSCollectionViewExtensionTests.swift in Sources */, 683 | BDBF80DF20924D880002CE93 /* IndexPathManagerTests.swift in Sources */, 684 | ); 685 | runOnlyForDeploymentPostprocessing = 0; 686 | }; 687 | /* End PBXSourcesBuildPhase section */ 688 | 689 | /* Begin PBXTargetDependency section */ 690 | D284B1071F79038B00D94AF3 /* PBXTargetDependency */ = { 691 | isa = PBXTargetDependency; 692 | target = D284B0FB1F79038B00D94AF3 /* Differific-tvOS */; 693 | targetProxy = D284B1061F79038B00D94AF3 /* PBXContainerItemProxy */; 694 | }; 695 | D5B2E8AC1C3A780C00C0327D /* PBXTargetDependency */ = { 696 | isa = PBXTargetDependency; 697 | target = D5B2E89E1C3A780C00C0327D /* Differific-iOS */; 698 | targetProxy = D5B2E8AB1C3A780C00C0327D /* PBXContainerItemProxy */; 699 | }; 700 | D5C6294C1C3A7FAA007F7B7C /* PBXTargetDependency */ = { 701 | isa = PBXTargetDependency; 702 | target = D5C6293F1C3A7FAA007F7B7C /* Differific-macOS */; 703 | targetProxy = D5C6294B1C3A7FAA007F7B7C /* PBXContainerItemProxy */; 704 | }; 705 | /* End PBXTargetDependency section */ 706 | 707 | /* Begin XCBuildConfiguration section */ 708 | D284B10E1F79038B00D94AF3 /* Debug */ = { 709 | isa = XCBuildConfiguration; 710 | buildSettings = { 711 | CLANG_ANALYZER_NONNULL = YES; 712 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 713 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 714 | CLANG_ENABLE_MODULES = YES; 715 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 716 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 717 | CODE_SIGN_IDENTITY = ""; 718 | CODE_SIGN_STYLE = Automatic; 719 | DEFINES_MODULE = YES; 720 | DYLIB_COMPATIBILITY_VERSION = 1; 721 | DYLIB_CURRENT_VERSION = 1; 722 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 723 | GCC_C_LANGUAGE_STANDARD = gnu11; 724 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-tvOS.plist"; 725 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 726 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 727 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-tvOS"; 728 | PRODUCT_NAME = Differific; 729 | SDKROOT = appletvos; 730 | SKIP_INSTALL = YES; 731 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 732 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 733 | SWIFT_VERSION = 4.2; 734 | TARGETED_DEVICE_FAMILY = 3; 735 | TVOS_DEPLOYMENT_TARGET = 11.0; 736 | }; 737 | name = Debug; 738 | }; 739 | D284B10F1F79038B00D94AF3 /* Release */ = { 740 | isa = XCBuildConfiguration; 741 | buildSettings = { 742 | CLANG_ANALYZER_NONNULL = YES; 743 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 744 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 745 | CLANG_ENABLE_MODULES = YES; 746 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 747 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 748 | CODE_SIGN_IDENTITY = ""; 749 | CODE_SIGN_STYLE = Automatic; 750 | DEFINES_MODULE = YES; 751 | DYLIB_COMPATIBILITY_VERSION = 1; 752 | DYLIB_CURRENT_VERSION = 1; 753 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 754 | GCC_C_LANGUAGE_STANDARD = gnu11; 755 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-tvOS.plist"; 756 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 757 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 758 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-tvOS"; 759 | PRODUCT_NAME = Differific; 760 | SDKROOT = appletvos; 761 | SKIP_INSTALL = YES; 762 | SWIFT_VERSION = 4.2; 763 | TARGETED_DEVICE_FAMILY = 3; 764 | TVOS_DEPLOYMENT_TARGET = 11.0; 765 | }; 766 | name = Release; 767 | }; 768 | D284B1111F79038B00D94AF3 /* Debug */ = { 769 | isa = XCBuildConfiguration; 770 | buildSettings = { 771 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 772 | CLANG_ANALYZER_NONNULL = YES; 773 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 774 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 775 | CLANG_ENABLE_MODULES = YES; 776 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 777 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 778 | CODE_SIGN_STYLE = Automatic; 779 | GCC_C_LANGUAGE_STANDARD = gnu11; 780 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-tvOS-Tests.plist"; 781 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 782 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-tvOSTests"; 783 | PRODUCT_NAME = "$(TARGET_NAME)"; 784 | SDKROOT = appletvos; 785 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 786 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 787 | SWIFT_VERSION = 4.2; 788 | TARGETED_DEVICE_FAMILY = 3; 789 | TVOS_DEPLOYMENT_TARGET = 11.0; 790 | }; 791 | name = Debug; 792 | }; 793 | D284B1121F79038B00D94AF3 /* Release */ = { 794 | isa = XCBuildConfiguration; 795 | buildSettings = { 796 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 797 | CLANG_ANALYZER_NONNULL = YES; 798 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 799 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 800 | CLANG_ENABLE_MODULES = YES; 801 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 802 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 803 | CODE_SIGN_STYLE = Automatic; 804 | GCC_C_LANGUAGE_STANDARD = gnu11; 805 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-tvOS-Tests.plist"; 806 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 807 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-tvOSTests"; 808 | PRODUCT_NAME = "$(TARGET_NAME)"; 809 | SDKROOT = appletvos; 810 | SWIFT_VERSION = 4.2; 811 | TARGETED_DEVICE_FAMILY = 3; 812 | TVOS_DEPLOYMENT_TARGET = 11.0; 813 | }; 814 | name = Release; 815 | }; 816 | D284B11E1F79039F00D94AF3 /* Debug */ = { 817 | isa = XCBuildConfiguration; 818 | buildSettings = { 819 | APPLICATION_EXTENSION_API_ONLY = YES; 820 | CLANG_ANALYZER_NONNULL = YES; 821 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 822 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 823 | CLANG_ENABLE_MODULES = YES; 824 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 825 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 826 | CODE_SIGN_IDENTITY = ""; 827 | CODE_SIGN_STYLE = Automatic; 828 | DEFINES_MODULE = YES; 829 | DYLIB_COMPATIBILITY_VERSION = 1; 830 | DYLIB_CURRENT_VERSION = 1; 831 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 832 | GCC_C_LANGUAGE_STANDARD = gnu11; 833 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-watchOS.plist"; 834 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 835 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 836 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-watchOS"; 837 | PRODUCT_NAME = Differific; 838 | SDKROOT = watchos; 839 | SKIP_INSTALL = YES; 840 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 841 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 842 | SWIFT_VERSION = 4.2; 843 | TARGETED_DEVICE_FAMILY = 4; 844 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 845 | }; 846 | name = Debug; 847 | }; 848 | D284B11F1F79039F00D94AF3 /* Release */ = { 849 | isa = XCBuildConfiguration; 850 | buildSettings = { 851 | APPLICATION_EXTENSION_API_ONLY = YES; 852 | CLANG_ANALYZER_NONNULL = YES; 853 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 854 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 855 | CLANG_ENABLE_MODULES = YES; 856 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 857 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 858 | CODE_SIGN_IDENTITY = ""; 859 | CODE_SIGN_STYLE = Automatic; 860 | DEFINES_MODULE = YES; 861 | DYLIB_COMPATIBILITY_VERSION = 1; 862 | DYLIB_CURRENT_VERSION = 1; 863 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 864 | GCC_C_LANGUAGE_STANDARD = gnu11; 865 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-watchOS.plist"; 866 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 867 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 868 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-watchOS"; 869 | PRODUCT_NAME = Differific; 870 | SDKROOT = watchos; 871 | SKIP_INSTALL = YES; 872 | SWIFT_VERSION = 4.2; 873 | TARGETED_DEVICE_FAMILY = 4; 874 | WATCHOS_DEPLOYMENT_TARGET = 3.0; 875 | }; 876 | name = Release; 877 | }; 878 | D5B2E8B11C3A780C00C0327D /* Debug */ = { 879 | isa = XCBuildConfiguration; 880 | buildSettings = { 881 | ALWAYS_SEARCH_USER_PATHS = NO; 882 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 883 | CLANG_CXX_LIBRARY = "libc++"; 884 | CLANG_ENABLE_MODULES = YES; 885 | CLANG_ENABLE_OBJC_ARC = YES; 886 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 887 | CLANG_WARN_BOOL_CONVERSION = YES; 888 | CLANG_WARN_COMMA = YES; 889 | CLANG_WARN_CONSTANT_CONVERSION = YES; 890 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 891 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 892 | CLANG_WARN_EMPTY_BODY = YES; 893 | CLANG_WARN_ENUM_CONVERSION = YES; 894 | CLANG_WARN_INFINITE_RECURSION = YES; 895 | CLANG_WARN_INT_CONVERSION = YES; 896 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 897 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 898 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 899 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 900 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 901 | CLANG_WARN_STRICT_PROTOTYPES = YES; 902 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 903 | CLANG_WARN_UNREACHABLE_CODE = YES; 904 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 905 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 906 | COPY_PHASE_STRIP = NO; 907 | CURRENT_PROJECT_VERSION = 1; 908 | DEBUG_INFORMATION_FORMAT = dwarf; 909 | ENABLE_STRICT_OBJC_MSGSEND = YES; 910 | ENABLE_TESTABILITY = YES; 911 | GCC_C_LANGUAGE_STANDARD = gnu99; 912 | GCC_DYNAMIC_NO_PIC = NO; 913 | GCC_NO_COMMON_BLOCKS = YES; 914 | GCC_OPTIMIZATION_LEVEL = 0; 915 | GCC_PREPROCESSOR_DEFINITIONS = ( 916 | "DEBUG=1", 917 | "$(inherited)", 918 | ); 919 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 920 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 921 | GCC_WARN_UNDECLARED_SELECTOR = YES; 922 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 923 | GCC_WARN_UNUSED_FUNCTION = YES; 924 | GCC_WARN_UNUSED_VARIABLE = YES; 925 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 926 | MTL_ENABLE_DEBUG_INFO = YES; 927 | ONLY_ACTIVE_ARCH = YES; 928 | SDKROOT = iphoneos; 929 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 930 | SWIFT_VERSION = 4.2; 931 | TARGETED_DEVICE_FAMILY = "1,2"; 932 | VERSIONING_SYSTEM = "apple-generic"; 933 | VERSION_INFO_PREFIX = ""; 934 | }; 935 | name = Debug; 936 | }; 937 | D5B2E8B21C3A780C00C0327D /* Release */ = { 938 | isa = XCBuildConfiguration; 939 | buildSettings = { 940 | ALWAYS_SEARCH_USER_PATHS = NO; 941 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 942 | CLANG_CXX_LIBRARY = "libc++"; 943 | CLANG_ENABLE_MODULES = YES; 944 | CLANG_ENABLE_OBJC_ARC = YES; 945 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 946 | CLANG_WARN_BOOL_CONVERSION = YES; 947 | CLANG_WARN_COMMA = YES; 948 | CLANG_WARN_CONSTANT_CONVERSION = YES; 949 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 950 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 951 | CLANG_WARN_EMPTY_BODY = YES; 952 | CLANG_WARN_ENUM_CONVERSION = YES; 953 | CLANG_WARN_INFINITE_RECURSION = YES; 954 | CLANG_WARN_INT_CONVERSION = YES; 955 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 956 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 957 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 958 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 959 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 960 | CLANG_WARN_STRICT_PROTOTYPES = YES; 961 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 962 | CLANG_WARN_UNREACHABLE_CODE = YES; 963 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 964 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 965 | COPY_PHASE_STRIP = NO; 966 | CURRENT_PROJECT_VERSION = 1; 967 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 968 | ENABLE_NS_ASSERTIONS = NO; 969 | ENABLE_STRICT_OBJC_MSGSEND = YES; 970 | GCC_C_LANGUAGE_STANDARD = gnu99; 971 | GCC_NO_COMMON_BLOCKS = YES; 972 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 973 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 974 | GCC_WARN_UNDECLARED_SELECTOR = YES; 975 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 976 | GCC_WARN_UNUSED_FUNCTION = YES; 977 | GCC_WARN_UNUSED_VARIABLE = YES; 978 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 979 | MTL_ENABLE_DEBUG_INFO = NO; 980 | SDKROOT = iphoneos; 981 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 982 | SWIFT_VERSION = 4.2; 983 | TARGETED_DEVICE_FAMILY = "1,2"; 984 | VALIDATE_PRODUCT = YES; 985 | VERSIONING_SYSTEM = "apple-generic"; 986 | VERSION_INFO_PREFIX = ""; 987 | }; 988 | name = Release; 989 | }; 990 | D5B2E8B41C3A780C00C0327D /* Debug */ = { 991 | isa = XCBuildConfiguration; 992 | buildSettings = { 993 | CLANG_ENABLE_MODULES = YES; 994 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 995 | DEFINES_MODULE = YES; 996 | DYLIB_COMPATIBILITY_VERSION = 1; 997 | DYLIB_CURRENT_VERSION = 1; 998 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 999 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-iOS.plist"; 1000 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1001 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1002 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1003 | PRODUCT_BUNDLE_IDENTIFIER = "com.zenangst.Differific-iOS"; 1004 | PRODUCT_NAME = Differific; 1005 | SKIP_INSTALL = YES; 1006 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1007 | SWIFT_VERSION = 5.0; 1008 | }; 1009 | name = Debug; 1010 | }; 1011 | D5B2E8B51C3A780C00C0327D /* Release */ = { 1012 | isa = XCBuildConfiguration; 1013 | buildSettings = { 1014 | CLANG_ENABLE_MODULES = YES; 1015 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 1016 | DEFINES_MODULE = YES; 1017 | DYLIB_COMPATIBILITY_VERSION = 1; 1018 | DYLIB_CURRENT_VERSION = 1; 1019 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1020 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-iOS.plist"; 1021 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1022 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1023 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1024 | PRODUCT_BUNDLE_IDENTIFIER = "com.zenangst.Differific-iOS"; 1025 | PRODUCT_NAME = Differific; 1026 | SKIP_INSTALL = YES; 1027 | SWIFT_VERSION = 5.0; 1028 | }; 1029 | name = Release; 1030 | }; 1031 | D5B2E8B71C3A780C00C0327D /* Debug */ = { 1032 | isa = XCBuildConfiguration; 1033 | buildSettings = { 1034 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1035 | CLANG_ENABLE_MODULES = YES; 1036 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1037 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-iOS-Tests.plist"; 1038 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 1039 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.DifferificTests; 1040 | PRODUCT_NAME = "$(TARGET_NAME)"; 1041 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1042 | SWIFT_VERSION = 5.0; 1043 | }; 1044 | name = Debug; 1045 | }; 1046 | D5B2E8B81C3A780C00C0327D /* Release */ = { 1047 | isa = XCBuildConfiguration; 1048 | buildSettings = { 1049 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1050 | CLANG_ENABLE_MODULES = YES; 1051 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1052 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-iOS-Tests.plist"; 1053 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 1054 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.DifferificTests; 1055 | PRODUCT_NAME = "$(TARGET_NAME)"; 1056 | SWIFT_VERSION = 5.0; 1057 | }; 1058 | name = Release; 1059 | }; 1060 | D5C629521C3A7FAA007F7B7C /* Debug */ = { 1061 | isa = XCBuildConfiguration; 1062 | buildSettings = { 1063 | CLANG_ENABLE_MODULES = YES; 1064 | CODE_SIGN_IDENTITY = "-"; 1065 | COMBINE_HIDPI_IMAGES = YES; 1066 | DEFINES_MODULE = YES; 1067 | DYLIB_COMPATIBILITY_VERSION = 1; 1068 | DYLIB_CURRENT_VERSION = 1; 1069 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1070 | FRAMEWORK_VERSION = A; 1071 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-macOS.plist"; 1072 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1073 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1074 | MACOSX_DEPLOYMENT_TARGET = 10.11; 1075 | PRODUCT_BUNDLE_IDENTIFIER = "com.zenangst.Differific-macOS"; 1076 | PRODUCT_NAME = Differific; 1077 | SDKROOT = macosx; 1078 | SKIP_INSTALL = YES; 1079 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1080 | SWIFT_VERSION = 5.0; 1081 | }; 1082 | name = Debug; 1083 | }; 1084 | D5C629531C3A7FAA007F7B7C /* Release */ = { 1085 | isa = XCBuildConfiguration; 1086 | buildSettings = { 1087 | CLANG_ENABLE_MODULES = YES; 1088 | CODE_SIGN_IDENTITY = "-"; 1089 | COMBINE_HIDPI_IMAGES = YES; 1090 | DEFINES_MODULE = YES; 1091 | DYLIB_COMPATIBILITY_VERSION = 1; 1092 | DYLIB_CURRENT_VERSION = 1; 1093 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 1094 | FRAMEWORK_VERSION = A; 1095 | INFOPLIST_FILE = "$(SRCROOT)/Info/Info-macOS.plist"; 1096 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 1097 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 1098 | MACOSX_DEPLOYMENT_TARGET = 10.11; 1099 | PRODUCT_BUNDLE_IDENTIFIER = "com.zenangst.Differific-macOS"; 1100 | PRODUCT_NAME = Differific; 1101 | SDKROOT = macosx; 1102 | SKIP_INSTALL = YES; 1103 | SWIFT_VERSION = 5.0; 1104 | }; 1105 | name = Release; 1106 | }; 1107 | D5C629551C3A7FAA007F7B7C /* Debug */ = { 1108 | isa = XCBuildConfiguration; 1109 | buildSettings = { 1110 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1111 | CODE_SIGN_IDENTITY = "-"; 1112 | COMBINE_HIDPI_IMAGES = YES; 1113 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1114 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-macOS-Tests.plist"; 1115 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 1116 | MACOSX_DEPLOYMENT_TARGET = 10.11; 1117 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-MacTests"; 1118 | PRODUCT_NAME = "$(TARGET_NAME)"; 1119 | SDKROOT = macosx; 1120 | SWIFT_VERSION = 5.0; 1121 | }; 1122 | name = Debug; 1123 | }; 1124 | D5C629561C3A7FAA007F7B7C /* Release */ = { 1125 | isa = XCBuildConfiguration; 1126 | buildSettings = { 1127 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1128 | CODE_SIGN_IDENTITY = "-"; 1129 | COMBINE_HIDPI_IMAGES = YES; 1130 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 1131 | INFOPLIST_FILE = "$(SRCROOT)/Tests/Info-macOS-Tests.plist"; 1132 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 1133 | MACOSX_DEPLOYMENT_TARGET = 10.11; 1134 | PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Differific-MacTests"; 1135 | PRODUCT_NAME = "$(TARGET_NAME)"; 1136 | SDKROOT = macosx; 1137 | SWIFT_VERSION = 5.0; 1138 | }; 1139 | name = Release; 1140 | }; 1141 | /* End XCBuildConfiguration section */ 1142 | 1143 | /* Begin XCConfigurationList section */ 1144 | D284B10D1F79038B00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-tvOS" */ = { 1145 | isa = XCConfigurationList; 1146 | buildConfigurations = ( 1147 | D284B10E1F79038B00D94AF3 /* Debug */, 1148 | D284B10F1F79038B00D94AF3 /* Release */, 1149 | ); 1150 | defaultConfigurationIsVisible = 0; 1151 | defaultConfigurationName = Release; 1152 | }; 1153 | D284B1101F79038B00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-tvOS-Tests" */ = { 1154 | isa = XCConfigurationList; 1155 | buildConfigurations = ( 1156 | D284B1111F79038B00D94AF3 /* Debug */, 1157 | D284B1121F79038B00D94AF3 /* Release */, 1158 | ); 1159 | defaultConfigurationIsVisible = 0; 1160 | defaultConfigurationName = Release; 1161 | }; 1162 | D284B11D1F79039F00D94AF3 /* Build configuration list for PBXNativeTarget "Differific-watchOS" */ = { 1163 | isa = XCConfigurationList; 1164 | buildConfigurations = ( 1165 | D284B11E1F79039F00D94AF3 /* Debug */, 1166 | D284B11F1F79039F00D94AF3 /* Release */, 1167 | ); 1168 | defaultConfigurationIsVisible = 0; 1169 | defaultConfigurationName = Release; 1170 | }; 1171 | D5B2E8991C3A780C00C0327D /* Build configuration list for PBXProject "Differific" */ = { 1172 | isa = XCConfigurationList; 1173 | buildConfigurations = ( 1174 | D5B2E8B11C3A780C00C0327D /* Debug */, 1175 | D5B2E8B21C3A780C00C0327D /* Release */, 1176 | ); 1177 | defaultConfigurationIsVisible = 0; 1178 | defaultConfigurationName = Release; 1179 | }; 1180 | D5B2E8B31C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Differific-iOS" */ = { 1181 | isa = XCConfigurationList; 1182 | buildConfigurations = ( 1183 | D5B2E8B41C3A780C00C0327D /* Debug */, 1184 | D5B2E8B51C3A780C00C0327D /* Release */, 1185 | ); 1186 | defaultConfigurationIsVisible = 0; 1187 | defaultConfigurationName = Release; 1188 | }; 1189 | D5B2E8B61C3A780C00C0327D /* Build configuration list for PBXNativeTarget "Differific-iOS-Tests" */ = { 1190 | isa = XCConfigurationList; 1191 | buildConfigurations = ( 1192 | D5B2E8B71C3A780C00C0327D /* Debug */, 1193 | D5B2E8B81C3A780C00C0327D /* Release */, 1194 | ); 1195 | defaultConfigurationIsVisible = 0; 1196 | defaultConfigurationName = Release; 1197 | }; 1198 | D5C629511C3A7FAA007F7B7C /* Build configuration list for PBXNativeTarget "Differific-macOS" */ = { 1199 | isa = XCConfigurationList; 1200 | buildConfigurations = ( 1201 | D5C629521C3A7FAA007F7B7C /* Debug */, 1202 | D5C629531C3A7FAA007F7B7C /* Release */, 1203 | ); 1204 | defaultConfigurationIsVisible = 0; 1205 | defaultConfigurationName = Release; 1206 | }; 1207 | D5C629541C3A7FAA007F7B7C /* Build configuration list for PBXNativeTarget "Differific-macOS-Tests" */ = { 1208 | isa = XCConfigurationList; 1209 | buildConfigurations = ( 1210 | D5C629551C3A7FAA007F7B7C /* Debug */, 1211 | D5C629561C3A7FAA007F7B7C /* Release */, 1212 | ); 1213 | defaultConfigurationIsVisible = 0; 1214 | defaultConfigurationName = Release; 1215 | }; 1216 | /* End XCConfigurationList section */ 1217 | }; 1218 | rootObject = D5B2E8961C3A780C00C0327D /* Project object */; 1219 | } 1220 | --------------------------------------------------------------------------------