├── Screenshots ├── ss-load-more.png ├── ss-initial-loading.png ├── ss-load-more-error.png ├── ss-pull-to-refresh.png └── ss-initial-load-error.png ├── Demo ├── Demo.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── Podfile ├── Demo.xcworkspace │ └── contents.xcworkspacedata ├── Podfile.lock ├── DemoTests │ ├── Info.plist │ └── DemoTests.swift ├── DemoUITests │ ├── Info.plist │ └── DemoUITests.swift └── Demo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ └── ViewController.swift ├── StatefulTableView.podspec ├── LICENSE ├── Sources ├── StatefulTableView+State.swift ├── StatefulTableView+InitialLoad.swift ├── StatefulTableView+PullToRefresh.swift ├── StatefulTableView+LoadMore.swift ├── StatefulTableDelegate.swift ├── StatefulTableView.swift ├── StatefulTableView+Views.swift └── StatefulTableView+UITableView.swift ├── .gitignore └── README.md /Screenshots/ss-load-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/StatefulTableView/master/Screenshots/ss-load-more.png -------------------------------------------------------------------------------- /Screenshots/ss-initial-loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/StatefulTableView/master/Screenshots/ss-initial-loading.png -------------------------------------------------------------------------------- /Screenshots/ss-load-more-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/StatefulTableView/master/Screenshots/ss-load-more-error.png -------------------------------------------------------------------------------- /Screenshots/ss-pull-to-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/StatefulTableView/master/Screenshots/ss-pull-to-refresh.png -------------------------------------------------------------------------------- /Screenshots/ss-initial-load-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/StatefulTableView/master/Screenshots/ss-initial-load-error.png -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | use_frameworks! 3 | 4 | target 'Demo' do 5 | 6 | pod 'StatefulTableView', :path => '../' 7 | 8 | end 9 | 10 | target 'DemoTests' do 11 | 12 | end 13 | 14 | target 'DemoUITests' do 15 | 16 | end 17 | 18 | -------------------------------------------------------------------------------- /Demo/Demo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - StatefulTableView (0.0.14) 3 | 4 | DEPENDENCIES: 5 | - StatefulTableView (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | StatefulTableView: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | StatefulTableView: 8b061afb2fb9e7b9ece1f996b0ae925c1ad586d3 13 | 14 | PODFILE CHECKSUM: e6944d74483536f64ec788f95fbe36a46c8b7346 15 | 16 | COCOAPODS: 1.0.0 17 | -------------------------------------------------------------------------------- /StatefulTableView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'StatefulTableView' 3 | s.version = '0.0.15' 4 | s.license = { 5 | :type => 'MIT', 6 | :file => 'LICENSE' 7 | } 8 | s.homepage = 'http://github.com/timominous/StatefulTableView' 9 | s.description = 'Custom UITableView container class that supports pull-to-refresh, load-more, initial load, and empty states. Swift port of SKStatefulTableViewController' 10 | s.summary = 'Custom UITableView container class that supports pull-to-refresh, load-more, initial load, and empty states.' 11 | s.author = { 12 | 'timominous' => 'timominous@gmail.com' 13 | } 14 | s.source = { 15 | :git => 'https://github.com/timominous/StatefulTableView.git', 16 | :tag => s.version.to_s 17 | } 18 | s.ios.deployment_target = "8.0" 19 | s.source_files = 'Sources/*.swift' 20 | s.requires_arc = true 21 | end -------------------------------------------------------------------------------- /Demo/DemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Demo/DemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Demo 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(application: UIApplication) { 21 | } 22 | 23 | func applicationDidEnterBackground(application: UIApplication) { 24 | } 25 | 26 | func applicationWillEnterForeground(application: UIApplication) { 27 | } 28 | 29 | func applicationDidBecomeActive(application: UIApplication) { 30 | } 31 | 32 | func applicationWillTerminate(application: UIApplication) { 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Demo/DemoTests/DemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoTests.swift 3 | // DemoTests 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Demo 11 | 12 | class DemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 timominous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Demo/DemoUITests/DemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoUITests.swift 3 | // DemoUITests 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class DemoUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Sources/StatefulTableView+State.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+State.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension StatefulTableView { 12 | // MARK: - States 13 | 14 | internal func setState(_ newState: State) { 15 | setState(newState, updateView: true, error: nil) 16 | } 17 | 18 | internal func setState(_ newState: State, error: NSError?) { 19 | setState(newState, updateView: true, error: error) 20 | } 21 | 22 | internal func setState(_ newState: State, updateView: Bool, error: NSError?) { 23 | state = newState 24 | 25 | switch state { 26 | case .initialLoading: 27 | resetdynamicContentView(withChildView: viewForInitialLoad) 28 | case .emptyOrInitialLoadError: 29 | resetdynamicContentView(withChildView: viewForEmptyInitialLoad(withError: error)) 30 | default: break 31 | } 32 | 33 | switch state { 34 | case .idle: 35 | watchForLoadMoreIfApplicable(true) 36 | case .emptyOrInitialLoadError: 37 | watchForLoadMoreIfApplicable(false) 38 | default: break 39 | } 40 | 41 | if updateView { 42 | let mode: ViewMode 43 | 44 | switch state { 45 | case .initialLoading: fallthrough 46 | case .emptyOrInitialLoadError: 47 | mode = .static 48 | default: mode = .table 49 | } 50 | 51 | viewMode = mode 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Sources/StatefulTableView+InitialLoad.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+InitialLoad.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension StatefulTableView { 12 | // MARK: - Initial load 13 | 14 | /** 15 | Triggers initial load of data programatically. Defaults to hiding the tableView. 16 | 17 | - returns: Boolean for success status. 18 | */ 19 | public func triggerInitialLoad() -> Bool { 20 | return triggerInitialLoad(false) 21 | } 22 | 23 | /** 24 | Triggers initial load of data programatically. 25 | 26 | - parameter shouldShowTableView: Control if the container should show the tableView or not. 27 | 28 | - returns: Boolean for success status. 29 | */ 30 | public func triggerInitialLoad(_ shouldShowTableView: Bool) -> Bool { 31 | guard !state.isLoading else { return false } 32 | 33 | if shouldShowTableView { 34 | self.setState(.initialLoadingTableView) 35 | } else { 36 | self.setState(.initialLoading) 37 | } 38 | 39 | if let delegate = statefulDelegate { 40 | delegate.statefulTableViewWillBeginInitialLoad(self, handler: { [weak self](tableIsEmpty, errorOrNil) in 41 | DispatchQueue.main.async(execute: { 42 | self?.setHasFinishedInitialLoad(tableIsEmpty, error: errorOrNil) 43 | }) 44 | }) 45 | } 46 | 47 | return true 48 | } 49 | 50 | fileprivate func setHasFinishedInitialLoad(_ tableIsEmpty: Bool, error: NSError?) { 51 | guard state.isInitialLoading else { return } 52 | 53 | if tableIsEmpty { 54 | self.setState(.emptyOrInitialLoadError, updateView: true, error: error) 55 | } else { 56 | self.setState(.idle) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xcuserstate 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | Demo/Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /Sources/StatefulTableView+PullToRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+PullToRefresh.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension StatefulTableView { 12 | // MARK: - Pull to refresh 13 | 14 | func refreshControlValueChanged() { 15 | if state != .loadingFromPullToRefresh && !state.isLoading { 16 | if (!triggerPullToRefresh()) { 17 | refreshControl.endRefreshing() 18 | } 19 | } else { 20 | refreshControl.endRefreshing() 21 | } 22 | } 23 | 24 | /** 25 | Triggers pull to refresh programatically. Also called when the user pulls down to refresh on the tableView. 26 | 27 | - returns: Boolean for success status. 28 | */ 29 | public func triggerPullToRefresh() -> Bool { 30 | guard !state.isLoading && canPullToRefresh else { return false } 31 | 32 | self.setState(.loadingFromPullToRefresh, updateView: false, error: nil) 33 | 34 | if let delegate = statefulDelegate { 35 | delegate.statefulTableViewWillBeginLoadingFromRefresh(self, handler: { [weak self](tableIsEmpty, errorOrNil) in 36 | DispatchQueue.main.async(execute: { 37 | self?.setHasFinishedLoadingFromPullToRefresh(tableIsEmpty, error: errorOrNil) 38 | }) 39 | }) 40 | } 41 | 42 | refreshControl.beginRefreshing() 43 | 44 | return true 45 | } 46 | 47 | fileprivate func setHasFinishedLoadingFromPullToRefresh(_ tableIsEmpty: Bool, error: NSError?) { 48 | guard state == .loadingFromPullToRefresh else { return } 49 | 50 | refreshControl.endRefreshing() 51 | 52 | if tableIsEmpty { 53 | self.setState(.emptyOrInitialLoadError, updateView: true, error: error) 54 | } else { 55 | self.setState(.idle) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StatefulTableView 2 | [![Version](https://img.shields.io/cocoapods/v/StatefulTableView.svg?style=flat)](http://cocoadocs.org/docsets/StatefulTableView) 3 | [![License](https://img.shields.io/cocoapods/l/StatefulTableView.svg?style=flat)](http://cocoadocs.org/docsets/StatefulTableView) 4 | [![Platform](https://img.shields.io/cocoapods/p/StatefulTableView.svg?style=flat)](http://cocoadocs.org/docsets/StatefulTableView) 5 | [![Platform](https://img.shields.io/cocoapods/metrics/doc-percent/StatefulTableView.svg?style=flat)](http://cocoadocs.org/docsets/StatefulTableView) 6 | 7 | Custom UITableView container class that supports pull-to-refresh, load-more, initial load, and empty states. This library aims to be a drop in replacement for `UITableView`. Swift port of [SKStatefulTableViewController](http://github.com/shiki/SKStatefulTableViewController). 8 | 9 | This is a *work in progress*. A lot of things may break as of the moment. 10 | 11 | ## Screenshots 12 | 13 | Initial loading: 14 | 15 | 16 | 17 | Pull-to-refresh: 18 | 19 | 20 | 21 | Load more: 22 | 23 | 24 | 25 | Initial load error: 26 | 27 | 28 | 29 | Load more error: 30 | 31 | 32 | 33 | ## Usage 34 | 35 | Currently, you can only assign the delegates and data source through code. 36 | 37 | ```swift 38 | tableView.dataSource = self // Confofrms to UITableViewDataSource 39 | tableView.delegate = self // Conforms to UITableViewDelegate 40 | tableView.statefulDelegate = self // Conforms to StatefulTableDelegate 41 | ``` 42 | 43 | For initial loading, pull-to-refresh, and load more, you have to implement the following statefulDelegate methods: 44 | 45 | ```swift 46 | func statefulTableViewWillBeginInitialLoad(tvc: StatefulTableView, handler: InitialLoadCompletionHandler) 47 | func statefulTableViewWillBeginLoadingFromRefresh(tvc: StatefulTableView, handler: InitialLoadCompletionHandler) 48 | func statefulTableViewWillBeginLoadingMore(tvc: StatefulTableView, handler: LoadMoreCompletionHandler) 49 | ``` 50 | 51 | To show custom views, return them through the following statefulDelegate methods. Otherwise, return `nil`. 52 | 53 | ```swift 54 | func statefulTableViewViewForInitialLoad(tvc: StatefulTableView) -> UIView? 55 | func statefulTableViewInitialErrorView(tvc: StatefulTableView, forInitialLoadError: NSError?) -> UIView? 56 | func statefulTableViewLoadMoreErrorView(tvc: StatefulTableView, forLoadMoreError: NSError?) -> UIView? 57 | ``` 58 | 59 | ## Installation 60 | 61 | ### Cocoapods 62 | 63 | Add this to your Podfile. 64 | 65 | ```ruby 66 | pod 'StatefulTableView', '0.0.15' 67 | ``` 68 | 69 | ### Credits 70 | 71 | * [SKStatefulTableViewController](http://github.com/shiki/SKStatefulTableViewController) by [shiki](http://github.com/shiki) 72 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Sources/StatefulTableView+LoadMore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+LoadMore.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension StatefulTableView { 12 | // MARK: - Load more 13 | 14 | /** 15 | Tiggers loading more of data. Also called when the scroll content offset reaches the `loadMoreTriggerThreshold`. 16 | */ 17 | public func triggerLoadMore() { 18 | guard !state.isLoading else { return } 19 | 20 | loadMoreViewIsErrorView = false 21 | lastLoadMoreError = nil 22 | updateLoadMoreView() 23 | 24 | setState(.loadingMore) 25 | 26 | if let delegate = statefulDelegate { 27 | delegate.statefulTableViewWillBeginLoadingMore(self, handler: { [weak self](canLoadMore, errorOrNil, showErrorView) in 28 | DispatchQueue.main.async(execute: { 29 | self?.setHasFinishedLoadingMore(canLoadMore, error: errorOrNil, showErrorView: showErrorView) 30 | }) 31 | }) 32 | } 33 | } 34 | 35 | internal func updateLoadMoreView() { 36 | if watchForLoadMore || lastLoadMoreError != nil { 37 | tableView.tableFooterView = viewForLoadingMore(withError: (loadMoreViewIsErrorView ? lastLoadMoreError : nil)) 38 | } else { 39 | tableView.tableFooterView = UIView() 40 | } 41 | } 42 | 43 | internal func viewForLoadingMore(withError error: NSError?) -> UIView? { 44 | if let delegateMethod = statefulDelegate?.statefulTableViewLoadMoreErrorView, error != nil { 45 | return delegateMethod(self, error) 46 | } 47 | 48 | let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: tableView.bounds.width, height: 44))) 49 | 50 | let sub: UIView 51 | 52 | if let error = error { 53 | let label = UILabel() 54 | label.translatesAutoresizingMaskIntoConstraints = false 55 | label.text = error.localizedDescription 56 | label.font = UIFont.systemFont(ofSize: 12) 57 | label.textAlignment = .center 58 | sub = label 59 | } else { 60 | let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) 61 | activityIndicator.translatesAutoresizingMaskIntoConstraints = false 62 | activityIndicator.startAnimating() 63 | sub = activityIndicator 64 | } 65 | 66 | container.addSubview(sub) 67 | centerView(sub, inContainer: container) 68 | 69 | return container 70 | } 71 | 72 | internal func setHasFinishedLoadingMore(_ canLoadMore: Bool, error: NSError?, showErrorView: Bool) { 73 | guard state == .loadingMore else { return } 74 | 75 | self.canLoadMore = canLoadMore 76 | loadMoreViewIsErrorView = (error != nil) && showErrorView 77 | lastLoadMoreError = error 78 | 79 | setState(.idle) 80 | } 81 | 82 | internal func watchForLoadMoreIfApplicable(_ watch: Bool) { 83 | var watch = watch 84 | 85 | if (watch && !canLoadMore) { 86 | watch = false 87 | } 88 | watchForLoadMore = watch 89 | updateLoadMoreView() 90 | 91 | triggerLoadMoreIfApplicable(tableView) 92 | } 93 | 94 | /** 95 | Should be called when scrolling the tableView. This determines when to call `triggerLoadMore` 96 | 97 | - parameter scrollView: The scrolling view. 98 | */ 99 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 100 | triggerLoadMoreIfApplicable(scrollView) 101 | } 102 | 103 | internal func triggerLoadMoreIfApplicable(_ scrollView: UIScrollView) { 104 | guard watchForLoadMore && !loadMoreViewIsErrorView else { return } 105 | 106 | let scrollPosition = scrollView.contentSize.height - scrollView.frame.size.height - scrollView.contentOffset.y 107 | if scrollPosition < loadMoreTriggerThreshold { 108 | triggerLoadMore() 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/StatefulTableDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableDelegate.swift 3 | // Demo 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | A closure declaration describing if the table is empty and has an optional error. 13 | 14 | - parameter tableIsEmpty: Describes if the table is empty. 15 | - parameter errorOrNil: Describes the error received from loading. May be nil. 16 | */ 17 | public typealias InitialLoadCompletionHandler = (_ tableIsEmpty: Bool, _ errorOrNil: NSError?) -> Void 18 | 19 | /** 20 | A closure declaration describing if the table can load more, received an error, and should show an error view. 21 | 22 | - parameter canLoadMore: Describes if the table can loa dmore data. 23 | - parameter errorOrNil: Describes the error received from loading. May be nil. 24 | - parameter showErrorView: Describes if an error view should be shown. 25 | */ 26 | public typealias LoadMoreCompletionHandler = (_ canLoadMore: Bool, _ errorOrNil: NSError?, _ showErrorView: Bool) -> Void 27 | 28 | /** 29 | This protocol represents the loading behavior of the `StatefulTableView`. 30 | */ 31 | @objc public protocol StatefulTableDelegate: class { 32 | // MARK: - Managing Loading 33 | 34 | /** 35 | This delegate method will be called when the tableView is triggered to load data initially. 36 | 37 | - parameter tvc: The tableView calling the method. 38 | - parameter handler: The completion handler describing if the table is empty and if there is an error. 39 | */ 40 | func statefulTableViewWillBeginInitialLoad(_ tvc: StatefulTableView, handler: @escaping InitialLoadCompletionHandler) 41 | 42 | /** 43 | This delegate method will be called when the user pulls down to refresh. 44 | 45 | - parameter tvc: The tableView calling the method. 46 | - parameter handler: The completion handler describing if the table is empty and if there is an error. 47 | */ 48 | func statefulTableViewWillBeginLoadingFromRefresh(_ tvc: StatefulTableView, handler: @escaping InitialLoadCompletionHandler) 49 | 50 | /** 51 | This delegate method will be called when the user scrolls to load more. 52 | 53 | - parameter tvc: The tableView calling the method. 54 | - parameter handler: The completion handler describing if the table can load more, has an error, and should show an error view. 55 | */ 56 | func statefulTableViewWillBeginLoadingMore(_ tvc: StatefulTableView, handler: @escaping LoadMoreCompletionHandler) 57 | 58 | // MARK: - Using Custom Views 59 | 60 | /** 61 | This delegate method will be called when the tableView is in need of a view to show when it is loading data initially. 62 | 63 | - parameter tvc: The tableView calling the method. 64 | 65 | - returns: An optional view to show. 66 | */ 67 | @objc optional func statefulTableViewViewForInitialLoad(_ tvc: StatefulTableView) -> UIView? 68 | 69 | /** 70 | This delegate method will be called when the tableView is in need of a view to show when it's done loading initially and no data/an error was found. 71 | 72 | - parameter tvc: The tableView calling the method. 73 | - parameter forInitialLoadError: The optional error found. 74 | 75 | - returns: An optional view to show. 76 | */ 77 | @objc optional func statefulTableViewInitialErrorView(_ tvc: StatefulTableView, forInitialLoadError: NSError?) -> UIView? 78 | 79 | /** 80 | This delegate method will be called when the tableView failed to load more data. 81 | 82 | - parameter tvc: The tableView calling the method. 83 | - parameter forLoadMoreError: The optional error found. 84 | 85 | - returns: An optional view to show. 86 | */ 87 | @objc optional func statefulTableViewLoadMoreErrorView(_ tvc: StatefulTableView, forLoadMoreError: NSError?) -> UIView? 88 | } 89 | -------------------------------------------------------------------------------- /Sources/StatefulTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView.swift 3 | // Demo 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Drop-in replacement for `UITableView` that supports pull-to-refresh, load-more, initial load, and empty states. 13 | */ 14 | public final class StatefulTableView: UIView { 15 | internal enum State { 16 | case idle 17 | case initialLoading 18 | case initialLoadingTableView 19 | case emptyOrInitialLoadError 20 | case loadingFromPullToRefresh 21 | case loadingMore 22 | 23 | var isLoading: Bool { 24 | switch self { 25 | case .initialLoading: fallthrough 26 | case .initialLoadingTableView: fallthrough 27 | case .loadingFromPullToRefresh: fallthrough 28 | case .loadingMore: 29 | return true 30 | default: return false 31 | } 32 | } 33 | 34 | var isInitialLoading: Bool { 35 | switch self { 36 | case .initialLoading: fallthrough 37 | case .initialLoadingTableView: 38 | return true 39 | default: return false 40 | } 41 | } 42 | } 43 | 44 | internal enum ViewMode { 45 | case table 46 | case `static` 47 | } 48 | 49 | /** 50 | Returns an object initialized from data in a given unarchiver. 51 | 52 | - Parameter aDecoder: An unarchiver object. 53 | 54 | - Returns: An initialized StatefulTableView object. 55 | */ 56 | required public init?(coder aDecoder: NSCoder) { 57 | super.init(coder: aDecoder) 58 | commonInit() 59 | } 60 | 61 | /** 62 | Initializes and returns a newly allocatied view object with the specified frame rectangle. 63 | 64 | - Parameter frame: The frame rectangle for the view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. this method uses the frame rectangle to set the center and bounds properties accordingly. 65 | 66 | - Returns: An initialized StatefulTableView object. 67 | */ 68 | public override init(frame: CGRect) { 69 | super.init(frame: frame) 70 | commonInit() 71 | } 72 | 73 | func commonInit() { 74 | addSubview(tableView) 75 | addSubview(dynamicContentView) 76 | 77 | refreshControl.addTarget(self, 78 | action: #selector(refreshControlValueChanged), for: .valueChanged) 79 | tableView.addSubview(refreshControl) 80 | } 81 | 82 | /** 83 | Lays out subviews. 84 | */ 85 | override public func layoutSubviews() { 86 | super.layoutSubviews() 87 | tableView.frame = bounds 88 | dynamicContentView.frame = bounds 89 | } 90 | 91 | internal lazy var tableView = UITableView() 92 | 93 | /** 94 | An accessor to the contained `UITableView`. 95 | */ 96 | public var innerTable: UITableView { 97 | return tableView 98 | } 99 | 100 | internal lazy var dynamicContentView: UIView = { [unowned self] in 101 | let view = UIView(frame: self.bounds) 102 | view.backgroundColor = UIColor.white 103 | view.isHidden = true 104 | return view 105 | }() 106 | 107 | internal lazy var refreshControl = UIRefreshControl() 108 | 109 | // MARK: - Properties 110 | 111 | /** 112 | Enables the user to pull down on the tableView to initiate a refresh 113 | */ 114 | public var canPullToRefresh = false 115 | 116 | /** 117 | Enables the user to control whether to trigger loading of more objects or not 118 | */ 119 | public var canLoadMore = false 120 | 121 | /** 122 | Distance from the bottom of the tableView's vertical content offset where load more will be triggered 123 | */ 124 | public var loadMoreTriggerThreshold: CGFloat = 64 125 | 126 | internal var loadMoreViewIsErrorView = false 127 | internal var lastLoadMoreError: NSError? 128 | internal var watchForLoadMore = false 129 | 130 | internal var state: State = .idle 131 | 132 | internal var viewMode: ViewMode = .table { 133 | didSet { 134 | let hidden = viewMode == .table 135 | 136 | guard dynamicContentView.isHidden != hidden else { return } 137 | dynamicContentView.isHidden = hidden 138 | } 139 | } 140 | 141 | // MARK: - Stateful Delegate 142 | 143 | /** 144 | The object that acts as the stateful delegate of the table view. 145 | 146 | - Discussion: The stateful delegate must adopt the `StatefulTableDelegate` protocol. The stateful delegate is not retained. 147 | */ 148 | weak public var statefulDelegate: StatefulTableDelegate? 149 | 150 | } 151 | -------------------------------------------------------------------------------- /Demo/Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Demo 4 | // 5 | // Created by Tim on 12/05/2016. 6 | // Copyright © 2016 timominous. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StatefulTableView 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var statefulTableView: StatefulTableView! 15 | 16 | var items = 0 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | statefulTableView.canPullToRefresh = true 21 | statefulTableView.canLoadMore = true 22 | 23 | statefulTableView.statefulDelegate = self 24 | statefulTableView.dataSource = self 25 | statefulTableView.delegate = self 26 | statefulTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "identifier") 27 | } 28 | 29 | override func viewDidAppear(animated: Bool) { 30 | super.viewDidAppear(animated) 31 | statefulTableView.triggerInitialLoad() 32 | } 33 | 34 | override func didReceiveMemoryWarning() { 35 | super.didReceiveMemoryWarning() 36 | } 37 | 38 | } 39 | 40 | extension ViewController: StatefulTableDelegate { 41 | func statefulTableViewWillBeginLoadingFromRefresh(tvc: StatefulTableView, handler: InitialLoadCompletionHandler) { 42 | items = Int(arc4random_uniform(15)) 43 | let empty = items == 0 44 | 45 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * NSEC_PER_SEC)) 46 | dispatch_after(time, dispatch_get_main_queue()) { 47 | let error = NSError(domain: "test", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown error"]) 48 | tvc.reloadData() 49 | handler(tableIsEmpty: empty, errorOrNil: error) 50 | } 51 | } 52 | 53 | func statefulTableViewWillBeginInitialLoad(tvc: StatefulTableView, handler: InitialLoadCompletionHandler) { 54 | items = Int(arc4random_uniform(15)) 55 | let empty = items == 0 56 | 57 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * NSEC_PER_SEC)) 58 | dispatch_after(time, dispatch_get_main_queue()) { 59 | let error = NSError(domain: "test", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown error"]) 60 | tvc.reloadData() 61 | handler(tableIsEmpty: empty, errorOrNil: error) 62 | } 63 | } 64 | 65 | func statefulTableViewWillBeginLoadingMore(tvc: StatefulTableView, handler: LoadMoreCompletionHandler) { 66 | items += Int(arc4random_uniform(20)) 67 | let loadMore = items < 50 68 | 69 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * NSEC_PER_SEC)) 70 | dispatch_after(time, dispatch_get_main_queue()) { 71 | let error = NSError(domain: "test", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown error"]) 72 | tvc.reloadData() 73 | handler(canLoadMore: loadMore, errorOrNil: error, showErrorView: !loadMore) 74 | } 75 | } 76 | 77 | // Uncomment to use a custom initial loading view 78 | // func statefulTableViewViewForInitialLoad(tvc: StatefulTableView) -> UIView? { 79 | // let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) 80 | // view.backgroundColor = .blueColor() 81 | // return view 82 | // } 83 | 84 | // Uncomment to use a custom initial loading error view 85 | // func statefulTableViewInitialErrorView(tvc: StatefulTableView, forInitialLoadError: NSError?) -> UIView? { 86 | // let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) 87 | // view.backgroundColor = .redColor() 88 | // return view 89 | // } 90 | 91 | // Uncommen to use a custom load more error view 92 | // func statefulTableViewLoadMoreErrorView(tvc: StatefulTableView, forLoadMoreError: NSError?) -> UIView? { 93 | // let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) 94 | // view.backgroundColor = .greenColor() 95 | // return view 96 | // } 97 | } 98 | 99 | extension ViewController: UITableViewDataSource { 100 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 101 | return items 102 | } 103 | 104 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 105 | return tableView.dequeueReusableCellWithIdentifier("identifier", forIndexPath: indexPath) 106 | } 107 | } 108 | 109 | extension ViewController: UITableViewDelegate { 110 | func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { 111 | cell.textLabel?.text = "Cell \(indexPath.row)" 112 | } 113 | 114 | func scrollViewDidScroll(scrollView: UIScrollView) { 115 | statefulTableView.scrollViewDidScroll(scrollView) 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /Sources/StatefulTableView+Views.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+Views.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension StatefulTableView { 12 | // MARK: - Views 13 | 14 | internal func resetdynamicContentView(withChildView childView: UIView?) { 15 | dynamicContentView.subviews.forEach { $0.removeFromSuperview() } 16 | 17 | guard let childView = childView else { return } 18 | 19 | dynamicContentView.addSubview(childView) 20 | 21 | childView.translatesAutoresizingMaskIntoConstraints = false 22 | 23 | pinView(childView, toContainer: dynamicContentView) 24 | } 25 | 26 | internal var viewForInitialLoad: UIView? { 27 | if let delegateMethod = statefulDelegate?.statefulTableViewViewForInitialLoad { 28 | return delegateMethod(self) 29 | } 30 | 31 | let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) 32 | activityIndicatorView.startAnimating() 33 | 34 | return activityIndicatorView 35 | } 36 | 37 | internal func viewForEmptyInitialLoad(withError error: NSError?) -> UIView? { 38 | if let delegateMethod = statefulDelegate?.statefulTableViewInitialErrorView { 39 | return delegateMethod(self, error) 40 | } 41 | 42 | let container = UIView(frame: .zero) 43 | 44 | var centeredSize: CGSize = .zero 45 | 46 | let centered = UIView(frame: .zero) 47 | centered.translatesAutoresizingMaskIntoConstraints = false 48 | 49 | let label = UILabel() 50 | label.translatesAutoresizingMaskIntoConstraints = false 51 | label.textAlignment = .center 52 | label.text = error?.localizedDescription ?? "No records found" 53 | label.sizeToFit() 54 | 55 | label.setWidthConstraintToCurrent() 56 | label.setHeightConstraintToCurrent() 57 | 58 | centered.addSubview(label) 59 | 60 | apply([.top, .centerX], ofView: label, toView: centered) 61 | 62 | centeredSize.width = label.bounds.width 63 | centeredSize.height = label.bounds.height 64 | 65 | if let _ = error { 66 | let button = UIButton(type: .system) 67 | button.translatesAutoresizingMaskIntoConstraints = false 68 | button.setTitle("Try Again", for: UIControlState()) 69 | button.addTarget(self, action: #selector(triggerInitialLoad(_:)), for: .touchUpInside) 70 | button.sizeToFit() 71 | 72 | button.setWidthConstraintToCurrent() 73 | button.setHeightConstraintToCurrent() 74 | 75 | centeredSize.width = max(centeredSize.width, button.bounds.width) 76 | centeredSize.height = label.bounds.height + button.bounds.height + 5 77 | 78 | centered.addSubview(button) 79 | 80 | apply([.bottom, .centerX], ofView: button, toView: centered) 81 | } 82 | 83 | centered.setWidthConstraint(centeredSize.width) 84 | centered.setHeightConstraint(centeredSize.height) 85 | 86 | container.addSubview(centered) 87 | 88 | centerView(centered, inContainer: container) 89 | 90 | return container 91 | } 92 | } 93 | 94 | internal extension StatefulTableView { 95 | // MARK: - Helpers 96 | 97 | internal func pinView(_ view: UIView, toContainer container: UIView) { 98 | let attributes: [NSLayoutAttribute] = [.top, .bottom, .leading, .trailing] 99 | apply(attributes, ofView: view, toView: container) 100 | } 101 | 102 | internal func centerView(_ view: UIView, inContainer container: UIView) { 103 | let attributes: [NSLayoutAttribute] = [.centerX, .centerY] 104 | apply(attributes, ofView: view, toView: container) 105 | } 106 | 107 | internal func centerViewHorizontally(_ view: UIView, inContainer container: UIView) { 108 | apply([.centerX], ofView: view, toView: container) 109 | } 110 | 111 | internal func centerViewVertically(_ view: UIView, inContainer container: UIView) { 112 | apply([.centerY], ofView: view, toView: container) 113 | } 114 | 115 | internal func apply(_ attributes: [NSLayoutAttribute], ofView childView: UIView, toView containerView: UIView) { 116 | let constraints = attributes.map { 117 | return NSLayoutConstraint(item: childView, attribute: $0, relatedBy: .equal, 118 | toItem: containerView, attribute: $0, multiplier: 1, constant: 0) 119 | } 120 | 121 | containerView.addConstraints(constraints) 122 | } 123 | } 124 | 125 | internal extension UIView { 126 | internal func setWidthConstraintToCurrent() { 127 | setWidthConstraint(bounds.width) 128 | } 129 | 130 | internal func setHeightConstraintToCurrent() { 131 | setHeightConstraint(bounds.height) 132 | } 133 | 134 | internal func setWidthConstraint(_ width: CGFloat) { 135 | addConstraint(NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, 136 | attribute: .notAnAttribute, multiplier: 1, constant: width)) 137 | } 138 | 139 | internal func setHeightConstraint(_ height: CGFloat) { 140 | addConstraint(NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, 141 | attribute: .notAnAttribute, multiplier: 1, constant: height)) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2CBFD961A70C9B1577F0286B /* Pods_Demo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E18E2637AAF070943DDCB0A9 /* Pods_Demo.framework */; }; 11 | 8C78531E50789A0105B4072D /* Pods_DemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA9251D21C9D91E90A5327F2 /* Pods_DemoTests.framework */; }; 12 | 906D171C1CE4BF99001B30EF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906D171B1CE4BF99001B30EF /* AppDelegate.swift */; }; 13 | 906D171E1CE4BF99001B30EF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906D171D1CE4BF99001B30EF /* ViewController.swift */; }; 14 | 906D17211CE4BF99001B30EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 906D171F1CE4BF99001B30EF /* Main.storyboard */; }; 15 | 906D17231CE4BF99001B30EF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 906D17221CE4BF99001B30EF /* Assets.xcassets */; }; 16 | 906D17261CE4BF99001B30EF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 906D17241CE4BF99001B30EF /* LaunchScreen.storyboard */; }; 17 | 906D17311CE4BF99001B30EF /* DemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906D17301CE4BF99001B30EF /* DemoTests.swift */; }; 18 | 906D173C1CE4BF99001B30EF /* DemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906D173B1CE4BF99001B30EF /* DemoUITests.swift */; }; 19 | D884A08032E51E35936B1A02 /* Pods_DemoUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49C819E087348795B5CBB37E /* Pods_DemoUITests.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 906D172D1CE4BF99001B30EF /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 906D17101CE4BF99001B30EF /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 906D17171CE4BF99001B30EF; 28 | remoteInfo = Demo; 29 | }; 30 | 906D17381CE4BF99001B30EF /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 906D17101CE4BF99001B30EF /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 906D17171CE4BF99001B30EF; 35 | remoteInfo = Demo; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 0A563EA3A78DC7437869051D /* Pods-DemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests.debug.xcconfig"; sourceTree = ""; }; 41 | 1EAF6CDFC38F75E74127F13C /* Pods-Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Demo/Pods-Demo.debug.xcconfig"; sourceTree = ""; }; 42 | 210E8BF35549B671ABAA71B0 /* Pods-DemoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests.release.xcconfig"; sourceTree = ""; }; 43 | 49C819E087348795B5CBB37E /* Pods_DemoUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DemoUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 6CC9EC0297CBB38896C7B7E1 /* Pods-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Demo.release.xcconfig"; path = "Pods/Target Support Files/Pods-Demo/Pods-Demo.release.xcconfig"; sourceTree = ""; }; 45 | 906D17181CE4BF99001B30EF /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 906D171B1CE4BF99001B30EF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | 906D171D1CE4BF99001B30EF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 48 | 906D17201CE4BF99001B30EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 906D17221CE4BF99001B30EF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 906D17251CE4BF99001B30EF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 906D17271CE4BF99001B30EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 906D172C1CE4BF99001B30EF /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 906D17301CE4BF99001B30EF /* DemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoTests.swift; sourceTree = ""; }; 54 | 906D17321CE4BF99001B30EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | 906D17371CE4BF99001B30EF /* DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 906D173B1CE4BF99001B30EF /* DemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoUITests.swift; sourceTree = ""; }; 57 | 906D173D1CE4BF99001B30EF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | AA9251D21C9D91E90A5327F2 /* Pods_DemoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DemoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | CC164385BCFA6CC7DDC9D1F9 /* Pods-DemoUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-DemoUITests/Pods-DemoUITests.debug.xcconfig"; sourceTree = ""; }; 60 | E179559F3B224B0B84374FAE /* Pods-DemoUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DemoUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-DemoUITests/Pods-DemoUITests.release.xcconfig"; sourceTree = ""; }; 61 | E18E2637AAF070943DDCB0A9 /* Pods_Demo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Demo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 906D17151CE4BF99001B30EF /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | 2CBFD961A70C9B1577F0286B /* Pods_Demo.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | 906D17291CE4BF99001B30EF /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | 8C78531E50789A0105B4072D /* Pods_DemoTests.framework in Frameworks */, 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 906D17341CE4BF99001B30EF /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | D884A08032E51E35936B1A02 /* Pods_DemoUITests.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 906D170F1CE4BF99001B30EF = { 93 | isa = PBXGroup; 94 | children = ( 95 | 906D171A1CE4BF99001B30EF /* Demo */, 96 | 906D172F1CE4BF99001B30EF /* DemoTests */, 97 | 906D173A1CE4BF99001B30EF /* DemoUITests */, 98 | 906D17191CE4BF99001B30EF /* Products */, 99 | 99D9B40D95A3F5108647351E /* Pods */, 100 | C545F881EFF8BB3EAD6954EA /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 906D17191CE4BF99001B30EF /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 906D17181CE4BF99001B30EF /* Demo.app */, 108 | 906D172C1CE4BF99001B30EF /* DemoTests.xctest */, 109 | 906D17371CE4BF99001B30EF /* DemoUITests.xctest */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | 906D171A1CE4BF99001B30EF /* Demo */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 906D171B1CE4BF99001B30EF /* AppDelegate.swift */, 118 | 906D171D1CE4BF99001B30EF /* ViewController.swift */, 119 | 906D171F1CE4BF99001B30EF /* Main.storyboard */, 120 | 906D17221CE4BF99001B30EF /* Assets.xcassets */, 121 | 906D17241CE4BF99001B30EF /* LaunchScreen.storyboard */, 122 | 906D17271CE4BF99001B30EF /* Info.plist */, 123 | ); 124 | path = Demo; 125 | sourceTree = ""; 126 | }; 127 | 906D172F1CE4BF99001B30EF /* DemoTests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 906D17301CE4BF99001B30EF /* DemoTests.swift */, 131 | 906D17321CE4BF99001B30EF /* Info.plist */, 132 | ); 133 | path = DemoTests; 134 | sourceTree = ""; 135 | }; 136 | 906D173A1CE4BF99001B30EF /* DemoUITests */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 906D173B1CE4BF99001B30EF /* DemoUITests.swift */, 140 | 906D173D1CE4BF99001B30EF /* Info.plist */, 141 | ); 142 | path = DemoUITests; 143 | sourceTree = ""; 144 | }; 145 | 99D9B40D95A3F5108647351E /* Pods */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 1EAF6CDFC38F75E74127F13C /* Pods-Demo.debug.xcconfig */, 149 | 6CC9EC0297CBB38896C7B7E1 /* Pods-Demo.release.xcconfig */, 150 | 0A563EA3A78DC7437869051D /* Pods-DemoTests.debug.xcconfig */, 151 | 210E8BF35549B671ABAA71B0 /* Pods-DemoTests.release.xcconfig */, 152 | CC164385BCFA6CC7DDC9D1F9 /* Pods-DemoUITests.debug.xcconfig */, 153 | E179559F3B224B0B84374FAE /* Pods-DemoUITests.release.xcconfig */, 154 | ); 155 | name = Pods; 156 | sourceTree = ""; 157 | }; 158 | C545F881EFF8BB3EAD6954EA /* Frameworks */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | E18E2637AAF070943DDCB0A9 /* Pods_Demo.framework */, 162 | AA9251D21C9D91E90A5327F2 /* Pods_DemoTests.framework */, 163 | 49C819E087348795B5CBB37E /* Pods_DemoUITests.framework */, 164 | ); 165 | name = Frameworks; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 906D17171CE4BF99001B30EF /* Demo */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 906D17401CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "Demo" */; 174 | buildPhases = ( 175 | FEB65081904F90F064C377A1 /* 📦 Check Pods Manifest.lock */, 176 | 906D17141CE4BF99001B30EF /* Sources */, 177 | 906D17151CE4BF99001B30EF /* Frameworks */, 178 | 906D17161CE4BF99001B30EF /* Resources */, 179 | 86804F2319AE8F80CD8FFAF9 /* 📦 Embed Pods Frameworks */, 180 | 5F6248DCDFF53F5D059D2D5E /* 📦 Copy Pods Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = Demo; 187 | productName = Demo; 188 | productReference = 906D17181CE4BF99001B30EF /* Demo.app */; 189 | productType = "com.apple.product-type.application"; 190 | }; 191 | 906D172B1CE4BF99001B30EF /* DemoTests */ = { 192 | isa = PBXNativeTarget; 193 | buildConfigurationList = 906D17431CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "DemoTests" */; 194 | buildPhases = ( 195 | 22ADE59AA9009ED6D5DB509B /* 📦 Check Pods Manifest.lock */, 196 | 906D17281CE4BF99001B30EF /* Sources */, 197 | 906D17291CE4BF99001B30EF /* Frameworks */, 198 | 906D172A1CE4BF99001B30EF /* Resources */, 199 | 389F862436469705440E9EC9 /* 📦 Embed Pods Frameworks */, 200 | 6A4D202622A93480BA8D8468 /* 📦 Copy Pods Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | 906D172E1CE4BF99001B30EF /* PBXTargetDependency */, 206 | ); 207 | name = DemoTests; 208 | productName = DemoTests; 209 | productReference = 906D172C1CE4BF99001B30EF /* DemoTests.xctest */; 210 | productType = "com.apple.product-type.bundle.unit-test"; 211 | }; 212 | 906D17361CE4BF99001B30EF /* DemoUITests */ = { 213 | isa = PBXNativeTarget; 214 | buildConfigurationList = 906D17461CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "DemoUITests" */; 215 | buildPhases = ( 216 | D6D4202278DCFD55DD82F020 /* 📦 Check Pods Manifest.lock */, 217 | 906D17331CE4BF99001B30EF /* Sources */, 218 | 906D17341CE4BF99001B30EF /* Frameworks */, 219 | 906D17351CE4BF99001B30EF /* Resources */, 220 | 6A9D1057246E8E96D8E1F3BD /* 📦 Copy Pods Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | 906D17391CE4BF99001B30EF /* PBXTargetDependency */, 226 | ); 227 | name = DemoUITests; 228 | productName = DemoUITests; 229 | productReference = 906D17371CE4BF99001B30EF /* DemoUITests.xctest */; 230 | productType = "com.apple.product-type.bundle.ui-testing"; 231 | }; 232 | /* End PBXNativeTarget section */ 233 | 234 | /* Begin PBXProject section */ 235 | 906D17101CE4BF99001B30EF /* Project object */ = { 236 | isa = PBXProject; 237 | attributes = { 238 | LastSwiftUpdateCheck = 0730; 239 | LastUpgradeCheck = 0730; 240 | ORGANIZATIONNAME = timominous; 241 | TargetAttributes = { 242 | 906D17171CE4BF99001B30EF = { 243 | CreatedOnToolsVersion = 7.3; 244 | }; 245 | 906D172B1CE4BF99001B30EF = { 246 | CreatedOnToolsVersion = 7.3; 247 | TestTargetID = 906D17171CE4BF99001B30EF; 248 | }; 249 | 906D17361CE4BF99001B30EF = { 250 | CreatedOnToolsVersion = 7.3; 251 | TestTargetID = 906D17171CE4BF99001B30EF; 252 | }; 253 | }; 254 | }; 255 | buildConfigurationList = 906D17131CE4BF99001B30EF /* Build configuration list for PBXProject "Demo" */; 256 | compatibilityVersion = "Xcode 3.2"; 257 | developmentRegion = English; 258 | hasScannedForEncodings = 0; 259 | knownRegions = ( 260 | en, 261 | Base, 262 | ); 263 | mainGroup = 906D170F1CE4BF99001B30EF; 264 | productRefGroup = 906D17191CE4BF99001B30EF /* Products */; 265 | projectDirPath = ""; 266 | projectRoot = ""; 267 | targets = ( 268 | 906D17171CE4BF99001B30EF /* Demo */, 269 | 906D172B1CE4BF99001B30EF /* DemoTests */, 270 | 906D17361CE4BF99001B30EF /* DemoUITests */, 271 | ); 272 | }; 273 | /* End PBXProject section */ 274 | 275 | /* Begin PBXResourcesBuildPhase section */ 276 | 906D17161CE4BF99001B30EF /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 906D17261CE4BF99001B30EF /* LaunchScreen.storyboard in Resources */, 281 | 906D17231CE4BF99001B30EF /* Assets.xcassets in Resources */, 282 | 906D17211CE4BF99001B30EF /* Main.storyboard in Resources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 906D172A1CE4BF99001B30EF /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | 906D17351CE4BF99001B30EF /* Resources */ = { 294 | isa = PBXResourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | /* End PBXResourcesBuildPhase section */ 301 | 302 | /* Begin PBXShellScriptBuildPhase section */ 303 | 22ADE59AA9009ED6D5DB509B /* 📦 Check Pods Manifest.lock */ = { 304 | isa = PBXShellScriptBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | ); 308 | inputPaths = ( 309 | ); 310 | name = "📦 Check Pods Manifest.lock"; 311 | outputPaths = ( 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | shellPath = /bin/sh; 315 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 316 | showEnvVarsInLog = 0; 317 | }; 318 | 389F862436469705440E9EC9 /* 📦 Embed Pods Frameworks */ = { 319 | isa = PBXShellScriptBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | ); 323 | inputPaths = ( 324 | ); 325 | name = "📦 Embed Pods Frameworks"; 326 | outputPaths = ( 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | shellPath = /bin/sh; 330 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests-frameworks.sh\"\n"; 331 | showEnvVarsInLog = 0; 332 | }; 333 | 5F6248DCDFF53F5D059D2D5E /* 📦 Copy Pods Resources */ = { 334 | isa = PBXShellScriptBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | ); 338 | inputPaths = ( 339 | ); 340 | name = "📦 Copy Pods Resources"; 341 | outputPaths = ( 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | shellPath = /bin/sh; 345 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Demo/Pods-Demo-resources.sh\"\n"; 346 | showEnvVarsInLog = 0; 347 | }; 348 | 6A4D202622A93480BA8D8468 /* 📦 Copy Pods Resources */ = { 349 | isa = PBXShellScriptBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | ); 353 | inputPaths = ( 354 | ); 355 | name = "📦 Copy Pods Resources"; 356 | outputPaths = ( 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | shellPath = /bin/sh; 360 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DemoTests/Pods-DemoTests-resources.sh\"\n"; 361 | showEnvVarsInLog = 0; 362 | }; 363 | 6A9D1057246E8E96D8E1F3BD /* 📦 Copy Pods Resources */ = { 364 | isa = PBXShellScriptBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | ); 368 | inputPaths = ( 369 | ); 370 | name = "📦 Copy Pods Resources"; 371 | outputPaths = ( 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | shellPath = /bin/sh; 375 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DemoUITests/Pods-DemoUITests-resources.sh\"\n"; 376 | showEnvVarsInLog = 0; 377 | }; 378 | 86804F2319AE8F80CD8FFAF9 /* 📦 Embed Pods Frameworks */ = { 379 | isa = PBXShellScriptBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | ); 383 | inputPaths = ( 384 | ); 385 | name = "📦 Embed Pods Frameworks"; 386 | outputPaths = ( 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | shellPath = /bin/sh; 390 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Demo/Pods-Demo-frameworks.sh\"\n"; 391 | showEnvVarsInLog = 0; 392 | }; 393 | D6D4202278DCFD55DD82F020 /* 📦 Check Pods Manifest.lock */ = { 394 | isa = PBXShellScriptBuildPhase; 395 | buildActionMask = 2147483647; 396 | files = ( 397 | ); 398 | inputPaths = ( 399 | ); 400 | name = "📦 Check Pods Manifest.lock"; 401 | outputPaths = ( 402 | ); 403 | runOnlyForDeploymentPostprocessing = 0; 404 | shellPath = /bin/sh; 405 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 406 | showEnvVarsInLog = 0; 407 | }; 408 | FEB65081904F90F064C377A1 /* 📦 Check Pods Manifest.lock */ = { 409 | isa = PBXShellScriptBuildPhase; 410 | buildActionMask = 2147483647; 411 | files = ( 412 | ); 413 | inputPaths = ( 414 | ); 415 | name = "📦 Check Pods Manifest.lock"; 416 | outputPaths = ( 417 | ); 418 | runOnlyForDeploymentPostprocessing = 0; 419 | shellPath = /bin/sh; 420 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 421 | showEnvVarsInLog = 0; 422 | }; 423 | /* End PBXShellScriptBuildPhase section */ 424 | 425 | /* Begin PBXSourcesBuildPhase section */ 426 | 906D17141CE4BF99001B30EF /* Sources */ = { 427 | isa = PBXSourcesBuildPhase; 428 | buildActionMask = 2147483647; 429 | files = ( 430 | 906D171E1CE4BF99001B30EF /* ViewController.swift in Sources */, 431 | 906D171C1CE4BF99001B30EF /* AppDelegate.swift in Sources */, 432 | ); 433 | runOnlyForDeploymentPostprocessing = 0; 434 | }; 435 | 906D17281CE4BF99001B30EF /* Sources */ = { 436 | isa = PBXSourcesBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | 906D17311CE4BF99001B30EF /* DemoTests.swift in Sources */, 440 | ); 441 | runOnlyForDeploymentPostprocessing = 0; 442 | }; 443 | 906D17331CE4BF99001B30EF /* Sources */ = { 444 | isa = PBXSourcesBuildPhase; 445 | buildActionMask = 2147483647; 446 | files = ( 447 | 906D173C1CE4BF99001B30EF /* DemoUITests.swift in Sources */, 448 | ); 449 | runOnlyForDeploymentPostprocessing = 0; 450 | }; 451 | /* End PBXSourcesBuildPhase section */ 452 | 453 | /* Begin PBXTargetDependency section */ 454 | 906D172E1CE4BF99001B30EF /* PBXTargetDependency */ = { 455 | isa = PBXTargetDependency; 456 | target = 906D17171CE4BF99001B30EF /* Demo */; 457 | targetProxy = 906D172D1CE4BF99001B30EF /* PBXContainerItemProxy */; 458 | }; 459 | 906D17391CE4BF99001B30EF /* PBXTargetDependency */ = { 460 | isa = PBXTargetDependency; 461 | target = 906D17171CE4BF99001B30EF /* Demo */; 462 | targetProxy = 906D17381CE4BF99001B30EF /* PBXContainerItemProxy */; 463 | }; 464 | /* End PBXTargetDependency section */ 465 | 466 | /* Begin PBXVariantGroup section */ 467 | 906D171F1CE4BF99001B30EF /* Main.storyboard */ = { 468 | isa = PBXVariantGroup; 469 | children = ( 470 | 906D17201CE4BF99001B30EF /* Base */, 471 | ); 472 | name = Main.storyboard; 473 | sourceTree = ""; 474 | }; 475 | 906D17241CE4BF99001B30EF /* LaunchScreen.storyboard */ = { 476 | isa = PBXVariantGroup; 477 | children = ( 478 | 906D17251CE4BF99001B30EF /* Base */, 479 | ); 480 | name = LaunchScreen.storyboard; 481 | sourceTree = ""; 482 | }; 483 | /* End PBXVariantGroup section */ 484 | 485 | /* Begin XCBuildConfiguration section */ 486 | 906D173E1CE4BF99001B30EF /* Debug */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ALWAYS_SEARCH_USER_PATHS = NO; 490 | CLANG_ANALYZER_NONNULL = YES; 491 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 492 | CLANG_CXX_LIBRARY = "libc++"; 493 | CLANG_ENABLE_MODULES = YES; 494 | CLANG_ENABLE_OBJC_ARC = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INT_CONVERSION = YES; 501 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 502 | CLANG_WARN_UNREACHABLE_CODE = YES; 503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 504 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 505 | COPY_PHASE_STRIP = NO; 506 | DEBUG_INFORMATION_FORMAT = dwarf; 507 | ENABLE_STRICT_OBJC_MSGSEND = YES; 508 | ENABLE_TESTABILITY = YES; 509 | GCC_C_LANGUAGE_STANDARD = gnu99; 510 | GCC_DYNAMIC_NO_PIC = NO; 511 | GCC_NO_COMMON_BLOCKS = YES; 512 | GCC_OPTIMIZATION_LEVEL = 0; 513 | GCC_PREPROCESSOR_DEFINITIONS = ( 514 | "DEBUG=1", 515 | "$(inherited)", 516 | ); 517 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 518 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 519 | GCC_WARN_UNDECLARED_SELECTOR = YES; 520 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 521 | GCC_WARN_UNUSED_FUNCTION = YES; 522 | GCC_WARN_UNUSED_VARIABLE = YES; 523 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 524 | MTL_ENABLE_DEBUG_INFO = YES; 525 | ONLY_ACTIVE_ARCH = YES; 526 | SDKROOT = iphoneos; 527 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 528 | TARGETED_DEVICE_FAMILY = "1,2"; 529 | }; 530 | name = Debug; 531 | }; 532 | 906D173F1CE4BF99001B30EF /* Release */ = { 533 | isa = XCBuildConfiguration; 534 | buildSettings = { 535 | ALWAYS_SEARCH_USER_PATHS = NO; 536 | CLANG_ANALYZER_NONNULL = YES; 537 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 538 | CLANG_CXX_LIBRARY = "libc++"; 539 | CLANG_ENABLE_MODULES = YES; 540 | CLANG_ENABLE_OBJC_ARC = YES; 541 | CLANG_WARN_BOOL_CONVERSION = YES; 542 | CLANG_WARN_CONSTANT_CONVERSION = YES; 543 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 544 | CLANG_WARN_EMPTY_BODY = YES; 545 | CLANG_WARN_ENUM_CONVERSION = YES; 546 | CLANG_WARN_INT_CONVERSION = YES; 547 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 548 | CLANG_WARN_UNREACHABLE_CODE = YES; 549 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 550 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 551 | COPY_PHASE_STRIP = NO; 552 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 553 | ENABLE_NS_ASSERTIONS = NO; 554 | ENABLE_STRICT_OBJC_MSGSEND = YES; 555 | GCC_C_LANGUAGE_STANDARD = gnu99; 556 | GCC_NO_COMMON_BLOCKS = YES; 557 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 558 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 559 | GCC_WARN_UNDECLARED_SELECTOR = YES; 560 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 561 | GCC_WARN_UNUSED_FUNCTION = YES; 562 | GCC_WARN_UNUSED_VARIABLE = YES; 563 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 564 | MTL_ENABLE_DEBUG_INFO = NO; 565 | SDKROOT = iphoneos; 566 | TARGETED_DEVICE_FAMILY = "1,2"; 567 | VALIDATE_PRODUCT = YES; 568 | }; 569 | name = Release; 570 | }; 571 | 906D17411CE4BF99001B30EF /* Debug */ = { 572 | isa = XCBuildConfiguration; 573 | baseConfigurationReference = 1EAF6CDFC38F75E74127F13C /* Pods-Demo.debug.xcconfig */; 574 | buildSettings = { 575 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 576 | INFOPLIST_FILE = Demo/Info.plist; 577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 578 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.Demo; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | }; 581 | name = Debug; 582 | }; 583 | 906D17421CE4BF99001B30EF /* Release */ = { 584 | isa = XCBuildConfiguration; 585 | baseConfigurationReference = 6CC9EC0297CBB38896C7B7E1 /* Pods-Demo.release.xcconfig */; 586 | buildSettings = { 587 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 588 | INFOPLIST_FILE = Demo/Info.plist; 589 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 590 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.Demo; 591 | PRODUCT_NAME = "$(TARGET_NAME)"; 592 | }; 593 | name = Release; 594 | }; 595 | 906D17441CE4BF99001B30EF /* Debug */ = { 596 | isa = XCBuildConfiguration; 597 | baseConfigurationReference = 0A563EA3A78DC7437869051D /* Pods-DemoTests.debug.xcconfig */; 598 | buildSettings = { 599 | BUNDLE_LOADER = "$(TEST_HOST)"; 600 | INFOPLIST_FILE = DemoTests/Info.plist; 601 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 602 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.DemoTests; 603 | PRODUCT_NAME = "$(TARGET_NAME)"; 604 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 605 | }; 606 | name = Debug; 607 | }; 608 | 906D17451CE4BF99001B30EF /* Release */ = { 609 | isa = XCBuildConfiguration; 610 | baseConfigurationReference = 210E8BF35549B671ABAA71B0 /* Pods-DemoTests.release.xcconfig */; 611 | buildSettings = { 612 | BUNDLE_LOADER = "$(TEST_HOST)"; 613 | INFOPLIST_FILE = DemoTests/Info.plist; 614 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 615 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.DemoTests; 616 | PRODUCT_NAME = "$(TARGET_NAME)"; 617 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 618 | }; 619 | name = Release; 620 | }; 621 | 906D17471CE4BF99001B30EF /* Debug */ = { 622 | isa = XCBuildConfiguration; 623 | baseConfigurationReference = CC164385BCFA6CC7DDC9D1F9 /* Pods-DemoUITests.debug.xcconfig */; 624 | buildSettings = { 625 | INFOPLIST_FILE = DemoUITests/Info.plist; 626 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 627 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.DemoUITests; 628 | PRODUCT_NAME = "$(TARGET_NAME)"; 629 | TEST_TARGET_NAME = Demo; 630 | }; 631 | name = Debug; 632 | }; 633 | 906D17481CE4BF99001B30EF /* Release */ = { 634 | isa = XCBuildConfiguration; 635 | baseConfigurationReference = E179559F3B224B0B84374FAE /* Pods-DemoUITests.release.xcconfig */; 636 | buildSettings = { 637 | INFOPLIST_FILE = DemoUITests/Info.plist; 638 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 639 | PRODUCT_BUNDLE_IDENTIFIER = com.timomnious.DemoUITests; 640 | PRODUCT_NAME = "$(TARGET_NAME)"; 641 | TEST_TARGET_NAME = Demo; 642 | }; 643 | name = Release; 644 | }; 645 | /* End XCBuildConfiguration section */ 646 | 647 | /* Begin XCConfigurationList section */ 648 | 906D17131CE4BF99001B30EF /* Build configuration list for PBXProject "Demo" */ = { 649 | isa = XCConfigurationList; 650 | buildConfigurations = ( 651 | 906D173E1CE4BF99001B30EF /* Debug */, 652 | 906D173F1CE4BF99001B30EF /* Release */, 653 | ); 654 | defaultConfigurationIsVisible = 0; 655 | defaultConfigurationName = Release; 656 | }; 657 | 906D17401CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "Demo" */ = { 658 | isa = XCConfigurationList; 659 | buildConfigurations = ( 660 | 906D17411CE4BF99001B30EF /* Debug */, 661 | 906D17421CE4BF99001B30EF /* Release */, 662 | ); 663 | defaultConfigurationIsVisible = 0; 664 | defaultConfigurationName = Release; 665 | }; 666 | 906D17431CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "DemoTests" */ = { 667 | isa = XCConfigurationList; 668 | buildConfigurations = ( 669 | 906D17441CE4BF99001B30EF /* Debug */, 670 | 906D17451CE4BF99001B30EF /* Release */, 671 | ); 672 | defaultConfigurationIsVisible = 0; 673 | defaultConfigurationName = Release; 674 | }; 675 | 906D17461CE4BF99001B30EF /* Build configuration list for PBXNativeTarget "DemoUITests" */ = { 676 | isa = XCConfigurationList; 677 | buildConfigurations = ( 678 | 906D17471CE4BF99001B30EF /* Debug */, 679 | 906D17481CE4BF99001B30EF /* Release */, 680 | ); 681 | defaultConfigurationIsVisible = 0; 682 | defaultConfigurationName = Release; 683 | }; 684 | /* End XCConfigurationList section */ 685 | }; 686 | rootObject = 906D17101CE4BF99001B30EF /* Project object */; 687 | } 688 | -------------------------------------------------------------------------------- /Sources/StatefulTableView+UITableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulTableView+UITableView.swift 3 | // Pods 4 | // 5 | // Created by Tim on 23/06/2016. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension StatefulTableView { 12 | // MARK: - Configuring a Table View 13 | 14 | /** 15 | Returns the number of rows (table cells) in a specified section. 16 | 17 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/numberOfRowsInSection:) for more details. 18 | */ 19 | public func numberOfRowsInSection(_ section: Int) -> Int { 20 | return tableView.numberOfRows(inSection: section) 21 | } 22 | 23 | /** 24 | The number of sections in the table view. (read-only) 25 | 26 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/numberOfSections) for more details. 27 | */ 28 | public var numberOfSections: Int { 29 | return tableView.numberOfSections 30 | } 31 | 32 | /** 33 | The height of each row (that is, table cell) in the table view. 34 | 35 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/rowHeight) for more details 36 | */ 37 | public var rowHeight: CGFloat { 38 | set { tableView.rowHeight = newValue } 39 | get { return tableView.rowHeight } 40 | } 41 | 42 | /** 43 | The style for table cells used as separators. 44 | 45 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/separatorStyle) for more details. 46 | */ 47 | public var separatorStyle: UITableViewCellSeparatorStyle { 48 | set { tableView.separatorStyle = newValue } 49 | get { return tableView.separatorStyle } 50 | } 51 | 52 | /** 53 | The color of separator rows in the table view. 54 | 55 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/separatorColor) for more details. 56 | */ 57 | public var separatorColor: UIColor? { 58 | set { tableView.separatorColor = newValue } 59 | get { return tableView.separatorColor } 60 | } 61 | 62 | /** 63 | The effect applied to table separators. 64 | 65 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/separatorEffect) for more details. 66 | */ 67 | @available(iOS 8.0, *) 68 | public var separatorEffect: UIVisualEffect? { 69 | set { tableView.separatorEffect = newValue } 70 | get { return tableView.separatorEffect } 71 | } 72 | 73 | /** 74 | Specifies the default inset of cell separators. 75 | 76 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/separatorInset) for more details. 77 | */ 78 | @available(iOS 7.0, *) 79 | public var separatorInset: UIEdgeInsets { 80 | set { tableView.separatorInset = newValue } 81 | get { return tableView.separatorInset } 82 | } 83 | 84 | /** 85 | A Boolean value that indicates whether the cell margins are derived from the width of the readable content guide. 86 | 87 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/cellLayoutMarginsFollowReadableWidth) for more details. 88 | */ 89 | @available(iOS 9.0, *) 90 | public var cellLayoutMarginsFollowReadableWidth: Bool { 91 | set { tableView.cellLayoutMarginsFollowReadableWidth = newValue } 92 | get { return tableView.cellLayoutMarginsFollowReadableWidth } 93 | } 94 | } 95 | 96 | extension StatefulTableView { 97 | // MARK: - Creating Table View Cells 98 | 99 | /** 100 | Registers a nib object containing a cell with the table view under a specified identifier. 101 | 102 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/registerNib:forCellReuseIdentifier:) for more details. 103 | */ 104 | @available(iOS 5.0, *) 105 | public func registerNib(_ nib: UINib?, forCellReuseIdentifier identifier: String) { 106 | tableView.register(nib, forCellReuseIdentifier: identifier) 107 | } 108 | 109 | /** 110 | Registers a class for use in creating new table cells. 111 | 112 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/registerClass:forCellReuseIdentifier:) for more details. 113 | */ 114 | @available(iOS 6.0, *) 115 | public func registerClass(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String) { 116 | tableView.register(cellClass, forCellReuseIdentifier: identifier) 117 | } 118 | 119 | /** 120 | Returns a reusable table-view cell object for the specified reuse identifier and adds it to the table. 121 | 122 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:forIndexPath:) for more details. 123 | */ 124 | @available(iOS 6.0, *) 125 | public func dequeueReusableCellWithIdentifier(_ identifier: String, forIndexPath indexPath: IndexPath) -> UITableViewCell { 126 | return tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) 127 | } 128 | 129 | /** 130 | Returns a reusable table-view cell object located by its identifier. 131 | 132 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:) for more details. 133 | */ 134 | public func dequeueReusableCellWithIdentifier(_ identifier: String) -> UITableViewCell? { 135 | return tableView.dequeueReusableCell(withIdentifier: identifier) 136 | } 137 | } 138 | 139 | extension StatefulTableView { 140 | // MARK: - Accessing Header and Footer Views 141 | 142 | /** 143 | Registers a nib object containing a header or footer with the table view under a specified identifier. 144 | 145 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/registerNib:forHeaderFooterViewReuseIdentifier:) for more details. 146 | */ 147 | @available(iOS 6.0, *) 148 | public func registerNib(_ nib: UINib?, forHeaderFooterViewReuseIdentifier identifier: String) { 149 | tableView.register(nib, forHeaderFooterViewReuseIdentifier: identifier) 150 | } 151 | 152 | /** 153 | Registers a class for use in creating new table header or footer views. 154 | 155 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/registerClass:forHeaderFooterViewReuseIdentifier:) for more details. 156 | */ 157 | @available(iOS 6.0, *) 158 | public func registerClass(_ aClass: AnyClass?, forHeaderFooterViewReuseIdentifier identifier: String) { 159 | tableView.register(aClass, forHeaderFooterViewReuseIdentifier: identifier) 160 | } 161 | 162 | /** 163 | Returns a reusable header or footer view located by its identifier. 164 | 165 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/dequeueReusableHeaderFooterViewWithIdentifier:) for more details. 166 | */ 167 | @available(iOS 6.0, *) 168 | public func dequeueReusableHeaderFooterViewWithIdentifier(_ identifier: String) -> UITableViewHeaderFooterView? { 169 | return tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier) 170 | } 171 | 172 | /** 173 | Returns an accessory view that is displayed above the table. 174 | 175 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/tableHeaderView) for more details. 176 | */ 177 | public var tableHeaderView: UIView? { 178 | set { tableView.tableHeaderView = newValue } 179 | get { return tableView.tableHeaderView } 180 | } 181 | 182 | /** 183 | Returns an accessory view that is displayed below the table. 184 | 185 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/tableFooterView) for more details. 186 | */ 187 | public var tableFooterView: UIView? { 188 | set { tableView.tableFooterView = newValue } 189 | get { return tableView.tableFooterView } 190 | } 191 | 192 | /** 193 | The height of section headers in the table view. 194 | 195 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionHeaderHeight) for more details. 196 | */ 197 | public var sectionHeaderHeight: CGFloat { 198 | set { tableView.sectionHeaderHeight = newValue } 199 | get { return tableView.sectionHeaderHeight } 200 | } 201 | 202 | /** 203 | The height of section footers in the table view. 204 | 205 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionFooterHeight) for more details. 206 | */ 207 | public var sectionFooterHeight: CGFloat { 208 | set { tableView.sectionFooterHeight = newValue } 209 | get { return tableView.sectionFooterHeight } 210 | } 211 | 212 | /** 213 | Returns the header view associated with the specified section. 214 | 215 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/headerViewForSection:) for more details. 216 | */ 217 | @available(iOS 6.0, *) 218 | public func headerViewForSection(_ section: Int) -> UITableViewHeaderFooterView? { 219 | return tableView.headerView(forSection: section) 220 | } 221 | 222 | /** 223 | Returns the footer view associated with the specified section. 224 | 225 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/footerViewForSection:) for more details. 226 | */ 227 | @available(iOS 6.0, *) 228 | public func footerViewForSection(_ section: Int) -> UITableViewHeaderFooterView? { 229 | return tableView.footerView(forSection: section) 230 | } 231 | } 232 | 233 | extension StatefulTableView { 234 | // MARK: - Accessing Cells and Sections 235 | 236 | /** 237 | Returns the table cell at the specified index path. 238 | 239 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/cellForRowAtIndexPath:) for more details. 240 | */ 241 | public func cellForRowAtIndexPath(_ indexPath: IndexPath) -> UITableViewCell? { 242 | return tableView.cellForRow(at: indexPath) 243 | } 244 | 245 | /** 246 | Returns an index path representing the row and section of a given table-view cell. 247 | 248 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/indexPathForCell:) for more details. 249 | */ 250 | public func indexPathForCell(_ cell: UITableViewCell) -> IndexPath? { 251 | return tableView.indexPath(for: cell) 252 | } 253 | 254 | /** 255 | Returns an index path identifying the row and section at the given point. 256 | 257 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/indexPathForRowAtPoint:) for more details. 258 | */ 259 | public func indexPathForRowAtPoint(_ point: CGPoint) -> IndexPath? { 260 | return tableView.indexPathForRow(at: point) 261 | } 262 | 263 | /** 264 | An array of index paths each representing a row enclosed by a given rectangle. 265 | 266 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/indexPathsForRowsInRect:) for more details. 267 | */ 268 | public func indexPathsForRowsInRect(_ rect: CGRect) -> [IndexPath]? { 269 | return tableView.indexPathsForRows(in: rect) 270 | } 271 | 272 | /** 273 | The table cells that are visible in the table view. (read-only) 274 | 275 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/visibleCells) for more details. 276 | */ 277 | public var visibleCells: [UITableViewCell] { 278 | return tableView.visibleCells 279 | } 280 | 281 | /** 282 | An array of index paths each identifying a visible row in the table view. (read-only) 283 | 284 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/indexPathsForVisibleRows) for more details. 285 | */ 286 | public var indexPathsForVisibleRows: [IndexPath]? { 287 | return tableView.indexPathsForVisibleRows; 288 | } 289 | } 290 | 291 | extension StatefulTableView { 292 | // MARK: - Estimating Element Heights 293 | 294 | /** 295 | The estimated height of rows in the table view. 296 | 297 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/estimatedRowHeight) for more details. 298 | */ 299 | @available(iOS 7.0, *) 300 | public var estimatedRowHeight: CGFloat { 301 | set { tableView.estimatedRowHeight = newValue } 302 | get { return tableView.estimatedRowHeight } 303 | } 304 | 305 | /** 306 | The estimated height of section headers in the table view. 307 | 308 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/estimatedSectionHeaderHeight) for more details. 309 | */ 310 | @available(iOS 7.0, *) 311 | public var estimatedSectionHeaderHeight: CGFloat { 312 | set { tableView.estimatedSectionHeaderHeight = newValue } 313 | get { return tableView.estimatedSectionHeaderHeight } 314 | } 315 | 316 | /** 317 | The estimated height of section footers in the table view. 318 | 319 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/estimatedSectionFooterHeight) for more details. 320 | */ 321 | @available(iOS 7.0, *) 322 | public var estimatedSectionFooterHeight: CGFloat { 323 | set { tableView.estimatedSectionFooterHeight = newValue } 324 | get { return tableView.estimatedSectionHeaderHeight } 325 | } 326 | } 327 | 328 | extension StatefulTableView { 329 | // MARK: - Scrolling the Table View 330 | 331 | /** 332 | Scrolls through the table view until a row identified by index path is at a particular location on the screen. 333 | 334 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/scrollToRowAtIndexPath:atScrollPosition:animated:) for more details. 335 | */ 336 | public func scrollToRowAtIndexPath(_ indexPath: IndexPath, atScrollPosition scrollPosition: UITableViewScrollPosition, animated: Bool) { 337 | tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: animated) 338 | } 339 | 340 | /** 341 | Scrolls the table view so that the selected row nearest to a specified position in the table view is at that position. 342 | 343 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/scrollToNearestSelectedRowAtScrollPosition:animated:) for more details. 344 | */ 345 | public func scrollToNearestSelectedRowAtScrollPosition(_ scrollPosition: UITableViewScrollPosition, animated: Bool) { 346 | tableView.scrollToNearestSelectedRow(at: scrollPosition, animated: animated) 347 | } 348 | } 349 | 350 | extension StatefulTableView { 351 | // MARK: - Managing Selections 352 | 353 | /** 354 | An index path identifying the row and section of the selected row. (read-only) 355 | 356 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/indexPathForSelectedRow) for more details. 357 | */ 358 | public var indexPathForSelectedRow: IndexPath? { 359 | return tableView.indexPathForSelectedRow 360 | } 361 | 362 | /** 363 | The index paths representing the selected rows. (read-only) 364 | 365 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/indexPathsForSelectedRows) for more details. 366 | */ 367 | @available(iOS 5.0, *) 368 | public var indexPathsForSelectedRows: [IndexPath]? { 369 | return tableView.indexPathsForSelectedRows 370 | } 371 | 372 | /** 373 | Selects a row in the table view identified by index path, optionally scrolling the row to a location in the table view. 374 | 375 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/selectRowAtIndexPath:animated:scrollPosition:) for more details. 376 | */ 377 | public func selectRowAtIndexPath(_ indexPath: IndexPath?, animated: Bool, scrollPosition: UITableViewScrollPosition) { 378 | tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition) 379 | } 380 | 381 | /** 382 | Deselects a given row identified by index path, with an option to animate the deselection. 383 | 384 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/deselectRowAtIndexPath:animated:) for more details. 385 | */ 386 | public func deselectRowAtIndexPath(_ indexPath: IndexPath, animated: Bool) { 387 | tableView.deselectRow(at: indexPath, animated: animated) 388 | } 389 | 390 | /** 391 | A Boolean value that determines whether users can select a row. 392 | 393 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/allowsSelection) for more details. 394 | */ 395 | @available(iOS 3.0, *) 396 | public var allowsSelection: Bool { 397 | set { tableView.allowsSelection = newValue } 398 | get { return tableView.allowsSelection } 399 | } 400 | 401 | /** 402 | A Boolean value that determines whether users can select more than one row outside of editing mode. 403 | 404 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/allowsMultipleSelectionhttps://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/allowsMultipleSelection) for more details. 405 | */ 406 | @available(iOS 5.0, *) 407 | public var allowsMultipleSelection: Bool { 408 | set { tableView.allowsMultipleSelection = newValue } 409 | get { return tableView.allowsMultipleSelection } 410 | } 411 | 412 | /** 413 | A Boolean value that determines whether users can select cells while the table view is in editing mode. 414 | 415 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/allowsSelectionDuringEditing) for more details. 416 | */ 417 | public var allowsSelectionDuringEditing: Bool { 418 | set { tableView.allowsSelectionDuringEditing = newValue } 419 | get { return tableView.allowsSelectionDuringEditing } 420 | } 421 | 422 | /** 423 | A Boolean value that controls whether users can select more than one cell simultaneously in editing mode. 424 | 425 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/allowsMultipleSelectionDuringEditing) for more details. 426 | */ 427 | @available(iOS 5.0, *) 428 | public var allowsMultipleSelectionDuringEditing: Bool { 429 | set { tableView.allowsMultipleSelectionDuringEditing = newValue } 430 | get { return tableView.allowsMultipleSelectionDuringEditing } 431 | } 432 | } 433 | 434 | extension StatefulTableView { 435 | // MARK: - Inserting, Deleting, and Moving Rows and Sections 436 | 437 | /** 438 | Begins a series of method calls that insert, delete, or select rows and sections of the table view. 439 | 440 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/beginUpdates) for more details. 441 | */ 442 | public func beginUpdates() { 443 | tableView.beginUpdates() 444 | } 445 | 446 | /** 447 | Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. 448 | 449 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/endUpdates) for more details. 450 | */ 451 | public func endUpdates() { 452 | tableView.endUpdates() 453 | } 454 | 455 | /** 456 | Inserts rows in the table view at the locations identified by an array of index paths, with an option to animate the insertion. 457 | 458 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/insertRowsAtIndexPaths:withRowAnimation:) for more details. 459 | */ 460 | public func insertRowsAtIndexPaths(_ indexPaths: [IndexPath], withRowAnimation animation: UITableViewRowAnimation) { 461 | tableView.insertRows(at: indexPaths, with: animation) 462 | } 463 | 464 | /** 465 | Deletes the rows specified by an array of index paths, with an option to animate the deletion. 466 | 467 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/deleteRowsAtIndexPaths:withRowAnimation:) for more details. 468 | */ 469 | public func deleteRowsAtIndexPaths(indexPaths: [IndexPath], withRowAnimation animation: UITableViewRowAnimation) { 470 | tableView.deleteRows(at: indexPaths, with: animation) 471 | } 472 | 473 | /** 474 | Moves the row at a specified location to a destination location. 475 | 476 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/moveRowAtIndexPath:toIndexPath:) for more details. 477 | */ 478 | @available(iOS 5.0, *) 479 | public func moveRowAtIndexPath(_ indexPath: IndexPath, toIndexPath newIndexPath: IndexPath) { 480 | tableView.moveRow(at: indexPath, to: newIndexPath) 481 | } 482 | 483 | /** 484 | Inserts one or more sections in the table view, with an option to animate the insertion. 485 | 486 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/insertSections:withRowAnimation:) for more details. 487 | */ 488 | public func insertSections(_ sections: IndexSet, withRowAnimation animation: UITableViewRowAnimation) { 489 | tableView.insertSections(sections, with: animation) 490 | 491 | } 492 | 493 | /** 494 | Deletes one or more sections in the table view, with an option to animate the deletion. 495 | 496 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/deleteSections:withRowAnimation:) for more details. 497 | */ 498 | public func deleteSections(_ sections: IndexSet, withRowAnimation animation: UITableViewRowAnimation) { 499 | tableView.deleteSections(sections, with: animation) 500 | } 501 | 502 | /** 503 | Moves a section to a new location in the table view. 504 | 505 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/moveSection:toSection:) for more details. 506 | */ 507 | @available(iOS 5.0, *) 508 | public func moveSection(_ section: Int, toSection newSection: Int) { 509 | tableView.moveSection(section, toSection: newSection) 510 | } 511 | } 512 | 513 | extension StatefulTableView { 514 | // MARK: - Managing the Editing of Table Cells 515 | 516 | /** 517 | A Boolean value that determines whether the table view is in editing mode. 518 | 519 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/editing) for more details. 520 | */ 521 | public var editing: Bool { 522 | set { tableView.isEditing = newValue } 523 | get { return tableView.isEditing } 524 | } 525 | 526 | /** 527 | Toggles the table view into and out of editing mode. 528 | 529 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/setEditing:animated:) for more details. 530 | */ 531 | public func setEditing(_ editing: Bool, animated: Bool) { 532 | tableView.setEditing(editing, animated: animated) 533 | } 534 | } 535 | 536 | extension StatefulTableView { 537 | // MARK: - Reloading the Table View 538 | 539 | /** 540 | Reloads the rows and sections of the table view. 541 | 542 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/reloadData) for more details. 543 | */ 544 | public func reloadData() { 545 | DispatchQueue.main.async { 546 | self.tableView.reloadData() 547 | } 548 | } 549 | 550 | /** 551 | Reloads the specified rows using an animation effect. 552 | 553 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/reloadRowsAtIndexPaths:withRowAnimation:) for more details. 554 | */ 555 | @available(iOS 3.0, *) 556 | public func reloadRowsAtIndexPaths(_ indexPaths: [IndexPath], withRowAnimation animation: UITableViewRowAnimation) { 557 | tableView.reloadRows(at: indexPaths, with: animation) 558 | } 559 | 560 | /** 561 | Reloads the specified sections using a given animation effect. 562 | 563 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/reloadSections:withRowAnimation:) for more details. 564 | */ 565 | @available(iOS 3.0, *) 566 | public func reloadSections(_ sections: IndexSet, withRowAnimation animation: UITableViewRowAnimation) { 567 | tableView.reloadSections(sections, with: animation) 568 | } 569 | 570 | /** 571 | Reloads the items in the index bar along the right side of the table view. 572 | 573 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/reloadSectionIndexTitles) for more details. 574 | */ 575 | @available(iOS 3.0, *) 576 | public func reloadSectionIndexTitles() { 577 | DispatchQueue.main.async { 578 | self.tableView.reloadSectionIndexTitles() 579 | } 580 | } 581 | } 582 | 583 | extension StatefulTableView { 584 | // MARK: - Accessing Drawing Areas of the Table View 585 | 586 | /** 587 | Returns the drawing area for a specified section of the table view. 588 | 589 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/rectForSection:) for more details. 590 | */ 591 | public func rectForSection(_ section: Int) -> CGRect { 592 | return tableView.rect(forSection: section) 593 | } 594 | 595 | /** 596 | Returns the drawing area for a row identified by index path. 597 | 598 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/rectForRowAtIndexPath:) for more details. 599 | */ 600 | public func rectForRowRowAtIndexPath(_ indexPath: IndexPath) -> CGRect { 601 | return tableView.rectForRow(at: indexPath) 602 | } 603 | 604 | /** 605 | Returns the drawing area for the footer of the specified section. 606 | 607 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/rectForFooterInSection:) for more details. 608 | */ 609 | public func rectForFooterInSection(_ section: Int) -> CGRect { 610 | return tableView.rectForFooter(inSection: section) 611 | } 612 | 613 | /** 614 | Returns the drawing area for the header of the specified section. 615 | 616 | - Discussion: Visit this [link](Returns the drawing area for the header of the specified section.) for more details. 617 | */ 618 | public func rectFotHeaderInSection(_ section: Int) -> CGRect { 619 | return tableView.rectForHeader(inSection: section) 620 | } 621 | } 622 | 623 | extension StatefulTableView { 624 | // MARK: - Managing the Delegate and the Data Source 625 | 626 | /** 627 | The object that acts as the data source of the table view. 628 | 629 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/dataSource) for more details. 630 | */ 631 | public var dataSource: UITableViewDataSource? { 632 | set { tableView.dataSource = newValue } 633 | get { return tableView.dataSource } 634 | } 635 | 636 | /** 637 | The object that acts as the delegate of the table view. 638 | 639 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/delegate) for more details 640 | */ 641 | public var delegate: UITableViewDelegate? { 642 | set { tableView.delegate = newValue } 643 | get { return tableView.delegate } 644 | } 645 | } 646 | 647 | extension StatefulTableView { 648 | // MARK: - Configuring the Table Index 649 | 650 | /** 651 | The number of table rows at which to display the index list on the right edge of the table. 652 | 653 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionIndexMinimumDisplayRowCount) for more details. 654 | */ 655 | public var sectionIndexMinimumDisplayRowCount: Int { 656 | set { tableView.sectionIndexMinimumDisplayRowCount = newValue } 657 | get { return tableView.sectionIndexMinimumDisplayRowCount } 658 | } 659 | 660 | /** 661 | The color to use for the table view’s index text. 662 | 663 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionIndexColor) for more details. 664 | */ 665 | @available(iOS 6.0, *) 666 | public var sectionIndexColor: UIColor? { 667 | set { tableView.sectionIndexColor = newValue } 668 | get { return tableView.sectionIndexColor } 669 | } 670 | 671 | /** 672 | The color to use for the background of the table view’s section index while not being touched. 673 | 674 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionIndexBackgroundColor) for more details. 675 | */ 676 | @available(iOS 7.0, *) 677 | public var sectionIndexBackgroundColor: UIColor? { 678 | set { tableView.sectionIndexBackgroundColor = newValue } 679 | get { return tableView.sectionIndexBackgroundColor } 680 | } 681 | 682 | /** 683 | The color to use for the table view’s index background area. 684 | 685 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/sectionIndexTrackingBackgroundColor) for more details. 686 | */ 687 | @available(iOS 6.0, *) 688 | public var sectionIndexTrackingBackgroundColor: UIColor? { 689 | set { tableView.sectionIndexTrackingBackgroundColor = newValue } 690 | get { return tableView.sectionIndexTrackingBackgroundColor } 691 | } 692 | } 693 | 694 | extension StatefulTableView { 695 | // MARK: - Managing Focus 696 | 697 | /** 698 | A Boolean value that indicates whether the table view should automatically return the focus to the cell at the last focused index path. 699 | 700 | - Discussion: Visit this [link](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/remembersLastFocusedIndexPath) for more details. 701 | */ 702 | @available(iOS 9.0, *) 703 | public var remembersLastFocusedIndexPath: Bool { 704 | set { tableView.remembersLastFocusedIndexPath = newValue } 705 | get { return tableView.remembersLastFocusedIndexPath } 706 | } 707 | } 708 | --------------------------------------------------------------------------------