diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index f2b3a39..9052177 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 8C30CA5C1C8E71880008B717 /* ArrayExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA4B1C8E71880008B717 /* ArrayExt.swift */; }; - 8C30CA5D1C8E71880008B717 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA4C1C8E71880008B717 /* Common.swift */; }; 8C30CA5E1C8E71880008B717 /* BumpTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA4D1C8E71880008B717 /* BumpTracker.swift */; }; 8C30CA5F1C8E71880008B717 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA4E1C8E71880008B717 /* Cell.swift */; }; 8C30CA601C8E71880008B717 /* CellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA4F1C8E71880008B717 /* CellModel.swift */; }; @@ -18,9 +17,7 @@ 8C30CA641C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA531C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift */; }; 8C30CA651C8E71880008B717 /* Hakuba.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA541C8E71880008B717 /* Hakuba.swift */; }; 8C30CA661C8E71880008B717 /* HakubaDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA551C8E71880008B717 /* HakubaDelegate.swift */; }; - 8C30CA671C8E71880008B717 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA561C8E71880008B717 /* Label.swift */; }; 8C30CA681C8E71880008B717 /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA571C8E71880008B717 /* Section.swift */; }; - 8C30CA691C8E71880008B717 /* RangeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA581C8E71880008B717 /* RangeExt.swift */; }; 8C30CA6A1C8E71880008B717 /* TableViewCellExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA591C8E71880008B717 /* TableViewCellExt.swift */; }; 8C30CA6B1C8E71880008B717 /* TableViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA5A1C8E71880008B717 /* TableViewExt.swift */; }; 8C30CA6C1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C30CA5B1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift */; }; @@ -42,6 +39,9 @@ 8C60BC7F1C89D4550006CE38 /* ExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C60BC7E1C89D4550006CE38 /* ExampleUITests.swift */; }; 8CFF14821C9568C9005699CE /* CellType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFF14811C9568C9005699CE /* CellType.swift */; }; 8CFF14A61C96758A005699CE /* CustomCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CFF14A51C96758A005699CE /* CustomCellModel.swift */; }; + ABAB7F75211BFC3C004418CF /* BumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABAB7F74211BFC3C004418CF /* BumpType.swift */; }; + ABAB7F79211C0121004418CF /* SectionIndexType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABAB7F78211C0121004418CF /* SectionIndexType.swift */; }; + ABAB7F7B211C0146004418CF /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABAB7F7A211C0146004418CF /* Utilities.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,7 +63,6 @@ /* Begin PBXFileReference section */ 8C30CA4B1C8E71880008B717 /* ArrayExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExt.swift; sourceTree = ""; }; - 8C30CA4C1C8E71880008B717 /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; 8C30CA4D1C8E71880008B717 /* BumpTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BumpTracker.swift; sourceTree = ""; }; 8C30CA4E1C8E71880008B717 /* Cell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; }; 8C30CA4F1C8E71880008B717 /* CellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellModel.swift; sourceTree = ""; }; @@ -73,9 +72,7 @@ 8C30CA531C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Hakuba+UIScrollViewDelegate.swift"; sourceTree = ""; }; 8C30CA541C8E71880008B717 /* Hakuba.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hakuba.swift; sourceTree = ""; }; 8C30CA551C8E71880008B717 /* HakubaDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HakubaDelegate.swift; sourceTree = ""; }; - 8C30CA561C8E71880008B717 /* Label.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 8C30CA571C8E71880008B717 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; - 8C30CA581C8E71880008B717 /* RangeExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RangeExt.swift; sourceTree = ""; }; 8C30CA591C8E71880008B717 /* TableViewCellExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCellExt.swift; sourceTree = ""; }; 8C30CA5A1C8E71880008B717 /* TableViewExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewExt.swift; sourceTree = ""; }; 8C30CA5B1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewHeaderFooterViewExt.swift; sourceTree = ""; }; @@ -103,6 +100,9 @@ 8C60BC801C89D4550006CE38 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8CFF14811C9568C9005699CE /* CellType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellType.swift; sourceTree = ""; }; 8CFF14A51C96758A005699CE /* CustomCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomCellModel.swift; sourceTree = ""; }; + ABAB7F74211BFC3C004418CF /* BumpType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BumpType.swift; sourceTree = ""; }; + ABAB7F78211C0121004418CF /* SectionIndexType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionIndexType.swift; sourceTree = ""; }; + ABAB7F7A211C0146004418CF /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,25 +133,8 @@ 8C30CA4A1C8E71880008B717 /* Hakuba */ = { isa = PBXGroup; children = ( - 8C30CA4C1C8E71880008B717 /* Common.swift */, - 8C30CA4D1C8E71880008B717 /* BumpTracker.swift */, - 8CFF14811C9568C9005699CE /* CellType.swift */, - 8C30CA4E1C8E71880008B717 /* Cell.swift */, - 8C30CA4F1C8E71880008B717 /* CellModel.swift */, - 8C5820DA1C984FDD009FB9AE /* HeaderFooterViewType.swift */, - 8C30CA501C8E71880008B717 /* HeaderFooterView.swift */, - 8C30CA511C8E71880008B717 /* HeaderFooterViewModel.swift */, - 8C30CA571C8E71880008B717 /* Section.swift */, - 8C30CA541C8E71880008B717 /* Hakuba.swift */, - 8C30CA521C8E71880008B717 /* Hakuba+Registration.swift */, - 8C30CA531C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift */, - 8C30CA551C8E71880008B717 /* HakubaDelegate.swift */, - 8C30CA561C8E71880008B717 /* Label.swift */, - 8C30CA4B1C8E71880008B717 /* ArrayExt.swift */, - 8C30CA581C8E71880008B717 /* RangeExt.swift */, - 8C30CA591C8E71880008B717 /* TableViewCellExt.swift */, - 8C30CA5A1C8E71880008B717 /* TableViewExt.swift */, - 8C30CA5B1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift */, + ABAB7F72211BFBE6004418CF /* Extension */, + ABAB7F73211BFC08004418CF /* Source */, ); name = Hakuba; path = ../Hakuba; @@ -181,21 +164,10 @@ 8C60BC5D1C89D4550006CE38 /* Example */ = { isa = PBXGroup; children = ( - 8C60BC5E1C89D4550006CE38 /* AppDelegate.swift */, - 8C60BC601C89D4550006CE38 /* ViewController.swift */, - 8C5820DC1C9852E3009FB9AE /* HeaderFooterTestViewController.swift */, - 8C5820DE1C9852F7009FB9AE /* CellTestViewController.swift */, - 8C3446811C8E6F97002BC8CD /* CustomCell.swift */, - 8CFF14A51C96758A005699CE /* CustomCellModel.swift */, - 8C3446821C8E6F97002BC8CD /* CustomCell.xib */, - 8C3446801C8E6F97002BC8CD /* ChildViewController.swift */, - 8C5820E01C985625009FB9AE /* CustomHeaderView.swift */, - 8C5820E21C98562F009FB9AE /* CustomHeaderViewModel.swift */, - 8C5820E41C985640009FB9AE /* CustomHeaderView.xib */, - 8C60BC621C89D4550006CE38 /* Main.storyboard */, 8C60BC651C89D4550006CE38 /* Assets.xcassets */, - 8C60BC671C89D4550006CE38 /* LaunchScreen.storyboard */, 8C60BC6A1C89D4550006CE38 /* Info.plist */, + 8C60BC671C89D4550006CE38 /* LaunchScreen.storyboard */, + ABAB7F6E211BFB26004418CF /* Source */, ); path = Example; sourceTree = ""; @@ -218,6 +190,130 @@ path = ExampleUITests; sourceTree = ""; }; + ABAB7F6E211BFB26004418CF /* Source */ = { + isa = PBXGroup; + children = ( + 8C60BC5E1C89D4550006CE38 /* AppDelegate.swift */, + ABAB7F6F211BFB5D004418CF /* ViewController */, + ABAB7F70211BFB6F004418CF /* View */, + ); + path = Source; + sourceTree = ""; + }; + ABAB7F6F211BFB5D004418CF /* ViewController */ = { + isa = PBXGroup; + children = ( + 8C5820DE1C9852F7009FB9AE /* CellTestViewController.swift */, + 8C3446801C8E6F97002BC8CD /* ChildViewController.swift */, + 8C5820DC1C9852E3009FB9AE /* HeaderFooterTestViewController.swift */, + 8C60BC621C89D4550006CE38 /* Main.storyboard */, + 8C60BC601C89D4550006CE38 /* ViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + ABAB7F70211BFB6F004418CF /* View */ = { + isa = PBXGroup; + children = ( + 8C3446811C8E6F97002BC8CD /* CustomCell.swift */, + 8C3446821C8E6F97002BC8CD /* CustomCell.xib */, + 8C5820E01C985625009FB9AE /* CustomHeaderView.swift */, + 8C5820E41C985640009FB9AE /* CustomHeaderView.xib */, + ABAB7F71211BFB81004418CF /* Model */, + ); + path = View; + sourceTree = ""; + }; + ABAB7F71211BFB81004418CF /* Model */ = { + isa = PBXGroup; + children = ( + 8CFF14A51C96758A005699CE /* CustomCellModel.swift */, + 8C5820E21C98562F009FB9AE /* CustomHeaderViewModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + ABAB7F72211BFBE6004418CF /* Extension */ = { + isa = PBXGroup; + children = ( + 8C30CA4B1C8E71880008B717 /* ArrayExt.swift */, + 8C30CA591C8E71880008B717 /* TableViewCellExt.swift */, + 8C30CA5A1C8E71880008B717 /* TableViewExt.swift */, + 8C30CA5B1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift */, + ); + path = Extension; + sourceTree = ""; + }; + ABAB7F73211BFC08004418CF /* Source */ = { + isa = PBXGroup; + children = ( + ABAB7F7C211C0181004418CF /* Bump */, + ABAB7F7D211C018D004418CF /* Cell */, + ABAB7F81211C01C6004418CF /* Common */, + ABAB7F7E211C0198004418CF /* Core */, + ABAB7F7F211C01A1004418CF /* HeaderFooter */, + ABAB7F80211C01B7004418CF /* Section */, + ); + path = Source; + sourceTree = ""; + }; + ABAB7F7C211C0181004418CF /* Bump */ = { + isa = PBXGroup; + children = ( + 8C30CA4D1C8E71880008B717 /* BumpTracker.swift */, + ABAB7F74211BFC3C004418CF /* BumpType.swift */, + ); + path = Bump; + sourceTree = ""; + }; + ABAB7F7D211C018D004418CF /* Cell */ = { + isa = PBXGroup; + children = ( + 8C30CA4E1C8E71880008B717 /* Cell.swift */, + 8C30CA4F1C8E71880008B717 /* CellModel.swift */, + 8CFF14811C9568C9005699CE /* CellType.swift */, + ); + path = Cell; + sourceTree = ""; + }; + ABAB7F7E211C0198004418CF /* Core */ = { + isa = PBXGroup; + children = ( + 8C30CA541C8E71880008B717 /* Hakuba.swift */, + 8C30CA521C8E71880008B717 /* Hakuba+Registration.swift */, + 8C30CA531C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift */, + 8C30CA551C8E71880008B717 /* HakubaDelegate.swift */, + ); + path = Core; + sourceTree = ""; + }; + ABAB7F7F211C01A1004418CF /* HeaderFooter */ = { + isa = PBXGroup; + children = ( + 8C30CA501C8E71880008B717 /* HeaderFooterView.swift */, + 8C30CA511C8E71880008B717 /* HeaderFooterViewModel.swift */, + 8C5820DA1C984FDD009FB9AE /* HeaderFooterViewType.swift */, + ); + path = HeaderFooter; + sourceTree = ""; + }; + ABAB7F80211C01B7004418CF /* Section */ = { + isa = PBXGroup; + children = ( + 8C30CA571C8E71880008B717 /* Section.swift */, + ABAB7F78211C0121004418CF /* SectionIndexType.swift */, + ); + path = Section; + sourceTree = ""; + }; + ABAB7F81211C01C6004418CF /* Common */ = { + isa = PBXGroup; + children = ( + ABAB7F7A211C0146004418CF /* Utilities.swift */, + ); + path = Common; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -281,7 +377,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0940; TargetAttributes = { 8C60BC5A1C89D4550006CE38 = { CreatedOnToolsVersion = 7.2; @@ -356,7 +452,8 @@ 8C5820DD1C9852E3009FB9AE /* HeaderFooterTestViewController.swift in Sources */, 8CFF14A61C96758A005699CE /* CustomCellModel.swift in Sources */, 8C30CA681C8E71880008B717 /* Section.swift in Sources */, - 8C30CA671C8E71880008B717 /* Label.swift in Sources */, + ABAB7F7B211C0146004418CF /* Utilities.swift in Sources */, + ABAB7F79211C0121004418CF /* SectionIndexType.swift in Sources */, 8C30CA621C8E71880008B717 /* HeaderFooterViewModel.swift in Sources */, 8C30CA641C8E71880008B717 /* Hakuba+UIScrollViewDelegate.swift in Sources */, 8C30CA631C8E71880008B717 /* Hakuba+Registration.swift in Sources */, @@ -364,8 +461,8 @@ 8C30CA5E1C8E71880008B717 /* BumpTracker.swift in Sources */, 8C60BC611C89D4550006CE38 /* ViewController.swift in Sources */, 8C30CA5C1C8E71880008B717 /* ArrayExt.swift in Sources */, - 8C30CA691C8E71880008B717 /* RangeExt.swift in Sources */, 8C30CA6C1C8E71880008B717 /* TableViewHeaderFooterViewExt.swift in Sources */, + ABAB7F75211BFC3C004418CF /* BumpType.swift in Sources */, 8C30CA661C8E71880008B717 /* HakubaDelegate.swift in Sources */, 8C30CA651C8E71880008B717 /* Hakuba.swift in Sources */, 8C3446831C8E6F97002BC8CD /* ChildViewController.swift in Sources */, @@ -375,7 +472,6 @@ 8C30CA601C8E71880008B717 /* CellModel.swift in Sources */, 8C60BC5F1C89D4550006CE38 /* AppDelegate.swift in Sources */, 8C30CA6B1C8E71880008B717 /* TableViewExt.swift in Sources */, - 8C30CA5D1C8E71880008B717 /* Common.swift in Sources */, 8C5820E31C98562F009FB9AE /* CustomHeaderViewModel.swift in Sources */, 8C3446841C8E6F97002BC8CD /* CustomCell.swift in Sources */, ); @@ -440,13 +536,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -468,7 +574,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -485,13 +591,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = 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_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_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -507,9 +623,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -520,10 +637,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Example/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nghialv.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -532,10 +650,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Example/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nghialv.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/Example/Base.lproj/LaunchScreen.storyboard b/Example/Example/Base.lproj/LaunchScreen.storyboard index 2e721e1..12baca9 100644 --- a/Example/Example/Base.lproj/LaunchScreen.storyboard +++ b/Example/Example/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,12 @@ - - + + + + + - + + + @@ -13,10 +18,9 @@ - + - - + diff --git a/Example/Example/AppDelegate.swift b/Example/Example/Source/AppDelegate.swift similarity index 67% rename from Example/Example/AppDelegate.swift rename to Example/Example/Source/AppDelegate.swift index 2e40b31..e9c1b7f 100644 --- a/Example/Example/AppDelegate.swift +++ b/Example/Example/Source/AppDelegate.swift @@ -9,45 +9,33 @@ import UIKit @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - +final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { // Override point for customization after application launch. return true } - - func applicationWillResignActive(application: UIApplication) { + + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - - func applicationDidEnterBackground(application: UIApplication) { + + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - - func applicationWillEnterForeground(application: UIApplication) { + + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - - func applicationDidBecomeActive(application: UIApplication) { + + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - - func applicationWillTerminate(application: UIApplication) { + + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } - -func delay(delay:Double, closure:()->()) { - dispatch_after( - dispatch_time( - DISPATCH_TIME_NOW, - Int64(delay * Double(NSEC_PER_SEC)) - ), - dispatch_get_main_queue(), closure) -} - diff --git a/Example/Example/CustomCell.swift b/Example/Example/Source/View/CustomCell.swift similarity index 75% rename from Example/Example/CustomCell.swift rename to Example/Example/Source/View/CustomCell.swift index 086b5bd..c374eb0 100644 --- a/Example/Example/CustomCell.swift +++ b/Example/Example/Source/View/CustomCell.swift @@ -11,7 +11,7 @@ import UIKit class CustomCell: Cell, CellType { typealias CellModel = CustomCellModel - @IBOutlet weak var titleLabel: Label! + @IBOutlet weak var titleLabel: UILabel! override func configure() { guard let cellmodel = cellmodel else { @@ -20,9 +20,4 @@ class CustomCell: Cell, CellType { titleLabel.text = cellmodel.title + "(\(cellmodel.indexPath.section),\(cellmodel.indexPath.row))" } - - override func willDisplay(tableView: UITableView) { - super.willDisplay(tableView) - - } } diff --git a/Example/Example/CustomCell.xib b/Example/Example/Source/View/CustomCell.xib similarity index 83% rename from Example/Example/CustomCell.xib rename to Example/Example/Source/View/CustomCell.xib index b117545..6b7d8c5 100644 --- a/Example/Example/CustomCell.xib +++ b/Example/Example/Source/View/CustomCell.xib @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -11,13 +15,13 @@ - + - @@ -29,7 +33,7 @@ - + diff --git a/Example/Example/CustomHeaderView.swift b/Example/Example/Source/View/CustomHeaderView.swift similarity index 80% rename from Example/Example/CustomHeaderView.swift rename to Example/Example/Source/View/CustomHeaderView.swift index f36a224..77ddfac 100644 --- a/Example/Example/CustomHeaderView.swift +++ b/Example/Example/Source/View/CustomHeaderView.swift @@ -15,14 +15,12 @@ final class CustomHeaderView: HeaderFooterView, HeaderFooterViewType { @IBOutlet weak var floatingLabel: UILabel! override func configure() { - guard let vm = viewmodel else { - return - } + guard let vm = viewmodel else { return } label.text = vm.text } - override func didChangeFloatingState(isFloating: Bool, section: Int) { + override func didChangeFloatingState(_ isFloating: Bool, section: Int) { super.didChangeFloatingState(isFloating, section: section) let title = isFloating ? "F \(section)" : "n \(section)" diff --git a/Example/Example/CustomHeaderView.xib b/Example/Example/Source/View/CustomHeaderView.xib similarity index 86% rename from Example/Example/CustomHeaderView.xib rename to Example/Example/Source/View/CustomHeaderView.xib index 88d83d6..7e2b7b4 100644 --- a/Example/Example/CustomHeaderView.xib +++ b/Example/Example/Source/View/CustomHeaderView.xib @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -14,13 +18,13 @@ diff --git a/Example/Example/CustomCellModel.swift b/Example/Example/Source/View/Model/CustomCellModel.swift similarity index 78% rename from Example/Example/CustomCellModel.swift rename to Example/Example/Source/View/Model/CustomCellModel.swift index d8de15b..9e3156f 100644 --- a/Example/Example/CustomCellModel.swift +++ b/Example/Example/Source/View/Model/CustomCellModel.swift @@ -11,8 +11,9 @@ import Foundation class CustomCellModel: CellModel { let title: String - init(title: String, selectionHandler: SelectionHandler) { + init(title: String, selectionHandler: @escaping (Cell) -> Void) { self.title = title + super.init(cell: CustomCell.self, selectionHandler: selectionHandler) } } diff --git a/Example/Example/CustomHeaderViewModel.swift b/Example/Example/Source/View/Model/CustomHeaderViewModel.swift similarity index 99% rename from Example/Example/CustomHeaderViewModel.swift rename to Example/Example/Source/View/Model/CustomHeaderViewModel.swift index d2af260..75a5746 100644 --- a/Example/Example/CustomHeaderViewModel.swift +++ b/Example/Example/Source/View/Model/CustomHeaderViewModel.swift @@ -15,4 +15,4 @@ final class CustomHeaderViewModel: HeaderFooterViewModel { self.text = text super.init(view: CustomHeaderView.self) } -} \ No newline at end of file +} diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Source/ViewController/Base.lproj/Main.storyboard similarity index 81% rename from Example/Example/Base.lproj/Main.storyboard rename to Example/Example/Source/ViewController/Base.lproj/Main.storyboard index 556db92..4f0cf3a 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Source/ViewController/Base.lproj/Main.storyboard @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -14,15 +18,15 @@ - + - - + + - + @@ -37,7 +41,7 @@ - + @@ -48,14 +52,14 @@ - + - + - + @@ -66,15 +70,15 @@ - + - - + + - + @@ -88,7 +92,7 @@ - + @@ -99,15 +103,15 @@ - + - - - + + + - + @@ -121,7 +125,7 @@ - + @@ -129,7 +133,7 @@ - + diff --git a/Example/Example/CellTestViewController.swift b/Example/Example/Source/ViewController/CellTestViewController.swift similarity index 64% rename from Example/Example/CellTestViewController.swift rename to Example/Example/Source/ViewController/CellTestViewController.swift index 49fce8e..a408e11 100644 --- a/Example/Example/CellTestViewController.swift +++ b/Example/Example/Source/ViewController/CellTestViewController.swift @@ -9,8 +9,8 @@ import UIKit enum SectionIndex: Int, SectionIndexType { - case Top - case Center + case top + case center static let count = 2 } @@ -19,13 +19,15 @@ class CellTestViewController: UIViewController { @IBOutlet weak var tableView: UITableView! private lazy var hakuba: Hakuba = Hakuba(tableView: self.tableView) - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { hakuba.deselectAllCells(animated: true) } override func viewDidLoad() { super.viewDidLoad() - hakuba.registerCellByNib(CustomCell) + tableView.tableFooterView = .init() + + hakuba.registerCellByNib(CustomCell.self) // Top section @@ -37,13 +39,13 @@ class CellTestViewController: UIViewController { self?.pushChildViewController() } } - + hakuba - .reset(SectionIndex) + .reset(SectionIndex.self) .bump() - let topSection = hakuba[SectionIndex.Top] - let centerSection = hakuba[SectionIndex.Center] + let topSection = hakuba[SectionIndex.top] + let centerSection = hakuba[SectionIndex.center] topSection .reset(topCellmodels) @@ -60,47 +62,46 @@ class CellTestViewController: UIViewController { print("Did select cell with title = \(title)") self?.pushChildViewController() } - data.dynamicHeightEnabled = true return data } - delay(1.5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { centerSection .append(centerCellmodels) - .bump(.Left) + .bump(.left) } - delay(3) { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { centerSection - .remove(2...4) - .bump(.Right) + .remove(range: 2...4) + .bump(.right) } - delay(5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.hakuba - .move(0, to: 1) + .move(from: 0, to: 1) .bump() } - delay(7.5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 7.5) { topSection - .remove(1) - .bump(.Middle) + .remove(at: 1) + .bump(.middle) } - delay(10) { + DispatchQueue.main.asyncAfter(deadline: .now() + 10) { topSection - .remove(0) - .bump(.Right) + .remove(at: 0) + .bump(.right) } - delay(12.5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 12.5) { topSection - .remove(0) - .bump(.Right) + .remove(at: 0) + .bump(.right) } - delay(15) { + DispatchQueue.main.asyncAfter(deadline: .now() + 15) { self.hakuba .reset() .bump() @@ -109,7 +110,7 @@ class CellTestViewController: UIViewController { func pushChildViewController() { let storyboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewControllerWithIdentifier("ChildViewController") as! ChildViewController + let vc = storyboard.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController self.navigationController?.pushViewController(vc, animated: true) } } diff --git a/Example/Example/ChildViewController.swift b/Example/Example/Source/ViewController/ChildViewController.swift similarity index 75% rename from Example/Example/ChildViewController.swift rename to Example/Example/Source/ViewController/ChildViewController.swift index c513fba..96993f6 100644 --- a/Example/Example/ChildViewController.swift +++ b/Example/Example/Source/ViewController/ChildViewController.swift @@ -8,6 +8,4 @@ import UIKit -class ChildViewController : UIViewController { - -} \ No newline at end of file +class ChildViewController : UIViewController {} diff --git a/Example/Example/HeaderFooterTestViewController.swift b/Example/Example/Source/ViewController/HeaderFooterTestViewController.swift similarity index 74% rename from Example/Example/HeaderFooterTestViewController.swift rename to Example/Example/Source/ViewController/HeaderFooterTestViewController.swift index 48dc586..0091f2a 100644 --- a/Example/Example/HeaderFooterTestViewController.swift +++ b/Example/Example/Source/ViewController/HeaderFooterTestViewController.swift @@ -10,16 +10,20 @@ import UIKit class HeaderFooterTestViewController: UIViewController { @IBOutlet weak var tableView: UITableView! - private lazy var hakuba: Hakuba = Hakuba(tableView: self.tableView) - override func viewWillAppear(animated: Bool) { + private lazy var hakuba: Hakuba = Hakuba(tableView: tableView) + + override func viewWillAppear(_ animated: Bool) { hakuba.deselectAllCells(animated: true) } override func viewDidLoad() { super.viewDidLoad() - hakuba.registerCellByNib(CustomCell) - .registerHeaderFooterByNib(CustomHeaderView) + tableView.tableFooterView = .init() + + hakuba + .registerCellByNib(CustomCell.self) + .registerHeaderFooterByNib(CustomHeaderView.self) // Top section @@ -44,10 +48,10 @@ class HeaderFooterTestViewController: UIViewController { } let topSection = Section() - topSection.header = CustomHeaderViewModel(text: "Top header") + topSection.header = CustomHeaderViewModel(text: "Top header") let centerSection = Section() - centerSection.header = CustomHeaderViewModel(text: "Center header") + centerSection.header = CustomHeaderViewModel(text: "Center header") hakuba .reset([topSection, centerSection]) @@ -65,7 +69,7 @@ class HeaderFooterTestViewController: UIViewController { func pushChildViewController() { let storyboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewControllerWithIdentifier("ChildViewController") as! ChildViewController + let vc = storyboard.instantiateViewController(withIdentifier: "ChildViewController") as! ChildViewController self.navigationController?.pushViewController(vc, animated: true) } } diff --git a/Example/Example/ViewController.swift b/Example/Example/Source/ViewController/ViewController.swift similarity index 72% rename from Example/Example/ViewController.swift rename to Example/Example/Source/ViewController/ViewController.swift index 03965df..1560a0a 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/Source/ViewController/ViewController.swift @@ -12,24 +12,26 @@ class ViewController: UIViewController { @IBOutlet weak var tableView: UITableView! private lazy var hakuba: Hakuba = Hakuba(tableView: self.tableView) - override func viewWillAppear(animated: Bool) { + override func viewWillAppear(_ animated: Bool) { hakuba.deselectAllCells(animated: true) } override func viewDidLoad() { super.viewDidLoad() - hakuba.registerCellByNib(CustomCell) - + tableView.tableFooterView = .init() + + hakuba.registerCellByNib(CustomCell.self) + let cm1 = CustomCellModel(title: "Test cell") { [weak self] _ in let storyboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewControllerWithIdentifier("CellTestViewController") as! CellTestViewController + let vc = storyboard.instantiateViewController(withIdentifier: "CellTestViewController") as! CellTestViewController self?.navigationController?.pushViewController(vc, animated: true) } let cm2 = CustomCellModel(title: "Test header footer") { [weak self] _ in let storyboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewControllerWithIdentifier("HeaderFooterTestViewController") as! HeaderFooterTestViewController + let vc = storyboard.instantiateViewController(withIdentifier: "HeaderFooterTestViewController") as! HeaderFooterTestViewController self?.navigationController?.pushViewController(vc, animated: true) } diff --git a/Hakuba/BumpTracker.swift b/Hakuba/BumpTracker.swift deleted file mode 100644 index bd0167f..0000000 --- a/Hakuba/BumpTracker.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// BumpTracker.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import Foundation - -private enum UpdateState { - case Begin - case Reload - case Insert([Int]) - case Move(Int, Int) - case Remove([Int]) -} - -final class BumpTracker { - private var state: UpdateState = .Begin - - var changed: Bool { - if case .Begin = state { - return false - } - return true - } - - func didBump() { - state = .Begin - } - - func didReset() { - state = .Reload - } - - func didInsert(indexes: [Int]) { - switch state { - case .Begin: - state = .Insert(indexes) - - default: - state = .Reload - } - } - - func didMove(from: Int, to: Int) { - switch state { - case .Begin: - state = .Move(from, to) - - default: - state = .Reload - } - } - - func didRemove(indexes: [Int]) { - switch state { - case .Begin: - state = .Remove(indexes) - - default: - state = .Reload - } - } - - func getSectionBumpType(index: Int) -> SectionBumpType { - let toIndexPath = { (row: Int) -> NSIndexPath in - return NSIndexPath(forRow: row, inSection: index) - } - - switch state { - case .Insert(let indexes) where indexes.isNotEmpty: - return .Insert(indexes.map(toIndexPath)) - - case .Move(let from, let to): - return .Move(toIndexPath(from), toIndexPath(to)) - - case .Remove(let indexes) where indexes.isNotEmpty: - return .Delete(indexes.map(toIndexPath)) - - default: - return .Reload(NSIndexSet(index: index)) - } - } - - func getHakubaBumpType() -> HakubaBumpType { - let toIndexSet = { (indexes: [Int]) -> NSIndexSet in - let indexSet = NSMutableIndexSet() - for index in indexes { - indexSet.addIndex(index) - } - return indexSet - } - - switch state { - case .Insert(let indexes) where indexes.isNotEmpty: - return .Insert(toIndexSet(indexes)) - - case .Move(let from, let to): - return .Move(from, to) - - case .Remove(let indexes) where indexes.isNotEmpty: - return .Delete(toIndexSet(indexes)) - - default: - return .Reload - } - } -} \ No newline at end of file diff --git a/Hakuba/Cell.swift b/Hakuba/Cell.swift deleted file mode 100644 index accb9ed..0000000 --- a/Hakuba/Cell.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Cell.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -public class Cell: UITableViewCell { - weak var _cellmodel: CellModel? - - func configureCell(cellmodel: CellModel) { - _cellmodel = cellmodel - configure() - } - - public func configure() { - } -} - -// MARK - Cell events - -public extension Cell { - func willDisplay(tableView: UITableView) { - } - - func didEndDisplay(tableView: UITableView) { - } - - func willSelect(tableView: UITableView, indexPath: NSIndexPath) -> NSIndexPath? { - return indexPath - } - - func didSelect(tableView: UITableView) { - } - - func willDeselect(tableView: UITableView, indexPath: NSIndexPath) -> NSIndexPath? { - return indexPath - } - - func didDeselect(tableView: UITableView) { - } - - func willBeginEditing(tableView: UITableView) { - } - - func didEndEditing(tableView: UITableView) { - } - - func didHighlight(tableView: UITableView) { - } - - func didUnhighlight(tableView: UITableView) { - } -} diff --git a/Hakuba/CellModel.swift b/Hakuba/CellModel.swift deleted file mode 100644 index 30717f8..0000000 --- a/Hakuba/CellModel.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// CellModel.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -protocol CellModelDelegate: class { - func bumpMe(type: ItemBumpType, animation: Animation) - func getOffscreenCell(identifier: String) -> Cell - func tableViewWidth() -> CGFloat - func deselectCell(indexPath: NSIndexPath, animated: Bool) -} - -public class CellModel { - weak var delegate: CellModelDelegate? - - public let reuseIdentifier: String - public internal(set) var indexPath = NSIndexPath(forRow: 0, inSection: 0) - public var selectionHandler: SelectionHandler? - - public var editable = false - public var editingStyle: UITableViewCellEditingStyle = .None - public var shouldHighlight = true - - public var height: CGFloat { - get { - return dynamicHeightEnabled ? calculateHeight() : estimatedHeight - } - set { - estimatedHeight = newValue - } - } - - public var dynamicHeightEnabled: Bool = false { - didSet { - calculatedHeight = nil - } - } - - private var estimatedHeight: CGFloat = 0 - private var calculatedHeight: CGFloat? - - public init(cell: T.Type, height: CGFloat = 44, selectionHandler: SelectionHandler? = nil) { - self.reuseIdentifier = cell.reuseIdentifier - self.estimatedHeight = height - self.selectionHandler = selectionHandler - } - - public func bump(animation: Animation = .None) -> Self { - calculatedHeight = nil - delegate?.bumpMe(ItemBumpType.Reload(indexPath), animation: animation) - return self - } - - public func deselect(animated: Bool) { - delegate?.deselectCell(indexPath, animated: animated) - } -} - -// MARK - Internal methods - -extension CellModel { - func setup(indexPath: NSIndexPath, delegate: CellModelDelegate) { - self.indexPath = indexPath - self.delegate = delegate - } - - func didSelect(cell: Cell) { - selectionHandler?(cell) - } -} - -// MARK - Private methods - -private extension CellModel { - func calculateHeight() -> CGFloat { - if let height = calculatedHeight { - return height - } - - guard let cell = delegate?.getOffscreenCell(reuseIdentifier) else { - return estimatedHeight - } - - cell.configureCell(self) - - let width = delegate?.tableViewWidth() ?? UIScreen.mainScreen().bounds.width - cell.bounds = CGRectMake(0, 0, width, cell.bounds.height) - cell.setNeedsLayout() - cell.layoutIfNeeded() - - let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) - let height = size.height + 1 - calculatedHeight = height - - return height - } -} \ No newline at end of file diff --git a/Hakuba/Common.swift b/Hakuba/Common.swift deleted file mode 100644 index fd7ff3a..0000000 --- a/Hakuba/Common.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Common.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -public typealias SelectionHandler = (Cell) -> () - -public typealias Animation = UITableViewRowAnimation - -public protocol SectionIndexType { - var intValue: Int { get } - static var count: Int { get } -} - -public extension SectionIndexType where Self: RawRepresentable, Self.RawValue == Int { - var intValue: Int { - return rawValue - } -} - -public enum HakubaBumpType { - case Reload - case Insert(NSIndexSet) - case Move(Int, Int) - case Delete(NSIndexSet) -} - -public enum SectionBumpType { - case Reload(NSIndexSet) - case Insert([NSIndexPath]) - case Move(NSIndexPath, NSIndexPath) - case Delete([NSIndexPath]) -} - -public enum ItemBumpType { - case Reload(NSIndexPath) - case ReloadHeader(Int) - case ReloadFooter(Int) -} - -func classNameOf(aClass: AnyClass) -> String { - return NSStringFromClass(aClass).componentsSeparatedByString(".").last! -} diff --git a/Hakuba/ArrayExt.swift b/Hakuba/Extension/ArrayExt.swift similarity index 50% rename from Hakuba/ArrayExt.swift rename to Hakuba/Extension/ArrayExt.swift index 9018510..7b295f5 100644 --- a/Hakuba/ArrayExt.swift +++ b/Hakuba/Extension/ArrayExt.swift @@ -13,34 +13,36 @@ extension Array { return !isEmpty } - func hasIndex(index: Int) -> Bool { + func hasIndex(at index: Int) -> Bool { return indices ~= index } - func getSafeIndex(index: Int) -> Int { - let mIndex = max(0, index) - return min(count, mIndex) + func getSafeIndex(at index: Int) -> Int { + let mIndex = Swift.max(0, index) + return Swift.min(count, mIndex) } func getSafeRange(range: Range) -> Range? { - let start = max(0, range.startIndex) - let end = min(count, range.endIndex) + let start = Swift.max(0, range.lowerBound) + let end = Swift.min(count, range.upperBound) return start <= end ? start.. Element? { - return hasIndex(index) ? self[index] : nil + func get(at index: Int) -> Element? { + return hasIndex(at: index) ? self[index] : nil } - mutating func append(newArray: Array) -> Range { + @discardableResult + mutating func append(_ newArray: Array) -> Range { let range = count..<(count + newArray.count) self += newArray - return range + return .init(range) } - mutating func insert(newArray: Array, atIndex index: Int) -> Range { - let mIndex = max(0, index) - let start = min(count, mIndex) + @discardableResult + mutating func insert(_ newArray: Array, at index: Int) -> Range { + let mIndex = Swift.max(0, index) + let start = Swift.min(count, mIndex) let end = start + newArray.count let left = self[0.. Bool { - if !hasIndex(from) || !hasIndex(to) || from == to { + @discardableResult + mutating func move(from fromIndex: Int, to toIndex: Int) -> Bool { + if !hasIndex(at: fromIndex) || !hasIndex(at: toIndex) || fromIndex == toIndex { return false } - if let fromItem = get(from) { - remove(from) - insert(fromItem, atIndex: to) + if let fromItem = get(at: fromIndex) { + remove(fromIndex) + insert(fromItem, at: toIndex) return true } return false } - mutating func remove(index: Int) -> Range? { - if !hasIndex(index) { + @discardableResult + mutating func remove(_ index: Int) -> Range? { + if !hasIndex(at: index) { return nil } - removeAtIndex(index) - return index..<(index + 1) + remove(at: index) + return .init(index..<(index + 1)) } + @discardableResult mutating func remove(range: Range) -> Range? { - if let sr = getSafeRange(range) { - removeRange(sr) - return sr + if let sr = getSafeRange(range: range) { + return remove(range: sr) } return nil } @@ -81,7 +85,7 @@ extension Array { mutating func remove (element: T) { let anotherSelf = self - removeAll(keepCapacity: true) + removeAll(keepingCapacity: true) anotherSelf.each { (index: Int, current: Element) in if (current as! T) !== element { @@ -90,13 +94,14 @@ extension Array { } } + @discardableResult mutating func removeLast() -> Range? { return remove(count - 1) } func each(exe: (Int, Element) -> ()) { - for (index, item) in enumerate() { + for (index, item) in enumerated() { exe(index, item) } } -} \ No newline at end of file +} diff --git a/Hakuba/TableViewCellExt.swift b/Hakuba/Extension/TableViewCellExt.swift similarity index 99% rename from Hakuba/TableViewCellExt.swift rename to Hakuba/Extension/TableViewCellExt.swift index 79c0693..8b23f5c 100644 --- a/Hakuba/TableViewCellExt.swift +++ b/Hakuba/Extension/TableViewCellExt.swift @@ -16,4 +16,4 @@ extension UITableViewCell { static var reuseIdentifier: String { return classNameOf(self) } -} \ No newline at end of file +} diff --git a/Hakuba/Extension/TableViewExt.swift b/Hakuba/Extension/TableViewExt.swift new file mode 100644 index 0000000..d8b6166 --- /dev/null +++ b/Hakuba/Extension/TableViewExt.swift @@ -0,0 +1,33 @@ +// +// UITableViewExt.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import UIKit + +extension UITableView { + func registerCellByNib(cellType: T.Type) { + let nib = UINib(nibName: cellType.nibName, bundle: nil) + register(nib, forCellReuseIdentifier: cellType.reuseIdentifier) + } + + func registerCell(cellType: T.Type) { + register(cellType, forCellReuseIdentifier: cellType.reuseIdentifier) + } + + func registerHeaderFooterByNib(viewType: T.Type) { + let nib = UINib(nibName: viewType.nibName, bundle: nil) + register(nib, forHeaderFooterViewReuseIdentifier: viewType.reuseIdentifier) + } + + func registerHeaderFooter(viewType: T.Type) { + register(viewType, forHeaderFooterViewReuseIdentifier: viewType.reuseIdentifier) + } + + func dequeueCell(cellType: T.Type, for indexPath: IndexPath) -> T { + return dequeueReusableCell(withIdentifier: cellType.reuseIdentifier, for: indexPath) as! T + } +} diff --git a/Hakuba/TableViewHeaderFooterViewExt.swift b/Hakuba/Extension/TableViewHeaderFooterViewExt.swift similarity index 100% rename from Hakuba/TableViewHeaderFooterViewExt.swift rename to Hakuba/Extension/TableViewHeaderFooterViewExt.swift diff --git a/Hakuba/Hakuba+Registration.swift b/Hakuba/Hakuba+Registration.swift deleted file mode 100644 index e3c12b3..0000000 --- a/Hakuba/Hakuba+Registration.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Hakuba+Registration.swift -// Example -// -// Created by Le VanNghia on 3/7/16. -// -// - -import UIKit - -public extension Hakuba { - func registerCellByNib(cellType: T.Type) -> Self { - tableView?.registerCellByNib(cellType) - return self - } - - func registerCellsByNib(cellTypes: T.Type...) -> Self { - for cellType in cellTypes { - registerCellByNib(cellType) - } - return self - } - - func registerCell(cellType: T.Type) -> Self { - tableView?.registerCell(cellType) - return self - } - - func registerCells(cellTypes: T.Type...) -> Self { - for cellType in cellTypes { - registerCell(cellType) - } - return self - } - - func registerHeaderFooterByNib(t: T.Type) -> Self { - tableView?.registerHeaderFooterByNib(t) - return self - } - - func registerHeaderFooter(t: T.Type) -> Self { - tableView?.registerHeaderFooter(t) - return self - } -} diff --git a/Hakuba/Hakuba+UIScrollViewDelegate.swift b/Hakuba/Hakuba+UIScrollViewDelegate.swift deleted file mode 100644 index 746011f..0000000 --- a/Hakuba/Hakuba+UIScrollViewDelegate.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Hakuba+UIScrollViewDelegate.swift -// Example -// -// Created by Le VanNghia on 3/7/16. -// -// - -import UIKit - -extension Hakuba { - - public func scrollViewDidScroll(scrollView: UIScrollView) { - delegate?.scrollViewDidScroll?(scrollView) - - if let indexPath = tableView?.indexPathsForVisibleRows?.first { - let topSection = indexPath.section - - if currentTopSection != topSection { - if let headerView = tableView?.headerViewForSection(currentTopSection) as? HeaderFooterView { - headerView.didChangeFloatingState(false, section: currentTopSection) - } - if let headerView = tableView?.headerViewForSection(topSection) as? HeaderFooterView { - headerView.didChangeFloatingState(true, section: topSection) - } - if currentTopSection > topSection { - willFloatingSection = topSection - } - currentTopSection = topSection - } - } - - if !loadmoreEnabled { - return - } - - let offset = scrollView.contentOffset - let y = offset.y + scrollView.bounds.height - scrollView.contentInset.bottom - let h = scrollView.contentSize.height - - if y > h - loadmoreThreshold { - loadmoreEnabled = false - loadmoreHandler?() - } - } - - public func scrollViewWillBeginDecelerating(scrollView: UIScrollView) { - delegate?.scrollViewWillBeginDecelerating?(scrollView) - } - - public func scrollViewDidEndDecelerating(scrollView: UIScrollView) { - delegate?.scrollViewDidEndDecelerating?(scrollView) - } - - public func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) { - delegate?.scrollViewDidEndScrollingAnimation?(scrollView) - } - - public func scrollViewDidScrollToTop(scrollView: UIScrollView) { - delegate?.scrollViewDidScrollToTop?(scrollView) - } - - public func scrollViewWillBeginDragging(scrollView: UIScrollView) { - delegate?.scrollViewWillBeginDragging?(scrollView) - } - - public func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - delegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) - } - - public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { - delegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) - } -} diff --git a/Hakuba/Hakuba.swift b/Hakuba/Hakuba.swift deleted file mode 100644 index 3583e17..0000000 --- a/Hakuba/Hakuba.swift +++ /dev/null @@ -1,510 +0,0 @@ -// -// Hakuba.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -final public class Hakuba: NSObject { - weak var tableView: UITableView? - public weak var delegate: HakubaDelegate? - public private(set) var sections: [Section] = [] - - public var loadmoreHandler: (() -> ())? - public var loadmoreEnabled = false - public var loadmoreThreshold: CGFloat = 25 - - private let bumpTracker = BumpTracker() - private var offscreenCells: [String: Cell] = [:] - - public var selectedRows: [NSIndexPath] { - return tableView?.indexPathsForSelectedRows ?? [] - } - - public var visibleRows: [NSIndexPath] { - return tableView?.indexPathsForVisibleRows ?? [] - } - - public var visibleCells: [Cell] { - return (tableView?.visibleCells as? [Cell]) ?? [] - } - - var currentTopSection = NSNotFound - var willFloatingSection = NSNotFound - - public var sectionsCount: Int { - return sections.count - } - - public var cellEditable = false - public var commitEditingHandler: ((UITableViewCellEditingStyle, NSIndexPath) -> ())? - - public subscript(index: SectionIndexType) -> Section { - get { - return self[index.intValue] - } - set { - self[index.intValue] = newValue - } - } - - public subscript(index: Int) -> Section { - get { - return sections[index] - } - set { - setupSections([newValue], fromIndex: index) - sections[index] = newValue - } - } - - subscript(indexPath: NSIndexPath) -> CellModel? { - return self[indexPath.section][indexPath.row] - } - - public func getCellmodel(indexPath: NSIndexPath) -> CellModel? { - return sections.get(indexPath.section)?[indexPath.row] - } - - public func getSection(index: Int) -> Section? { - return sections.get(index) - } - - public func getSection(index: SectionIndexType) -> Section? { - return getSection(index.intValue) - } - - public init(tableView: UITableView) { - super.init() - self.tableView = tableView - tableView.delegate = self - tableView.dataSource = self - } - - deinit { - tableView?.delegate = nil - tableView?.dataSource = nil - } - - public func bump(animation: Animation = .None) -> Self { - let changedCount = sections.reduce(0) { $0 + ($1.changed ? 1 : 0) } - - if changedCount == 0 { - switch bumpTracker.getHakubaBumpType() { - case .Reload: - tableView?.reloadData() - - case let .Insert(indexSet): - tableView?.insertSections(indexSet, withRowAnimation: animation) - - case let .Delete(indexSet): - tableView?.deleteSections(indexSet, withRowAnimation: animation) - - case let .Move(from, to): - tableView?.moveSection(from, toSection: to) - } - } else { - tableView?.reloadData() - sections.forEach { $0.didReloadTableView() } - } - - bumpTracker.didBump() - return self - } -} - - -// MARK - UITableView methods - -public extension Hakuba { - func setEditing(editing: Bool, animated: Bool) { - tableView?.setEditing(editing, animated: animated) - } - - func selectCell(indexPath: NSIndexPath, animated: Bool, scrollPosition: UITableViewScrollPosition) { - tableView?.selectRowAtIndexPath(indexPath, animated: animated, scrollPosition: scrollPosition) - } - - func deselectCell(indexPath: NSIndexPath, animated: Bool) { - tableView?.deselectRowAtIndexPath(indexPath, animated: animated) - } - - func deselectAllCells(animated animated: Bool) { - selectedRows.forEach { - tableView?.deselectRowAtIndexPath($0, animated: animated) - } - } - - func getCell(indexPath: NSIndexPath) -> Cell? { - return tableView?.cellForRowAtIndexPath(indexPath) as? Cell - } -} - -// MARK - Sections - -public extension Hakuba { - // MARK - Reset - - func reset(listType: SectionIndexType.Type) -> Self { - let sections = (0.. Self { - return reset([]) - } - - func reset(section: Section) -> Self { - return reset([section]) - } - - func reset(sections: [Section]) -> Self { - setupSections(sections, fromIndex: 0) - self.sections = sections - bumpTracker.didReset() - return self - } - - // MARK - Append - - func append(section: Section) -> Self { - return append([section]) - } - - func append(sections: [Section]) -> Self { - return insert(sections, atIndex: sectionsCount) - } - - // MARK - Insert - - func insert(section: Section, atIndex index: Int) -> Self { - return insert([section], atIndex: index) - } - - func insert(sections: [Section], atIndex index: Int) -> Self { - guard sections.isNotEmpty else { - return self - } - - let sIndex = min(max(index, 0), sectionsCount) - setupSections(sections, fromIndex: sIndex) - let r = self.sections.insert(sections, atIndex: sIndex) - bumpTracker.didInsert(Array(r)) - - return self - } - - func insertBeforeLast(section: Section) -> Self { - return insertBeforeLast([section]) - } - - func insertBeforeLast(sections: [Section]) -> Self { - let index = max(sections.count - 1, 0) - return insert(sections, atIndex: index) - } - - // MARK - Remove - - func remove(index: Int) -> Self { - return remove(indexes: [index]) - } - - func remove(range: Range) -> Self { - let indexes = range.map { $0 } - return remove(indexes: indexes) - } - - func remove(indexes indexes: [Int]) -> Self { - guard indexes.isNotEmpty else { - return self - } - - let sortedIndexes = indexes - .sort(<) - .filter { $0 >= 0 && $0 < self.sectionsCount } - - var remainSections: [Section] = [] - var i = 0 - - for j in 0.. Self { - let index = sectionsCount - 1 - - guard index >= 0 else { - return self - } - - return remove(index) - } - - func remove(section: Section) -> Self { - let index = section.index - - guard index >= 0 && index < sectionsCount else { - return self - } - - return remove(index) - } - - func removeAll() -> Self { - return reset() - } - - // MAKR - Move - - func move(from: Int, to: Int) -> Self { - sections.move(fromIndex: from, toIndex: to) - setupSections([sections[from]], fromIndex: from) - setupSections([sections[to]], fromIndex: to) - - bumpTracker.didMove(from, to: to) - return self - } -} - -// MARK - SectionDelegate, CellModelDelegate - -extension Hakuba: SectionDelegate, CellModelDelegate { - func bumpMe(type: SectionBumpType, animation: Animation) { - switch type { - case .Reload(let indexSet): - tableView?.reloadSections(indexSet, withRowAnimation: animation) - - case .Insert(let indexPaths): - tableView?.insertRowsAtIndexPaths(indexPaths, withRowAnimation: animation) - - case .Move(let ori, let des): - tableView?.moveRowAtIndexPath(ori, toIndexPath: des) - - case .Delete(let indexPaths): - tableView?.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: animation) - } - } - - func bumpMe(type: ItemBumpType, animation: Animation) { - switch type { - case .Reload(let indexPath): - tableView?.reloadRowsAtIndexPaths([indexPath], withRowAnimation: animation) - - case .ReloadHeader: - break - - case .ReloadFooter: - break - } - } - - func getOffscreenCell(identifier: String) -> Cell { - if let cell = offscreenCells[identifier] { - return cell - } - - if let cell = tableView?.dequeueReusableCellWithIdentifier(identifier) as? Cell { - offscreenCells[identifier] = cell - return cell - } - return Cell() - } - - func tableViewWidth() -> CGFloat { - return tableView?.bounds.width ?? 0 - } -} - -// MARK - UITableViewDelegate cell - -extension Hakuba: UITableViewDelegate { - public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - return getCellmodel(indexPath)?.height ?? 0 - } - -// public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { -// } - - public func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? { - return getCell(indexPath)?.willSelect(tableView, indexPath: indexPath) - } - - public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { - guard let cellmodel = getCellmodel(indexPath), cell = getCell(indexPath) else { - return - } - - cellmodel.didSelect(cell) - cell.didSelect(tableView) - } - - public func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? { - return getCell(indexPath)?.willDeselect(tableView, indexPath: indexPath) - } - - public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) { - getCell(indexPath)?.didDeselect(tableView) - } - - public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { - (cell as? Cell)?.willDisplay(tableView) - } - - public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { - (cell as? Cell)?.didEndDisplay(tableView) - } - - public func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle { - return getCellmodel(indexPath)?.editingStyle ?? .None - } - - public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { - return getCellmodel(indexPath)?.shouldHighlight ?? true - } - - public func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) { - getCell(indexPath)?.didHighlight(tableView) - } - - public func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) { - getCell(indexPath)?.didUnhighlight(tableView) - } -} - -// MARK - UITableViewDelegate header-footer - -extension Hakuba { - public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return sections.get(section)?.header?.height ?? 0 - } - - public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let header = sections.get(section)?.header else { - return nil - } - - let headerView = tableView.dequeueReusableHeaderFooterViewWithIdentifier(header.reuseIdentifier) as? HeaderFooterView - headerView?.configureView(header) - - return headerView - } - - public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return sections.get(section)?.footer?.height ?? 0 - } - - public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - guard let footer = sections.get(section)?.footer else { - return nil - } - - let footerView = tableView.dequeueReusableHeaderFooterViewWithIdentifier(footer.reuseIdentifier) as? HeaderFooterView - footerView?.configureView(footer) - - return footerView - } - - public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - guard let view = view as? HeaderFooterView where section == willFloatingSection else { - return - } - - view.willDisplay(tableView, section: section) - view.didChangeFloatingState(true, section: section) - willFloatingSection = NSNotFound - } - - public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { - guard let view = view as? HeaderFooterView else { - return - } - - view.willDisplay(tableView, section: section) - } - - public func tableView(tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { - guard let view = view as? HeaderFooterView else { - return - } - - view.didEndDisplaying(tableView, section: section) - } - - public func tableView(tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { - guard let view = view as? HeaderFooterView else { - return - } - - view.didEndDisplaying(tableView, section: section) - } -} - -// MARK - UITableViewDataSource - -extension Hakuba: UITableViewDataSource { - public func numberOfSectionsInTableView(tableView: UITableView) -> Int { - return sectionsCount - } - - public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return sections.get(section)?.count ?? 0 - } - - public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - guard let cellmodel = getCellmodel(indexPath), - cell = tableView.dequeueReusableCellWithIdentifier(cellmodel.reuseIdentifier, forIndexPath: indexPath) as? Cell else { - return UITableViewCell() - } - - cell.configureCell(cellmodel) - - return cell - } - - public func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { - commitEditingHandler?(editingStyle, indexPath) - } - - public func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { - guard let cellmodel = getCellmodel(indexPath) else { - return false - } - - return cellmodel.editable || cellEditable - } - - public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return sections.get(section)?.header?.title - } - - public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return sections.get(section)?.footer?.title - } -} - -// MARK - Private methods - -private extension Hakuba { - func setupSections(sections: [Section], fromIndex start: Int) { - var start = start - sections.forEach { - $0.setup(start, delegate: self) - start += 1 - } - } -} \ No newline at end of file diff --git a/Hakuba/HakubaDelegate.swift b/Hakuba/HakubaDelegate.swift deleted file mode 100644 index 09ffb5c..0000000 --- a/Hakuba/HakubaDelegate.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// HakubaDelegate.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -@objc public protocol HakubaDelegate : class { - optional func scrollViewDidScroll(scrollView: UIScrollView) - - // Decelerating - optional func scrollViewWillBeginDecelerating(scrollView: UIScrollView) - optional func scrollViewDidEndDecelerating(scrollView: UIScrollView) - - optional func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) - optional func scrollViewDidScrollToTop(scrollView: UIScrollView) - - // Draging - optional func scrollViewWillBeginDragging(scrollView: UIScrollView) - optional func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) - optional func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) -} diff --git a/Hakuba/HeaderFooterView.swift b/Hakuba/HeaderFooterView.swift deleted file mode 100644 index 008f1be..0000000 --- a/Hakuba/HeaderFooterView.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// HeaderFooterView.swift -// Example -// -// Created by Le VanNghia on 3/5/16. -// -// - -import UIKit - -public class HeaderFooterView: UITableViewHeaderFooterView { - weak var _viewmodel: HeaderFooterViewModel? - - public func configureView(viewmodel: HeaderFooterViewModel) { - _viewmodel = viewmodel - configure() - } - - public func configure() { - } - - public func didChangeFloatingState(isFloating: Bool, section: Int) { - } - - public func willDisplay(tableView: UITableView, section: Int) { - } - - public func didEndDisplaying(tableView: UITableView, section: Int) { - } -} diff --git a/Hakuba/HeaderFooterViewModel.swift b/Hakuba/HeaderFooterViewModel.swift deleted file mode 100644 index 769b20c..0000000 --- a/Hakuba/HeaderFooterViewModel.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// HeaderFooterViewModel.swift -// Example -// -// Created by Le VanNghia on 3/5/16. -// -// - -import UIKit - -public class HeaderFooterViewModel { - public enum Type { - case Header - case Footer - } - - public let reuseIdentifier: String - - public internal(set) var section: Int = 0 - public internal(set) var type: Type = .Header - - public var title: String? - public var height: CGFloat = 44 - - public var isHeader: Bool { - return type == .Header - } - - public var isFooter: Bool { - return type == .Footer - } - - public init(view: T.Type) { - self.reuseIdentifier = view.reuseIdentifier - } -} diff --git a/Hakuba/Label.swift b/Hakuba/Label.swift deleted file mode 100644 index 3ecb460..0000000 --- a/Hakuba/Label.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Label.swift -// Example -// -// Created by Le VanNghia on 3/5/16. -// -// - -import UIKit - -public class Label : UILabel { - override public var bounds: CGRect { - didSet { - self.preferredMaxLayoutWidth = self.bounds.width - } - } -} diff --git a/Hakuba/RangeExt.swift b/Hakuba/RangeExt.swift deleted file mode 100644 index ef00d7d..0000000 --- a/Hakuba/RangeExt.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// RangeExt.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import Foundation - -extension NSRange { - init(range: Range) { - self.location = range.startIndex - self.length = range.endIndex - range.startIndex - } -} \ No newline at end of file diff --git a/Hakuba/Section.swift b/Hakuba/Section.swift deleted file mode 100644 index ef09884..0000000 --- a/Hakuba/Section.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// Section.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import Foundation - -protocol SectionDelegate: class { - func bumpMe(type: SectionBumpType, animation: Animation) -} - -public class Section { - weak var delegate: SectionDelegate? - public private(set) var cellmodels: [CellModel] = [] - private let bumpTracker = BumpTracker() - - internal(set) var index: Int = 0 - - var changed: Bool { - return bumpTracker.changed - } - - public var header: HeaderFooterViewModel? { - didSet { - header?.section = index - header?.type = .Header - } - } - - public var footer: HeaderFooterViewModel? { - didSet { - footer?.section = index - footer?.type = .Footer - } - } - - public subscript(index: Int) -> CellModel? { - get { - return cellmodels.get(index) - } - } - - public init() { - } - - public func bump(animation: Animation = .None) -> Self { - let type = bumpTracker.getSectionBumpType(index) - delegate?.bumpMe(type, animation: animation) - bumpTracker.didBump() - return self - } -} - -// MARK - Public methods - -public extension Section { - - // MARK - Reset - - func reset() -> Self { - return reset([]) - } - - func reset(cellmodel: CellModel) -> Self { - return reset([cellmodel]) - } - - func reset(cellmodels: [CellModel]) -> Self { - setupCellmodels(cellmodels, indexFrom: 0) - self.cellmodels = cellmodels - bumpTracker.didReset() - return self - } - - // MARK - Append - - func append(cellmodel: CellModel) -> Self { - return append([cellmodel]) - } - - func append(cellmodels: [CellModel]) -> Self { - return insert(cellmodels, atIndex: count) - } - - // MARK - Insert - - func insert(cellmodel: CellModel, atIndex index: Int) -> Self { - return insert([cellmodel], atIndex: index) - } - - func insert(cellmodels: [CellModel], atIndex index: Int) -> Self { - guard cellmodels.isNotEmpty else { - return self - } - - let start = min(count, index) - self.cellmodels.insert(cellmodels, atIndex: start) - - let affectedCellmodels = Array(self.cellmodels[start.. Self { - return insertBeforeLast([viewmodel]) - } - - func insertBeforeLast(viewmodels: [CellModel]) -> Self { - let index = max(cellmodels.count - 1, 0) - return insert(viewmodels, atIndex: index) - } - - // MARK - Remove - - func remove(index: Int) -> Self { - return remove([index]) - } - - func remove(range: Range) -> Self { - let indexes = range.map { $0 } - return remove(indexes) - } - - func remove(indexes: [Int]) -> Self { - guard indexes.isNotEmpty else { - return self - } - - let sortedIndexes = indexes - .sort(<) - .filter { $0 >= 0 && $0 < self.count } - - var remainCellmodels: [CellModel] = [] - var i = 0 - - for j in 0.. Self { - let index = cellmodels.count - 1 - guard index >= 0 else { - return self - } - - return remove(index) - } - - func remove(cellmodel: CellModel) -> Self { - let index = cellmodels.indexOf { return $0 === cellmodel } - - guard let i = index else { - return self - } - - return remove(i) - } - - // MAKR - Move - - func move(from: Int, to: Int) -> Self { - cellmodels.move(fromIndex: from, toIndex: to) - setupCellmodels([cellmodels[from]], indexFrom: from) - setupCellmodels([cellmodels[to]], indexFrom: to) - - bumpTracker.didMove(from, to: to) - return self - } -} - -// MARK - Utilities - -public extension Section { - var count: Int { - return cellmodels.count - } - - var isEmpty: Bool { - return count == 0 - } - - var isNotEmpty: Bool { - return !isEmpty - } - - var first: CellModel? { - return cellmodels.first - } - - var last: CellModel? { - return cellmodels.last - } -} - -// MARK - Internal methods - -extension Section { - func setup(index: Int, delegate: SectionDelegate) { - self.delegate = delegate - self.index = index - - header?.section = index - footer?.section = index - setupCellmodels(cellmodels, indexFrom: 0) - } - - func didReloadTableView() { - bumpTracker.didBump() - } -} - -// MARK - Private methods - -private extension Section { - func setupCellmodels(cellmodels: [CellModel], indexFrom start: Int) { - guard let delegate = delegate as? CellModelDelegate else { - return - } - - var start = start - - cellmodels.forEach { cellmodel in - let indexPath = NSIndexPath(forRow: start, inSection: index) - cellmodel.setup(indexPath, delegate: delegate) - start += 1 - } - } -} \ No newline at end of file diff --git a/Hakuba/Source/Bump/BumpTracker.swift b/Hakuba/Source/Bump/BumpTracker.swift new file mode 100644 index 0000000..96a99de --- /dev/null +++ b/Hakuba/Source/Bump/BumpTracker.swift @@ -0,0 +1,107 @@ +// +// BumpTracker.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import Foundation + +private enum UpdateState: Equatable { + case begin + case reload + case insert(indexes: [Int]) + case move(from: Int, to: Int) + case remove(indexes: [Int]) +} + +final class BumpTracker { + private var state: UpdateState = .begin + + var isChanged: Bool { + return state != .begin + } + + func didBump() { + state = .begin + } + + func didReset() { + state = .reload + } + + func didInsert(indexes: [Int]) { + switch state { + case .begin: + state = .insert(indexes: indexes) + + default: + state = .reload + } + } + + func didMove(from: Int, to: Int) { + switch state { + case .begin: + state = .move(from: from, to: to) + + default: + state = .reload + } + } + + func didRemove(indexes: [Int]) { + switch state { + case .begin: + state = .remove(indexes: indexes) + + default: + state = .reload + } + } + + func getSectionBumpType(at index: Int) -> SectionBumpType { + let indexPath = { (row: Int) -> IndexPath in + return .init(row: row, section: index) + } + + switch state { + case .insert(let indexes) where indexes.isNotEmpty: + return .insert(indexes.map(indexPath)) + + case .move(let from, let to): + return .move(indexPath(from), indexPath(to)) + + case .remove(let indexes) where indexes.isNotEmpty: + return .delete(indexes.map(indexPath)) + + default: + return .reload(.init(integer: index)) + } + } + + func getHakubaBumpType() -> HakubaBumpType { + let indexSet: ([Int]) -> IndexSet = { indexes in + let indexSet = NSMutableIndexSet() + for index in indexes { + indexSet.add(index) + } + return indexSet as IndexSet + } + + switch state { + case .insert(let indexes) where indexes.isNotEmpty: + return .insert(indexSet(indexes)) + + case .move(let from, let to): + return .move(from, to) + + case .remove(let indexes) where indexes.isNotEmpty: + return .delete(indexSet(indexes)) + + default: + return .reload + } + } +} diff --git a/Hakuba/Source/Bump/BumpType.swift b/Hakuba/Source/Bump/BumpType.swift new file mode 100644 index 0000000..3b3e883 --- /dev/null +++ b/Hakuba/Source/Bump/BumpType.swift @@ -0,0 +1,28 @@ +// +// Bump.swift +// Example +// +// Created by Keeeeen on 2018/08/09. +// + +import Foundation + +public enum HakubaBumpType { + case reload + case insert(IndexSet) + case move(Int, Int) + case delete(IndexSet) +} + +public enum SectionBumpType { + case reload(IndexSet) + case insert([IndexPath]) + case move(IndexPath, IndexPath) + case delete([IndexPath]) +} + +public enum ItemBumpType { + case reload(IndexPath) + case reloadHeader(Int) + case reloadFooter(Int) +} diff --git a/Hakuba/Source/Cell/Cell.swift b/Hakuba/Source/Cell/Cell.swift new file mode 100644 index 0000000..9bb668e --- /dev/null +++ b/Hakuba/Source/Cell/Cell.swift @@ -0,0 +1,38 @@ +// +// Cell.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import UIKit + +open class Cell: UITableViewCell { + weak var _cellmodel: CellModel? + + func configureCell(_ cellmodel: CellModel) { + _cellmodel = cellmodel + configure() + } + + open func configure() {} + open func willDisplay(_ tableView: UITableView) {} + open func didEndDisplay(_ tableView: UITableView) {} + + open func willSelect(_ tableView: UITableView, at indexPath: IndexPath) -> IndexPath? { + return indexPath + } + + open func didSelect(_ tableView: UITableView) {} + + open func willDeselect(_ tableView: UITableView, at indexPath: IndexPath) -> IndexPath? { + return indexPath + } + + open func didDeselect(_ tableView: UITableView) {} + open func willBeginEditing(_ tableView: UITableView) {} + open func didEndEditing(_ tableView: UITableView) {} + open func didHighlight(_ tableView: UITableView) {} + open func didUnhighlight(_ tableView: UITableView) {} +} diff --git a/Hakuba/Source/Cell/CellModel.swift b/Hakuba/Source/Cell/CellModel.swift new file mode 100644 index 0000000..1d9476f --- /dev/null +++ b/Hakuba/Source/Cell/CellModel.swift @@ -0,0 +1,58 @@ +// +// CellModel.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import UIKit + +protocol CellModelDelegate: class { + func bumpMe(with type: ItemBumpType, animation: UITableViewRowAnimation) + func getOffscreenCell(by identifier: String) -> Cell + func tableViewWidth() -> CGFloat + func deselectCell(at indexPath: IndexPath, animated: Bool) +} + +open class CellModel { + weak var delegate: CellModelDelegate? + + open let reuseIdentifier: String + open var height: CGFloat + open var selectionHandler: ((Cell) -> Void)? + + open internal(set) var indexPath: IndexPath = .init(row: 0, section: 0) + open var editable = false + open var editingStyle: UITableViewCellEditingStyle = .none + open var shouldHighlight = true + + public init(cell: T.Type, height: CGFloat = UITableViewAutomaticDimension, selectionHandler: ((Cell) -> Void)? = nil) { + self.reuseIdentifier = cell.reuseIdentifier + self.height = height + self.selectionHandler = selectionHandler + } + + @discardableResult + open func bump(_ animation: UITableViewRowAnimation = .none) -> Self { + delegate?.bumpMe(with: .reload(indexPath), animation: animation) + return self + } + + open func deselect(_ animated: Bool) { + delegate?.deselectCell(at: indexPath, animated: animated) + } +} + +// MARK - Internal methods + +extension CellModel { + func setup(indexPath: IndexPath, delegate: CellModelDelegate) { + self.indexPath = indexPath + self.delegate = delegate + } + + func didSelect(cell: Cell) { + selectionHandler?(cell) + } +} diff --git a/Hakuba/CellType.swift b/Hakuba/Source/Cell/CellType.swift similarity index 100% rename from Hakuba/CellType.swift rename to Hakuba/Source/Cell/CellType.swift diff --git a/Hakuba/Source/Common/Utilities.swift b/Hakuba/Source/Common/Utilities.swift new file mode 100644 index 0000000..b758c0f --- /dev/null +++ b/Hakuba/Source/Common/Utilities.swift @@ -0,0 +1,12 @@ +// +// Utilities.swift +// Example +// +// Created by Keeeeen on 2018/08/09. +// + +import Foundation + +func classNameOf(_ aClass: AnyClass) -> String { + return String(describing: aClass) +} diff --git a/Hakuba/Source/Core/Hakuba+Registration.swift b/Hakuba/Source/Core/Hakuba+Registration.swift new file mode 100644 index 0000000..9a58dfa --- /dev/null +++ b/Hakuba/Source/Core/Hakuba+Registration.swift @@ -0,0 +1,51 @@ +// +// Hakuba+Registration.swift +// Example +// +// Created by Le VanNghia on 3/7/16. +// +// + +import UIKit + +public extension Hakuba { + @discardableResult + func registerCellByNib(_ cellType: T.Type) -> Self { + tableView?.registerCellByNib(cellType: cellType) + return self + } + + @discardableResult + func registerCellsByNib(_ cellTypes: T.Type...) -> Self { + for cellType in cellTypes { + registerCellByNib(cellType) + } + return self + } + + @discardableResult + func registerCell(_ cellType: T.Type) -> Self { + tableView?.registerCell(cellType: cellType) + return self + } + + @discardableResult + func registerCells(_ cellTypes: T.Type...) -> Self { + for cellType in cellTypes { + registerCell(cellType) + } + return self + } + + @discardableResult + func registerHeaderFooterByNib(_ viewTypet: T.Type) -> Self { + tableView?.registerHeaderFooterByNib(viewType: viewTypet) + return self + } + + @discardableResult + func registerHeaderFooter(_ viewTypet: T.Type) -> Self { + tableView?.registerHeaderFooter(viewType: viewTypet) + return self + } +} diff --git a/Hakuba/Source/Core/Hakuba+UIScrollViewDelegate.swift b/Hakuba/Source/Core/Hakuba+UIScrollViewDelegate.swift new file mode 100644 index 0000000..7ee726c --- /dev/null +++ b/Hakuba/Source/Core/Hakuba+UIScrollViewDelegate.swift @@ -0,0 +1,72 @@ +// +// Hakuba+UIScrollViewDelegate.swift +// Example +// +// Created by Le VanNghia on 3/7/16. +// +// + +import UIKit + +extension Hakuba { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + delegate?.scrollViewDidScroll?(scrollView) + + if let topSection = tableView?.indexPathsForVisibleRows?.first?.section, currentTopSection != topSection { + if let headerView = tableView?.headerView(forSection: currentTopSection) as? HeaderFooterView { + headerView.didChangeFloatingState(false, section: currentTopSection) + } + + if let headerView = tableView?.headerView(forSection: topSection) as? HeaderFooterView { + headerView.didChangeFloatingState(true, section: topSection) + } + + if currentTopSection > topSection { + willFloatingSection = topSection + } + + currentTopSection = topSection + } + + if !loadmoreEnabled { + return + } + + let offset = scrollView.contentOffset + let y = offset.y + scrollView.bounds.height - scrollView.contentInset.bottom + let h = scrollView.contentSize.height + + if y > h - loadmoreThreshold { + loadmoreEnabled = false + loadmoreHandler?() + } + } + + public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + delegate?.scrollViewWillBeginDecelerating?(scrollView) + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + delegate?.scrollViewDidEndDecelerating?(scrollView) + } + + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + delegate?.scrollViewDidEndScrollingAnimation?(scrollView) + } + + public func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { + delegate?.scrollViewDidScrollToTop?(scrollView) + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + delegate?.scrollViewWillBeginDragging?(scrollView) + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + delegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + delegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) + } +} diff --git a/Hakuba/Source/Core/Hakuba.swift b/Hakuba/Source/Core/Hakuba.swift new file mode 100644 index 0000000..f3bac89 --- /dev/null +++ b/Hakuba/Source/Core/Hakuba.swift @@ -0,0 +1,517 @@ +// +// Hakuba.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import UIKit + +final public class Hakuba: NSObject { + weak var tableView: UITableView? + public weak var delegate: HakubaDelegate? + public private(set) var sections: [Section] = [] + + public var loadmoreHandler: (() -> ())? + public var loadmoreEnabled = false + public var loadmoreThreshold: CGFloat = 25 + + private let bumpTracker = BumpTracker() + private var offscreenCells: [String: Cell] = [:] + + public var selectedRows: [IndexPath] { + return tableView?.indexPathsForSelectedRows ?? [] + } + + public var visibleRows: [IndexPath] { + return tableView?.indexPathsForVisibleRows ?? [] + } + + public var visibleCells: [Cell] { + return (tableView?.visibleCells as? [Cell]) ?? [] + } + + var currentTopSection = NSNotFound + var willFloatingSection = NSNotFound + + public var sectionsCount: Int { + return sections.count + } + + public var cellEditable = false + public var commitEditingHandler: ((UITableViewCellEditingStyle, IndexPath) -> ())? + + public subscript(index: T) -> Section { + get { + return self[index.rawValue] + } + set { + self[index.rawValue] = newValue + } + } + + public subscript(index: Int) -> Section { + get { + return sections[index] + } + set { + setupSections([newValue], from: index) + sections[index] = newValue + } + } + + subscript(indexPath: IndexPath) -> CellModel? { + return self[indexPath.section][indexPath.row] + } + + public func getCellmodel(at indexPath: IndexPath) -> CellModel? { + return sections.get(at: indexPath.section)?[indexPath.row] + } + + public func getSection(at index: Int) -> Section? { + return sections.get(at: index) + } + + public func getSection(at index: T) -> Section? { + return getSection(at: index.rawValue) + } + + public init(tableView: UITableView) { + super.init() + self.tableView = tableView + tableView.delegate = self + tableView.dataSource = self + } + + deinit { + tableView?.delegate = nil + tableView?.dataSource = nil + } + + @discardableResult + public func bump(_ animation: UITableViewRowAnimation = .none) -> Self { + let changedCount = sections.reduce(0) { $0 + ($1.isChanged ? 1 : 0) } + + if changedCount == 0 { + switch bumpTracker.getHakubaBumpType() { + case .reload: + tableView?.reloadData() + + case let .insert(indexSet): + tableView?.insertSections(indexSet, with: animation) + + case let .delete(indexSet): + tableView?.deleteSections(indexSet, with: animation) + + case let .move(from, to): + tableView?.moveSection(from, toSection: to) + } + } else { + tableView?.reloadData() + sections.forEach { $0.didReloadTableView() } + } + + bumpTracker.didBump() + return self + } +} + + +// MARK - UITableView methods + +public extension Hakuba { + func setEditing(_ editing: Bool, animated: Bool) { + tableView?.setEditing(editing, animated: animated) + } + + func selectCell(at indexPath: IndexPath, animated: Bool, scrollPosition: UITableViewScrollPosition) { + tableView?.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition) + } + + func deselectCell(at indexPath: IndexPath, animated: Bool) { + tableView?.deselectRow(at: indexPath, animated: animated) + } + + func deselectAllCells(animated: Bool) { + selectedRows.forEach { + tableView?.deselectRow(at: $0, animated: animated) + } + } + + func cellForRow(at indexPath: IndexPath) -> Cell? { + return tableView?.cellForRow(at: indexPath) as? Cell + } +} + +// MARK - Sections + +public extension Hakuba { + // MARK - Reset + + @discardableResult + func reset(_ listType: T.Type) -> Self { + let sections = (0.. Self { + return reset([]) + } + + @discardableResult + func reset(_ section: Section) -> Self { + return reset([section]) + } + + @discardableResult + func reset(_ sections: [Section]) -> Self { + setupSections(sections, from: 0) + self.sections = sections + bumpTracker.didReset() + return self + } + + // MARK - Append + + @discardableResult + func append(_ section: Section) -> Self { + return append([section]) + } + + @discardableResult + func append(_ sections: [Section]) -> Self { + return insert(sections, at: sectionsCount) + } + + // MARK - Insert + + @discardableResult + func insert(_ section: Section, at index: Int) -> Self { + return insert([section], at: index) + } + + @discardableResult + func insert(_ sections: [Section], at index: Int) -> Self { + guard sections.isNotEmpty else { return self } + + let sIndex = min(max(index, 0), sectionsCount) + setupSections(sections, from: sIndex) + let r = self.sections.insert(sections, at: sIndex) + bumpTracker.didInsert(indexes: Array(r.lowerBound...r.upperBound)) + + return self + } + + @discardableResult + func insertBeforeLast(_ section: Section) -> Self { + return insertBeforeLast([section]) + } + + @discardableResult + func insertBeforeLast(_ sections: [Section]) -> Self { + let index = max(sections.count - 1, 0) + return insert(sections, at: index) + } + + // MARK - Remove + + @discardableResult + func remove(at index: Int) -> Self { + return remove(at: [index]) + } + + @discardableResult + func remove(range: CountableRange) -> Self { + return remove(at: range.map { $0 }) + } + + @discardableResult + func remove(range: CountableClosedRange) -> Self { + return remove(at: range.map { $0 }) + } + + @discardableResult + func remove(at indexes: [Int]) -> Self { + guard indexes.isNotEmpty else { return self } + + let sortedIndexes = indexes + .sorted(by: <) + .filter { $0 >= 0 && $0 < self.sectionsCount } + + var remainSections: [Section] = [] + var i = 0 + + for j in 0.. Self { + let index = sectionsCount - 1 + + guard index >= 0 else { return self } + + return remove(at: index) + } + + @discardableResult + func remove(_ section: Section) -> Self { + let index = section.index + + guard index >= 0 && index < sectionsCount else { return self } + + return remove(at: index) + } + + @discardableResult + func removeAll() -> Self { + return reset() + } + + // MAKR - Move + + @discardableResult + func move(from fromIndex: Int, to toIndex: Int) -> Self { + sections.move(from: fromIndex, to: toIndex) + setupSections([sections[fromIndex]], from: fromIndex) + setupSections([sections[toIndex]], from: toIndex) + + bumpTracker.didMove(from: fromIndex, to: toIndex) + return self + } +} + +// MARK - SectionDelegate, CellModelDelegate + +extension Hakuba: SectionDelegate, CellModelDelegate { + func bumpMe(with type: SectionBumpType, animation: UITableViewRowAnimation) { + switch type { + case .reload(let indexSet): + tableView?.reloadSections(indexSet, with: animation) + + case .insert(let indexPaths): + tableView?.insertRows(at: indexPaths, with: animation) + + case .move(let ori, let des): + tableView?.moveRow(at: ori, to: des) + + case .delete(let indexPaths): + tableView?.deleteRows(at: indexPaths, with: animation) + } + } + + func bumpMe(with type: ItemBumpType, animation: UITableViewRowAnimation) { + switch type { + case .reload(let indexPath): + tableView?.reloadRows(at: [indexPath], with: animation) + + case .reloadHeader: + break + + case .reloadFooter: + break + } + } + + func getOffscreenCell(by identifier: String) -> Cell { + if let cell = offscreenCells[identifier] { + return cell + } + + guard let cell = tableView?.dequeueReusableCell(withIdentifier: identifier) as? Cell else { return .init() } + + offscreenCells[identifier] = cell + + return cell + } + + func tableViewWidth() -> CGFloat { + return tableView?.bounds.width ?? 0 + } +} + +// MARK - UITableViewDelegate cell + +extension Hakuba: UITableViewDelegate { + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return getCellmodel(at: indexPath)?.height ?? 0 + } + + public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return self.tableView(tableView, heightForRowAt: indexPath) + } + + public func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + return cellForRow(at: indexPath)?.willSelect(tableView, at: indexPath) + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let cellmodel = getCellmodel(at: indexPath), let cell = cellForRow(at: indexPath) else { return } + + cellmodel.didSelect(cell: cell) + cell.didSelect(tableView) + } + + public func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { + return cellForRow(at: indexPath)?.willDeselect(tableView, at: indexPath) + } + + public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + cellForRow(at: indexPath)?.didDeselect(tableView) + } + + public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let cell = cell as? Cell else { return } + + cell.willDisplay(tableView) + } + + public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let cell = cell as? Cell else { return } + + cell.didEndDisplay(tableView) + } + + public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { + return getCellmodel(at: indexPath)?.editingStyle ?? .none + } + + public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return getCellmodel(at: indexPath)?.shouldHighlight ?? true + } + + public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { + cellForRow(at: indexPath)?.didHighlight(tableView) + } + + public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { + cellForRow(at: indexPath)?.didUnhighlight(tableView) + } +} + +// MARK - UITableViewDelegate header-footer + +extension Hakuba { + public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return sections.get(at: section)?.header?.height ?? 0 + } + + public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let header = sections.get(at: section)?.header, + let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: header.reuseIdentifier) as? HeaderFooterView else { + return nil + } + + headerView.configureView(header) + + return headerView + } + + public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return sections.get(at: section)?.footer?.height ?? 0 + } + + public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard let footer = sections.get(at: section)?.footer, + let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: footer.reuseIdentifier) as? HeaderFooterView else { + return nil + } + + footerView.configureView(footer) + + return footerView + } + + public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + guard let view = view as? HeaderFooterView, section == willFloatingSection else { return } + + view.willDisplay(tableView, section: section) + view.didChangeFloatingState(true, section: section) + willFloatingSection = NSNotFound + } + + public func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { + guard let view = view as? HeaderFooterView else { return } + + view.willDisplay(tableView, section: section) + } + + public func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { + guard let view = view as? HeaderFooterView else { return } + + view.didEndDisplaying(tableView, section: section) + } + + public func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { + guard let view = view as? HeaderFooterView else { return } + + view.didEndDisplaying(tableView, section: section) + } +} + +// MARK - UITableViewDataSource + +extension Hakuba: UITableViewDataSource { + public func numberOfSections(in tableView: UITableView) -> Int { + return sectionsCount + } + + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return sections.get(at: section)?.count ?? 0 + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cellmodel = getCellmodel(at: indexPath), + let cell = tableView.dequeueReusableCell(withIdentifier: cellmodel.reuseIdentifier, for: indexPath) as? Cell else { + return .init() + } + + cell.configureCell(cellmodel) + + return cell + } + + public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + commitEditingHandler?(editingStyle, indexPath) + } + + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + guard let cellmodel = getCellmodel(at: indexPath) else { return false } + + return cellmodel.editable || cellEditable + } + + public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return sections.get(at: section)?.header?.title + } + + public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return sections.get(at: section)?.footer?.title + } +} + +// MARK - Private methods + +private extension Hakuba { + func setupSections(_ sections: [Section], from index: Int) { + var index = index + sections.forEach { + $0.setup(at: index, delegate: self) + index += 1 + } + } +} diff --git a/Hakuba/Source/Core/HakubaDelegate.swift b/Hakuba/Source/Core/HakubaDelegate.swift new file mode 100644 index 0000000..3f60824 --- /dev/null +++ b/Hakuba/Source/Core/HakubaDelegate.swift @@ -0,0 +1,25 @@ +// +// HakubaDelegate.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import UIKit + +@objc public protocol HakubaDelegate : class { + @objc optional func scrollViewDidScroll(_ scrollView: UIScrollView) + + // Decelerating + @objc optional func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) + @objc optional func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) + + @objc optional func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) + @objc optional func scrollViewDidScrollToTop(_ scrollView: UIScrollView) + + // Draging + @objc optional func scrollViewWillBeginDragging(_ scrollView: UIScrollView) + @objc optional func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) + @objc optional func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) +} diff --git a/Hakuba/Source/HeaderFooter/HeaderFooterView.swift b/Hakuba/Source/HeaderFooter/HeaderFooterView.swift new file mode 100644 index 0000000..e563366 --- /dev/null +++ b/Hakuba/Source/HeaderFooter/HeaderFooterView.swift @@ -0,0 +1,23 @@ +// +// HeaderFooterView.swift +// Example +// +// Created by Le VanNghia on 3/5/16. +// +// + +import UIKit + +open class HeaderFooterView: UITableViewHeaderFooterView { + weak var _viewmodel: HeaderFooterViewModel? + + open func configureView(_ viewmodel: HeaderFooterViewModel) { + _viewmodel = viewmodel + configure() + } + + open func configure() {} + open func didChangeFloatingState(_ isFloating: Bool, section: Int) {} + open func willDisplay(_ tableView: UITableView, section: Int) {} + open func didEndDisplaying(_ tableView: UITableView, section: Int) {} +} diff --git a/Hakuba/Source/HeaderFooter/HeaderFooterViewModel.swift b/Hakuba/Source/HeaderFooter/HeaderFooterViewModel.swift new file mode 100644 index 0000000..e0f7b20 --- /dev/null +++ b/Hakuba/Source/HeaderFooter/HeaderFooterViewModel.swift @@ -0,0 +1,36 @@ +// +// HeaderFooterViewModel.swift +// Example +// +// Created by Le VanNghia on 3/5/16. +// +// + +import UIKit + +open class HeaderFooterViewModel { + public enum `Type` { + case header + case footer + } + + open let reuseIdentifier: String + + open internal(set) var section: Int = 0 + open internal(set) var type: Type = .header + + open var title: String? + open var height: CGFloat = UITableViewAutomaticDimension + + open var isHeader: Bool { + return type == .header + } + + open var isFooter: Bool { + return type == .footer + } + + public init(view: T.Type) { + reuseIdentifier = view.reuseIdentifier + } +} diff --git a/Hakuba/HeaderFooterViewType.swift b/Hakuba/Source/HeaderFooter/HeaderFooterViewType.swift similarity index 100% rename from Hakuba/HeaderFooterViewType.swift rename to Hakuba/Source/HeaderFooter/HeaderFooterViewType.swift diff --git a/Hakuba/Source/Section/Section.swift b/Hakuba/Source/Section/Section.swift new file mode 100644 index 0000000..047dea6 --- /dev/null +++ b/Hakuba/Source/Section/Section.swift @@ -0,0 +1,255 @@ +// +// Section.swift +// Example +// +// Created by Le VanNghia on 3/4/16. +// +// + +import Foundation +import UIKit + +protocol SectionDelegate: class { + func bumpMe(with type: SectionBumpType, animation: UITableViewRowAnimation) +} + +open class Section { + weak var delegate: SectionDelegate? + open private(set) var cellmodels: [CellModel] = [] + private let bumpTracker = BumpTracker() + + internal(set) var index: Int = 0 + + var isChanged: Bool { + return bumpTracker.isChanged + } + + open var header: HeaderFooterViewModel? { + didSet { + header?.section = index + header?.type = .header + } + } + + open var footer: HeaderFooterViewModel? { + didSet { + footer?.section = index + footer?.type = .footer + } + } + + open subscript(index: Int) -> CellModel? { + return cellmodels.get(at: index) + } + + public init() {} + + @discardableResult + open func bump(_ animation: UITableViewRowAnimation = .none) -> Self { + let type = bumpTracker.getSectionBumpType(at: index) + delegate?.bumpMe(with: type, animation: animation) + bumpTracker.didBump() + return self + } +} + +// MARK - Public methods + +public extension Section { + + // MARK - Reset + + @discardableResult + func reset() -> Self { + return reset([]) + } + + @discardableResult + func reset(_ cellmodel: CellModel) -> Self { + return reset([cellmodel]) + } + + @discardableResult + func reset(_ cellmodels: [CellModel]) -> Self { + setupCellmodels(cellmodels, start: 0) + self.cellmodels = cellmodels + bumpTracker.didReset() + return self + } + + // MARK - Append + + @discardableResult + func append(_ cellmodel: CellModel) -> Self { + return append([cellmodel]) + } + + @discardableResult + func append(_ cellmodels: [CellModel]) -> Self { + return insert(cellmodels, at: count) + } + + // MARK - Insert + + @discardableResult + func insert(_ cellmodel: CellModel, at index: Int) -> Self { + return insert([cellmodel], at: index) + } + + @discardableResult + func insert(_ cellmodels: [CellModel], at index: Int) -> Self { + guard cellmodels.isNotEmpty else { return self } + + let start = min(count, index) + + self.cellmodels.insert(cellmodels, at: start) + let affectedCellmodels = Array(self.cellmodels[start.. Self { + return insertBeforeLast([viewmodel]) + } + + @discardableResult + func insertBeforeLast(_ viewmodels: [CellModel]) -> Self { + let index = max(cellmodels.count - 1, 0) + return insert(viewmodels, at: index) + } + + // MARK - Remove + + @discardableResult + func remove(at index: Int) -> Self { + return remove(at: [index]) + } + + @discardableResult + func remove(range: CountableRange) -> Self { + return remove(at: range.map { $0 }) + } + + @discardableResult + func remove(range: CountableClosedRange) -> Self { + return remove(at: range.map { $0 }) + } + + @discardableResult + func remove(at indexes: [Int]) -> Self { + guard indexes.isNotEmpty else { return self } + + let sortedIndexes = indexes + .sorted(by: <) + .filter { $0 >= 0 && $0 < count } + + var remainCellmodels: [CellModel] = [] + var i = 0 + + for j in 0.. Self { + let index = cellmodels.count - 1 + + guard index >= 0 else { return self } + + return remove(at: [index]) + } + + @discardableResult + func remove(_ cellmodel: CellModel) -> Self { + guard let index = cellmodels.index(where: { $0 === cellmodel }) else { return self } + + return remove(at: index) + } + + // MAKR - Move + + @discardableResult + func move(from fromIndex: Int, to toIndex: Int) -> Self { + cellmodels.move(from: toIndex, to: toIndex) + setupCellmodels([cellmodels[fromIndex]], start: fromIndex) + setupCellmodels([cellmodels[toIndex]], start: toIndex) + + bumpTracker.didMove(from: fromIndex, to: toIndex) + return self + } +} + +// MARK - Utilities + +public extension Section { + var count: Int { + return cellmodels.count + } + + var isEmpty: Bool { + return cellmodels.isEmpty + } + + var isNotEmpty: Bool { + return !isEmpty + } + + var first: CellModel? { + return cellmodels.first + } + + var last: CellModel? { + return cellmodels.last + } +} + +// MARK - Internal methods + +extension Section { + func setup(at index: Int, delegate: SectionDelegate) { + self.delegate = delegate + self.index = index + + header?.section = index + footer?.section = index + setupCellmodels(cellmodels, start: 0) + } + + func didReloadTableView() { + bumpTracker.didBump() + } +} + +// MARK - Private methods + +private extension Section { + func setupCellmodels(_ cellmodels: [CellModel], start index: Int) { + guard let delegate = delegate as? CellModelDelegate else { return } + + var start = index + + cellmodels.forEach { cellmodel in + let indexPath = IndexPath(row: start, section: self.index) + cellmodel.setup(indexPath: indexPath, delegate: delegate) + start += 1 + } + } +} diff --git a/Hakuba/Source/Section/SectionIndexType.swift b/Hakuba/Source/Section/SectionIndexType.swift new file mode 100644 index 0000000..e96581f --- /dev/null +++ b/Hakuba/Source/Section/SectionIndexType.swift @@ -0,0 +1,12 @@ +// +// SectionIndexType.swift +// Example +// +// Created by Keeeeen on 2018/08/09. +// + +import Foundation + +public protocol SectionIndexType: RawRepresentable where Self.RawValue == Int { + static var count: Int { get } +} diff --git a/Hakuba/TableViewExt.swift b/Hakuba/TableViewExt.swift deleted file mode 100644 index 5328a4e..0000000 --- a/Hakuba/TableViewExt.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// UITableViewExt.swift -// Example -// -// Created by Le VanNghia on 3/4/16. -// -// - -import UIKit - -extension UITableView { - func registerCellByNib(t: T.Type) { - let nib = UINib(nibName: t.nibName, bundle: nil) - registerNib(nib, forCellReuseIdentifier: t.reuseIdentifier) - } - - func registerCell(t: T.Type) { - registerClass(t, forCellReuseIdentifier: t.reuseIdentifier) - } - - func registerHeaderFooterByNib(t: T.Type) { - let nib = UINib(nibName: t.nibName, bundle: nil) - registerNib(nib, forHeaderFooterViewReuseIdentifier: t.reuseIdentifier) - } - - func registerHeaderFooter(t: T.Type) { - registerClass(t, forHeaderFooterViewReuseIdentifier: t.reuseIdentifier) - } - - func dequeueCell(t: T.Type, forIndexPath indexPath: NSIndexPath) -> T { - return dequeueReusableCellWithIdentifier(t.reuseIdentifier, forIndexPath: indexPath) as! T - } -} \ No newline at end of file diff --git a/README.md b/README.md index d151dc3..77c02eb 100644 --- a/README.md +++ b/README.md @@ -35,238 +35,225 @@ Features ##### Quick example ``` swift - // viewController swift file +// viewController swift file - hakuba = Hakuba(tableView: tableView) +hakuba = Hakuba(tableView: tableView) - let cellmodel = YourCellModel(title: "Title", des: "description") { - println("Did select cell with title = \(title)") - } +let cellmodel = YourCellModel(title: "Title", des: "description") { + println("Did select cell with title = \(title)") +} - hakuba[2] - .append(cellmodel) // append a new cell model into datasource - .bump(.Fade) // show the cell of your cell model in the table view +hakuba[2] + .append(cellmodel) // append a new cell model into datasource + .bump(.fade) // show the cell of your cell model in the table view - hakuba[1] - .remove(1...3) - .bump(.Right) +hakuba[1] + .remove(1...3) + .bump(.Right) ``` + ``` swift - // your cell swift file +// your cell swift file - class YourCellModel: CellModel { - let title: String - let des: String +class YourCellModel: CellModel { + let title: String + let des: String - init(title: String, des: String, selectionHandler: SelectionHandler) { - self.title = title - self.des = des - super.init(YourCell.self, selectionHandler: selectionHandler) - } + init(title: String, des: String, selectionHandler: @escaping (Cell) -> Void) { + self.title = title + self.des = des + super.init(YourCell.self, selectionHandler: selectionHandler) } +} - class YourCell: Cell, CellType { - typealias CellModel = YourCellModel +class YourCell: Cell, CellType { + typealias CellModel = YourCellModel - @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var titleLabel: UILabel! - override func configure() { - guard let cellmodel = cellmodel else { - return - } + override func configure() { + guard let cellmodel = cellmodel else { + return + } - titleLabel.text = cellmodel.title - } - } + titleLabel.text = cellmodel.title + } +} ``` Usage ----- - * Initilization +* Initilization ``` swift - private lazy var hakuba = Hakuba(tableView: tableView) +private lazy var hakuba = Hakuba(tableView: tableView) ``` * Section handling ``` swift - let section = Section() // create a new section - - // inserting - hakuba - .insert(section, atIndex: 1) - .bump() - - // removing - hakuba - .remove(index) - .bump(.Left) - - hakuba - .remove(section) - .bump() - - hakuba - .removeAll() - .bump() - - // handing section index by enum - enum YourSection: Int, SectionIndexType { - case Top = 0 - case Center - case Bottom - - static let count = 3 - } +let section = Section() // create a new section + +// inserting +hakuba + .insert(section, at: 1) + .bump() + +// removing +hakuba + .remove(at: index) + .bump(.left) + +hakuba + .remove(section) + .bump() + +hakuba + .removeAll() + .bump() + +// handing section index by enum +enum YourSection: Int, SectionIndexType { + case top + case center + case bottom + + static let count = 3 +} - let topSection = hakuba[YourSection.Top] +let topSection = hakuba[YourSection.top] ``` * Cell handling ``` swift - // 1. appending - hakuba[0] - .append(cellmodel) // append a cellmodel - .bump(.Fade) // and bump with `Fade` animation - - hakuba[1] - .append(cellmodels) // append a list of cellmodes - .bump(.Left) - - // by using section - let section = hakuba[YourSection.Top] - section - .append(cellmodel) - .bump() - - - // 2. inserting - section - .insert(cellmodels, atIndex: 1) - .bump(.Middle) - - - // 3. reseting - section - .reset(cellmodels) // replace current data in section by the new data - .bump() - - section - .reset() // or remove all data in section - .bump() - - - // 4. removing - section - .remove(1) - .bump(.Right) - - section - .remove(2...5) - .bump() - - section - .removeLast() - .bump() +// 1. appending +hakuba[0] + .append(cellmodel) // append a cellmodel + .bump(.fade) // and bump with `Fade` animation + +hakuba[1] + .append(cellmodels) // append a list of cellmodes + .bump(.left) + +// by using section +let section = hakuba[YourSection.top] +section + .append(cellmodel) + .bump() + +// 2. inserting +section + .insert(cellmodels, at: 1) + .bump(.middle) + +// 3. reseting +section + .reset(cellmodels) // replace current data in section by the new data + .bump() + +section + .reset() // or remove all data in section + .bump() + +// 4. removing +section + .remove(at: 1) + .bump(.right) + +section + .remove(range: 2...5) + .bump() + +section + .removeLast() + .bump() ``` - ``` swift - // updating cell data - let section = hakuba[YourSection.Top] - section[1].property = newData - section[1] - .bump() +// updating cell data +let section = hakuba[YourSection.top] +section[1].property = newData +section[1] + .bump() ``` - ``` swift - section.sort().bump() - section.shuffle().bump() - section.map - section.filter - section.reduce - section.mapFilter - section.each - - section.first - section.last - section[1] - section.count +section.sort().bump() +section.shuffle().bump() +section.map +section.filter +section.reduce +section.mapFilter +section.each + +section.first +section.last +section[1] +section.count ``` * Register cell, header, footer ``` swift - hakuba - .registerCellByNib(CellClass) +hakuba + .registerCellByNib(CellClass.self) - hakuba - .registerCell(CellClass) +hakuba + .registerCell(CellClass.self) - hakuba - .registerHeaderFooterByNib(HeaderOrFooterClass) +hakuba + .registerHeaderFooterByNib(HeaderOrFooterClass.self) - hakuba - .registerHeaderFooter(HeaderOrFooterClass) +hakuba + .registerHeaderFooter(HeaderOrFooterClass.self) - // register a list of cells by using variadic parameters - hakuba.registerCellByNibs(CellClass1.self, CellClass2.self, ..., CellClassN.self) +// register a list of cells by using variadic parameters +hakuba.registerCellByNibs(CellClass1.self, CellClass2.self, ..., CellClassN.self) ``` * Section header/footer ``` swift - let header = HeaderFooterViewModel(view: CustomHeaderView) { - println("Did select header view") - } - hakuba[Section.Top].header = header +let header = HeaderFooterViewModel(view: CustomHeaderView) { + println("Did select header view") +} +hakuba[Section.top].header = header ``` * Loadmore ``` swift - hakuba.loadmoreEnabled = true - hakuba.loadmoreHandler = { - // request api - // append new data - } +hakuba.loadmoreEnabled = true +hakuba.loadmoreHandler = { + // request api + // append new data +} ``` * Commit editing ``` swift - hakuba.commitEditingHandler = { [weak self] style, indexPath in - self?.hakuba[indexPath.section] - .remove(indexPath.row) - } +hakuba.commitEditingHandler = { [weak self] style, indexPath in +self?.hakuba[indexPath.section] + .remove(indexPath.row) +} ``` * Deselect all cells ``` swift - hakuba.deselectAllCells(animated: true) +hakuba.deselectAllCells(animated: true) ``` -* Dynamic cell height : when you want to enable dynamic cell height, you only need to set the value of estimated height to the `height` parameter and set `dynamicHeightEnabled = true` - -``` swift - let cellmodel = CellModel(cellClass: YourCell.self, height: 50, userData: yourCellData) { - println("Did select cell") - } - cellmodel.dynamicHeightEnabled = true - -``` * Callback methods in the cell class ``` swift - func willAppear(data: CellModel) - func didDisappear(data: CellModel) +func willAppear(data: CellModel) +func didDisappear(data: CellModel) ``` @@ -275,7 +262,7 @@ Installation * Installation with CocoaPods ``` - pod 'Hakuba' +pod 'Hakuba' ``` * Copying all the files into your project @@ -283,8 +270,9 @@ Installation Requirements ----- -- iOS 7.0+ -- Xcode 6.1 +- iOS 9.0+ +- Xcode 9+ +- Swift 4 License -----