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 @@ - -