From 73e3522202828e93483fc1eec1e8cb0d9528ff2d Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Tue, 30 Apr 2019 18:48:35 +0900
Subject: [PATCH 01/20] Update iOS example
---
.../Example-iOS.xcodeproj/project.pbxproj | 140 +++++++++++--
.../Example-iOS/Sources/AppDelegate.swift | 14 ++
.../Sources/Common/NibLoadable.swift | 26 +++
.../Example-iOS/Sources/Common/Reusable.swift | 11 +
.../Common/ReusableViewExtensions.swift | 65 ++++++
.../StringExtensions.swift} | 1 -
.../HeaderFooter/HeaderFooterCell.swift | 3 +
.../HeaderFooter/HeaderFooterMoreView.swift | 9 +
.../HeaderFooter/HeaderFooterMoreView.xib | 39 ++++
.../HeaderFooterSectionModel.swift | 14 ++
.../HeaderFooterViewController.swift | 90 ++++++++
.../HeaderFooterSectionViewController.swift | 147 --------------
.../Example-iOS/Sources/Home/HomeCell.swift | 6 +
.../Example-iOS/Sources/Home/HomeCell.xib | 44 ++++
.../Sources/Home/HomeViewController.swift | 46 +++++
.../Sources/HomeViewController.swift | 51 -----
.../Sources/Random/RandomLabelView.swift | 5 +
.../Sources/Random/RandomLabelView.xib | 41 ++++
.../Sources/Random/RandomModel.swift | 20 ++
.../Sources/Random/RandomPlainCell.swift | 10 +
.../{ => Random}/RandomViewController.swift | 88 ++------
.../Sources/Random/RandomViewController.xib | 46 +++++
.../Sources/ShuffleEmoticon/EmojiCell.swift | 11 +
.../Sources/ShuffleEmoticon/EmojiCell.xib | 49 +++++
.../ShuffleEmoticon/EmojiViewController.swift | 87 ++++++++
.../ShuffleEmoticon/EmojiViewController.xib | 74 +++++++
.../ShuffleEmoticonViewController.swift | 192 ------------------
.../Sources/TextCollectionReusableView.swift | 32 ---
28 files changed, 854 insertions(+), 507 deletions(-)
create mode 100644 Examples/Example-iOS/Sources/Common/NibLoadable.swift
create mode 100644 Examples/Example-iOS/Sources/Common/Reusable.swift
create mode 100644 Examples/Example-iOS/Sources/Common/ReusableViewExtensions.swift
rename Examples/Example-iOS/Sources/{String+Differentiable.swift => Common/StringExtensions.swift} (81%)
create mode 100644 Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterCell.swift
create mode 100644 Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.swift
create mode 100644 Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.xib
create mode 100644 Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterSectionModel.swift
create mode 100644 Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterViewController.swift
delete mode 100644 Examples/Example-iOS/Sources/HeaderFooterSectionViewController.swift
create mode 100644 Examples/Example-iOS/Sources/Home/HomeCell.swift
create mode 100644 Examples/Example-iOS/Sources/Home/HomeCell.xib
create mode 100644 Examples/Example-iOS/Sources/Home/HomeViewController.swift
delete mode 100644 Examples/Example-iOS/Sources/HomeViewController.swift
create mode 100644 Examples/Example-iOS/Sources/Random/RandomLabelView.swift
create mode 100644 Examples/Example-iOS/Sources/Random/RandomLabelView.xib
create mode 100644 Examples/Example-iOS/Sources/Random/RandomModel.swift
create mode 100644 Examples/Example-iOS/Sources/Random/RandomPlainCell.swift
rename Examples/Example-iOS/Sources/{ => Random}/RandomViewController.swift (68%)
create mode 100644 Examples/Example-iOS/Sources/Random/RandomViewController.xib
create mode 100644 Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.swift
create mode 100644 Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.xib
create mode 100644 Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.swift
create mode 100644 Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.xib
delete mode 100644 Examples/Example-iOS/Sources/ShuffleEmoticonViewController.swift
delete mode 100644 Examples/Example-iOS/Sources/TextCollectionReusableView.swift
diff --git a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj
index a13b348..5268c24 100644
--- a/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj
+++ b/Examples/Example-iOS/Example-iOS.xcodeproj/project.pbxproj
@@ -7,17 +7,33 @@
objects = {
/* Begin PBXBuildFile section */
- 3363C4F821502C45008EEDE4 /* String+Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3363C4F721502C44008EEDE4 /* String+Differentiable.swift */; };
6B2DF92F210E522F004D2D40 /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DF916210E4AF0004D2D40 /* DifferenceKit.framework */; };
6B2DF930210E522F004D2D40 /* DifferenceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DF916210E4AF0004D2D40 /* DifferenceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 6B956B832110B38700DE3D29 /* HeaderFooterSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B7A2110B38700DE3D29 /* HeaderFooterSectionViewController.swift */; };
- 6B956B842110B38700DE3D29 /* ShuffleEmoticonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B7B2110B38700DE3D29 /* ShuffleEmoticonViewController.swift */; };
+ 6B7EEDAD2251305400060872 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDAC2251305400060872 /* NibLoadable.swift */; };
+ 6B7EEDAF2251309000060872 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDAE2251309000060872 /* Reusable.swift */; };
+ 6B7EEDB12251315500060872 /* ReusableViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDB02251315500060872 /* ReusableViewExtensions.swift */; };
+ 6B7EEDB5225134F000060872 /* HomeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6B7EEDB4225134F000060872 /* HomeCell.xib */; };
+ 6B7EEDB7225134FA00060872 /* HomeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDB6225134FA00060872 /* HomeCell.swift */; };
+ 6B7EEDBA2251368700060872 /* EmojiViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6B7EEDB92251368700060872 /* EmojiViewController.xib */; };
+ 6B7EEDBD2251393900060872 /* EmojiCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6B7EEDBB2251393900060872 /* EmojiCell.xib */; };
+ 6B7EEDBE2251393900060872 /* EmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDBC2251393900060872 /* EmojiCell.swift */; };
+ 6B7EEDC122513EA300060872 /* HeaderFooterSectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDC022513EA300060872 /* HeaderFooterSectionModel.swift */; };
+ 6B7EEDC7225142A500060872 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDC6225142A500060872 /* StringExtensions.swift */; };
+ 6B7EEDC92251431B00060872 /* HeaderFooterMoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7EEDC82251431B00060872 /* HeaderFooterMoreView.swift */; };
+ 6B956B832110B38700DE3D29 /* HeaderFooterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B7A2110B38700DE3D29 /* HeaderFooterViewController.swift */; };
+ 6B956B842110B38700DE3D29 /* EmojiViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B7B2110B38700DE3D29 /* EmojiViewController.swift */; };
6B956B852110B38700DE3D29 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B956B7C2110B38700DE3D29 /* Assets.xcassets */; };
6B956B872110B38700DE3D29 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B7E2110B38700DE3D29 /* HomeViewController.swift */; };
6B956B882110B38700DE3D29 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6B956B7F2110B38700DE3D29 /* LaunchScreen.storyboard */; };
6B956B892110B38700DE3D29 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B812110B38700DE3D29 /* AppDelegate.swift */; };
+ 6BAE816E2278377E0060866E /* HeaderFooterMoreView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BAE816D2278377E0060866E /* HeaderFooterMoreView.xib */; };
+ 6BAE81702278393B0060866E /* HeaderFooterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE816F2278393B0060866E /* HeaderFooterCell.swift */; };
+ 6BAE817322783C130060866E /* RandomLabelView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BAE817222783C130060866E /* RandomLabelView.xib */; };
+ 6BAE817522783D1E0060866E /* RandomModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE817422783D1E0060866E /* RandomModel.swift */; };
+ 6BAE817722783D810060866E /* RandomViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BAE817622783D810060866E /* RandomViewController.xib */; };
+ 6BAE817922783EC80060866E /* RandomPlainCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE817822783EC80060866E /* RandomPlainCell.swift */; };
6BD9445D2136EDC100093E7A /* RandomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD9445C2136EDC100093E7A /* RandomViewController.swift */; };
- 6BD944602137062000093E7A /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD9445F2137062000093E7A /* TextCollectionReusableView.swift */; };
+ 6BD944602137062000093E7A /* RandomLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BD9445F2137062000093E7A /* RandomLabelView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -59,18 +75,34 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 3363C4F721502C44008EEDE4 /* String+Differentiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Differentiable.swift"; sourceTree = ""; };
6B2DF8FA210E4AE1004D2D40 /* Example-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
6B2DF90F210E4AF0004D2D40 /* DifferenceKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DifferenceKit.xcodeproj; path = ../../DifferenceKit.xcodeproj; sourceTree = ""; };
- 6B956B7A2110B38700DE3D29 /* HeaderFooterSectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterSectionViewController.swift; sourceTree = ""; };
- 6B956B7B2110B38700DE3D29 /* ShuffleEmoticonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShuffleEmoticonViewController.swift; sourceTree = ""; };
+ 6B7EEDAC2251305400060872 /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; };
+ 6B7EEDAE2251309000060872 /* Reusable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; };
+ 6B7EEDB02251315500060872 /* ReusableViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableViewExtensions.swift; sourceTree = ""; };
+ 6B7EEDB4225134F000060872 /* HomeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeCell.xib; sourceTree = ""; };
+ 6B7EEDB6225134FA00060872 /* HomeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCell.swift; sourceTree = ""; };
+ 6B7EEDB92251368700060872 /* EmojiViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiViewController.xib; sourceTree = ""; };
+ 6B7EEDBB2251393900060872 /* EmojiCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EmojiCell.xib; sourceTree = ""; };
+ 6B7EEDBC2251393900060872 /* EmojiCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiCell.swift; sourceTree = ""; };
+ 6B7EEDC022513EA300060872 /* HeaderFooterSectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFooterSectionModel.swift; sourceTree = ""; };
+ 6B7EEDC6225142A500060872 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
+ 6B7EEDC82251431B00060872 /* HeaderFooterMoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFooterMoreView.swift; sourceTree = ""; };
+ 6B956B7A2110B38700DE3D29 /* HeaderFooterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterViewController.swift; sourceTree = ""; };
+ 6B956B7B2110B38700DE3D29 /* EmojiViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiViewController.swift; sourceTree = ""; };
6B956B7C2110B38700DE3D29 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
6B956B7E2110B38700DE3D29 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; };
6B956B802110B38700DE3D29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
6B956B812110B38700DE3D29 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
6B956B822110B38700DE3D29 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6BAE816D2278377E0060866E /* HeaderFooterMoreView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HeaderFooterMoreView.xib; sourceTree = ""; };
+ 6BAE816F2278393B0060866E /* HeaderFooterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFooterCell.swift; sourceTree = ""; };
+ 6BAE817222783C130060866E /* RandomLabelView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RandomLabelView.xib; sourceTree = ""; };
+ 6BAE817422783D1E0060866E /* RandomModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomModel.swift; sourceTree = ""; };
+ 6BAE817622783D810060866E /* RandomViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RandomViewController.xib; sourceTree = ""; };
+ 6BAE817822783EC80060866E /* RandomPlainCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomPlainCell.swift; sourceTree = ""; };
6BD9445C2136EDC100093E7A /* RandomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomViewController.swift; sourceTree = ""; };
- 6BD9445F2137062000093E7A /* TextCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCollectionReusableView.swift; sourceTree = ""; };
+ 6BD9445F2137062000093E7A /* RandomLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomLabelView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -119,15 +151,58 @@
name = Frameworks;
sourceTree = "";
};
- 6B956B792110B38700DE3D29 /* Sources */ = {
+ 6B7EEDA722512CA300060872 /* Home */ = {
isa = PBXGroup;
children = (
6B956B7E2110B38700DE3D29 /* HomeViewController.swift */,
- 6B956B7B2110B38700DE3D29 /* ShuffleEmoticonViewController.swift */,
- 6B956B7A2110B38700DE3D29 /* HeaderFooterSectionViewController.swift */,
- 3363C4F721502C44008EEDE4 /* String+Differentiable.swift */,
- 6BD9445C2136EDC100093E7A /* RandomViewController.swift */,
- 6BD9445F2137062000093E7A /* TextCollectionReusableView.swift */,
+ 6B7EEDB6225134FA00060872 /* HomeCell.swift */,
+ 6B7EEDB4225134F000060872 /* HomeCell.xib */,
+ );
+ path = Home;
+ sourceTree = "";
+ };
+ 6B7EEDAB2251304C00060872 /* Common */ = {
+ isa = PBXGroup;
+ children = (
+ 6B7EEDAC2251305400060872 /* NibLoadable.swift */,
+ 6B7EEDAE2251309000060872 /* Reusable.swift */,
+ 6B7EEDB02251315500060872 /* ReusableViewExtensions.swift */,
+ 6B7EEDC6225142A500060872 /* StringExtensions.swift */,
+ );
+ path = Common;
+ sourceTree = "";
+ };
+ 6B7EEDB82251366A00060872 /* ShuffleEmoticon */ = {
+ isa = PBXGroup;
+ children = (
+ 6B956B7B2110B38700DE3D29 /* EmojiViewController.swift */,
+ 6B7EEDB92251368700060872 /* EmojiViewController.xib */,
+ 6B7EEDBC2251393900060872 /* EmojiCell.swift */,
+ 6B7EEDBB2251393900060872 /* EmojiCell.xib */,
+ );
+ path = ShuffleEmoticon;
+ sourceTree = "";
+ };
+ 6B7EEDBF22513E6F00060872 /* HeaderFooter */ = {
+ isa = PBXGroup;
+ children = (
+ 6B956B7A2110B38700DE3D29 /* HeaderFooterViewController.swift */,
+ 6B7EEDC82251431B00060872 /* HeaderFooterMoreView.swift */,
+ 6BAE816D2278377E0060866E /* HeaderFooterMoreView.xib */,
+ 6B7EEDC022513EA300060872 /* HeaderFooterSectionModel.swift */,
+ 6BAE816F2278393B0060866E /* HeaderFooterCell.swift */,
+ );
+ path = HeaderFooter;
+ sourceTree = "";
+ };
+ 6B956B792110B38700DE3D29 /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ 6B7EEDAB2251304C00060872 /* Common */,
+ 6B7EEDA722512CA300060872 /* Home */,
+ 6B7EEDB82251366A00060872 /* ShuffleEmoticon */,
+ 6B7EEDBF22513E6F00060872 /* HeaderFooter */,
+ 6BAE817122783BC40060866E /* Random */,
6B956B812110B38700DE3D29 /* AppDelegate.swift */,
6B956B7C2110B38700DE3D29 /* Assets.xcassets */,
6B956B7F2110B38700DE3D29 /* LaunchScreen.storyboard */,
@@ -136,6 +211,19 @@
path = Sources;
sourceTree = "";
};
+ 6BAE817122783BC40060866E /* Random */ = {
+ isa = PBXGroup;
+ children = (
+ 6BD9445C2136EDC100093E7A /* RandomViewController.swift */,
+ 6BAE817622783D810060866E /* RandomViewController.xib */,
+ 6BAE817822783EC80060866E /* RandomPlainCell.swift */,
+ 6BD9445F2137062000093E7A /* RandomLabelView.swift */,
+ 6BAE817222783C130060866E /* RandomLabelView.xib */,
+ 6BAE817422783D1E0060866E /* RandomModel.swift */,
+ );
+ path = Random;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -220,7 +308,13 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 6B7EEDBD2251393900060872 /* EmojiCell.xib in Resources */,
+ 6BAE817722783D810060866E /* RandomViewController.xib in Resources */,
+ 6BAE816E2278377E0060866E /* HeaderFooterMoreView.xib in Resources */,
+ 6BAE817322783C130060866E /* RandomLabelView.xib in Resources */,
+ 6B7EEDBA2251368700060872 /* EmojiViewController.xib in Resources */,
6B956B852110B38700DE3D29 /* Assets.xcassets in Resources */,
+ 6B7EEDB5225134F000060872 /* HomeCell.xib in Resources */,
6B956B882110B38700DE3D29 /* LaunchScreen.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -232,12 +326,22 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 6B956B842110B38700DE3D29 /* ShuffleEmoticonViewController.swift in Sources */,
- 6B956B832110B38700DE3D29 /* HeaderFooterSectionViewController.swift in Sources */,
+ 6B956B842110B38700DE3D29 /* EmojiViewController.swift in Sources */,
+ 6B7EEDAD2251305400060872 /* NibLoadable.swift in Sources */,
+ 6B956B832110B38700DE3D29 /* HeaderFooterViewController.swift in Sources */,
+ 6B7EEDB7225134FA00060872 /* HomeCell.swift in Sources */,
+ 6B7EEDAF2251309000060872 /* Reusable.swift in Sources */,
6BD9445D2136EDC100093E7A /* RandomViewController.swift in Sources */,
+ 6BAE817922783EC80060866E /* RandomPlainCell.swift in Sources */,
+ 6B7EEDBE2251393900060872 /* EmojiCell.swift in Sources */,
+ 6B7EEDB12251315500060872 /* ReusableViewExtensions.swift in Sources */,
6B956B892110B38700DE3D29 /* AppDelegate.swift in Sources */,
- 3363C4F821502C45008EEDE4 /* String+Differentiable.swift in Sources */,
- 6BD944602137062000093E7A /* TextCollectionReusableView.swift in Sources */,
+ 6B7EEDC92251431B00060872 /* HeaderFooterMoreView.swift in Sources */,
+ 6B7EEDC122513EA300060872 /* HeaderFooterSectionModel.swift in Sources */,
+ 6BD944602137062000093E7A /* RandomLabelView.swift in Sources */,
+ 6BAE81702278393B0060866E /* HeaderFooterCell.swift in Sources */,
+ 6B7EEDC7225142A500060872 /* StringExtensions.swift in Sources */,
+ 6BAE817522783D1E0060866E /* RandomModel.swift in Sources */,
6B956B872110B38700DE3D29 /* HomeViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Examples/Example-iOS/Sources/AppDelegate.swift b/Examples/Example-iOS/Sources/AppDelegate.swift
index 3efd158..3bb334a 100644
--- a/Examples/Example-iOS/Sources/AppDelegate.swift
+++ b/Examples/Example-iOS/Sources/AppDelegate.swift
@@ -5,6 +5,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ configureUIAppearance()
+
let window = UIWindow()
let navigationController = UINavigationController(rootViewController: HomeViewController())
window.rootViewController = navigationController
@@ -12,4 +14,16 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
self.window = window
return true
}
+
+ func configureUIAppearance() {
+ let appearance = UINavigationBar.appearance()
+ let titleTextAttributes: [NSAttributedString.Key: Any] = [
+ .foregroundColor: UIColor.black
+ ]
+
+ appearance.tintColor = .darkText
+ appearance.prefersLargeTitles = true
+ appearance.titleTextAttributes = titleTextAttributes
+ appearance.largeTitleTextAttributes = titleTextAttributes
+ }
}
diff --git a/Examples/Example-iOS/Sources/Common/NibLoadable.swift b/Examples/Example-iOS/Sources/Common/NibLoadable.swift
new file mode 100644
index 0000000..d744609
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Common/NibLoadable.swift
@@ -0,0 +1,26 @@
+import UIKit
+
+protocol NibLoadable: class {
+ static var nibName: String { get }
+ static var nibBundle: Bundle? { get }
+}
+
+extension NibLoadable {
+ static var nib: UINib {
+ return UINib(nibName: nibName, bundle: nibBundle)
+ }
+
+ static var nibName: String {
+ return String(describing: self)
+ }
+
+ static var nibBundle: Bundle? {
+ return Bundle(for: self)
+ }
+}
+
+extension NibLoadable where Self: UIView {
+ static func loadFromNib() -> Self {
+ return nib.instantiate(withOwner: nil, options: nil).first as! Self
+ }
+}
diff --git a/Examples/Example-iOS/Sources/Common/Reusable.swift b/Examples/Example-iOS/Sources/Common/Reusable.swift
new file mode 100644
index 0000000..b84d337
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Common/Reusable.swift
@@ -0,0 +1,11 @@
+protocol Reusable: class {
+ static var reuseIdentifier: String { get }
+}
+
+extension Reusable {
+ static var reuseIdentifier: String {
+ return String(reflecting: self)
+ }
+}
+
+typealias NibReusable = NibLoadable & Reusable
diff --git a/Examples/Example-iOS/Sources/Common/ReusableViewExtensions.swift b/Examples/Example-iOS/Sources/Common/ReusableViewExtensions.swift
new file mode 100644
index 0000000..b9108de
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Common/ReusableViewExtensions.swift
@@ -0,0 +1,65 @@
+import UIKit
+
+extension UITableView {
+ func register(cellType: T.Type) where T: NibReusable {
+ register(T.nib, forCellReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(cellType: T.Type) where T: Reusable {
+ register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type) where T: NibReusable {
+ register(T.nib, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type) where T: Reusable {
+ register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T where T: Reusable {
+ guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return cell
+ }
+
+ func dequeueReusableHeaderFooterView(viewType: T.Type = T.self) -> T where T: Reusable {
+ guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
+ fatalError()
+ }
+ return view
+ }
+}
+
+extension UICollectionView {
+ func register(cellType: T.Type) where T: NibReusable {
+ register(T.nib, forCellWithReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(cellType: T.Type) where T: Reusable {
+ register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type, forSupplementaryViewOfKind kind: String) where T: NibReusable {
+ register(T.nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type, forSupplementaryViewOfKind kind: String) where T: Reusable {
+ register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T where T: Reusable {
+ guard let cell = dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return cell
+ }
+
+ func dequeueReusableSupplementaryView(ofKind kind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T where T: Reusable {
+ guard let view = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return view
+ }
+}
diff --git a/Examples/Example-iOS/Sources/String+Differentiable.swift b/Examples/Example-iOS/Sources/Common/StringExtensions.swift
similarity index 81%
rename from Examples/Example-iOS/Sources/String+Differentiable.swift
rename to Examples/Example-iOS/Sources/Common/StringExtensions.swift
index cc31c8d..32068bc 100644
--- a/Examples/Example-iOS/Sources/String+Differentiable.swift
+++ b/Examples/Example-iOS/Sources/Common/StringExtensions.swift
@@ -1,4 +1,3 @@
-import UIKit
import DifferenceKit
extension String: Differentiable {}
diff --git a/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterCell.swift b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterCell.swift
new file mode 100644
index 0000000..5d84cd8
--- /dev/null
+++ b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterCell.swift
@@ -0,0 +1,3 @@
+import UIKit
+
+final class HeaderFooterPlainCell: UITableViewCell, Reusable {}
diff --git a/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.swift b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.swift
new file mode 100644
index 0000000..a390839
--- /dev/null
+++ b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.swift
@@ -0,0 +1,9 @@
+import UIKit
+
+final class HeaderFooterMoreView: UITableViewHeaderFooterView, NibReusable {
+ var onMorePressed: (() -> Void)?
+
+ @IBAction func handleMorePressed() {
+ onMorePressed?()
+ }
+}
diff --git a/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.xib b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.xib
new file mode 100644
index 0000000..318b883
--- /dev/null
+++ b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterMoreView.xib
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterSectionModel.swift b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterSectionModel.swift
new file mode 100644
index 0000000..ad6860a
--- /dev/null
+++ b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterSectionModel.swift
@@ -0,0 +1,14 @@
+import DifferenceKit
+
+struct HeaderFooterSectionModel: Differentiable, Equatable {
+ var id: Int
+ var hasFooter: Bool
+
+ var differenceIdentifier: Int {
+ return id
+ }
+
+ var headerTitle: String {
+ return "Section \(id)"
+ }
+}
diff --git a/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterViewController.swift b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterViewController.swift
new file mode 100644
index 0000000..6ea1331
--- /dev/null
+++ b/Examples/Example-iOS/Sources/HeaderFooter/HeaderFooterViewController.swift
@@ -0,0 +1,90 @@
+import UIKit
+import DifferenceKit
+
+final class HeaderFooterViewController: UITableViewController {
+ typealias Section = ArraySection
+
+ private var data = [Section]()
+
+ private var dataInput: [Section] {
+ get { return data }
+ set {
+ let changeset = StagedChangeset(source: data, target: newValue)
+ tableView.reload(using: changeset, with: .fade) { data in
+ self.data = data
+ }
+ }
+ }
+
+ private let allTexts = (0x0041...0x005A).compactMap { UnicodeScalar($0).map(String.init) }
+
+ @objc private func refresh() {
+ let model = HeaderFooterSectionModel(id: 0, hasFooter: true)
+ let section = Section(model: model, elements: allTexts.prefix(7))
+ dataInput = [section]
+ }
+
+ private func showMore(in sectionIndex: Int) {
+ var section = dataInput[sectionIndex]
+ let texts = allTexts.dropFirst(section.elements.count).prefix(7)
+ section.elements.append(contentsOf: texts)
+ section.model.hasFooter = section.elements.count < allTexts.count
+ dataInput[sectionIndex] = section
+
+ let lastIndex = section.elements.index(before: section.elements.endIndex)
+ let lastIndexPath = IndexPath(row: lastIndex, section: sectionIndex)
+ tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ refresh()
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Header Footer"
+ tableView.allowsSelection = false
+
+ tableView.register(cellType: HeaderFooterPlainCell.self)
+ tableView.register(viewType: HeaderFooterMoreView.self)
+
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
+ }
+}
+
+extension HeaderFooterViewController {
+ override func numberOfSections(in tableView: UITableView) -> Int {
+ return data.count
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return data[section].elements.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell: HeaderFooterPlainCell = tableView.dequeueReusableCell(for: indexPath)
+ cell.textLabel?.text = data[indexPath.section].elements[indexPath.row]
+ return cell
+ }
+
+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+ return data[section].model.headerTitle
+ }
+
+ override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+ guard data[section].model.hasFooter else { return nil }
+
+ let view: HeaderFooterMoreView = tableView.dequeueReusableHeaderFooterView()
+ view.onMorePressed = { [weak self] in
+ self?.showMore(in: section)
+ }
+
+ return view
+ }
+
+ override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+ return data[section].model.hasFooter ? 44 : 0
+ }
+}
diff --git a/Examples/Example-iOS/Sources/HeaderFooterSectionViewController.swift b/Examples/Example-iOS/Sources/HeaderFooterSectionViewController.swift
deleted file mode 100644
index d7f549f..0000000
--- a/Examples/Example-iOS/Sources/HeaderFooterSectionViewController.swift
+++ /dev/null
@@ -1,147 +0,0 @@
-import UIKit
-import DifferenceKit
-
-private struct HeaderFooterSectionModel: Differentiable {
- var id: Int
- var hasFooter: Bool
-
- var headerTitle: String {
- return "Section \(id)"
- }
-
- var differenceIdentifier: Int {
- return id
- }
-
- func isContentEqual(to source: HeaderFooterSectionModel) -> Bool {
- return hasFooter == source.hasFooter
- }
-}
-
-private typealias HeaderFooterSection = ArraySection
-
-final class HeaderFooterSectionViewController: UITableViewController {
- private var data = [HeaderFooterSection]()
-
- private var dataInput: [HeaderFooterSection] {
- get { return data }
- set {
- let changeset = StagedChangeset(source: data, target: newValue)
- tableView.reload(using: changeset, with: .fade) { data in
- self.data = data
- }
- }
- }
-
- private let allTexts = (0x0041...0x005A).compactMap { UnicodeScalar($0).map(String.init) }
-
- @objc private func refresh() {
- let model = HeaderFooterSectionModel(id: 0, hasFooter: true)
- let section = HeaderFooterSection(model: model, elements: allTexts.prefix(7))
- dataInput = [section]
- }
-
- private func showMore(in sectionIndex: Int) {
- var section = dataInput[sectionIndex]
- let texts = allTexts.dropFirst(section.elements.count).prefix(7)
- section.elements.append(contentsOf: texts)
- section.model.hasFooter = section.elements.count < allTexts.count
- dataInput[sectionIndex] = section
-
- let lastIndex = section.elements.index(before: section.elements.endIndex)
- let lastIndexPath = IndexPath(row: lastIndex, section: sectionIndex)
- tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- refresh()
- }
-
- init() {
- super.init(style: .plain)
-
- title = "Header Footer Section"
- tableView.allowsSelection = false
- tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.reuseIdentifier)
- tableView.register(HeaderFooterSectionFooterView.self, forHeaderFooterViewReuseIdentifier: HeaderFooterSectionFooterView.reuseIdentifier)
-
- navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-extension HeaderFooterSectionViewController {
- override func numberOfSections(in tableView: UITableView) -> Int {
- return data.count
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return data[section].elements.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.reuseIdentifier, for: indexPath)
- cell.textLabel?.text = data[indexPath.section].elements[indexPath.row]
- return cell
- }
-
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- return data[section].model.headerTitle
- }
-
- override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
- guard data[section].model.hasFooter else { return nil }
-
- let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderFooterSectionFooterView.reuseIdentifier) as! HeaderFooterSectionFooterView
- view.morePressedAction = { [weak self] in
- self?.showMore(in: section)
- }
- return view
- }
-
- override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
- return data[section].model.hasFooter ? 44 : 0
- }
-}
-
-private final class HeaderFooterSectionFooterView: UITableViewHeaderFooterView {
- static let reuseIdentifier = String(describing: UITableViewCell.self)
-
- var morePressedAction: (() -> Void)?
-
- private let moreButton = UIButton(type: .system)
-
- override init(reuseIdentifier: String?) {
- super.init(reuseIdentifier: reuseIdentifier)
- contentView.addSubview(moreButton)
- moreButton.setTitle("More", for: .normal)
- moreButton.translatesAutoresizingMaskIntoConstraints = false
- moreButton.addTarget(self, action: #selector(morePressed), for: .touchUpInside)
-
- let constraints = [
- moreButton.topAnchor.constraint(equalTo: contentView.topAnchor),
- moreButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- moreButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- moreButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- @objc private func morePressed() {
- morePressedAction?()
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-private extension UITableViewCell {
- static let reuseIdentifier = String(describing: UITableViewCell.self)
-}
diff --git a/Examples/Example-iOS/Sources/Home/HomeCell.swift b/Examples/Example-iOS/Sources/Home/HomeCell.swift
new file mode 100644
index 0000000..58a911b
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Home/HomeCell.swift
@@ -0,0 +1,6 @@
+import UIKit
+
+final class HomeCell: UITableViewCell, NibReusable {
+ @IBOutlet weak var titleLabel: UILabel!
+ @IBOutlet weak var subtitleLabel: UILabel!
+}
diff --git a/Examples/Example-iOS/Sources/Home/HomeCell.xib b/Examples/Example-iOS/Sources/Home/HomeCell.xib
new file mode 100644
index 0000000..8bc5d6c
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Home/HomeCell.xib
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/Home/HomeViewController.swift b/Examples/Example-iOS/Sources/Home/HomeViewController.swift
new file mode 100644
index 0000000..59232d5
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Home/HomeViewController.swift
@@ -0,0 +1,46 @@
+import UIKit
+
+final class HomeViewController: UITableViewController {
+ struct Component {
+ var title: String
+ var subtitle: String
+ var initViewController: () -> UIViewController
+ }
+
+ private let components = [
+ Component(title: "Shuffle Emojis", subtitle: "Shuffle sectioned Emojis in UICollectionView", initViewController: EmojiViewController.init),
+ Component(title: "Header Footer Section", subtitle: "Update header/footer by reload section in UITableView", initViewController: HeaderFooterViewController.init),
+ Component(title: "Random", subtitle: "Random diff in UICollectionView", initViewController: RandomViewController.init)
+ ]
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Home"
+ tableView.tableFooterView = UIView()
+ tableView.register(cellType: HomeCell.self)
+ }
+}
+
+extension HomeViewController {
+ override func numberOfSections(in tableView: UITableView) -> Int {
+ return 1
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return components.count
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell: HomeCell = tableView.dequeueReusableCell(for: indexPath)
+ let component = components[indexPath.row]
+ cell.titleLabel.text = component.title
+ cell.subtitleLabel.text = component.subtitle
+ return cell
+ }
+
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let viewController = components[indexPath.row].initViewController()
+ navigationController?.pushViewController(viewController, animated: true)
+ }
+}
diff --git a/Examples/Example-iOS/Sources/HomeViewController.swift b/Examples/Example-iOS/Sources/HomeViewController.swift
deleted file mode 100644
index 2854e7b..0000000
--- a/Examples/Example-iOS/Sources/HomeViewController.swift
+++ /dev/null
@@ -1,51 +0,0 @@
-import UIKit
-
-final class HomeViewController: UITableViewController {
- private let data: [(cell: UITableViewCell, initViewController: () -> UIViewController)]
-
- init() {
- let tableComponents: [(title: String, subtitle: String, initViewController: () -> UIViewController)] = [
- (title: "Shuffle Emoticons", subtitle: "Shuffle sectioned emoticons in UICollectionView", initViewController: ShuffleEmoticonViewController.init),
- (title: "Header Footer Section", subtitle: "Update header/footer by reload section in UITableView", initViewController: HeaderFooterSectionViewController.init),
- (title: "Random", subtitle: "Random diff in UICollectionView", initViewController: RandomViewController.init)
- ]
-
- data = tableComponents.map { component in
- let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
- cell.textLabel?.text = component.title
- cell.detailTextLabel?.text = component.subtitle
- return (cell: cell, initViewController: component.initViewController)
- }
-
- super.init(style: .plain)
-
- title = "Home"
- view.backgroundColor = .white
- tableView.tableFooterView = UIView()
- tableView.reloadData()
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-extension HomeViewController {
- override func numberOfSections(in tableView: UITableView) -> Int {
- return 1
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return data.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- return data[indexPath.row].cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let viewController = data[indexPath.row].initViewController()
- navigationController?.pushViewController(viewController, animated: true)
- }
-}
diff --git a/Examples/Example-iOS/Sources/Random/RandomLabelView.swift b/Examples/Example-iOS/Sources/Random/RandomLabelView.swift
new file mode 100644
index 0000000..6cbaee2
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Random/RandomLabelView.swift
@@ -0,0 +1,5 @@
+import UIKit
+
+final class RandomLabelView: UICollectionReusableView, NibReusable {
+ @IBOutlet weak var label: UILabel!
+}
diff --git a/Examples/Example-iOS/Sources/Random/RandomLabelView.xib b/Examples/Example-iOS/Sources/Random/RandomLabelView.xib
new file mode 100644
index 0000000..f668225
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Random/RandomLabelView.xib
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/Random/RandomModel.swift b/Examples/Example-iOS/Sources/Random/RandomModel.swift
new file mode 100644
index 0000000..b95e8e4
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Random/RandomModel.swift
@@ -0,0 +1,20 @@
+import Foundation
+import DifferenceKit
+
+struct RandomModel: Differentiable {
+ var id: UUID
+ var isUpdated: Bool
+
+ var differenceIdentifier: UUID {
+ return id
+ }
+
+ init(_ id: UUID = UUID(), _ isUpdated: Bool = false) {
+ self.id = id
+ self.isUpdated = isUpdated
+ }
+
+ func isContentEqual(to source: RandomModel) -> Bool {
+ return isUpdated == source.isUpdated
+ }
+}
diff --git a/Examples/Example-iOS/Sources/Random/RandomPlainCell.swift b/Examples/Example-iOS/Sources/Random/RandomPlainCell.swift
new file mode 100644
index 0000000..30e0d86
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Random/RandomPlainCell.swift
@@ -0,0 +1,10 @@
+import UIKit
+
+final class RandomPlainCell: UICollectionViewCell, Reusable {
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ layer.masksToBounds = true
+ layer.cornerRadius = bounds.height / 2
+ }
+}
diff --git a/Examples/Example-iOS/Sources/RandomViewController.swift b/Examples/Example-iOS/Sources/Random/RandomViewController.swift
similarity index 68%
rename from Examples/Example-iOS/Sources/RandomViewController.swift
rename to Examples/Example-iOS/Sources/Random/RandomViewController.swift
index e3b366e..1672a38 100644
--- a/Examples/Example-iOS/Sources/RandomViewController.swift
+++ b/Examples/Example-iOS/Sources/Random/RandomViewController.swift
@@ -1,31 +1,13 @@
import UIKit
import DifferenceKit
-private struct RandomModel: Differentiable {
- var id: UUID
- var isUpdated: Bool
-
- var differenceIdentifier: UUID {
- return id
- }
-
- init(_ id: UUID = UUID(), _ isUpdated: Bool = false) {
- self.id = id
- self.isUpdated = isUpdated
- }
-
- func isContentEqual(to source: RandomModel) -> Bool {
- return isUpdated == source.isUpdated
- }
-}
-
-private typealias RandomSection = ArraySection
-
final class RandomViewController: UIViewController {
- private let collectionView: UICollectionView
- private var data = [RandomSection]()
+ private typealias Section = ArraySection
+
+ @IBOutlet private weak var collectionView: UICollectionView!
- private var dataInput: [RandomSection] {
+ private var data = [Section]()
+ private var dataInput: [Section] {
get { return data }
set {
let changeset = StagedChangeset(source: data, target: newValue)
@@ -35,6 +17,18 @@ final class RandomViewController: UIViewController {
}
}
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Random"
+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Random", style: .plain, target: self, action: #selector(refresh))
+
+ collectionView.dataSource = self
+ collectionView.delegate = self
+ collectionView.register(cellType: RandomPlainCell.self)
+ collectionView.register(viewType: RandomLabelView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader)
+ }
+
@objc private func refresh() {
let defaultSourceSectionCount = 20
let defaultSourceElementCount = 20
@@ -132,41 +126,6 @@ final class RandomViewController: UIViewController {
super.viewWillAppear(animated)
refresh()
}
-
- init() {
- let flowLayout = UICollectionViewFlowLayout()
- flowLayout.sectionInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
- flowLayout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.size.width, height: 30)
- collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
-
- super.init(nibName: nil, bundle: nil)
-
- title = "Random"
- view.backgroundColor = .white
- navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
-
- collectionView.backgroundColor = .clear
- collectionView.dataSource = self
- collectionView.delegate = self
- collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: UICollectionViewCell.reuseIdentifier)
- collectionView.register(TextCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: TextCollectionReusableView.reuseIdentifier)
- collectionView.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(collectionView)
-
- let constraints = [
- collectionView.topAnchor.constraint(equalTo: view.topAnchor),
- collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
- ]
-
- NSLayoutConstraint.activate(constraints)
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
}
extension RandomViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
@@ -179,9 +138,9 @@ extension RandomViewController: UICollectionViewDataSource, UICollectionViewDele
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UICollectionViewCell.reuseIdentifier, for: indexPath)
+ let cell: RandomPlainCell = collectionView.dequeueReusableCell(for: indexPath)
let model = data[indexPath.section].elements[indexPath.item]
- cell.contentView.backgroundColor = model.isUpdated ? .yellow : .red
+ cell.contentView.backgroundColor = model.isUpdated ? .cyan : .orange
return cell
}
@@ -191,12 +150,9 @@ extension RandomViewController: UICollectionViewDataSource, UICollectionViewDele
}
let model = data[indexPath.section].model
- let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TextCollectionReusableView.reuseIdentifier, for: indexPath) as! TextCollectionReusableView
- view.text = "Section \(model.id)" + (model.isUpdated ? "+" : "")
+ let view: RandomLabelView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, for: indexPath)
+ view.label.text = "Section ID: \(model.id)"
+ view.label.textColor = model.isUpdated ? .red : .darkText
return view
}
}
-
-private extension UICollectionViewCell {
- static let reuseIdentifier = String(describing: self)
-}
diff --git a/Examples/Example-iOS/Sources/Random/RandomViewController.xib b/Examples/Example-iOS/Sources/Random/RandomViewController.xib
new file mode 100644
index 0000000..56259fc
--- /dev/null
+++ b/Examples/Example-iOS/Sources/Random/RandomViewController.xib
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.swift b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.swift
new file mode 100644
index 0000000..8b21c97
--- /dev/null
+++ b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.swift
@@ -0,0 +1,11 @@
+import UIKit
+
+final class EmojiCell: UICollectionViewCell, NibReusable {
+ @IBOutlet weak var label: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ label.layer.cornerRadius = 8
+ }
+}
diff --git a/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.xib b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.xib
new file mode 100644
index 0000000..83bcdec
--- /dev/null
+++ b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiCell.xib
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.swift b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.swift
new file mode 100644
index 0000000..cef2280
--- /dev/null
+++ b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.swift
@@ -0,0 +1,87 @@
+import UIKit
+import DifferenceKit
+
+final class EmojiViewController: UIViewController {
+ enum SectionID: Differentiable, CaseIterable {
+ case first, second, third
+ }
+
+ typealias Section = ArraySection
+
+ @IBOutlet private weak var collectionView: UICollectionView!
+
+ private var data = [Section]()
+ private var dataInput: [Section] {
+ get { return data }
+ set {
+ let changeset = StagedChangeset(source: data, target: newValue)
+ collectionView.reload(using: changeset) { data in
+ self.data = data
+ }
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Emoji"
+ collectionView.delegate = self
+ collectionView.dataSource = self
+ collectionView.register(cellType: EmojiCell.self)
+
+ refresh()
+ }
+
+ @IBAction func refresh() {
+ let ids = SectionID.allCases
+ let Emojis = (0x1F600...0x1F647).compactMap { UnicodeScalar($0).map(String.init) }
+ let splitedCount = Int((Double(Emojis.count) / Double(ids.count)).rounded(.up))
+
+ dataInput = ids.enumerated().map { offset, model in
+ let start = offset * splitedCount
+ let end = min(start + splitedCount, Emojis.endIndex)
+ let Emojis = Emojis[start.. Int {
+ return data.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return data[section].elements.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cell: EmojiCell = collectionView.dequeueReusableCell(for: indexPath)
+ cell.label.text = data[indexPath.section].elements[indexPath.item]
+ return cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ remove(at: indexPath)
+ }
+}
diff --git a/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.xib b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.xib
new file mode 100644
index 0000000..35d1b30
--- /dev/null
+++ b/Examples/Example-iOS/Sources/ShuffleEmoticon/EmojiViewController.xib
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-iOS/Sources/ShuffleEmoticonViewController.swift b/Examples/Example-iOS/Sources/ShuffleEmoticonViewController.swift
deleted file mode 100644
index d5b0b44..0000000
--- a/Examples/Example-iOS/Sources/ShuffleEmoticonViewController.swift
+++ /dev/null
@@ -1,192 +0,0 @@
-import UIKit
-import DifferenceKit
-
-private enum EmoticonSectionID: Differentiable, CaseIterable {
- case first, second, third
-}
-
-private typealias EmoticonSection = ArraySection
-
-final class ShuffleEmoticonViewController: UIViewController {
- private let collectionView: UICollectionView
- private var data = [EmoticonSection]()
-
- private var dataInput: [EmoticonSection] {
- get { return data }
- set {
- let changeset = StagedChangeset(source: data, target: newValue)
- collectionView.reload(using: changeset) { data in
- self.data = data
- }
- }
- }
-
- private func remove(at indexPath: IndexPath) {
- dataInput[indexPath.section].elements.remove(at: indexPath.item)
- }
-
- @objc private func shuffleSections() {
- dataInput.shuffle()
- }
-
- @objc private func shuffleAllEmoticons() {
- var flattenEmoticons = ArraySlice(dataInput.flatMap { $0.elements })
- flattenEmoticons.shuffle()
-
- dataInput = dataInput.map { section in
- var section = section
- section.elements = Array(flattenEmoticons.prefix(section.elements.count))
- flattenEmoticons.removeFirst(section.elements.count)
- return section
- }
- }
-
- @objc private func refresh() {
- let ids = EmoticonSectionID.allCases
- let emoticons = (0x1F600...0x1F647).compactMap { UnicodeScalar($0).map(String.init) }
- let splitedCount = Int((Double(emoticons.count) / Double(ids.count)).rounded(.up))
-
- dataInput = ids.enumerated().map { offset, model in
- let start = offset * splitedCount
- let end = min(start + splitedCount, emoticons.endIndex)
- let emoticons = emoticons[start.. Int {
- return data.count
- }
-
- func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- return data[section].elements.count
- }
-
- func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmoticonCollectionViewCell.reuseIdentifier, for: indexPath) as! EmoticonCollectionViewCell
- let emoticon = data[indexPath.section].elements[indexPath.item]
- cell.emoticon = emoticon
- return cell
- }
-
- func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
- guard kind == UICollectionView.elementKindSectionHeader else {
- return UICollectionReusableView()
- }
-
- let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TextCollectionReusableView.reuseIdentifier, for: indexPath) as! TextCollectionReusableView
- view.text = "Section \(data[indexPath.section].model)"
- return view
- }
-
- func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- remove(at: indexPath)
- }
-}
-
-private final class EmoticonCollectionViewCell: UICollectionViewCell {
- static let reuseIdentifier = String(describing: self)
-
- var emoticon: String? {
- get { return label.text }
- set { label.text = newValue }
- }
-
- private let label = UILabel()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- contentView.backgroundColor = UIColor.black.withAlphaComponent(0.05)
- contentView.layer.cornerRadius = 8
-
- contentView.addSubview(label)
- label.textAlignment = .center
- label.translatesAutoresizingMaskIntoConstraints = false
-
- let constraints = [
- label.topAnchor.constraint(equalTo: contentView.topAnchor),
- label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- override var isHighlighted: Bool {
- didSet { alpha = isHighlighted ? 0.2 : 1 }
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
diff --git a/Examples/Example-iOS/Sources/TextCollectionReusableView.swift b/Examples/Example-iOS/Sources/TextCollectionReusableView.swift
deleted file mode 100644
index 339805e..0000000
--- a/Examples/Example-iOS/Sources/TextCollectionReusableView.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import UIKit
-
-final class TextCollectionReusableView: UICollectionReusableView {
- static let reuseIdentifier = String(describing: self)
-
- var text: String? {
- get { return label.text }
- set { label.text = newValue }
- }
-
- private let label = UILabel()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- backgroundColor = UIColor.black.withAlphaComponent(0.05)
- addSubview(label)
- label.translatesAutoresizingMaskIntoConstraints = false
-
- let constraints = [
- label.topAnchor.constraint(equalTo: topAnchor),
- label.bottomAnchor.constraint(equalTo: bottomAnchor),
- label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
- label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
From 255b0f46f31324212732547628bdcca38508d7bc Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Tue, 30 Apr 2019 18:58:16 +0900
Subject: [PATCH 02/20] Update macOS example
---
.../Example-macOS.xcodeproj/project.pbxproj | 12 ++++--
.../Sources/Base.lproj/MainMenu.xib | 17 ++++----
.../ShuffleEmoticonCollectionViewItem.swift | 36 +++++++++++++++++
.../ShuffleEmoticonViewController.swift | 40 ++-----------------
...rentiable.swift => StringExtensions.swift} | 0
5 files changed, 56 insertions(+), 49 deletions(-)
create mode 100644 Examples/Example-macOS/Sources/ShuffleEmoticonCollectionViewItem.swift
rename Examples/Example-macOS/Sources/{String+Differentiable.swift => StringExtensions.swift} (100%)
diff --git a/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj b/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj
index 2bc43f7..e3d2b79 100644
--- a/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj
+++ b/Examples/Example-macOS/Example-macOS.xcodeproj/project.pbxproj
@@ -7,7 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
- 6BE7D4F5215225A600D2F8E9 /* String+Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE7D4F4215225A600D2F8E9 /* String+Differentiable.swift */; };
+ 6BAE817D227853360060866E /* ShuffleEmoticonCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE817C227853360060866E /* ShuffleEmoticonCollectionViewItem.swift */; };
+ 6BE7D4F5215225A600D2F8E9 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE7D4F4215225A600D2F8E9 /* StringExtensions.swift */; };
750C1E6E21515AF800034704 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750C1E6D21515AF800034704 /* AppDelegate.swift */; };
750C1E7021515AFB00034704 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 750C1E6F21515AFB00034704 /* Assets.xcassets */; };
750C1E7321515AFB00034704 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 750C1E7121515AFB00034704 /* MainMenu.xib */; };
@@ -55,7 +56,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 6BE7D4F4215225A600D2F8E9 /* String+Differentiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Differentiable.swift"; sourceTree = ""; };
+ 6BAE817C227853360060866E /* ShuffleEmoticonCollectionViewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShuffleEmoticonCollectionViewItem.swift; sourceTree = ""; };
+ 6BE7D4F4215225A600D2F8E9 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
750C1E6A21515AF800034704 /* Example-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
750C1E6D21515AF800034704 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
750C1E6F21515AFB00034704 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -99,7 +101,8 @@
children = (
750C1E6D21515AF800034704 /* AppDelegate.swift */,
750C1E7B21515BCC00034704 /* ShuffleEmoticonViewController.swift */,
- 6BE7D4F4215225A600D2F8E9 /* String+Differentiable.swift */,
+ 6BAE817C227853360060866E /* ShuffleEmoticonCollectionViewItem.swift */,
+ 6BE7D4F4215225A600D2F8E9 /* StringExtensions.swift */,
750C1E6F21515AFB00034704 /* Assets.xcassets */,
750C1E7121515AFB00034704 /* MainMenu.xib */,
750C1E7421515AFB00034704 /* Info.plist */,
@@ -211,9 +214,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 6BE7D4F5215225A600D2F8E9 /* String+Differentiable.swift in Sources */,
+ 6BE7D4F5215225A600D2F8E9 /* StringExtensions.swift in Sources */,
750C1E7C21515BCC00034704 /* ShuffleEmoticonViewController.swift in Sources */,
750C1E6E21515AF800034704 /* AppDelegate.swift in Sources */,
+ 6BAE817D227853360060866E /* ShuffleEmoticonCollectionViewItem.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Examples/Example-macOS/Sources/Base.lproj/MainMenu.xib b/Examples/Example-macOS/Sources/Base.lproj/MainMenu.xib
index 450f0aa..391f348 100644
--- a/Examples/Example-macOS/Sources/Base.lproj/MainMenu.xib
+++ b/Examples/Example-macOS/Sources/Base.lproj/MainMenu.xib
@@ -1,7 +1,8 @@
-
+
-
+
+
@@ -634,7 +635,7 @@
-
+
@@ -716,7 +717,7 @@
-
+
@@ -735,11 +736,11 @@
-
+
-
+
@@ -814,11 +815,11 @@
-
+
-
+
diff --git a/Examples/Example-macOS/Sources/ShuffleEmoticonCollectionViewItem.swift b/Examples/Example-macOS/Sources/ShuffleEmoticonCollectionViewItem.swift
new file mode 100644
index 0000000..2603d0d
--- /dev/null
+++ b/Examples/Example-macOS/Sources/ShuffleEmoticonCollectionViewItem.swift
@@ -0,0 +1,36 @@
+import Cocoa
+
+final class ShuffleEmoticonCollectionViewItem: NSCollectionViewItem {
+ static var itemIdentifier: NSUserInterfaceItemIdentifier {
+ return NSUserInterfaceItemIdentifier(String(describing: self))
+ }
+
+ var emoticon: String {
+ get { return _textField.stringValue }
+ set { _textField.stringValue = newValue }
+ }
+
+ private let _textField = NSTextField()
+
+ override func loadView() {
+ view = NSView(frame: NSRect(x: 0, y: 0, width: 60, height: 54))
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ _textField.font = .systemFont(ofSize: 40)
+ _textField.alignment = .center
+ _textField.isEditable = false
+
+ _textField.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(_textField)
+
+ let constraints = [
+ _textField.topAnchor.constraint(equalTo: view.topAnchor),
+ _textField.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ _textField.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ _textField.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+ ]
+ NSLayoutConstraint.activate(constraints)
+ }
+}
diff --git a/Examples/Example-macOS/Sources/ShuffleEmoticonViewController.swift b/Examples/Example-macOS/Sources/ShuffleEmoticonViewController.swift
index 93f59f0..eb5308d 100755
--- a/Examples/Example-macOS/Sources/ShuffleEmoticonViewController.swift
+++ b/Examples/Example-macOS/Sources/ShuffleEmoticonViewController.swift
@@ -26,7 +26,8 @@ final class ShuffleEmoticonViewController: NSViewController {
override func awakeFromNib() {
super.awakeFromNib()
- collectionView.register(EmoticonCollectionViewItem.self, forItemWithIdentifier: EmoticonCollectionViewItem.itemIdentifier)
+ tableView.selectionHighlightStyle = .none
+ collectionView.register(ShuffleEmoticonCollectionViewItem.self, forItemWithIdentifier: ShuffleEmoticonCollectionViewItem.itemIdentifier)
}
}
@@ -40,7 +41,7 @@ extension ShuffleEmoticonViewController: NSCollectionViewDataSource {
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
- let item = collectionView.makeItem(withIdentifier: EmoticonCollectionViewItem.itemIdentifier, for: indexPath) as! EmoticonCollectionViewItem
+ let item = collectionView.makeItem(withIdentifier: ShuffleEmoticonCollectionViewItem.itemIdentifier, for: indexPath) as! ShuffleEmoticonCollectionViewItem
item.emoticon = data[indexPath.item]
return item
}
@@ -63,38 +64,3 @@ private extension NSTableCellView {
return NSUserInterfaceItemIdentifier(String(describing: self))
}
}
-
-private final class EmoticonCollectionViewItem: NSCollectionViewItem {
- static var itemIdentifier: NSUserInterfaceItemIdentifier {
- return NSUserInterfaceItemIdentifier(String(describing: self))
- }
-
- var emoticon: String {
- get { return _textField.stringValue }
- set { _textField.stringValue = newValue }
- }
-
- private let _textField = NSTextField()
-
- override func loadView() {
- view = NSView(frame: NSRect(x: 0, y: 0, width: 60, height: 54))
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
- _textField.font = .systemFont(ofSize: 40)
- _textField.alignment = .center
- _textField.isEditable = false
-
- _textField.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(_textField)
-
- let constraints = [
- _textField.topAnchor.constraint(equalTo: view.topAnchor),
- _textField.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- _textField.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- _textField.trailingAnchor.constraint(equalTo: view.trailingAnchor)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-}
diff --git a/Examples/Example-macOS/Sources/String+Differentiable.swift b/Examples/Example-macOS/Sources/StringExtensions.swift
similarity index 100%
rename from Examples/Example-macOS/Sources/String+Differentiable.swift
rename to Examples/Example-macOS/Sources/StringExtensions.swift
From 1fd5738e8491dab2a168ffc7e787378942f5ebea Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Thu, 2 May 2019 22:17:49 +0900
Subject: [PATCH 03/20] Update tvOS example
---
.../Example-tvOS.xcodeproj/project.pbxproj | 50 ++--
.../Example-tvOS/Sources/AppDelegate.swift | 2 +-
Examples/Example-tvOS/Sources/EmojiCell.swift | 26 ++
Examples/Example-tvOS/Sources/EmojiCell.xib | 45 ++++
.../Sources/EmojiViewController.swift | 89 +++++++
.../Sources/EmojiViewController.xib | 69 +++++
.../HeaderFooterSectionViewController.swift | 147 -----------
.../Sources/HomeViewController.swift | 49 ----
.../Example-tvOS/Sources/NibLoadable.swift | 26 ++
Examples/Example-tvOS/Sources/Reusable.swift | 11 +
.../Sources/ReusableViewExtensions.swift | 65 +++++
.../ShuffleEmoticonViewController.swift | 239 ------------------
...rentiable.swift => StringExtensions.swift} | 1 -
13 files changed, 365 insertions(+), 454 deletions(-)
create mode 100644 Examples/Example-tvOS/Sources/EmojiCell.swift
create mode 100644 Examples/Example-tvOS/Sources/EmojiCell.xib
create mode 100644 Examples/Example-tvOS/Sources/EmojiViewController.swift
create mode 100644 Examples/Example-tvOS/Sources/EmojiViewController.xib
delete mode 100644 Examples/Example-tvOS/Sources/HeaderFooterSectionViewController.swift
delete mode 100644 Examples/Example-tvOS/Sources/HomeViewController.swift
create mode 100644 Examples/Example-tvOS/Sources/NibLoadable.swift
create mode 100644 Examples/Example-tvOS/Sources/Reusable.swift
create mode 100644 Examples/Example-tvOS/Sources/ReusableViewExtensions.swift
delete mode 100644 Examples/Example-tvOS/Sources/ShuffleEmoticonViewController.swift
rename Examples/Example-tvOS/Sources/{String+Differentiable.swift => StringExtensions.swift} (81%)
diff --git a/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj
index 33846e0..63d67cb 100644
--- a/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj
+++ b/Examples/Example-tvOS/Example-tvOS.xcodeproj/project.pbxproj
@@ -9,12 +9,16 @@
/* Begin PBXBuildFile section */
6B2DF96C210F1A0D004D2D40 /* DifferenceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DF95C210F185E004D2D40 /* DifferenceKit.framework */; };
6B2DF96D210F1A0D004D2D40 /* DifferenceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6B2DF95C210F185E004D2D40 /* DifferenceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 6B956B952110B4AA00DE3D29 /* HeaderFooterSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B8E2110B4AA00DE3D29 /* HeaderFooterSectionViewController.swift */; };
- 6B956B962110B4AA00DE3D29 /* ShuffleEmoticonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B8F2110B4AA00DE3D29 /* ShuffleEmoticonViewController.swift */; };
+ 6B956B962110B4AA00DE3D29 /* EmojiViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B8F2110B4AA00DE3D29 /* EmojiViewController.swift */; };
6B956B972110B4AA00DE3D29 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B956B902110B4AA00DE3D29 /* Assets.xcassets */; };
- 6B956B982110B4AA00DE3D29 /* String+Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B912110B4AA00DE3D29 /* String+Differentiable.swift */; };
- 6B956B992110B4AA00DE3D29 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B922110B4AA00DE3D29 /* HomeViewController.swift */; };
6B956B9A2110B4AA00DE3D29 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B956B932110B4AA00DE3D29 /* AppDelegate.swift */; };
+ 6BAE8186227854C20060866E /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE8182227854C20060866E /* Reusable.swift */; };
+ 6BAE8187227854C20060866E /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE8183227854C20060866E /* StringExtensions.swift */; };
+ 6BAE8188227854C20060866E /* ReusableViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE8184227854C20060866E /* ReusableViewExtensions.swift */; };
+ 6BAE8189227854C20060866E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE8185227854C20060866E /* NibLoadable.swift */; };
+ 6BAE818F2278550C0060866E /* EmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAE818C2278550C0060866E /* EmojiCell.swift */; };
+ 6BAE8195227855E90060866E /* EmojiViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BAE8194227855E90060866E /* EmojiViewController.xib */; };
+ 6BAE8197227858440060866E /* EmojiCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6BAE8196227858440060866E /* EmojiCell.xib */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -58,13 +62,17 @@
/* Begin PBXFileReference section */
6B2DF943210F184A004D2D40 /* Example-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
6B2DF955210F185E004D2D40 /* DifferenceKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DifferenceKit.xcodeproj; path = ../../DifferenceKit.xcodeproj; sourceTree = ""; };
- 6B956B8E2110B4AA00DE3D29 /* HeaderFooterSectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterSectionViewController.swift; sourceTree = ""; };
- 6B956B8F2110B4AA00DE3D29 /* ShuffleEmoticonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShuffleEmoticonViewController.swift; sourceTree = ""; };
+ 6B956B8F2110B4AA00DE3D29 /* EmojiViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiViewController.swift; sourceTree = ""; };
6B956B902110B4AA00DE3D29 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 6B956B912110B4AA00DE3D29 /* String+Differentiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Differentiable.swift"; sourceTree = ""; };
- 6B956B922110B4AA00DE3D29 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; };
6B956B932110B4AA00DE3D29 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
6B956B942110B4AA00DE3D29 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6BAE8182227854C20060866E /* Reusable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reusable.swift; sourceTree = ""; };
+ 6BAE8183227854C20060866E /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
+ 6BAE8184227854C20060866E /* ReusableViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableViewExtensions.swift; sourceTree = ""; };
+ 6BAE8185227854C20060866E /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; };
+ 6BAE818C2278550C0060866E /* EmojiCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiCell.swift; sourceTree = ""; };
+ 6BAE8194227855E90060866E /* EmojiViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiViewController.xib; sourceTree = ""; };
+ 6BAE8196227858440060866E /* EmojiCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiCell.xib; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -108,12 +116,16 @@
6B956B8D2110B4AA00DE3D29 /* Sources */ = {
isa = PBXGroup;
children = (
- 6B956B8E2110B4AA00DE3D29 /* HeaderFooterSectionViewController.swift */,
- 6B956B8F2110B4AA00DE3D29 /* ShuffleEmoticonViewController.swift */,
- 6B956B902110B4AA00DE3D29 /* Assets.xcassets */,
- 6B956B912110B4AA00DE3D29 /* String+Differentiable.swift */,
- 6B956B922110B4AA00DE3D29 /* HomeViewController.swift */,
+ 6B956B8F2110B4AA00DE3D29 /* EmojiViewController.swift */,
+ 6BAE8194227855E90060866E /* EmojiViewController.xib */,
+ 6BAE818C2278550C0060866E /* EmojiCell.swift */,
+ 6BAE8196227858440060866E /* EmojiCell.xib */,
+ 6BAE8182227854C20060866E /* Reusable.swift */,
+ 6BAE8183227854C20060866E /* StringExtensions.swift */,
+ 6BAE8184227854C20060866E /* ReusableViewExtensions.swift */,
+ 6BAE8185227854C20060866E /* NibLoadable.swift */,
6B956B932110B4AA00DE3D29 /* AppDelegate.swift */,
+ 6B956B902110B4AA00DE3D29 /* Assets.xcassets */,
6B956B942110B4AA00DE3D29 /* Info.plist */,
);
path = Sources;
@@ -204,6 +216,8 @@
buildActionMask = 2147483647;
files = (
6B956B972110B4AA00DE3D29 /* Assets.xcassets in Resources */,
+ 6BAE8195227855E90060866E /* EmojiViewController.xib in Resources */,
+ 6BAE8197227858440060866E /* EmojiCell.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -214,11 +228,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 6B956B962110B4AA00DE3D29 /* ShuffleEmoticonViewController.swift in Sources */,
- 6B956B982110B4AA00DE3D29 /* String+Differentiable.swift in Sources */,
- 6B956B952110B4AA00DE3D29 /* HeaderFooterSectionViewController.swift in Sources */,
+ 6BAE8186227854C20060866E /* Reusable.swift in Sources */,
+ 6BAE818F2278550C0060866E /* EmojiCell.swift in Sources */,
+ 6B956B962110B4AA00DE3D29 /* EmojiViewController.swift in Sources */,
+ 6BAE8189227854C20060866E /* NibLoadable.swift in Sources */,
+ 6BAE8187227854C20060866E /* StringExtensions.swift in Sources */,
6B956B9A2110B4AA00DE3D29 /* AppDelegate.swift in Sources */,
- 6B956B992110B4AA00DE3D29 /* HomeViewController.swift in Sources */,
+ 6BAE8188227854C20060866E /* ReusableViewExtensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Examples/Example-tvOS/Sources/AppDelegate.swift b/Examples/Example-tvOS/Sources/AppDelegate.swift
index 3efd158..1ce0d4f 100644
--- a/Examples/Example-tvOS/Sources/AppDelegate.swift
+++ b/Examples/Example-tvOS/Sources/AppDelegate.swift
@@ -6,7 +6,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow()
- let navigationController = UINavigationController(rootViewController: HomeViewController())
+ let navigationController = UINavigationController(rootViewController: EmojiViewController())
window.rootViewController = navigationController
window.makeKeyAndVisible()
self.window = window
diff --git a/Examples/Example-tvOS/Sources/EmojiCell.swift b/Examples/Example-tvOS/Sources/EmojiCell.swift
new file mode 100644
index 0000000..ac935a7
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/EmojiCell.swift
@@ -0,0 +1,26 @@
+import UIKit
+
+final class EmojiCell: UICollectionViewCell, NibReusable {
+ @IBOutlet weak var label: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ contentView.backgroundColor = UIColor(white: 0.95, alpha: 1)
+ contentView.layer.cornerRadius = 8
+ contentView.layer.shadowOffset = CGSize(width: 0, height: 7)
+ }
+
+ override var isHighlighted: Bool {
+ didSet { alpha = isHidden ? 0.2 : 1 }
+ }
+
+ override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
+ coordinator.addCoordinatedAnimations({ [weak self] in
+ guard let self = self else { return }
+ self.contentView.layer.shadowOpacity = self.isFocused ? 0.3 : 0
+ self.contentView.layer.transform = self.isFocused ? CATransform3DMakeScale(1.1, 1.1, 1) : CATransform3DIdentity
+ self.layer.zPosition = self.isFocused ? 1 : 0
+ })
+ }
+}
diff --git a/Examples/Example-tvOS/Sources/EmojiCell.xib b/Examples/Example-tvOS/Sources/EmojiCell.xib
new file mode 100644
index 0000000..4268555
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/EmojiCell.xib
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-tvOS/Sources/EmojiViewController.swift b/Examples/Example-tvOS/Sources/EmojiViewController.swift
new file mode 100644
index 0000000..3b9bca5
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/EmojiViewController.swift
@@ -0,0 +1,89 @@
+import UIKit
+import DifferenceKit
+
+final class EmojiViewController: UIViewController {
+ enum SectionID: Differentiable, CaseIterable {
+ case first, second, third
+ }
+
+ typealias Section = ArraySection
+
+ @IBOutlet private weak var collectionView: UICollectionView!
+
+ private var data = [Section]()
+ private var dataInput: [Section] {
+ get { return data }
+ set {
+ let changeset = StagedChangeset(source: data, target: newValue)
+ collectionView.reload(using: changeset) { data in
+ self.data = data
+ }
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "Emoji"
+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
+
+ collectionView.delegate = self
+ collectionView.dataSource = self
+ collectionView.register(cellType: EmojiCell.self)
+
+ refresh()
+ }
+
+ @IBAction func refresh() {
+ let ids = SectionID.allCases
+ let Emojis = (0x1F600...0x1F647).compactMap { UnicodeScalar($0).map(String.init) }
+ let splitedCount = Int((Double(Emojis.count) / Double(ids.count)).rounded(.up))
+
+ dataInput = ids.enumerated().map { offset, model in
+ let start = offset * splitedCount
+ let end = min(start + splitedCount, Emojis.endIndex)
+ let Emojis = Emojis[start.. Int {
+ return data.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return data[section].elements.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cell: EmojiCell = collectionView.dequeueReusableCell(for: indexPath)
+ cell.label.text = data[indexPath.section].elements[indexPath.item]
+ return cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ remove(at: indexPath)
+ }
+}
diff --git a/Examples/Example-tvOS/Sources/EmojiViewController.xib b/Examples/Example-tvOS/Sources/EmojiViewController.xib
new file mode 100644
index 0000000..22cda1c
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/EmojiViewController.xib
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/Example-tvOS/Sources/HeaderFooterSectionViewController.swift b/Examples/Example-tvOS/Sources/HeaderFooterSectionViewController.swift
deleted file mode 100644
index 145e45a..0000000
--- a/Examples/Example-tvOS/Sources/HeaderFooterSectionViewController.swift
+++ /dev/null
@@ -1,147 +0,0 @@
-import UIKit
-import DifferenceKit
-
-private struct HeaderFooterSectionModel: Differentiable {
- var id: Int
- var hasFooter: Bool
-
- var headerTitle: String {
- return "Section \(id)"
- }
-
- var differenceIdentifier: Int {
- return id
- }
-
- func isContentEqual(to source: HeaderFooterSectionModel) -> Bool {
- return hasFooter == source.hasFooter
- }
-}
-
-private typealias HeaderFooterSection = ArraySection
-
-final class HeaderFooterSectionViewController: UITableViewController {
- private var data = [HeaderFooterSection]()
-
- private var dataInput: [HeaderFooterSection] {
- get { return data }
- set {
- let changeset = StagedChangeset(source: data, target: newValue)
- tableView.reload(using: changeset, with: .fade) { data in
- self.data = data
- }
- }
- }
-
- private let allTexts = (0x0041...0x005A).compactMap { UnicodeScalar($0).map(String.init) }
-
- @objc private func refresh() {
- let model = HeaderFooterSectionModel(id: 0, hasFooter: true)
- let section = HeaderFooterSection(model: model, elements: allTexts.prefix(7))
- dataInput = [section]
- }
-
- private func showMore(in sectionIndex: Int) {
- var section = dataInput[sectionIndex]
- let texts = allTexts.dropFirst(section.elements.count).prefix(7)
- section.elements.append(contentsOf: texts)
- section.model.hasFooter = section.elements.count < allTexts.count
- dataInput[sectionIndex] = section
-
- let lastIndex = section.elements.index(before: section.elements.endIndex)
- let lastIndexPath = IndexPath(row: lastIndex, section: sectionIndex)
- tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- refresh()
- }
-
- init() {
- super.init(style: .plain)
-
- title = "Header Footer Section"
- tableView.allowsSelection = false
- tableView.register(UITableViewCell.self, forCellReuseIdentifier: UITableViewCell.reuseIdentifier)
- tableView.register(HeaderFooterSectionFooterView.self, forHeaderFooterViewReuseIdentifier: HeaderFooterSectionFooterView.reuseIdentifier)
-
- navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-extension HeaderFooterSectionViewController {
- override func numberOfSections(in tableView: UITableView) -> Int {
- return data.count
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return data[section].elements.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: UITableViewCell.reuseIdentifier, for: indexPath)
- cell.textLabel?.text = data[indexPath.section].elements[indexPath.row]
- return cell
- }
-
- override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
- return data[section].model.headerTitle
- }
-
- override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
- guard data[section].model.hasFooter else { return nil }
-
- let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: HeaderFooterSectionFooterView.reuseIdentifier) as! HeaderFooterSectionFooterView
- view.morePressedAction = { [weak self] in
- self?.showMore(in: section)
- }
- return view
- }
-
- override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
- return data[section].model.hasFooter ? 80 : 0
- }
-}
-
-private final class HeaderFooterSectionFooterView: UITableViewHeaderFooterView {
- static let reuseIdentifier = String(describing: UITableViewCell.self)
-
- var morePressedAction: (() -> Void)?
-
- private let moreButton = UIButton(type: .system)
-
- override init(reuseIdentifier: String?) {
- super.init(reuseIdentifier: reuseIdentifier)
- contentView.addSubview(moreButton)
- moreButton.setTitle("More", for: .normal)
- moreButton.translatesAutoresizingMaskIntoConstraints = false
- moreButton.addTarget(self, action: #selector(morePressed), for: .primaryActionTriggered)
-
- let constraints = [
- moreButton.topAnchor.constraint(equalTo: contentView.topAnchor),
- moreButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- moreButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- moreButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- @objc private func morePressed() {
- morePressedAction?()
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-private extension UITableViewCell {
- static let reuseIdentifier = String(describing: UITableViewCell.self)
-}
diff --git a/Examples/Example-tvOS/Sources/HomeViewController.swift b/Examples/Example-tvOS/Sources/HomeViewController.swift
deleted file mode 100644
index 0817706..0000000
--- a/Examples/Example-tvOS/Sources/HomeViewController.swift
+++ /dev/null
@@ -1,49 +0,0 @@
-import UIKit
-
-final class HomeViewController: UITableViewController {
- private let data: [(cell: UITableViewCell, initViewController: () -> UIViewController)]
-
- init() {
- let tableComponents: [(title: String, subtitle: String, initViewController: () -> UIViewController)] = [
- (title: "Shuffle Emoticons", subtitle: "Diff of shuffled emoticons in UICollectionView", initViewController: ShuffleEmoticonViewController.init),
- (title: "Header Footer Section", subtitle: "Diff including section header/footer in UITableView", initViewController: HeaderFooterSectionViewController.init)
- ]
-
- data = tableComponents.map { component in
- let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
- cell.textLabel?.text = component.title
- cell.detailTextLabel?.text = component.subtitle
- return (cell: cell, initViewController: component.initViewController)
- }
-
- super.init(style: .plain)
-
- title = "Home"
- view.backgroundColor = .white
- tableView.tableFooterView = UIView()
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-extension HomeViewController {
- override func numberOfSections(in tableView: UITableView) -> Int {
- return 1
- }
-
- override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return data.count
- }
-
- override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- return data[indexPath.row].cell
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let viewController = data[indexPath.row].initViewController()
- navigationController?.pushViewController(viewController, animated: true)
- }
-}
diff --git a/Examples/Example-tvOS/Sources/NibLoadable.swift b/Examples/Example-tvOS/Sources/NibLoadable.swift
new file mode 100644
index 0000000..d744609
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/NibLoadable.swift
@@ -0,0 +1,26 @@
+import UIKit
+
+protocol NibLoadable: class {
+ static var nibName: String { get }
+ static var nibBundle: Bundle? { get }
+}
+
+extension NibLoadable {
+ static var nib: UINib {
+ return UINib(nibName: nibName, bundle: nibBundle)
+ }
+
+ static var nibName: String {
+ return String(describing: self)
+ }
+
+ static var nibBundle: Bundle? {
+ return Bundle(for: self)
+ }
+}
+
+extension NibLoadable where Self: UIView {
+ static func loadFromNib() -> Self {
+ return nib.instantiate(withOwner: nil, options: nil).first as! Self
+ }
+}
diff --git a/Examples/Example-tvOS/Sources/Reusable.swift b/Examples/Example-tvOS/Sources/Reusable.swift
new file mode 100644
index 0000000..b84d337
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/Reusable.swift
@@ -0,0 +1,11 @@
+protocol Reusable: class {
+ static var reuseIdentifier: String { get }
+}
+
+extension Reusable {
+ static var reuseIdentifier: String {
+ return String(reflecting: self)
+ }
+}
+
+typealias NibReusable = NibLoadable & Reusable
diff --git a/Examples/Example-tvOS/Sources/ReusableViewExtensions.swift b/Examples/Example-tvOS/Sources/ReusableViewExtensions.swift
new file mode 100644
index 0000000..b9108de
--- /dev/null
+++ b/Examples/Example-tvOS/Sources/ReusableViewExtensions.swift
@@ -0,0 +1,65 @@
+import UIKit
+
+extension UITableView {
+ func register(cellType: T.Type) where T: NibReusable {
+ register(T.nib, forCellReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(cellType: T.Type) where T: Reusable {
+ register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type) where T: NibReusable {
+ register(T.nib, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type) where T: Reusable {
+ register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T where T: Reusable {
+ guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return cell
+ }
+
+ func dequeueReusableHeaderFooterView(viewType: T.Type = T.self) -> T where T: Reusable {
+ guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
+ fatalError()
+ }
+ return view
+ }
+}
+
+extension UICollectionView {
+ func register(cellType: T.Type) where T: NibReusable {
+ register(T.nib, forCellWithReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(cellType: T.Type) where T: Reusable {
+ register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type, forSupplementaryViewOfKind kind: String) where T: NibReusable {
+ register(T.nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func register(viewType: T.Type, forSupplementaryViewOfKind kind: String) where T: Reusable {
+ register(T.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: T.reuseIdentifier)
+ }
+
+ func dequeueReusableCell(for indexPath: IndexPath, cellType: T.Type = T.self) -> T where T: Reusable {
+ guard let cell = dequeueReusableCell(withReuseIdentifier: cellType.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return cell
+ }
+
+ func dequeueReusableSupplementaryView(ofKind kind: String, for indexPath: IndexPath, viewType: T.Type = T.self) -> T where T: Reusable {
+ guard let view = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+ fatalError()
+ }
+ return view
+ }
+}
diff --git a/Examples/Example-tvOS/Sources/ShuffleEmoticonViewController.swift b/Examples/Example-tvOS/Sources/ShuffleEmoticonViewController.swift
deleted file mode 100644
index 84bc55e..0000000
--- a/Examples/Example-tvOS/Sources/ShuffleEmoticonViewController.swift
+++ /dev/null
@@ -1,239 +0,0 @@
-import UIKit
-import DifferenceKit
-
-private enum EmoticonSectionID: Differentiable, CaseIterable {
- case first, second, third
-}
-
-private typealias EmoticonSection = ArraySection
-
-final class ShuffleEmoticonViewController: UIViewController {
- private let collectionView: UICollectionView
- private var data = [EmoticonSection]()
-
- private var dataInput: [EmoticonSection] {
- get { return data }
- set {
- let changeset = StagedChangeset(source: data, target: newValue)
- collectionView.reload(using: changeset) { data in
- self.data = data
- }
- }
- }
-
- private func remove(at indexPath: IndexPath) {
- dataInput[indexPath.section].elements.remove(at: indexPath.item)
- }
-
- @objc private func shuffleSections() {
- dataInput.shuffle()
- }
-
- @objc private func shuffleAllEmoticons() {
- var flattenEmoticons = ArraySlice(dataInput.flatMap { $0.elements })
- flattenEmoticons.shuffle()
-
- dataInput = dataInput.map { section in
- var section = section
- section.elements = Array(flattenEmoticons.prefix(section.elements.count))
- flattenEmoticons.removeFirst(section.elements.count)
- return section
- }
- }
-
- @objc private func refresh() {
- let ids = EmoticonSectionID.allCases
- let emoticons = (0x1F600...0x1F647).compactMap { UnicodeScalar($0).map(String.init) }
- let splitedCount = Int((Double(emoticons.count) / Double(ids.count)).rounded(.up))
-
- dataInput = ids.enumerated().map { offset, model in
- let start = offset * splitedCount
- let end = min(start + splitedCount, emoticons.endIndex)
- let emoticons = emoticons[start.. Int {
- return data.count
- }
-
- func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- return data[section].elements.count
- }
-
- func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmoticonCollectionViewCell.reuseIdentifier, for: indexPath) as! EmoticonCollectionViewCell
- let emoticon = data[indexPath.section].elements[indexPath.item]
- cell.emoticon = emoticon
- return cell
- }
-
- func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
- guard kind == UICollectionView.elementKindSectionHeader else {
- return UICollectionReusableView()
- }
-
- let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TextCollectionReusableView.reuseIdentifier, for: indexPath) as! TextCollectionReusableView
- view.text = "Section \(data[indexPath.section].model)"
- return view
- }
-
- func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
- remove(at: indexPath)
- }
-}
-
-private final class EmoticonCollectionViewCell: UICollectionViewCell {
- static let reuseIdentifier = String(describing: self)
-
- var emoticon: String? {
- get { return label.text }
- set { label.text = newValue }
- }
-
- private let label = UILabel()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- contentView.backgroundColor = UIColor(white: 0.95, alpha: 1)
- contentView.layer.cornerRadius = 8
- contentView.layer.shadowOffset = CGSize(width: 0, height: 7)
- contentView.addSubview(label)
- label.textAlignment = .center
- label.translatesAutoresizingMaskIntoConstraints = false
-
- let constraints = [
- label.topAnchor.constraint(equalTo: contentView.topAnchor),
- label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
- label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
- label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- override var isHighlighted: Bool {
- didSet { alpha = isHighlighted ? 0.2 : 1 }
- }
-
- override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
- coordinator.addCoordinatedAnimations({ [weak self] in
- guard let `self` = self else { return }
- self.contentView.layer.shadowOpacity = self.isFocused ? 0.3 : 0
- self.contentView.layer.transform = self.isFocused ? CATransform3DMakeScale(1.1, 1.1, 1) : CATransform3DIdentity
- self.layer.zPosition = self.isFocused ? 1 : 0
- })
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-private final class TextCollectionReusableView: UICollectionReusableView {
- static let reuseIdentifier = String(describing: self)
-
- var text: String? {
- get { return label.text }
- set { label.text = newValue }
- }
-
- private let label = UILabel()
-
- override init(frame: CGRect) {
- super.init(frame: frame)
- backgroundColor = UIColor.black.withAlphaComponent(0.05)
- addSubview(label)
- label.translatesAutoresizingMaskIntoConstraints = false
-
- let constraints = [
- label.topAnchor.constraint(equalTo: topAnchor),
- label.bottomAnchor.constraint(equalTo: bottomAnchor),
- label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24),
- label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 24)
- ]
- NSLayoutConstraint.activate(constraints)
- }
-
- @available(*, unavailable)
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
diff --git a/Examples/Example-tvOS/Sources/String+Differentiable.swift b/Examples/Example-tvOS/Sources/StringExtensions.swift
similarity index 81%
rename from Examples/Example-tvOS/Sources/String+Differentiable.swift
rename to Examples/Example-tvOS/Sources/StringExtensions.swift
index cc31c8d..32068bc 100644
--- a/Examples/Example-tvOS/Sources/String+Differentiable.swift
+++ b/Examples/Example-tvOS/Sources/StringExtensions.swift
@@ -1,4 +1,3 @@
-import UIKit
import DifferenceKit
extension String: Differentiable {}
From 9a3fcc8a5e5cd50d5e45d6ab170df63fb3b1f004 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Fri, 3 May 2019 07:22:31 +0900
Subject: [PATCH 04/20] Add benchmark
---
.gitignore | 8 +-
.swiftlint.yml | 1 +
Benchmark/Benchmark.xcodeproj/project.pbxproj | 434 ++++++++++++++++++
.../contents.xcworkspacedata | 7 +
.../xcshareddata/IDEWorkspaceChecks.plist | 8 +
.../xcshareddata/WorkspaceSettings.xcsettings | 5 +
.../xcshareddata/xcschemes/Benchmark.xcscheme | 91 ++++
.../contents.xcworkspacedata | 10 +
.../xcshareddata/IDEWorkspaceChecks.plist | 8 +
.../xcshareddata/WorkspaceSettings.xcsettings | 5 +
Benchmark/Gemfile | 3 +
Benchmark/Gemfile.lock | 76 +++
Benchmark/Makefile | 5 +
Benchmark/Podfile | 16 +
Benchmark/Podfile.lock | 70 +++
Benchmark/Sources/BenchmarkTools.swift | 113 +++++
Benchmark/Sources/Info.plist | 37 ++
Benchmark/Sources/main.swift | 76 +++
Sources/Algorithm.swift | 2 +-
19 files changed, 972 insertions(+), 3 deletions(-)
create mode 100644 Benchmark/Benchmark.xcodeproj/project.pbxproj
create mode 100644 Benchmark/Benchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata
create mode 100644 Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
create mode 100644 Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
create mode 100644 Benchmark/Benchmark.xcodeproj/xcshareddata/xcschemes/Benchmark.xcscheme
create mode 100644 Benchmark/Benchmark.xcworkspace/contents.xcworkspacedata
create mode 100644 Benchmark/Benchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
create mode 100644 Benchmark/Benchmark.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
create mode 100644 Benchmark/Gemfile
create mode 100644 Benchmark/Gemfile.lock
create mode 100644 Benchmark/Makefile
create mode 100644 Benchmark/Podfile
create mode 100644 Benchmark/Podfile.lock
create mode 100644 Benchmark/Sources/BenchmarkTools.swift
create mode 100644 Benchmark/Sources/Info.plist
create mode 100644 Benchmark/Sources/main.swift
diff --git a/.gitignore b/.gitignore
index f202646..2408063 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
*.perspectivev3
!default.perspectivev3
xcuserdata
+xcbaselines
profile
*.moved-aside
DerivedData
@@ -24,7 +25,10 @@ docs/undocumented.json
## Gems
.bundle
-vendor/bundle
+vendor
+
+## CocoaPods
+Pods
## Swift Package build
-.build
\ No newline at end of file
+.build
diff --git a/.swiftlint.yml b/.swiftlint.yml
index e540a32..649c7c6 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -2,6 +2,7 @@
excluded:
- Tests/XCTestManifests.swift
+ - Benchmark
disabled_rules:
- type_name
diff --git a/Benchmark/Benchmark.xcodeproj/project.pbxproj b/Benchmark/Benchmark.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..3251e63
--- /dev/null
+++ b/Benchmark/Benchmark.xcodeproj/project.pbxproj
@@ -0,0 +1,434 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 42B95AAFE416F321389CBCD7 /* Pods_Benchmark.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD84D76E5EA92D26F09756BD /* Pods_Benchmark.framework */; };
+ 6BAEA3D1227E12020026F81E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAEA367227B30C00026F81E /* main.swift */; };
+ 6BAEA3D5227E134B0026F81E /* BenchmarkTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAEA3D4227E134B0026F81E /* BenchmarkTools.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 323FDF42B213E120212194DE /* Pods-Benchmarkk.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmarkk.debug.xcconfig"; path = "Target Support Files/Pods-Benchmarkk/Pods-Benchmarkk.debug.xcconfig"; sourceTree = ""; };
+ 35DEAFE3650B113B52C06D8F /* Pods-Benchmark.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmark.debug.xcconfig"; path = "Target Support Files/Pods-Benchmark/Pods-Benchmark.debug.xcconfig"; sourceTree = ""; };
+ 6BAEA367227B30C00026F81E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 6BAEA372227B37090026F81E /* DifferenceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DifferenceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA374227B37180026F81E /* Differentiator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Differentiator.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA376227B372C0026F81E /* FlexibleDiff.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FlexibleDiff.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA378227B37360026F81E /* IGListKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = IGListKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA37A227B373E0026F81E /* DeepDiff.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeepDiff.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA37C227B37450026F81E /* Differ.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Differ.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA37E227B374D0026F81E /* Dwifft.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Dwifft.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA3BF227E11B00026F81E /* Benchmark.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Benchmark.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6BAEA3D2227E121C0026F81E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6BAEA3D4227E134B0026F81E /* BenchmarkTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BenchmarkTools.swift; sourceTree = ""; };
+ AD84D76E5EA92D26F09756BD /* Pods_Benchmark.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Benchmark.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ AFE9934A2A3C4B0D844D31DB /* Pods-Benchmarkk.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmarkk.release.xcconfig"; path = "Target Support Files/Pods-Benchmarkk/Pods-Benchmarkk.release.xcconfig"; sourceTree = ""; };
+ C75A2D4EA77E934E03A40063 /* Pods_Benchmarkk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Benchmarkk.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ C8D2F7872D3B4951EAA9CD15 /* Pods-Benchmark.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Benchmark.release.xcconfig"; path = "Target Support Files/Pods-Benchmark/Pods-Benchmark.release.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 6BAEA3BC227E11B00026F81E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 42B95AAFE416F321389CBCD7 /* Pods_Benchmark.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 1BD4D0F0E04789CCDF0571A3 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 323FDF42B213E120212194DE /* Pods-Benchmarkk.debug.xcconfig */,
+ AFE9934A2A3C4B0D844D31DB /* Pods-Benchmarkk.release.xcconfig */,
+ 35DEAFE3650B113B52C06D8F /* Pods-Benchmark.debug.xcconfig */,
+ C8D2F7872D3B4951EAA9CD15 /* Pods-Benchmark.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
+ 6BAE8198227B288F0060866E = {
+ isa = PBXGroup;
+ children = (
+ 6BAE81A4227B288F0060866E /* Sources */,
+ 6BAE81A3227B288F0060866E /* Products */,
+ 6BAE81BE227B2C050060866E /* Frameworks */,
+ 1BD4D0F0E04789CCDF0571A3 /* Pods */,
+ );
+ sourceTree = "";
+ };
+ 6BAE81A3227B288F0060866E /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 6BAEA3BF227E11B00026F81E /* Benchmark.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 6BAE81A4227B288F0060866E /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ 6BAEA367227B30C00026F81E /* main.swift */,
+ 6BAEA3D4227E134B0026F81E /* BenchmarkTools.swift */,
+ 6BAEA3D2227E121C0026F81E /* Info.plist */,
+ );
+ path = Sources;
+ sourceTree = "";
+ };
+ 6BAE81BE227B2C050060866E /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 6BAEA37E227B374D0026F81E /* Dwifft.framework */,
+ 6BAEA37C227B37450026F81E /* Differ.framework */,
+ 6BAEA37A227B373E0026F81E /* DeepDiff.framework */,
+ 6BAEA378227B37360026F81E /* IGListKit.framework */,
+ 6BAEA376227B372C0026F81E /* FlexibleDiff.framework */,
+ 6BAEA374227B37180026F81E /* Differentiator.framework */,
+ 6BAEA372227B37090026F81E /* DifferenceKit.framework */,
+ C75A2D4EA77E934E03A40063 /* Pods_Benchmarkk.framework */,
+ AD84D76E5EA92D26F09756BD /* Pods_Benchmark.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 6BAEA3BE227E11B00026F81E /* Benchmark */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6BAEA3CE227E11B00026F81E /* Build configuration list for PBXNativeTarget "Benchmark" */;
+ buildPhases = (
+ E405D9A6B552915E2373315B /* [CP] Check Pods Manifest.lock */,
+ 6BAEA3BB227E11B00026F81E /* Sources */,
+ 6BAEA3BC227E11B00026F81E /* Frameworks */,
+ 6BAEA3BD227E11B00026F81E /* Resources */,
+ CD6845C17A174E192E6CDC0A /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Benchmark;
+ productName = Benchmark;
+ productReference = 6BAEA3BF227E11B00026F81E /* Benchmark.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 6BAE8199227B288F0060866E /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1020;
+ LastUpgradeCheck = 1020;
+ ORGANIZATIONNAME = "Ryo Aoyama";
+ TargetAttributes = {
+ 6BAEA3BE227E11B00026F81E = {
+ CreatedOnToolsVersion = 10.2;
+ };
+ };
+ };
+ buildConfigurationList = 6BAE819C227B288F0060866E /* Build configuration list for PBXProject "Benchmark" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 6BAE8198227B288F0060866E;
+ productRefGroup = 6BAE81A3227B288F0060866E /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 6BAEA3BE227E11B00026F81E /* Benchmark */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 6BAEA3BD227E11B00026F81E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ CD6845C17A174E192E6CDC0A /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/DeepDiff/DeepDiff.framework",
+ "${BUILT_PRODUCTS_DIR}/Differ/Differ.framework",
+ "${BUILT_PRODUCTS_DIR}/DifferenceKit/DifferenceKit.framework",
+ "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework",
+ "${BUILT_PRODUCTS_DIR}/Dwifft/Dwifft.framework",
+ "${BUILT_PRODUCTS_DIR}/FlexibleDiff/FlexibleDiff.framework",
+ "${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework",
+ "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
+ "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework",
+ "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework",
+ "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeepDiff.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differ.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DifferenceKit.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Dwifft.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FlexibleDiff.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListKit.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ E405D9A6B552915E2373315B /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Benchmark-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /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";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 6BAEA3BB227E11B00026F81E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6BAEA3D1227E12020026F81E /* main.swift in Sources */,
+ 6BAEA3D5227E134B0026F81E /* BenchmarkTools.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 6BAE81B4227B288F0060866E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 6BAE81B5227B288F0060866E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 6BAEA3CF227E11B00026F81E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 35DEAFE3650B113B52C06D8F /* Pods-Benchmark.debug.xcconfig */;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ INFOPLIST_FILE = Sources/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.ryo.Benchmark;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 6BAEA3D0227E11B00026F81E /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = C8D2F7872D3B4951EAA9CD15 /* Pods-Benchmark.release.xcconfig */;
+ buildSettings = {
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Manual;
+ DEVELOPMENT_TEAM = "";
+ GCC_OPTIMIZATION_LEVEL = s;
+ INFOPLIST_FILE = Sources/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.ryo.Benchmark;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 6BAE819C227B288F0060866E /* Build configuration list for PBXProject "Benchmark" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6BAE81B4227B288F0060866E /* Debug */,
+ 6BAE81B5227B288F0060866E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 6BAEA3CE227E11B00026F81E /* Build configuration list for PBXNativeTarget "Benchmark" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6BAEA3CF227E11B00026F81E /* Debug */,
+ 6BAEA3D0227E11B00026F81E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 6BAE8199227B288F0060866E /* Project object */;
+}
diff --git a/Benchmark/Benchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..c911975
--- /dev/null
+++ b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/Benchmark/Benchmark.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Benchmark/Benchmark.xcodeproj/xcshareddata/xcschemes/Benchmark.xcscheme b/Benchmark/Benchmark.xcodeproj/xcshareddata/xcschemes/Benchmark.xcscheme
new file mode 100644
index 0000000..1f42c73
--- /dev/null
+++ b/Benchmark/Benchmark.xcodeproj/xcshareddata/xcschemes/Benchmark.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Benchmark/Benchmark.xcworkspace/contents.xcworkspacedata b/Benchmark/Benchmark.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..abf2eb3
--- /dev/null
+++ b/Benchmark/Benchmark.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Benchmark/Benchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Benchmark/Benchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Benchmark/Benchmark.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Benchmark/Benchmark.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Benchmark/Benchmark.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..0c67376
--- /dev/null
+++ b/Benchmark/Benchmark.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Benchmark/Gemfile b/Benchmark/Gemfile
new file mode 100644
index 0000000..aaba17d
--- /dev/null
+++ b/Benchmark/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem 'cocoapods', '1.6.1'
diff --git a/Benchmark/Gemfile.lock b/Benchmark/Gemfile.lock
new file mode 100644
index 0000000..4066a29
--- /dev/null
+++ b/Benchmark/Gemfile.lock
@@ -0,0 +1,76 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.0)
+ activesupport (4.2.11.1)
+ i18n (~> 0.7)
+ minitest (~> 5.1)
+ thread_safe (~> 0.3, >= 0.3.4)
+ tzinfo (~> 1.1)
+ atomos (0.1.3)
+ claide (1.0.2)
+ cocoapods (1.6.1)
+ activesupport (>= 4.0.2, < 5)
+ claide (>= 1.0.2, < 2.0)
+ cocoapods-core (= 1.6.1)
+ cocoapods-deintegrate (>= 1.0.2, < 2.0)
+ cocoapods-downloader (>= 1.2.2, < 2.0)
+ cocoapods-plugins (>= 1.0.0, < 2.0)
+ cocoapods-search (>= 1.0.0, < 2.0)
+ cocoapods-stats (>= 1.0.0, < 2.0)
+ cocoapods-trunk (>= 1.3.1, < 2.0)
+ cocoapods-try (>= 1.1.0, < 2.0)
+ colored2 (~> 3.1)
+ escape (~> 0.0.4)
+ fourflusher (>= 2.2.0, < 3.0)
+ gh_inspector (~> 1.0)
+ molinillo (~> 0.6.6)
+ nap (~> 1.0)
+ ruby-macho (~> 1.4)
+ xcodeproj (>= 1.8.1, < 2.0)
+ cocoapods-core (1.6.1)
+ activesupport (>= 4.0.2, < 6)
+ fuzzy_match (~> 2.0.4)
+ nap (~> 1.0)
+ cocoapods-deintegrate (1.0.4)
+ cocoapods-downloader (1.2.2)
+ cocoapods-plugins (1.0.0)
+ nap
+ cocoapods-search (1.0.0)
+ cocoapods-stats (1.1.0)
+ cocoapods-trunk (1.3.1)
+ nap (>= 0.8, < 2.0)
+ netrc (~> 0.11)
+ cocoapods-try (1.1.0)
+ colored2 (3.1.2)
+ concurrent-ruby (1.1.5)
+ escape (0.0.4)
+ fourflusher (2.2.0)
+ fuzzy_match (2.0.4)
+ gh_inspector (1.1.3)
+ i18n (0.9.5)
+ concurrent-ruby (~> 1.0)
+ minitest (5.11.3)
+ molinillo (0.6.6)
+ nanaimo (0.2.6)
+ nap (1.1.0)
+ netrc (0.11.0)
+ ruby-macho (1.4.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.5)
+ thread_safe (~> 0.1)
+ xcodeproj (1.8.2)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.2.6)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ cocoapods (= 1.6.1)
+
+BUNDLED WITH
+ 2.0.1
diff --git a/Benchmark/Makefile b/Benchmark/Makefile
new file mode 100644
index 0000000..06b4ef6
--- /dev/null
+++ b/Benchmark/Makefile
@@ -0,0 +1,5 @@
+pods-install:
+ bundle exec pod install || bundle exec pod install --repo-update
+
+gems-install:
+ bundle check || bundle install --path vendor/bundle --clean --jobs=4
diff --git a/Benchmark/Podfile b/Benchmark/Podfile
new file mode 100644
index 0000000..aac0c41
--- /dev/null
+++ b/Benchmark/Podfile
@@ -0,0 +1,16 @@
+platform :ios, '12.2'
+
+use_frameworks!
+inhibit_all_warnings!
+
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+target 'Benchmark' do
+ pod 'DifferenceKit', path: '../'
+ pod 'RxDataSources', '4.0.1'
+ pod 'FlexibleDiff', '0.0.8'
+ pod 'IGListKit', '3.4.0'
+ pod 'DeepDiff', '2.0.1'
+ pod 'Differ', '1.4.1'
+ pod 'Dwifft', '0.9'
+end
diff --git a/Benchmark/Podfile.lock b/Benchmark/Podfile.lock
new file mode 100644
index 0000000..883a051
--- /dev/null
+++ b/Benchmark/Podfile.lock
@@ -0,0 +1,70 @@
+PODS:
+ - DeepDiff (2.0.1)
+ - Differ (1.4.1)
+ - DifferenceKit (1.1.1):
+ - DifferenceKit/Core (= 1.1.1)
+ - DifferenceKit/UIKitExtension (= 1.1.1)
+ - DifferenceKit/Core (1.1.1)
+ - DifferenceKit/UIKitExtension (1.1.1):
+ - DifferenceKit/Core
+ - Differentiator (4.0.1)
+ - Dwifft (0.9)
+ - FlexibleDiff (0.0.8)
+ - IGListKit (3.4.0):
+ - IGListKit/Default (= 3.4.0)
+ - IGListKit/Default (3.4.0):
+ - IGListKit/Diffing
+ - IGListKit/Diffing (3.4.0)
+ - RxCocoa (5.0.0):
+ - RxRelay (~> 5)
+ - RxSwift (~> 5)
+ - RxDataSources (4.0.1):
+ - Differentiator (~> 4.0)
+ - RxCocoa (~> 5.0)
+ - RxSwift (~> 5.0)
+ - RxRelay (5.0.0):
+ - RxSwift (~> 5)
+ - RxSwift (5.0.0)
+
+DEPENDENCIES:
+ - DeepDiff (= 2.0.1)
+ - Differ (= 1.4.1)
+ - DifferenceKit (from `../`)
+ - Dwifft (= 0.9)
+ - FlexibleDiff (= 0.0.8)
+ - IGListKit (= 3.4.0)
+ - RxDataSources (= 4.0.1)
+
+SPEC REPOS:
+ https://github.com/cocoapods/specs.git:
+ - DeepDiff
+ - Differ
+ - Differentiator
+ - Dwifft
+ - FlexibleDiff
+ - IGListKit
+ - RxCocoa
+ - RxDataSources
+ - RxRelay
+ - RxSwift
+
+EXTERNAL SOURCES:
+ DifferenceKit:
+ :path: "../"
+
+SPEC CHECKSUMS:
+ DeepDiff: 1944bb2c841ab238a66b31de1faa7627f3ca84bd
+ Differ: ca4350872e32a1aeedf3d9e2d9e47127833bfa9c
+ DifferenceKit: cf1b84e2207ea45c3640381cb229cf46c1647466
+ Differentiator: 886080237d9f87f322641dedbc5be257061b0602
+ Dwifft: 42912068ed2a8146077d1a1404df18625bd086e1
+ FlexibleDiff: 4f487778bd152088a9528a0a9be06eba7952bb00
+ IGListKit: 7a5d788e9fb746bcd402baa8e8b24bc3bd2a5a07
+ RxCocoa: fcf32050ac00d801f34a7f71d5e8e7f23026dcd8
+ RxDataSources: efee07fa4de48477eca0a4611e6d11e2da9c1114
+ RxRelay: 4f7409406a51a55cd88483f21ed898c234d60f18
+ RxSwift: 8b0671caa829a763bbce7271095859121cbd895f
+
+PODFILE CHECKSUM: 551831665cc13a12570a723635fb39fa0aaab88b
+
+COCOAPODS: 1.6.1
diff --git a/Benchmark/Sources/BenchmarkTools.swift b/Benchmark/Sources/BenchmarkTools.swift
new file mode 100644
index 0000000..b3267a4
--- /dev/null
+++ b/Benchmark/Sources/BenchmarkTools.swift
@@ -0,0 +1,113 @@
+import DifferenceKit
+import Differentiator
+import IGListKit
+import DeepDiff
+
+extension UUID: Differentiable {}
+
+extension UUID: IdentifiableType {
+ public var identity: UUID {
+ return self
+ }
+}
+
+extension UUID: DiffAware {
+ public var diffId: Int {
+ return hashValue
+ }
+
+ public static func compareContent(_ a: UUID, _ b: UUID) -> Bool {
+ return a == b
+ }
+}
+
+final class UUIDWrapper: ListDiffable {
+ let uuid: UUID
+
+ init(uuid: UUID) {
+ self.uuid = uuid
+ }
+
+ func diffIdentifier() -> NSObjectProtocol {
+ return uuid as NSUUID
+ }
+
+ func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
+ guard let object = object as? UUIDWrapper else {
+ return false
+ }
+
+ return uuid == object.uuid
+ }
+}
+
+struct BenchmarkData {
+ var source: [UUID]
+ var target: [UUID]
+ var deleteRange: CountableRange
+ var insertRange: CountableRange
+
+ init(count: Int, deleteRange: CountableRange, insertRange: CountableRange) {
+ source = (0.. () -> Void
+
+ func measure(with data: BenchmarkData) -> CFAbsoluteTime {
+ let action = prepare(data)
+ let start = CFAbsoluteTimeGetCurrent()
+ action()
+ let end = CFAbsoluteTimeGetCurrent()
+
+ return end - start
+ }
+}
+
+struct BenchmarkRunner {
+ var benchmarks: [Benchmark]
+
+ init(_ benchmarks: Benchmark...) {
+ self.benchmarks = benchmarks
+ }
+
+ func run(with data: BenchmarkData) {
+ let sourceCount = String.localizedStringWithFormat("%d", data.source.count)
+ let deleteCount = String.localizedStringWithFormat("%d", data.deleteRange.count)
+ let insertCount = String.localizedStringWithFormat("%d", data.insertRange.count)
+
+ let maxLength = benchmarks.lazy
+ .map { $0.name.count }
+ .max() ?? 0
+
+ let empty = String(repeating: " ", count: maxLength)
+ let timeTitle = "Time(second)".padding(toLength: maxLength, withPad: " ", startingAt: 0)
+ let spacer = ":" + String(repeating: "-", count: maxLength - 1)
+
+ print("#### - From \(sourceCount) elements to \(deleteCount) deleted and \(insertCount) inserted")
+ print()
+ print("""
+ |\(empty)|\(timeTitle)|
+ |\(spacer)|\(spacer)|
+ """)
+
+ for benchmark in benchmarks {
+ let paddingName = benchmark.name.padding(toLength: maxLength, withPad: " ", startingAt: 0)
+ print("|\(paddingName)|", terminator: "")
+
+ let result = benchmark.measure(with: data)
+ let paddingTime = String(format: "%.4f", result).padding(toLength: maxLength, withPad: " ", startingAt: 0)
+ print("\(paddingTime)|")
+ }
+
+ print()
+ }
+}
diff --git a/Benchmark/Sources/Info.plist b/Benchmark/Sources/Info.plist
new file mode 100644
index 0000000..e1ab737
--- /dev/null
+++ b/Benchmark/Sources/Info.plist
@@ -0,0 +1,37 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/Benchmark/Sources/main.swift b/Benchmark/Sources/main.swift
new file mode 100644
index 0000000..7b63bbc
--- /dev/null
+++ b/Benchmark/Sources/main.swift
@@ -0,0 +1,76 @@
+import Foundation
+import DifferenceKit
+import Differentiator
+import FlexibleDiff
+import IGListKit
+import DeepDiff
+import Differ
+import Dwifft
+
+let runner = BenchmarkRunner(
+ Benchmark(name: "DifferenceKit") { data in
+ let model = UUID()
+ let source = [ArraySection(model: model, elements: data.source)]
+ let target = [ArraySection(model: model, elements: data.target)]
+
+ return {
+ _ = StagedChangeset(source: source, target: target)
+ }
+ },
+ Benchmark(name: "RxDataSources") { data in
+ let model = UUID()
+ let initialSections = [AnimatableSectionModel(model: model, items: data.source)]
+ let finalSections = [AnimatableSectionModel(model: model, items: data.target)]
+
+ return {
+ _ = try! Diff.differencesForSectionedView(initialSections: initialSections, finalSections: finalSections)
+ }
+ },
+ Benchmark(name: "IGListKit") { data in
+ let oldArray = data.source.map(UUIDWrapper.init)
+ let newArray = data.target.map(UUIDWrapper.init)
+
+ return {
+ _ = ListDiff(oldArray: oldArray, newArray: newArray, option: .equality)
+ }
+ },
+ Benchmark(name: "FlexibleDiff") { data in
+ let model = UUID()
+ let previous = [ArraySection(model: model, elements: data.source)]
+ let current = [ArraySection(model: model, elements: data.target)]
+
+ return {
+ _ = SectionedChangeset(
+ previous: previous,
+ current: current,
+ sectionIdentifier: { $0.model },
+ areMetadataEqual: { $0.model == $1.model },
+ items: { $0.elements },
+ itemIdentifier: { $0 },
+ areItemsEqual: ==
+ )
+ }
+ },
+ Benchmark(name: "DeepDiff") { data in
+ return {
+ _ = DeepDiff.diff(old: data.source, new: data.target)
+ }
+ },
+ Benchmark(name: "Differ") { data in
+ return {
+ _ = data.source.extendedDiff(data.target)
+ }
+ },
+ Benchmark(name: "Dwifft") { data in
+ let section = UUID()
+ let lhs = SectionedValues([(section, data.source)])
+ let rhs = SectionedValues([(section, data.target)])
+
+ return {
+ _ = Dwifft.diff(lhs: lhs, rhs: rhs)
+ }
+ }
+)
+
+runner.run(with: BenchmarkData(count: 5000, deleteRange: 2000..<3000, insertRange: 3000..<4000))
+runner.run(with: BenchmarkData(count: 100000, deleteRange: 20000..<30000, insertRange: 30000..<40000))
diff --git a/Sources/Algorithm.swift b/Sources/Algorithm.swift
index c9d88a5..7b2b741 100644
--- a/Sources/Algorithm.swift
+++ b/Sources/Algorithm.swift
@@ -674,7 +674,7 @@ internal struct TableKey: Hashable {
@inlinable
internal func hash(into hasher: inout Hasher) {
- hasher.combine(pointer.pointee)
+ hasher.combine(pointeeHashValue)
}
}
From e15bfd175bc0e3af941cb79cdccd066dc4815fba Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Sun, 5 May 2019 05:48:36 +0900
Subject: [PATCH 05/20] Fix Makefile
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 14704ff..59db4fc 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ pod-release:
bundle exec pod trunk push --allow-warnings
test-linux:
- sh scripts/test-linux.sh
+ sh test-linux.sh
generate-linuxmain:
swift test --generate-linuxmain
From a7c6606eaf718a5970afc7b1c700485c3d07bfec Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Sun, 5 May 2019 06:05:58 +0900
Subject: [PATCH 06/20] Fix benchmark output format
---
Benchmark/Sources/BenchmarkTools.swift | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Benchmark/Sources/BenchmarkTools.swift b/Benchmark/Sources/BenchmarkTools.swift
index b3267a4..ff294bc 100644
--- a/Benchmark/Sources/BenchmarkTools.swift
+++ b/Benchmark/Sources/BenchmarkTools.swift
@@ -90,13 +90,14 @@ struct BenchmarkRunner {
let empty = String(repeating: " ", count: maxLength)
let timeTitle = "Time(second)".padding(toLength: maxLength, withPad: " ", startingAt: 0)
- let spacer = ":" + String(repeating: "-", count: maxLength - 1)
+ let leftAlignSpacer = ":" + String(repeating: "-", count: maxLength - 1)
+ let rightAlignSpacer = String(repeating: "-", count: maxLength - 1) + ":"
print("#### - From \(sourceCount) elements to \(deleteCount) deleted and \(insertCount) inserted")
print()
print("""
|\(empty)|\(timeTitle)|
- |\(spacer)|\(spacer)|
+ |\(leftAlignSpacer)|\(rightAlignSpacer)|
""")
for benchmark in benchmarks {
From 0c03f28dd038b789285f8022f0d48c48bcc62fe1 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Sun, 5 May 2019 06:25:23 +0900
Subject: [PATCH 07/20] Add readme for the benchmark
---
Benchmark/README.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 Benchmark/README.md
diff --git a/Benchmark/README.md b/Benchmark/README.md
new file mode 100644
index 0000000..d5bbb99
--- /dev/null
+++ b/Benchmark/README.md
@@ -0,0 +1,8 @@
+# How to Run
+
+1. Change directory from the root of repository `cd ./Benchmark`
+1. Install gems by Bundler `make gems-install`
+1. Install dependencies by CocoaPods `make pods-install`
+1. Open `Benchmark.xcworkspace` on Xcode
+1. Run `Benchmark` scheme
+1. See the benchmark result on the Xcode console
From 643f5b932a968808599d294fa48ed0ba65932a5f Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Sun, 5 May 2019 06:32:26 +0900
Subject: [PATCH 08/20] Use NSUUID for IGListKit benchmark
---
Benchmark/Sources/BenchmarkTools.swift | 19 ++++++-------------
Benchmark/Sources/main.swift | 4 ++--
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/Benchmark/Sources/BenchmarkTools.swift b/Benchmark/Sources/BenchmarkTools.swift
index ff294bc..af1ac5c 100644
--- a/Benchmark/Sources/BenchmarkTools.swift
+++ b/Benchmark/Sources/BenchmarkTools.swift
@@ -21,23 +21,16 @@ extension UUID: DiffAware {
}
}
-final class UUIDWrapper: ListDiffable {
- let uuid: UUID
-
- init(uuid: UUID) {
- self.uuid = uuid
- }
-
- func diffIdentifier() -> NSObjectProtocol {
- return uuid as NSUUID
+extension NSUUID: ListDiffable {
+ public func diffIdentifier() -> NSObjectProtocol {
+ return self
}
- func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
- guard let object = object as? UUIDWrapper else {
+ public func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
+ guard let other = object as? NSUUID else {
return false
}
-
- return uuid == object.uuid
+ return self == other
}
}
diff --git a/Benchmark/Sources/main.swift b/Benchmark/Sources/main.swift
index 7b63bbc..745fe31 100644
--- a/Benchmark/Sources/main.swift
+++ b/Benchmark/Sources/main.swift
@@ -27,8 +27,8 @@ let runner = BenchmarkRunner(
}
},
Benchmark(name: "IGListKit") { data in
- let oldArray = data.source.map(UUIDWrapper.init)
- let newArray = data.target.map(UUIDWrapper.init)
+ let oldArray = data.source.map { $0 as NSUUID }
+ let newArray = data.target.map { $0 as NSUUID }
return {
_ = ListDiff(oldArray: oldArray, newArray: newArray, option: .equality)
From 59b1c72bbb1cda82e90a50b67376ec66f75f2014 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Mon, 6 May 2019 05:04:07 +0900
Subject: [PATCH 09/20] Update benchmark
---
Benchmark/Sources/BenchmarkTools.swift | 10 ++++--
Benchmark/Sources/main.swift | 43 ++++++++++----------------
2 files changed, 24 insertions(+), 29 deletions(-)
diff --git a/Benchmark/Sources/BenchmarkTools.swift b/Benchmark/Sources/BenchmarkTools.swift
index af1ac5c..beaa1db 100644
--- a/Benchmark/Sources/BenchmarkTools.swift
+++ b/Benchmark/Sources/BenchmarkTools.swift
@@ -39,15 +39,18 @@ struct BenchmarkData {
var target: [UUID]
var deleteRange: CountableRange
var insertRange: CountableRange
+ var shuffleRange: CountableRange
- init(count: Int, deleteRange: CountableRange, insertRange: CountableRange) {
+ init(count: Int, deleteRange: CountableRange, insertRange: CountableRange, shuffleRange: CountableRange) {
source = (0..
Date: Mon, 6 May 2019 05:05:37 +0900
Subject: [PATCH 10/20] Update podspec
---
DifferenceKit.podspec | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/DifferenceKit.podspec b/DifferenceKit.podspec
index 7165d70..09728f4 100644
--- a/DifferenceKit.podspec
+++ b/DifferenceKit.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'DifferenceKit'
- spec.version = '1.1.1'
+ spec.version = '1.1.2'
spec.author = { 'ra1028' => 'r.fe51028.r@gmail.com' }
spec.homepage = 'https://github.com/ra1028/DifferenceKit'
spec.documentation_url = 'https://ra1028.github.io/DifferenceKit'
@@ -13,7 +13,7 @@ Pod::Spec.new do |spec|
spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
spec.requires_arc = true
spec.default_subspecs = 'Core', 'UIKitExtension'
- spec.swift_version = '4.2'
+ spec.swift_versions = ['4.2', '5.0']
spec.ios.deployment_target = '9.0'
spec.tvos.deployment_target = '9.0'
From 03920504e74b14bdde634d38ab682de813675844 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Mon, 6 May 2019 05:54:24 +0900
Subject: [PATCH 11/20] Update benchmark
---
Benchmark/Sources/main.swift | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Benchmark/Sources/main.swift b/Benchmark/Sources/main.swift
index a50001f..b95d2d6 100644
--- a/Benchmark/Sources/main.swift
+++ b/Benchmark/Sources/main.swift
@@ -56,12 +56,12 @@ runner.run(with: BenchmarkData(
count: 5000,
deleteRange: 2000..<3000,
insertRange: 3000..<4000,
- shuffleRange: 0..<1000
+ shuffleRange: 0..<200
))
runner.run(with: BenchmarkData(
count: 100000,
deleteRange: 20000..<30000,
insertRange: 30000..<40000,
- shuffleRange: 0..<10000
+ shuffleRange: 0..<2000
))
From 396be5840c395d3ea5ac9524cf2c81863a41bc89 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Mon, 6 May 2019 06:10:03 +0900
Subject: [PATCH 12/20] Update CocoaPods
---
Benchmark/Benchmark.xcodeproj/project.pbxproj | 29 ++-------------
Benchmark/Gemfile | 2 +-
Benchmark/Gemfile.lock | 14 ++++----
Benchmark/Podfile | 2 +-
Benchmark/Podfile.lock | 36 +++++--------------
Gemfile | 2 +-
Gemfile.lock | 20 +++++------
7 files changed, 31 insertions(+), 74 deletions(-)
diff --git a/Benchmark/Benchmark.xcodeproj/project.pbxproj b/Benchmark/Benchmark.xcodeproj/project.pbxproj
index 3251e63..e6b989a 100644
--- a/Benchmark/Benchmark.xcodeproj/project.pbxproj
+++ b/Benchmark/Benchmark.xcodeproj/project.pbxproj
@@ -171,36 +171,11 @@
files = (
);
inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/DeepDiff/DeepDiff.framework",
- "${BUILT_PRODUCTS_DIR}/Differ/Differ.framework",
- "${BUILT_PRODUCTS_DIR}/DifferenceKit/DifferenceKit.framework",
- "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework",
- "${BUILT_PRODUCTS_DIR}/Dwifft/Dwifft.framework",
- "${BUILT_PRODUCTS_DIR}/FlexibleDiff/FlexibleDiff.framework",
- "${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework",
- "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
- "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework",
- "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework",
- "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
+ "${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
- );
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeepDiff.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differ.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DifferenceKit.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Dwifft.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FlexibleDiff.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListKit.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
+ "${PODS_ROOT}/Target Support Files/Pods-Benchmark/Pods-Benchmark-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
diff --git a/Benchmark/Gemfile b/Benchmark/Gemfile
index aaba17d..7821fdd 100644
--- a/Benchmark/Gemfile
+++ b/Benchmark/Gemfile
@@ -1,3 +1,3 @@
source "https://rubygems.org"
-gem 'cocoapods', '1.6.1'
+gem 'cocoapods', '1.7.0.rc.1'
diff --git a/Benchmark/Gemfile.lock b/Benchmark/Gemfile.lock
index 4066a29..9251803 100644
--- a/Benchmark/Gemfile.lock
+++ b/Benchmark/Gemfile.lock
@@ -9,11 +9,11 @@ GEM
tzinfo (~> 1.1)
atomos (0.1.3)
claide (1.0.2)
- cocoapods (1.6.1)
+ cocoapods (1.7.0.rc.1)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.6.1)
- cocoapods-deintegrate (>= 1.0.2, < 2.0)
+ cocoapods-core (= 1.7.0.rc.1)
+ cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
@@ -27,8 +27,8 @@ GEM
molinillo (~> 0.6.6)
nap (~> 1.0)
ruby-macho (~> 1.4)
- xcodeproj (>= 1.8.1, < 2.0)
- cocoapods-core (1.6.1)
+ xcodeproj (>= 1.8.2, < 2.0)
+ cocoapods-core (1.7.0.rc.1)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
@@ -59,7 +59,7 @@ GEM
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
- xcodeproj (1.8.2)
+ xcodeproj (1.9.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -70,7 +70,7 @@ PLATFORMS
ruby
DEPENDENCIES
- cocoapods (= 1.6.1)
+ cocoapods (= 1.7.0.rc.1)
BUNDLED WITH
2.0.1
diff --git a/Benchmark/Podfile b/Benchmark/Podfile
index aac0c41..601264e 100644
--- a/Benchmark/Podfile
+++ b/Benchmark/Podfile
@@ -7,7 +7,7 @@ ENV['COCOAPODS_DISABLE_STATS'] = 'true'
target 'Benchmark' do
pod 'DifferenceKit', path: '../'
- pod 'RxDataSources', '4.0.1'
+ pod 'Differentiator', '4.0.1'
pod 'FlexibleDiff', '0.0.8'
pod 'IGListKit', '3.4.0'
pod 'DeepDiff', '2.0.1'
diff --git a/Benchmark/Podfile.lock b/Benchmark/Podfile.lock
index 883a051..440335c 100644
--- a/Benchmark/Podfile.lock
+++ b/Benchmark/Podfile.lock
@@ -1,11 +1,11 @@
PODS:
- DeepDiff (2.0.1)
- Differ (1.4.1)
- - DifferenceKit (1.1.1):
- - DifferenceKit/Core (= 1.1.1)
- - DifferenceKit/UIKitExtension (= 1.1.1)
- - DifferenceKit/Core (1.1.1)
- - DifferenceKit/UIKitExtension (1.1.1):
+ - DifferenceKit (1.1.2):
+ - DifferenceKit/Core (= 1.1.2)
+ - DifferenceKit/UIKitExtension (= 1.1.2)
+ - DifferenceKit/Core (1.1.2)
+ - DifferenceKit/UIKitExtension (1.1.2):
- DifferenceKit/Core
- Differentiator (4.0.1)
- Dwifft (0.9)
@@ -15,25 +15,15 @@ PODS:
- IGListKit/Default (3.4.0):
- IGListKit/Diffing
- IGListKit/Diffing (3.4.0)
- - RxCocoa (5.0.0):
- - RxRelay (~> 5)
- - RxSwift (~> 5)
- - RxDataSources (4.0.1):
- - Differentiator (~> 4.0)
- - RxCocoa (~> 5.0)
- - RxSwift (~> 5.0)
- - RxRelay (5.0.0):
- - RxSwift (~> 5)
- - RxSwift (5.0.0)
DEPENDENCIES:
- DeepDiff (= 2.0.1)
- Differ (= 1.4.1)
- DifferenceKit (from `../`)
+ - Differentiator (= 4.0.1)
- Dwifft (= 0.9)
- FlexibleDiff (= 0.0.8)
- IGListKit (= 3.4.0)
- - RxDataSources (= 4.0.1)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
@@ -43,10 +33,6 @@ SPEC REPOS:
- Dwifft
- FlexibleDiff
- IGListKit
- - RxCocoa
- - RxDataSources
- - RxRelay
- - RxSwift
EXTERNAL SOURCES:
DifferenceKit:
@@ -55,16 +41,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
DeepDiff: 1944bb2c841ab238a66b31de1faa7627f3ca84bd
Differ: ca4350872e32a1aeedf3d9e2d9e47127833bfa9c
- DifferenceKit: cf1b84e2207ea45c3640381cb229cf46c1647466
+ DifferenceKit: 3a1cff9ac9a9cdf2a3f205671067797e83c3f066
Differentiator: 886080237d9f87f322641dedbc5be257061b0602
Dwifft: 42912068ed2a8146077d1a1404df18625bd086e1
FlexibleDiff: 4f487778bd152088a9528a0a9be06eba7952bb00
IGListKit: 7a5d788e9fb746bcd402baa8e8b24bc3bd2a5a07
- RxCocoa: fcf32050ac00d801f34a7f71d5e8e7f23026dcd8
- RxDataSources: efee07fa4de48477eca0a4611e6d11e2da9c1114
- RxRelay: 4f7409406a51a55cd88483f21ed898c234d60f18
- RxSwift: 8b0671caa829a763bbce7271095859121cbd895f
-PODFILE CHECKSUM: 551831665cc13a12570a723635fb39fa0aaab88b
+PODFILE CHECKSUM: 23af01fe899c46fcc51f3bfb5ea8d445f4c1d895
-COCOAPODS: 1.6.1
+COCOAPODS: 1.7.0.rc.1
diff --git a/Gemfile b/Gemfile
index d6d1149..9074cfb 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
source "https://rubygems.org"
-gem 'cocoapods', '1.6.1'
+gem 'cocoapods', '1.7.0.rc.1'
gem 'jazzy', '0.9.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3d15ae1..fa394b7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -9,11 +9,11 @@ GEM
tzinfo (~> 1.1)
atomos (0.1.3)
claide (1.0.2)
- cocoapods (1.6.1)
+ cocoapods (1.7.0.rc.1)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.6.1)
- cocoapods-deintegrate (>= 1.0.2, < 2.0)
+ cocoapods-core (= 1.7.0.rc.1)
+ cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
@@ -27,12 +27,12 @@ GEM
molinillo (~> 0.6.6)
nap (~> 1.0)
ruby-macho (~> 1.4)
- xcodeproj (>= 1.8.1, < 2.0)
- cocoapods-core (1.6.1)
+ xcodeproj (>= 1.8.2, < 2.0)
+ cocoapods-core (1.7.0.rc.1)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
- cocoapods-deintegrate (1.0.3)
+ cocoapods-deintegrate (1.0.4)
cocoapods-downloader (1.2.2)
cocoapods-plugins (1.0.0)
nap
@@ -74,18 +74,18 @@ GEM
redcarpet (3.4.0)
rouge (3.3.0)
ruby-macho (1.4.0)
- sass (3.7.3)
+ sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
- sqlite3 (1.4.0)
+ sqlite3 (1.4.1)
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
xcinvoke (0.3.0)
liferaft (~> 0.0.6)
- xcodeproj (1.8.1)
+ xcodeproj (1.9.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -96,7 +96,7 @@ PLATFORMS
ruby
DEPENDENCIES
- cocoapods (= 1.6.1)
+ cocoapods (= 1.7.0.rc.1)
jazzy (= 0.9.4)
BUNDLED WITH
From 7a98512f32a3cc6e035298698556464610a9bfb9 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Tue, 7 May 2019 05:30:39 +0900
Subject: [PATCH 13/20] Update README
---
Benchmark/Sources/BenchmarkTools.swift | 2 +-
README.md | 320 +++++++++++--------------
2 files changed, 147 insertions(+), 175 deletions(-)
diff --git a/Benchmark/Sources/BenchmarkTools.swift b/Benchmark/Sources/BenchmarkTools.swift
index beaa1db..a848981 100644
--- a/Benchmark/Sources/BenchmarkTools.swift
+++ b/Benchmark/Sources/BenchmarkTools.swift
@@ -102,7 +102,7 @@ struct BenchmarkRunner {
print("|\(paddingName)|", terminator: "")
let result = benchmark.measure(with: data)
- let paddingTime = String(format: "%.4f", result).padding(toLength: maxLength, withPad: " ", startingAt: 0)
+ let paddingTime = String(format: "`%.4f`", result).padding(toLength: maxLength, withPad: " ", startingAt: 0)
print("\(paddingTime)|")
}
diff --git a/README.md b/README.md
index e0da414..5ae0ec1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
+
+
+
A fast and flexible O(n) difference algorithm framework for Swift collection.
The algorithm is optimized based on the Paul Heckel's algorithm.
@@ -14,60 +17,55 @@ The algorithm is optimized based on the Paul Heckel's algorithm.
-
+
+
+Made with ❤️ by Ryo Aoyama and Contributors
+
+
+
---
## Features
-✅ Automate to calculate operations for batch-updates of UITableView, UICollectionView, NSTableView and NSCollectionView
-
-✅ **O(n)** difference algorithm optimized for performance in Swift
-✅ Supports both linear and sectioned collection
+💡 Fastest **O(n)** diffing algorithm optimized for Swift collection
-✅ Supports calculating differences with best effort even if elements or section contains duplicates
+💡 Calculate diffs for batch updates of list UI in `UIKit`, `AppKit` and [Texture](https://github.com/TextureGroup/Texture)
-✅ Supports **all operations** for animated UI batch-updates including section reloads
+💡 Supports both linear and sectioned collection even if contains duplicates
----
-
-
-
-
-
-Sample app is imitated from DataSources 💾
-
+💡 Supports **all kind of diffs** for animated UI batch updates
---
## Algorithm
-The algorithm used in DifferenceKit is optimized based on the Paul Heckel's algorithm.
+
+This is a diffing algorithm developed for [Carbon](https://github.com/ra1028/Carbon), works stand alone.
+The algorithm optimized based on the Paul Heckel's algorithm.
See also his paper ["A technique for isolating differences between files"](https://dl.acm.org/citation.cfm?id=359467) released in 1978.
+It allows all kind of diffs to be calculated in linear time **O(n)**.
[RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) and [IGListKit](https://github.com/Instagram/IGListKit) are also implemented based on his algorithm.
-This allows all types of differences to be computed in linear time **O(n)**.
-However, in `performBatchUpdates` of `UITableView` and `UICollectionView`, there are combinations of operations that cause crash when applied simultaneously.
-To solve this problem, `DifferenceKit` takes an approach of split the set of differences at the minimal stages that can be perform batch-updates with no crashes.
+However, in `performBatchUpdates` of `UITableView`, `UICollectionView`, etc, there are combinations of diffs that cause crash when applied simultaneously.
+To solve this problem, `DifferenceKit` takes an approach of split the set of diffs at the minimal stages that can be perform batch updates with no crashes.
Implementation is [here](https://github.com/ra1028/DifferenceKit/blob/master/Sources/Algorithm.swift).
---
-## Documentation
-See docs in [GitHub Pages](https://ra1028.github.io/DifferenceKit/).
-Documentation is generated by [jazzy](https://github.com/realm/jazzy).
-
----
-
## Getting Started
-- [Example app](https://github.com/ra1028/DifferenceKit/blob/master/Examples)
+
+- [API Documentation](https://ra1028.github.io/DifferenceKit)
+- [Example Apps](https://github.com/ra1028/DifferenceKit/blob/master/Examples)
+- [Benchmark](https://github.com/ra1028/DifferenceKit/blob/master/Benchmark)
- [Playground](https://github.com/ra1028/DifferenceKit/blob/master/DifferenceKit.playground/Contents.swift)
-### Example codes
-The type of the element that to take the differences must be conform to the `Differentiable` protocol.
-The `differenceIdentifier`'s type is determined generic by the associated type:
+## Basic Usage
+
+The type of the element that to take diffs must be conform to the `Differentiable` protocol.
+The `differenceIdentifier`'s type is generic associated type:
```swift
struct User: Differentiable {
let id: Int
@@ -85,24 +83,24 @@ struct User: Differentiable {
In the case of definition above, `id` uniquely identifies the element and get to know the user updated by comparing equality of `name` of the elements in source and target.
-There are default implementations of `Differentiable` for the types that conformed to `Equatable` or `Hashable`:
+There are default implementations of `Differentiable` for the types that conforming to `Equatable` or `Hashable`:
```swift
-// If `Self` conform to `Hashable`.
+// If `Self` conforming to `Hashable`.
var differenceIdentifier: Self {
return self
}
-// If `Self` conform to `Equatable`.
+// If `Self` conforming to `Equatable`.
func isContentEqual(to source: Self) -> Bool {
return self == source
}
```
-So, you can simply:
+Therefore, you can simply:
```swift
extension String: Differentiable {}
```
-Calculates the differences by creating `StagedChangeset` from two collections of elements conforming to `Differentiable`:
+Calculate the diffs by creating `StagedChangeset` from two collections of elements conforming to `Differentiable`:
```swift
let source = [
User(id: 0, name: "Vincent"),
@@ -117,7 +115,7 @@ let target = [
let changeset = StagedChangeset(source: source, target: target)
```
-If you want to include multiple types conformed to `Differentiable` in the collection, use `AnyDifferentiable`:
+If you want to include multiple types conforming to `Differentiable` in the collection, use `AnyDifferentiable`:
```swift
let source = [
AnyDifferentiable("A"),
@@ -126,8 +124,8 @@ let source = [
```
In the case of sectioned collection, the section itself must have a unique identifier and be able to compare whether there is an update.
-So each section must conform to `DifferentiableSection` protocol, but in most cases you can use `ArraySection` that general type conformed to it.
-`ArraySection` requires a model conforming to `Differentiable` for differentiate from other sections:
+So each section must conforming to `DifferentiableSection` protocol, but in most cases you can use `ArraySection` that general type conforming to it.
+`ArraySection` requires a model conforming to `Differentiable` for diffing from other sections:
```swift
enum Model: Differentiable {
case a, b, c
@@ -146,9 +144,9 @@ let target: [ArraySection] = [
let changeset = StagedChangeset(source: source, target: target)
```
-You can perform incremental updates on `UITableView` and `UICollectionView` using the created `StagedChangeset`.
+You can perform diffing batch updates of `UITableView` and `UICollectionView` using the created `StagedChangeset`.
-⚠️ **Don't forget** to **synchronously** update the data referenced by the data-source, with the data passed in the `setData` closure. The differences are applied in stages, and failing to do so is bound to create a crash:
+⚠️ **Don't forget** to **synchronously** update the data referenced by the data-source, with the data passed in the `setData` closure. The diffs are applied in stages, and failing to do so is bound to create a crash:
```swift
tableView.reload(using: changeset, with: .fade) { data in
@@ -156,7 +154,7 @@ tableView.reload(using: changeset, with: .fade) { data in
}
```
-Batch-updates using too large amount of differences may adversely affect to performance.
+Batch updates using too large amount of diffs may adversely affect to performance.
Returning `true` with `interrupt` closure then falls back to `reloadData`:
```swift
collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { data in
@@ -164,109 +162,103 @@ collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { d
}
```
+
+
---
## Comparison with Other Frameworks
-Made a fair comparison as much as possible in features and performance with other **popular** and **awesome** frameworks.
-This does **NOT** determine superiority or inferiority of the frameworks. I know that each framework has different benefits.
+
+Made a fair comparison as much as possible in performance and features with other **popular** and **awesome** frameworks.
+This does **NOT** determine superiority or inferiority of the frameworks.
+I know that each framework has different benefits.
The frameworks and its version that compared is below.
- [DifferenceKit](https://github.com/ra1028/DifferenceKit) - master
-- [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) ([Differentiator](https://github.com/RxSwiftCommunity/RxDataSources/tree/master/Sources/Differentiator)) - 3.0.2
-- [FlexibleDiff](https://github.com/RACCommunity/FlexibleDiff) - 0.0.5
+- [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) ([Differentiator](https://github.com/RxSwiftCommunity/RxDataSources/tree/master/Sources/Differentiator)) - 4.0.1
+- [FlexibleDiff](https://github.com/RACCommunity/FlexibleDiff) - 0.0.8
- [IGListKit](https://github.com/Instagram/IGListKit) - 3.4.0
-- [ListDiff](https://github.com/lxcid/ListDiff) - 0.1.0
-- [DeepDiff](https://github.com/onmyway133/DeepDiff) - 1.2.0
-- [Differ](https://github.com/tonyarnold/Differ) ([Diff.swift](https://github.com/wokalski/Diff.swift)) - 1.2.3
-- [Dwifft](https://github.com/jflinter/Dwifft) - 0.8
-
-### Features comparison
-#### - Supported collection
-| |Linear|Sectioned|Duplicate Element/Section|
+- [DeepDiff](https://github.com/onmyway133/DeepDiff) - 2.0.1
+- [Differ](https://github.com/tonyarnold/Differ) ([Diff.swift](https://github.com/wokalski/Diff.swift)) - 1.4.1
+- [Dwifft](https://github.com/jflinter/Dwifft) - 0.9
+
+### Performance Comparison
+
+Benchmark project is [here](https://github.com/ra1028/DifferenceKit/blob/master/Benchmark).
+Performance was mesured by code compiled using `Xcode10.2` and `Swift 5.0` with `-O -whole-module-optimization` and run on `iPhoneXs simulator`.
+Use `Foundation.UUID` as an element of collections.
+
+#### - From 5,000 elements to 1,000 deleted, 1,000 inserted and 200 shuffled
+
+| |Time(sec) |
+|:------------|------------:|
+|DifferenceKit|`0.0021` |
+|RxDataSources|`0.0067` |
+|IGListKit |`0.0490` |
+|FlexibleDiff |`0.0117` |
+|DeepDiff |`0.0263` |
+|Differ |`1.2661` |
+|Dwifft |`0.4552` |
+
+#### - From 100,000 elements to 10,000 deleted, 10,000 inserted and 2,000 shuffled
+
+| |Time(sec) |
+|:------------|------------:|
+|DifferenceKit|`0.0364` |
+|RxDataSources|`0.1167` |
+|IGListKit |`1.0130` |
+|FlexibleDiff |`0.2104` |
+|DeepDiff |`0.4180` |
+|Differ |`136.8958` |
+|Dwifft |`211.4457` |
+
+### Features Comparison
+
+#### - Supported Collection
+
+| |Linear|Sectioned|Duplicate element/section|
|:------------|:----:|:-------:|:-----------------------:|
-|DifferenceKit|✅ |✅ |✅ |
-|RxDataSources|❌ |✅ |❌ |
-|FlexibleDiff |✅ |✅ |✅ |
-|IGListKit |✅ |❌ |✅ |
-|ListDiff |✅ |❌ |✅ |
-|DeepDiff |✅ |❌ |✅ |
-|Differ |✅ |✅ |✅ |
-|Dwifft |✅ |✅ |✅ |
-
-`Linear` means 1-dimensional collection.
-`Sectioned` means 2-dimensional collection.
-
-#### - Supported element differences
-| |Delete|Insert|Move|Reload |Move across sections|
-|:------------|:----:|:----:|:--:|:------:|:------------------:|
-|DifferenceKit|✅ |✅ |✅ |✅ |✅ |
-|RxDataSources|✅ |✅ |✅ |✅ |✅ |
-|FlexibleDiff |✅ |✅ |✅ |✅ |❌ |
-|IGListKit |✅ |✅ |✅ |✅ |❌ |
-|ListDiff |✅ |✅ |✅ |✅ |❌ |
-|DeepDiff |✅ |✅ |✅ |✅ / ❌ |❌ |
-|Differ |✅ |✅ |✅ |❌ |❌ |
-|Dwifft |✅ |✅ |❌ |❌ |❌ |
-
-#### - Supported section differences
+|DifferenceKit|✅ |✅ |✅ |
+|RxDataSources|❌ |✅ |❌ |
+|FlexibleDiff |✅ |✅ |✅ |
+|IGListKit |✅ |❌ |✅ |
+|DeepDiff |✅ |❌ |✅ |
+|Differ |✅ |✅ |✅ |
+|Dwifft |✅ |✅ |✅ |
+
+\* **Linear** means 1-dimensional collection
+\* **Sectioned** means 2-dimensional collection
+
+#### - Supported Element Diff
+
+| |Delete|Insert|Move|Reload|Move across sections|
+|:------------|:----:|:----:|:--:|:----:|:------------------:|
+|DifferenceKit|✅ |✅ |✅ |✅ |✅ |
+|RxDataSources|✅ |✅ |✅ |✅ |✅ |
+|FlexibleDiff |✅ |✅ |✅ |✅ |❌ |
+|IGListKit |✅ |✅ |✅ |✅ |❌ |
+|DeepDiff |✅ |✅ |✅ |✅ |❌ |
+|Differ |✅ |✅ |✅ |❌ |❌ |
+|Dwifft |✅ |✅ |❌ |❌ |❌ |
+
+#### - Supported Section Diff
+
| |Delete|Insert|Move|Reload|
|:------------|:----:|:----:|:--:|:----:|
-|DifferenceKit|✅ |✅ |✅ |✅ |
-|RxDataSources|✅ |✅ |✅ |❌ |
-|FlexibleDiff |✅ |✅ |✅ |✅ |
-|IGListKit |❌ |❌ |❌ |❌ |
-|ListDiff |❌ |❌ |❌ |❌ |
-|DeepDiff |❌ |❌ |❌ |❌ |
-|Differ |✅ |✅ |✅ |❌ |
-|Dwifft |✅ |✅ |❌ |❌ |
-
-### Performance comparison
-Performance was measured using `XCTestCase.measure` on iPhoneX simulator with `-O -whole-module-optimization`.
-Use `Foundation.UUID` as an element.
-
-⚠️ If Move is included in the difference, performance may obviously decrease in some frameworks.
-⚠️ *DeepDiff may had increased the processing speed by misuse of Hashable in algorithm.*
-
-#### - From 5,000 elements to 500 deleted and 500 inserted
-| |Time(second)|
-|:------------|:-----------|
-|DifferenceKit|0.0022 |
-|RxDataSources|0.0078 |
-|FlexibleDiff |0.0168 |
-|IGListKit |0.0412 |
-|ListDiff |0.0388 |
-|DeepDiff |0.0150 |
-|Differ |0.3260 |
-|Dwifft |33.600 |
-
-#### - From 10,000 elements to 1,000 deleted and 1,000 inserted
-| |Time(second)|
-|:------------|:-----------|
-|DifferenceKit|0.0049 |
-|RxDataSources|0.0143 |
-|FlexibleDiff |0.0305 |
-|IGListKit |0.0891 |
-|ListDiff |0.0802 |
-|DeepDiff |0.0300 |
-|Differ |1.3450 |
-|Dwifft |❌ |
-
-#### - From 100,000 elements to 10,000 deleted and 10,000 inserted
-| |Time(second)|
-|:------------|:-----------|
-|DifferenceKit|0.057 |
-|RxDataSources|0.179 |
-|FlexibleDiff |0.356 |
-|IGListKit |1.329 |
-|ListDiff |1.026 |
-|DeepDiff |0.334 |
-|Differ |❌ |
-|Dwifft |❌ |
+|DifferenceKit|✅ |✅ |✅ |✅ |
+|RxDataSources|✅ |✅ |✅ |❌ |
+|FlexibleDiff |✅ |✅ |✅ |✅ |
+|IGListKit |❌ |❌ |❌ |❌ |
+|DeepDiff |❌ |❌ |❌ |❌ |
+|Differ |✅ |✅ |✅ |❌ |
+|Dwifft |✅ |✅ |❌ |❌ |
---
## Requirements
-- Swift4.2+
+
+- Swift 4.2+
- iOS 9.0+
- tvOS 9.0+
- OS X 10.9+
@@ -277,84 +269,66 @@ Use `Foundation.UUID` as an element.
## Installation
### [CocoaPods](https://cocoapods.org/)
-To use only algorithm without extensions for UI, add the following:
-```ruby
-use_frameworks!
-target 'TargetName' do
- pod 'DifferenceKit/Core'
-end
+To use only algorithm without extensions for UI, add the following to your `Podfile`:
+```ruby
+pod 'DifferenceKit/Core'
```
-#### iOS/tvOS
+#### iOS / tvOS
+
To use DifferenceKit with UIKit extension, add the following to your `Podfile`:
```ruby
-target 'TargetName' do
- pod 'DifferenceKit'
-end
+pod 'DifferenceKit'
```
or
```ruby
-target 'TargetName' do
- pod 'DifferenceKit/UIKitExtension'
-end
+pod 'DifferenceKit/UIKitExtension'
```
#### macOS
+
To use DifferenceKit with AppKit extension, add the following to your `Podfile`:
```ruby
-target 'TargetName' do
- pod 'DifferenceKit/AppKitExtension'
-end
+pod 'DifferenceKit/AppKitExtension'
```
-### watchOS
+#### watchOS
+
There is no UI extension for watchOS.
-You can use only the algorithm.
+To use only algorithm without extensions for UI, add the following to your `Podfile`:
+```ruby
+pod 'DifferenceKit/Core'
+```
### [Carthage](https://github.com/Carthage/Carthage)
+
Add the following to your `Cartfile`:
```ruby
github "ra1028/DifferenceKit"
```
-And run
-```sh
-carthage update
-```
### [Swift Package Manager](https://swift.org/package-manager/)
-To use DifferenceKit in a project with SPM, add the following to your `Package.swift`:
+
+The SwiftPM version does not include the extensions for UI.
+Add the following to the dependencies of your `Package.swift`:
```swift
-import PackageDescription
-
-let package = Package(
- name: "YourProjectName",
- products: [
- .executable(name: "yourexecutable", targets: ["yourexecutable"]),
- ],
- dependencies: [
- .package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")
- ],
- targets: [
- .target(name: "yourexecutable", dependencies: ["DifferenceKit"])
- ]
-)
+.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")
```
-The SPM version does not include the UIKit and AppKit extensions.
---
## Contribution
-Welcome to fork and submit pull requests.
-Before submitting pull request, please ensure you have passed the included tests.
-If your pull request including new function, please write test cases for it.
+Pull requests, bug reports and feature requests are welcome 🚀
+Please see the [CONTRIBUTING](https://github.com/ra1028/DifferenceKit/blob/master/CONTRIBUTING.md) file for learn how to contribute to DifferenceKit.
+
---
## Credit
#### Bibliography
-DifferenceKit was developed with reference to the following excellent materials.
+DifferenceKit was developed with reference to the following excellent materials and framework.
- [A technique for isolating differences between files](https://dl.acm.org/citation.cfm?id=359467) (by [Paul Heckel](https://dl.acm.org/author_page.cfm?id=81100051772))
- [DifferenceAlgorithmComparison](https://github.com/horita-yuya/DifferenceAlgorithmComparison) (by [@horita-yuya](https://github.com/horita-yuya))
@@ -376,11 +350,9 @@ I respect and ️❤️ all libraries involved in diffing.
- [IGListKit](https://github.com/Instagram/IGListKit) (by [Instagram](https://github.com/Instagram))
- [FlexibleDiff](https://github.com/RACCommunity/FlexibleDiff) (by [@andersio](https://github.com/andersio), [RACCommunity](https://github.com/RACCommunity))
- [DeepDiff](https://github.com/onmyway133/DeepDiff) (by [@onmyway133](https://github.com/onmyway133))
-- [Changeset](https://github.com/osteslag/Changeset) (by [@osteslag](https://github.com/osteslag))
- [Differ](https://github.com/tonyarnold/Differ) (by [@tonyarnold](https://github.com/tonyarnold))
-- [Diff.swift](https://github.com/wokalski/Diff.swift) (by [@wokalski](https://github.com/wokalski))
- [Dwifft](https://github.com/jflinter/Dwifft) (by [@jflinter](https://github.com/jflinter))
-- [ListDiff](https://github.com/lxcid/ListDiff) (by [@lxcid](https://github.com/lxcid))
+- [Changeset](https://github.com/osteslag/Changeset) (by [@osteslag](https://github.com/osteslag))
---
From 617edde8bf2e1e7023fc5200423982e422e3b672 Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Tue, 7 May 2019 11:44:22 +0900
Subject: [PATCH 14/20] Refactor
---
Sources/Algorithm.swift | 61 +++++++++++++++++++++--------------------
1 file changed, 32 insertions(+), 29 deletions(-)
diff --git a/Sources/Algorithm.swift b/Sources/Algorithm.swift
index 7b2b741..8125778 100644
--- a/Sources/Algorithm.swift
+++ b/Sources/Algorithm.swift
@@ -56,13 +56,13 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
return
}
- // Returns the changesets that all deletions if source is not empty and target is empty
+ // Returns the changesets that all deletions if source is not empty and target is empty.
if !sourceElements.isEmpty && targetElements.isEmpty {
self.init([Changeset(data: target, elementDeleted: sourceElements.indices.map { ElementPath(element: $0, section: section) })])
return
}
- // Returns the changesets that all insertions if source is empty and target is not empty
+ // Returns the changesets that all insertions if source is empty and target is not empty.
if sourceElements.isEmpty && !targetElements.isEmpty {
self.init([Changeset(data: target, elementInserted: targetElements.indices.map { ElementPath(element: $0, section: section) })])
return
@@ -187,7 +187,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
flattenSourceIdentifiers.reserveCapacity(flattenSourceCount)
flattenSourceElementPaths.reserveCapacity(flattenSourceCount)
- // Calculate the section differences.
+ // Calculate section differences.
let sectionResult = differentiate(
source: sourceSections,
@@ -196,7 +196,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
mapIndex: { $0 }
)
- // Calculate the element differences.
+ // Calculate element differences.
var elementDeleted = [ElementPath]()
var elementInserted = [ElementPath]()
@@ -214,9 +214,9 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
flattenSourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
// The pointer and the table key are for optimization.
- var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: flattenSourceCount * 2)
+ var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: flattenSourceCount)
- // Record the index where the element was found in flatten source collection into occurrences table.
+ // Track indices of elements found in flatten source collection into occurrences table.
for flattenSourceIndex in flattenSourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: flattenSourceIndex)
let key = TableKey(pointer: pointer)
@@ -234,7 +234,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
}
}
- // Record the target index and the source index that the element having the same identifier.
+ // Track an target and source indices of the elements having same identifier.
for targetSectionIndex in contiguousTargetSections.indices {
let targetElements = contiguousTargetSections[targetSectionIndex]
@@ -267,14 +267,14 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
}
}
- // Record the element deletions.
+ // Track element deletes.
for sourceSectionIndex in contiguousSourceSections.indices {
let sourceSection = sourceSections[sourceSectionIndex]
let sourceElements = contiguousSourceSections[sourceSectionIndex]
var firstStageElements = sourceElements
- // Should not calculate the element deletions in the deleted section.
- if case .some = sectionResult.metadata.sourceTraces[sourceSectionIndex].reference {
+ // Should not track element deletes in the deleted section.
+ if case .some = sectionResult.sourceTraces[sourceSectionIndex].reference {
var offsetByDelete = 0
var secondStageElements = ContiguousArray()
@@ -284,9 +284,9 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
sourceElementTraces[sourceElementPath].deleteOffset = offsetByDelete
- // If the element target section is recorded as insertion, record its element path as deletion.
+ // Track element deletes if target section is tracked as inserts.
if let targetElementPath = sourceElementTraces[sourceElementPath].reference,
- case .some = sectionResult.metadata.targetReferences[targetElementPath.section] {
+ case .some = sectionResult.targetReferences[targetElementPath.section] {
let targetElement = contiguousTargetSections[targetElementPath]
firstStageElements[sourceElementIndex] = targetElement
secondStageElements.append(targetElement)
@@ -307,10 +307,10 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
firstStageSections[sourceSectionIndex] = firstStageSection
}
- // Record the element updates/moves/insertions.
+ // Track element updates / moves / inserts.
for targetSectionIndex in contiguousTargetSections.indices {
- // Should not calculate the element updates/moves/insertions in the inserted section.
- guard let sourceSectionIndex = sectionResult.metadata.targetReferences[targetSectionIndex] else {
+ // Should not track element updates / moves / inserts in the inserted section.
+ guard let sourceSectionIndex = sectionResult.targetReferences[targetSectionIndex] else {
thirdStageSections.append(targetSections[targetSectionIndex])
fourthStageSections.append(targetSections[targetSectionIndex])
continue
@@ -319,7 +319,7 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
var untrackedSourceIndex: Int? = 0
let targetElements = contiguousTargetSections[targetSectionIndex]
- let sectionDeleteOffset = sectionResult.metadata.sourceTraces[sourceSectionIndex].deleteOffset
+ let sectionDeleteOffset = sectionResult.sourceTraces[sourceSectionIndex].deleteOffset
let thirdStageSection = secondStageSections[sourceSectionIndex - sectionDeleteOffset]
thirdStageSections.append(thirdStageSection)
@@ -335,9 +335,9 @@ public extension StagedChangeset where Collection: RangeReplaceableCollection, C
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
let targetElement = contiguousTargetSections[targetElementPath]
- // If the element source section is recorded as deletion, record its element path as insertion.
+ // Track element inserts if source section is tracked as deletes.
guard let sourceElementPath = targetElementReferences[targetElementPath],
- let movedSourceSectionIndex = sectionResult.metadata.sourceTraces[sourceElementPath.section].reference else {
+ let movedSourceSectionIndex = sectionResult.sourceTraces[sourceElementPath.section].reference else {
fourthStageElements.append(targetElement)
elementInserted.append(targetElementPath)
continue
@@ -471,9 +471,9 @@ internal func differentiate(
sourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
// The pointer and the table key are for optimization.
- var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: source.count * 2)
+ var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: source.count)
- // Record the index where the element was found in source collection into occurrences table.
+ // Track indices of elements found in source collection into occurrences table.
for sourceIndex in sourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: sourceIndex)
let key = TableKey(pointer: pointer)
@@ -491,7 +491,7 @@ internal func differentiate(
}
}
- // Record the target index and the source index that the element having the same identifier.
+ // Track an target and source indices of the elements having same identifier.
for targetIndex in target.indices {
var targetIdentifier = target[targetIndex].differenceIdentifier
let key = TableKey(pointer: &targetIdentifier)
@@ -518,7 +518,7 @@ internal func differentiate(
var offsetByDelete = 0
var untrackedSourceIndex: Int? = 0
- // Record the deletions.
+ // Track deletes.
for sourceIndex in source.indices {
sourceTraces[sourceIndex].deleteOffset = offsetByDelete
@@ -536,7 +536,7 @@ internal func differentiate(
}
}
- // Record the updates/moves/insertions.
+ // Track updates / moves / inserts.
for targetIndex in target.indices {
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
sourceTraces.suffix(from: index).firstIndex { !$0.isTracked }
@@ -567,15 +567,14 @@ internal func differentiate(
inserted: inserted,
updated: updated,
moved: moved,
- metadata: (sourceTraces: sourceTraces, targetReferences: targetReferences)
+ sourceTraces: sourceTraces,
+ targetReferences: targetReferences
)
}
/// A set of changes and metadata as a result of calculating differences in linear collection.
@usableFromInline
internal struct DifferentiateResult {
- @usableFromInline
- internal typealias Metadata = (sourceTraces: ContiguousArray>, targetReferences: ContiguousArray)
@usableFromInline
internal let deleted: [Index]
@usableFromInline
@@ -585,7 +584,9 @@ internal struct DifferentiateResult {
@usableFromInline
internal let moved: [(source: Index, target: Index)]
@usableFromInline
- internal let metadata: Metadata
+ internal let sourceTraces: ContiguousArray>
+ @usableFromInline
+ internal let targetReferences: ContiguousArray
@inlinable
internal init(
@@ -593,13 +594,15 @@ internal struct DifferentiateResult {
inserted: [Index] = [],
updated: [Index] = [],
moved: [(source: Index, target: Index)] = [],
- metadata: Metadata
+ sourceTraces: ContiguousArray>,
+ targetReferences: ContiguousArray
) {
self.deleted = deleted
self.inserted = inserted
self.updated = updated
self.moved = moved
- self.metadata = metadata
+ self.sourceTraces = sourceTraces
+ self.targetReferences = targetReferences
}
}
From 986e54b27f425908507a93cd63f4a282ac95c92f Mon Sep 17 00:00:00 2001
From: Ryo Aoyama
Date: Tue, 7 May 2019 12:15:08 +0900
Subject: [PATCH 15/20] Update sample gif
---
README.md | 4 +++-
assets/sample.gif | Bin 5334239 -> 3484229 bytes
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 5ae0ec1..966ee6b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
@@ -21,6 +21,8 @@ The algorithm is optimized based on the Paul Heckel's algorithm.
+
+
Made with ❤️ by Ryo Aoyama and Contributors
diff --git a/assets/sample.gif b/assets/sample.gif
index 14df4e8487f918e5220eac73c42079a7d4a97c2d..8bdb6ac3183946dc0819df86a149cf93e9925918 100644
GIT binary patch
literal 3484229
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⋙ty%Xv``~C5L)J0-C+;a#-X_H>m
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