├── images ├── custom.png ├── default.png └── image.png ├── SectionIndexViewDemo ├── SectionIndexViewDemo │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── recent.imageset │ │ │ ├── recent@2x.png │ │ │ ├── recent@3x.png │ │ │ └── Contents.json │ │ ├── recent_ind.imageset │ │ │ ├── recent_ind@2x.png │ │ │ ├── recent_ind@3x.png │ │ │ └── Contents.json │ │ ├── recent_sel.imageset │ │ │ ├── recent_sel@2x.png │ │ │ ├── recent_sel@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Person.swift │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AppDelegate.swift │ ├── ViewController.swift │ ├── CusViewController.swift │ └── data.json └── SectionIndexViewDemo.xcodeproj │ ├── project.xcworkspace │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── contents.xcworkspacedata │ ├── xcshareddata │ └── xcschemes │ │ └── SectionIndexViewDemo.xcscheme │ └── project.pbxproj ├── Tests ├── LinuxMain.swift └── SectionIndexViewTests │ ├── XCTestManifests.swift │ └── SectionIndexViewTests.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.swift ├── SectionIndexView.podspec ├── .gitignore ├── README.md └── Sources └── SectionIndexView ├── SectionIndexViewItemIndicator.swift ├── SectionIndexViewItem.swift ├── SectionIndexView.swift └── UITableView+SectionIndexView.swift /images/custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/images/custom.png -------------------------------------------------------------------------------- /images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/images/default.png -------------------------------------------------------------------------------- /images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/images/image.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SectionIndexViewTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SectionIndexViewTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent.imageset/recent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent.imageset/recent@2x.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent.imageset/recent@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent.imageset/recent@3x.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_ind.imageset/recent_ind@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_ind.imageset/recent_ind@2x.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_ind.imageset/recent_ind@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_ind.imageset/recent_ind@3x.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_sel.imageset/recent_sel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_sel.imageset/recent_sel@2x.png -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_sel.imageset/recent_sel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xcj/SectionIndexView/HEAD/SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_sel.imageset/recent_sel@3x.png -------------------------------------------------------------------------------- /Tests/SectionIndexViewTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SectionIndexViewTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "recent@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "recent@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/SectionIndexViewTests/SectionIndexViewTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SectionIndexView 3 | 4 | final class SectionIndexViewTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(SectionIndexView()) 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_ind.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "recent_ind@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "recent_ind@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/recent_sel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "recent_sel@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "recent_sel@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SectionIndexView", 8 | platforms: [ 9 | .iOS(.v10), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "SectionIndexView", 15 | targets: ["SectionIndexView"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "SectionIndexView", 26 | dependencies: []), 27 | .testTarget( 28 | name: "SectionIndexViewTests", 29 | dependencies: ["SectionIndexView"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /SectionIndexView.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint SectionIndexView.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "SectionIndexView" 19 | s.version = "3.0.0" 20 | s.summary = "Customizing the UITableView's section index written in Swift" 21 | s.author = { "ChenJian" => "57150718@qq.com" } 22 | s.homepage = "https://github.com/0xcj/SectionIndexView" 23 | s.framework = "UIKit" 24 | s.source = { :git => "https://github.com/0xcj/SectionIndexView.git", :tag => s.version } 25 | s.source_files = "Sources/SectionIndexView/**/*.{h,m,swift}" 26 | s.license = "MIT" 27 | s.platform = :ios, "10.0" 28 | s.swift_versions = ['4.0', '4.2', '5.0'] 29 | s.requires_arc = true 30 | end 31 | 32 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Person.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | 24 | class Person: NSObject { 25 | let fullName: String 26 | let firstCharacter: String 27 | init(dic: Dictionary) { 28 | self.fullName = dic["FirstNameLastName"] ?? "Bryon Grady" 29 | self.firstCharacter = String(self.fullName.prefix(1)) 30 | super.init() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/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 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/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 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo.xcodeproj/xcshareddata/xcschemes/SectionIndexViewDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | 24 | @UIApplicationMain 25 | class AppDelegate: UIResponder, UIApplicationDelegate { 26 | 27 | var window: UIWindow? 28 | 29 | 30 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 31 | // Override point for customization after application launch. 32 | return true 33 | } 34 | 35 | func applicationWillResignActive(_ application: UIApplication) { 36 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 37 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 38 | } 39 | 40 | func applicationDidEnterBackground(_ application: UIApplication) { 41 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 43 | } 44 | 45 | func applicationWillEnterForeground(_ application: UIApplication) { 46 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 47 | } 48 | 49 | func applicationDidBecomeActive(_ application: UIApplication) { 50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 51 | } 52 | 53 | func applicationWillTerminate(_ application: UIApplication) { 54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 55 | } 56 | 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 | ## Overview 16 | 17 | | default | custom | image | 18 | | ------ | ------ | ------ | 19 | ![default](https://upload-images.jianshu.io/upload_images/11200375-f16dec23eafc0e3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![custom](https://upload-images.jianshu.io/upload_images/11200375-1129a588359d0dca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) | ![image](https://upload-images.jianshu.io/upload_images/11200375-79228d354feac9d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 20 | 21 | ## Installation 22 | ### CocoaPods 23 | ``` 24 | pod 'SectionIndexView' 25 | ``` 26 | #### Swift Package Manager 27 | - File > Swift Packages > Add Package Dependency 28 | `https://github.com/0xcj/SectionIndexView.git` 29 | - Select "Up to Next Major" with "3.0.0" 30 | 31 | ### Manual 32 | Drop the swift files inside of [SectionIndexViewDemo/SectionIndexView](https://github.com/0xcj/SectionIndexView/tree/master/SectionIndexViewDemo/SectionIndexView) into your project. 33 | 34 | ## Usage 35 | 36 | Swift 37 | ```swift 38 | override func viewDidLoad() { 39 | ...... 40 | let titles = ["A","B","C","D","E","F","G"] 41 | let items = titles.compactMap { (title) -> SectionIndexViewItem? in 42 | let item = SectionIndexViewItemView.init() 43 | item.title = title 44 | item.indicator = SectionIndexViewItemIndicator.init(title: title) 45 | return item 46 | } 47 | self.tableView.sectionIndexView(items: items) 48 | } 49 | 50 | ``` 51 | Objective-C 52 | ```objc 53 | - (void)viewDidLoad { 54 | [super viewDidLoad]; 55 | ...... 56 | NSMutableArray*> *items = [[NSMutableArray alloc]init]; 57 | NSArray *titles = @[@"A",@"B",@"C",@"D",@"E",@"F",@"G"]; 58 | for (NSString *title in titles) { 59 | SectionIndexViewItemView *item = [[SectionIndexViewItemView alloc] init]; 60 | item.title = title 61 | item.indicator = [[SectionIndexViewItemIndicator alloc]initWithTitle:title]; 62 | [items addObject:item]; 63 | } 64 | [self.tableView sectionIndexViewWithItems:[NSArray arrayWithArray:items]]; 65 | } 66 | ``` 67 | ## Attention 68 | In order to assure `SectionIndexView` has correct scrolling when your navigationBar not hidden and UITableView use ` contentInsetAdjustmentBehavior` or ` automaticallyAdjustsScrollViewInsets` to adjust content. Set [adjustedContentInset](https://github.com/0xcj/SectionIndexView/blob/master/SectionIndexViewDemo/SectionIndexView/UITableView%2BSectionIndexView.swift) value equal to UITableView’s adjustment content inset 69 | ```swift 70 | override func viewDidLoad() { 71 | ...... 72 | let navigationBarHeight = self.navigationController.navigationBar.frame.height 73 | let statusBarHeight = UIApplication.shared.statusBarFrame.size.height 74 | let frame = CGRect.init(x: 0, y: 0, width: width, height: height) 75 | let tableView = UITableView.init(frame: frame, style: .plain) 76 | let configuration = SectionIndexViewConfiguration.init() 77 | configuration.adjustedContentInset = statusBarHeight + navigationBarHeight 78 | tableView.sectionIndexView(items: items, configuration: configuration) 79 | } 80 | ``` 81 | 82 | If you want to control the UITableView and SectionIndexView manually,you can use it like this. [There is an example.](https://github.com/0xcj/SectionIndexView/blob/master/SectionIndexViewDemo/SectionIndexViewDemo/CusViewController.swift) 83 | ```swift 84 | override func viewDidLoad() { 85 | ...... 86 | let indexView = SectionIndexView.init(frame: frame) 87 | indexView.delegate = self 88 | indexView.dataSource = self 89 | self.view.addSubview(indexView) 90 | } 91 | ``` 92 | Please see the demo for more details. 93 | 94 | ## License 95 | 96 | All source code is licensed under the [License](https://github.com/0xcj/SectionIndexView/blob/master/LICENSE) 97 | 98 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | 24 | class ViewController: UIViewController { 25 | 26 | private let identifier = "cell" 27 | private var dataSource = [(key: String, value: [Person])]() 28 | private lazy var tableView: UITableView = { 29 | let v = UITableView.init(frame: view.frame, style: .plain) 30 | v.showsVerticalScrollIndicator = false 31 | v.register(UITableViewCell.self, forCellReuseIdentifier: identifier) 32 | v.delegate = self 33 | v.dataSource = self 34 | return v 35 | }() 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | title = "SectionIndexView" 40 | self.loadData() 41 | view.addSubview(tableView) 42 | 43 | let items = self.items() 44 | let configuration = SectionIndexViewConfiguration.init() 45 | configuration.adjustedContentInset = UIApplication.shared.statusBarFrame.size.height + 44 46 | tableView.sectionIndexView(items: items, configuration: configuration) 47 | 48 | } 49 | 50 | private func loadData() { 51 | guard let path = Bundle.main.path(forResource: "data.json", ofType: nil), 52 | let url = URL.init(string: "file://" + path), 53 | let data = try? Data.init(contentsOf: url), 54 | let arr = (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? Array>) else { 55 | return 56 | } 57 | self.dataSource = arr.compactMap({Person.init(dic: $0)}).reduce(into: [String: [Person]]()) { 58 | $0[$1.firstCharacter] = ($0[$1.firstCharacter] ?? []) + [$1] 59 | }.sorted { $0.key < $1.key } 60 | } 61 | 62 | private func items() -> [SectionIndexViewItemView] { 63 | var items = [SectionIndexViewItemView]() 64 | for (i, key) in self.dataSource.compactMap({ $0.key }).enumerated() { 65 | let item = SectionIndexViewItemView.init() 66 | if i == 0 { 67 | item.image = UIImage.init(named: "recent") 68 | item.selectedImage = UIImage.init(named: "recent_sel") 69 | let indicator = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: 50, height: 50)) 70 | indicator.image = UIImage.init(named: "recent_ind") 71 | item.indicator = indicator 72 | } else { 73 | item.title = key 74 | item.indicator = SectionIndexViewItemIndicator.init(title: key) 75 | } 76 | items.append(item) 77 | } 78 | return items 79 | } 80 | 81 | override func didReceiveMemoryWarning() { 82 | super.didReceiveMemoryWarning() 83 | // Dispose of any resources that can be recreated. 84 | } 85 | } 86 | 87 | extension ViewController: UITableViewDelegate, UITableViewDataSource { 88 | func numberOfSections(in tableView: UITableView) -> Int { 89 | return self.dataSource.count 90 | } 91 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 92 | return self.dataSource[section].key 93 | } 94 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 95 | return self.dataSource[section].value.count 96 | } 97 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 98 | let cell = tableView.dequeueReusableCell(withIdentifier: self.identifier, for: indexPath) 99 | cell.textLabel?.text = self.dataSource[indexPath.section].value[indexPath.row].fullName 100 | return cell 101 | } 102 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 103 | self.navigationController?.pushViewController(CusViewController.init(), animated: true) 104 | } 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /Sources/SectionIndexView/SectionIndexViewItemIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | 23 | /// ┌─────────────────┐ 24 | /// │ │ 25 | /// │ ┌─┐│ ┌─┐ 26 | /// │ │A ││ │A │ ┌─┐ 27 | /// │ ├─┤│ ├─┤ │ A │-------> Item (SectionIndexViewItem) 28 | /// │ │B ││ │B │ └─┘ 29 | /// │ ├─┤│ ├─┤ 30 | /// │ │C ││ │C │ 31 | /// │ ├─┤│ ├─┤ 32 | /// │ │D ││ │D │ 33 | /// │ ├─┤│ ├─┤ 34 | /// │ │E ││ │E │---------------------------> SectionIndexView 35 | /// │ ┌─┐ ├─┤│ ├─┤ 36 | /// │ │G │ │F ││ │F │ 37 | /// │ └─┘ ├─┤│ ├─┤ 38 | /// │ │ │G ││ │G │ 39 | /// │ │ ├─┤│ ├─┤ 40 | /// │ ⇩ │H ││ │H │ 41 | /// │ Indicator (UIView) ├─┤│ ├─┤ 42 | /// │ │ I ││ │ I │ 43 | /// │ ├─┤│ ├─┤ 44 | /// │ │J ││ │J │ 45 | /// │ ├─┤│ ├─┤ 46 | /// │ │K ││ │K │ 47 | /// │ └─┘│ └─┘ 48 | /// │ │ 49 | /// │ │ 50 | /// │ │ 51 | /// └─────────────────┘ 52 | 53 | #if canImport(UIKit) 54 | 55 | import UIKit 56 | 57 | #endif 58 | 59 | /// SectionIndexViewItemIndicator is a kind of Indicator 60 | /// ┌─┐ 61 | /// │G │ 62 | /// └─┘ 63 | /// │ 64 | /// │ 65 | /// ⇩ 66 | /// Indicator (UIView) 67 | /// 68 | public class SectionIndexViewItemIndicator: UIView { 69 | @objc public var titleColor = UIColor.white { 70 | didSet { titleLabel.textColor = titleColor } 71 | } 72 | 73 | @objc public var titleFont = UIFont.boldSystemFont(ofSize: 35) { 74 | didSet { titleLabel.font = titleFont } 75 | } 76 | 77 | @objc public var indicatorBackgroundColor = #colorLiteral(red: 0.7841793895, green: 0.7883495688, blue: 0.7922672629, alpha: 1) { 78 | didSet { shapeLayer.fillColor = indicatorBackgroundColor.cgColor } 79 | } 80 | 81 | private lazy var titleLabel: UILabel = { 82 | let lab = UILabel.init() 83 | lab.frame = bounds 84 | lab.textColor = titleColor 85 | lab.backgroundColor = .clear 86 | lab.font = UIFont.boldSystemFont(ofSize: 35) 87 | lab.adjustsFontSizeToFitWidth = true 88 | lab.textAlignment = .center 89 | return lab 90 | }() 91 | 92 | private lazy var shapeLayer: CAShapeLayer = { 93 | let x = bounds.width * 0.5 94 | let y = bounds.height * 0.5 95 | let radius = bounds.width * 0.5 96 | let startAngle = CGFloat(Double.pi * 0.25) 97 | let endAngle = CGFloat(Double.pi * 1.75 ) 98 | 99 | let path = UIBezierPath.init(arcCenter: CGPoint.init(x: x, y: y), radius: radius, startAngle:startAngle, endAngle: endAngle, clockwise: true) 100 | 101 | let lineX = x * 2 + bounds.width * 0.2 102 | let lineY = y 103 | path.addLine(to: CGPoint.init(x: lineX, y: lineY)) 104 | path.close() 105 | let shapeLayer = CAShapeLayer.init() 106 | shapeLayer.frame = bounds 107 | shapeLayer.fillColor = indicatorBackgroundColor.cgColor 108 | shapeLayer.path = path.cgPath 109 | return shapeLayer 110 | }() 111 | 112 | @objc public convenience init(title: String) { 113 | let size = CGSize.init(width: 50, height: 50) 114 | self.init(size: size, title: title) 115 | } 116 | 117 | @objc public init(size: CGSize, title: String) { 118 | super.init(frame: CGRect.init(x: 0, y: 0, width: size.width, height: size.height)) 119 | layer.addSublayer(shapeLayer) 120 | addSubview(titleLabel) 121 | titleLabel.text = title 122 | } 123 | 124 | private override init(frame: CGRect) { 125 | fatalError("init(frame:) has not been implemented") 126 | } 127 | 128 | private init() { 129 | fatalError("init has not been implemented") 130 | } 131 | 132 | required internal init?(coder aDecoder: NSCoder) { 133 | fatalError("init(coder:) has not been implemented") 134 | } 135 | } 136 | 137 | 138 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/CusViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | import UIKit 23 | 24 | class CusViewController: UIViewController { 25 | private let identifier = "acell" 26 | private var dataSource = [(key: String, value: [Person])]() 27 | private lazy var tableView: UITableView = { 28 | let v = UITableView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height), style: .plain) 29 | v.showsVerticalScrollIndicator = false 30 | v.register(UITableViewCell.self, forCellReuseIdentifier: identifier) 31 | v.delegate = self 32 | v.dataSource = self 33 | return v 34 | }() 35 | private let adjustedContentInset = UIApplication.shared.statusBarFrame.size.height + 44 36 | private lazy var indexView: SectionIndexView = { 37 | let height = CGFloat(self.dataSource.count * 15) 38 | let frame = CGRect.init(x: view.bounds.width - 20, y: (view.bounds.height - height) * 0.5, width: 20, height: height) 39 | let v = SectionIndexView.init(frame: frame) 40 | v.isItemIndicatorAlwaysInCenterY = true 41 | v.itemIndicatorHorizontalOffset = -130 42 | v.delegate = self 43 | v.dataSource = self 44 | return v 45 | }() 46 | private var isOperated = false 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | self.view.backgroundColor = .white 51 | title = "Custom" 52 | self.loadData() 53 | view.addSubview(tableView) 54 | view.addSubview(indexView) 55 | } 56 | 57 | private func loadData() { 58 | guard let path = Bundle.main.path(forResource: "data.json", ofType: nil), 59 | let url = URL.init(string: "file://" + path), 60 | let data = try? Data.init(contentsOf: url), 61 | let arr = (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? Array>) else { 62 | return 63 | } 64 | self.dataSource = arr.compactMap({Person.init(dic: $0)}).reduce(into: [String: [Person]]()) { 65 | $0[$1.firstCharacter] = ($0[$1.firstCharacter] ?? []) + [$1] 66 | }.sorted { $0.key < $1.key } 67 | } 68 | 69 | override func didReceiveMemoryWarning() { 70 | super.didReceiveMemoryWarning() 71 | } 72 | } 73 | 74 | 75 | extension CusViewController: UITableViewDelegate, UITableViewDataSource { 76 | func numberOfSections(in tableView: UITableView) -> Int { 77 | return self.dataSource.count 78 | } 79 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 80 | return self.dataSource[section].key 81 | } 82 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 83 | return self.dataSource[section].value.count 84 | } 85 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 86 | let cell = tableView.dequeueReusableCell(withIdentifier: self.identifier, for: indexPath) 87 | cell.textLabel?.text = self.dataSource[indexPath.section].value[indexPath.row].fullName 88 | return cell 89 | } 90 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 91 | guard !self.indexView.isTouching else { return } 92 | guard self.isOperated || tableView.isTracking else { return } 93 | guard let visible = tableView.indexPathsForVisibleRows else { return } 94 | guard let start = visible.first?.section, let end = visible.last?.section else { return } 95 | guard let topSection = (start.. Bool { 104 | let rect = tableView.rect(forSection: section) 105 | return tableView.contentOffset.y + self.adjustedContentInset < rect.origin.y + rect.size.height 106 | } 107 | } 108 | 109 | 110 | extension CusViewController: SectionIndexViewDataSource, SectionIndexViewDelegate { 111 | 112 | func numberOfScetions(in sectionIndexView: SectionIndexView) -> Int { 113 | return self.dataSource.count 114 | } 115 | 116 | func sectionIndexView(_ sectionIndexView: SectionIndexView, itemAt section: Int) -> SectionIndexViewItem { 117 | let title = self.dataSource[section].key 118 | return self.item(with: title) 119 | } 120 | 121 | func sectionIndexView(_ sectionIndexView: SectionIndexView, didSelect section: Int) { 122 | sectionIndexView.hideCurrentItemIndicator() 123 | sectionIndexView.deselectCurrentItem() 124 | sectionIndexView.selectItem(at: section) 125 | sectionIndexView.showCurrentItemIndicator() 126 | sectionIndexView.impact() 127 | self.isOperated = true 128 | tableView.panGestureRecognizer.isEnabled = false 129 | if tableView.numberOfRows(inSection: section) > 0 { 130 | tableView.scrollToRow(at: IndexPath.init(row: 0, section: section), at: .top, animated: false) 131 | } else { 132 | tableView.scrollRectToVisible(tableView.rect(forSection: section), animated: false) 133 | } 134 | } 135 | 136 | func sectionIndexViewToucheEnded(_ sectionIndexView: SectionIndexView) { 137 | UIView.animate(withDuration: 0.3) { 138 | sectionIndexView.hideCurrentItemIndicator() 139 | } 140 | self.tableView.panGestureRecognizer.isEnabled = true 141 | } 142 | 143 | private func item(with title: String) -> SectionIndexViewItem { 144 | let item = SectionIndexViewItemView.init() 145 | item.title = title 146 | item.titleSelectedColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 147 | item.selectedColor = #colorLiteral(red: 0, green: 0.5291740298, blue: 1, alpha: 1) 148 | item.indicator = self.indicator(with: title) 149 | return item 150 | } 151 | 152 | private func indicator(with title: String) -> UILabel { 153 | let indicator = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 50, height: 50)) 154 | indicator.text = title 155 | indicator.font = UIFont.boldSystemFont(ofSize: 35) 156 | indicator.adjustsFontSizeToFitWidth = true 157 | indicator.textAlignment = .center 158 | indicator.backgroundColor = .clear 159 | indicator.textColor = #colorLiteral(red: 0, green: 0.5291740298, blue: 1, alpha: 1) 160 | indicator.layer.cornerRadius = 25 161 | indicator.layer.borderWidth = 5 162 | indicator.layer.borderColor = #colorLiteral(red: 0, green: 0.5291740298, blue: 1, alpha: 1) 163 | 164 | return indicator 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Sources/SectionIndexView/SectionIndexViewItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | 23 | /// ┌─────────────────┐ 24 | /// │ │ 25 | /// │ ┌─┐│ ┌─┐ 26 | /// │ │A ││ │A │ ┌─┐ 27 | /// │ ├─┤│ ├─┤ │ A │-------> Item (SectionIndexViewItem) 28 | /// │ │B ││ │B │ └─┘ 29 | /// │ ├─┤│ ├─┤ 30 | /// │ │C ││ │C │ 31 | /// │ ├─┤│ ├─┤ 32 | /// │ │D ││ │D │ 33 | /// │ ├─┤│ ├─┤ 34 | /// │ │E ││ │E │---------------------------> SectionIndexView 35 | /// │ ┌─┐ ├─┤│ ├─┤ 36 | /// │ │G │ │F ││ │F │ 37 | /// │ └─┘ ├─┤│ ├─┤ 38 | /// │ │ │G ││ │G │ 39 | /// │ │ ├─┤│ ├─┤ 40 | /// │ ⇩ │H ││ │H │ 41 | /// │ Indicator (UIView) ├─┤│ ├─┤ 42 | /// │ │ I ││ │ I │ 43 | /// │ ├─┤│ ├─┤ 44 | /// │ │J ││ │J │ 45 | /// │ ├─┤│ ├─┤ 46 | /// │ │K ││ │K │ 47 | /// │ └─┘│ └─┘ 48 | /// │ │ 49 | /// │ │ 50 | /// │ │ 51 | /// └─────────────────┘ 52 | 53 | #if canImport(UIKit) 54 | 55 | import UIKit 56 | 57 | #endif 58 | 59 | 60 | //MARK: - SectionIndexViewItem 61 | @objc public protocol SectionIndexViewItem where Self: UIView { 62 | 63 | /// A Boolean value indicating whether the `Item` is in the selected state. 64 | /// If the item’s `isSelected` were `false`, SectionIndexView would set `true` when touch inside the item’s bounds. 65 | /// If the item’s `isSelected` were `true`, SectionIndexView would set `false` when touch outside the item’s bounds. 66 | var isSelected: Bool { get set } 67 | 68 | /// Item’s indicator. 69 | /// When item is in the selected state, indicator will show, otherwise hide. 70 | var indicator: UIView? { get set } 71 | } 72 | 73 | //MARK: - SectionIndexViewItemView 74 | 75 | /// SectionIndexViewItemView is a kind of SectionIndexViewItem 76 | /// 77 | /// ┌─┐ 78 | /// │ A│-------> Item (SectionIndexViewItem) 79 | /// └─┘ 80 | /// 81 | public class SectionIndexViewItemView: UIView, SectionIndexViewItem { 82 | @objc public var isSelected: Bool = false { 83 | didSet { 84 | self.selectItem(isSelected) 85 | } 86 | } 87 | @objc public var indicator: UIView? 88 | 89 | @objc public var image: UIImage? { 90 | set { imageView.image = newValue } 91 | get { imageView.image } 92 | } 93 | @objc public var selectedImage: UIImage? { 94 | set { imageView.highlightedImage = newValue } 95 | get { imageView.highlightedImage } 96 | } 97 | @objc public var title: String? { 98 | set { titleLabel.text = newValue } 99 | get { titleLabel.text } 100 | } 101 | @objc public var titleFont: UIFont { 102 | set { titleLabel.font = newValue } 103 | get { titleLabel.font } 104 | } 105 | @objc public var titleColor: UIColor { 106 | set { titleLabel.textColor = newValue } 107 | get { titleLabel.textColor } 108 | } 109 | @objc public var titleSelectedColor: UIColor? { 110 | set { titleLabel.highlightedTextColor = newValue } 111 | get { titleLabel.highlightedTextColor } 112 | } 113 | 114 | @objc public var selectedColor: UIColor? { 115 | set { selectedView.backgroundColor = newValue } 116 | get { selectedView.backgroundColor } 117 | } 118 | 119 | private let titleLabel: UILabel = { 120 | let label = UILabel.init() 121 | label.adjustsFontSizeToFitWidth = true 122 | label.numberOfLines = 0 123 | label.textAlignment = .center 124 | label.backgroundColor = .clear 125 | label.textColor = #colorLiteral(red: 0.3005631345, green: 0.3005631345, blue: 0.3005631345, alpha: 1) 126 | label.highlightedTextColor = #colorLiteral(red: 0, green: 0.5291740298, blue: 1, alpha: 1) 127 | label.font = UIFont.boldSystemFont(ofSize: 10) 128 | return label 129 | }() 130 | 131 | private let imageView: UIImageView = { 132 | let v = UIImageView.init() 133 | v.contentMode = .center 134 | return v 135 | }() 136 | 137 | private let selectedView: UIView = { 138 | let v = UIView.init() 139 | v.backgroundColor = .clear 140 | v.isHidden = true 141 | return v 142 | }() 143 | 144 | @objc public required init() { 145 | super.init(frame: .zero) 146 | addSubview(selectedView) 147 | addSubview(imageView) 148 | addSubview(titleLabel) 149 | setLayoutConstraint() 150 | } 151 | 152 | required init?(coder: NSCoder) { 153 | fatalError("init(coder:) has not been implemented") 154 | } 155 | 156 | private func setLayoutConstraint() { 157 | titleLabel.translatesAutoresizingMaskIntoConstraints = false 158 | NSLayoutConstraint.activate([ 159 | titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), 160 | titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor), 161 | titleLabel.heightAnchor.constraint(equalTo: titleLabel.widthAnchor) 162 | ]) 163 | imageView.translatesAutoresizingMaskIntoConstraints = false 164 | NSLayoutConstraint.activate([ 165 | imageView.leadingAnchor.constraint(equalTo: leadingAnchor), 166 | imageView.trailingAnchor.constraint(equalTo: trailingAnchor), 167 | imageView.topAnchor.constraint(equalTo: topAnchor), 168 | imageView.bottomAnchor.constraint(equalTo: bottomAnchor) 169 | ]) 170 | selectedView.translatesAutoresizingMaskIntoConstraints = false 171 | NSLayoutConstraint.activate([ 172 | selectedView.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), 173 | selectedView.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor), 174 | selectedView.topAnchor.constraint(equalTo: titleLabel.topAnchor), 175 | selectedView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor) 176 | ]) 177 | } 178 | 179 | private func selectItem(_ select: Bool) { 180 | if selectedView.layer.cornerRadius == 0, selectedView.bounds.width > 0 { 181 | selectedView.layer.cornerRadius = selectedView.bounds.width * 0.5 182 | } 183 | titleLabel.isHighlighted = select 184 | imageView.isHighlighted = select 185 | selectedView.isHidden = !select 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Sources/SectionIndexView/SectionIndexView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | /// ┌─────────────────┐ 23 | /// │ │ 24 | /// │ ┌─┐│ ┌─┐ 25 | /// │ │A ││ │A │ ┌─┐ 26 | /// │ ├─┤│ ├─┤ │ A │-------> Item (SectionIndexViewItem) 27 | /// │ │B ││ │B │ └─┘ 28 | /// │ ├─┤│ ├─┤ 29 | /// │ │C ││ │C │ 30 | /// │ ├─┤│ ├─┤ 31 | /// │ │D ││ │D │ 32 | /// │ ├─┤│ ├─┤ 33 | /// │ │E ││ │E │---------------------------> SectionIndexView 34 | /// │ ┌─┐ ├─┤│ ├─┤ 35 | /// │ │G │ │F ││ │F │ 36 | /// │ └─┘ ├─┤│ ├─┤ 37 | /// │ │ │G ││ │G │ 38 | /// │ │ ├─┤│ ├─┤ 39 | /// │ ⇩ │H ││ │H │ 40 | /// │ Indicator (UIView) ├─┤│ ├─┤ 41 | /// │ │ I ││ │ I │ 42 | /// │ ├─┤│ ├─┤ 43 | /// │ │J ││ │J │ 44 | /// │ ├─┤│ ├─┤ 45 | /// │ │K ││ │K │ 46 | /// │ └─┘│ └─┘ 47 | /// │ │ 48 | /// │ │ 49 | /// │ │ 50 | /// └─────────────────┘ 51 | 52 | #if canImport(UIKit) 53 | 54 | import UIKit 55 | 56 | #endif 57 | 58 | // MARK: - SectionIndexViewDataSource 59 | 60 | @objc public protocol SectionIndexViewDataSource: NSObjectProtocol { 61 | @objc func numberOfScetions(in sectionIndexView: SectionIndexView) -> Int 62 | @objc func sectionIndexView(_ sectionIndexView: SectionIndexView, itemAt section: Int) -> SectionIndexViewItem 63 | } 64 | 65 | // MARK: - SectionIndexViewDelegate 66 | 67 | @objc public protocol SectionIndexViewDelegate: NSObjectProtocol { 68 | @objc func sectionIndexView(_ sectionIndexView: SectionIndexView, didSelect section: Int) 69 | @objc func sectionIndexViewToucheEnded(_ sectionIndexView: SectionIndexView) 70 | } 71 | 72 | // MARK: - SectionIndexView 73 | 74 | public class SectionIndexView: UIView { 75 | @objc public weak var dataSource: SectionIndexViewDataSource? { didSet { reloadData() } } 76 | @objc public weak var delegate: SectionIndexViewDelegate? 77 | 78 | @objc public var isItemIndicatorAlwaysInCenterY = false 79 | @objc public var itemIndicatorHorizontalOffset: CGFloat = -20 80 | 81 | @objc public private(set) var selectedItem: SectionIndexViewItem? 82 | @objc public private(set) var isTouching = false 83 | 84 | private lazy var generator: UIImpactFeedbackGenerator = { 85 | return UIImpactFeedbackGenerator.init(style: .light) 86 | }() 87 | 88 | private var items = [SectionIndexViewItem]() 89 | 90 | // MARK: - Func 91 | 92 | @objc public func reloadData() { 93 | for item in items { 94 | item.removeFromSuperview() 95 | item.indicator?.removeFromSuperview() 96 | } 97 | items.removeAll() 98 | loadView() 99 | } 100 | 101 | @objc public func item(at section: Int) -> SectionIndexViewItem? { 102 | guard section >= 0, section < items.count else { return nil } 103 | return items[section] 104 | } 105 | 106 | @objc public func impact() { 107 | guard #available(iOS 10.0, *) else { return } 108 | generator.prepare() 109 | generator.impactOccurred() 110 | } 111 | 112 | @objc public func selectItem(at section: Int) { 113 | guard let item = item(at: section) else { return } 114 | item.isSelected = true 115 | selectedItem = item 116 | } 117 | 118 | @objc public func deselectCurrentItem() { 119 | selectedItem?.isSelected = false 120 | selectedItem = nil 121 | } 122 | 123 | @objc public func showCurrentItemIndicator() { 124 | guard let selectedItem = selectedItem, let indicator = selectedItem.indicator else { return } 125 | guard indicator.superview != nil else { 126 | let x = -(indicator.bounds.width * 0.5) + itemIndicatorHorizontalOffset 127 | let y = isItemIndicatorAlwaysInCenterY ? (bounds.height - selectedItem.bounds.height) * 0.5 : selectedItem.center.y 128 | indicator.center = CGPoint(x: x, y: y) 129 | addSubview(indicator) 130 | return 131 | } 132 | indicator.alpha = 1 133 | } 134 | 135 | @objc public func hideCurrentItemIndicator() { 136 | guard let indicator = selectedItem?.indicator else { return } 137 | indicator.alpha = 0 138 | } 139 | 140 | private func loadView() { 141 | guard let dataSource = dataSource else { return } 142 | let numberOfItems = dataSource.numberOfScetions(in: self) 143 | items = Array(0 ..< numberOfItems).compactMap { dataSource.sectionIndexView(self, itemAt: $0) } 144 | setItemsLayoutConstraint() 145 | } 146 | 147 | private func setItemsLayoutConstraint() { 148 | guard !items.isEmpty else { return } 149 | let heightMultiplier = CGFloat(1) / CGFloat(items.count) 150 | for (i, item) in items.enumerated() { 151 | item.translatesAutoresizingMaskIntoConstraints = false 152 | addSubview(item) 153 | let constraints = [ 154 | item.leadingAnchor.constraint(equalTo: leadingAnchor), 155 | item.trailingAnchor.constraint(equalTo: trailingAnchor), 156 | item.heightAnchor.constraint(equalTo: heightAnchor, multiplier: heightMultiplier), 157 | item.topAnchor.constraint(equalTo: i == 0 ? topAnchor : items[i - 1].bottomAnchor), 158 | ] 159 | NSLayoutConstraint.activate(constraints) 160 | } 161 | } 162 | 163 | // MARK: - Touches handle 164 | 165 | private func point(_ point: CGPoint, isIn view: UIView) -> Bool { 166 | return point.y <= (view.frame.origin.y + view.frame.size.height) && point.y >= view.frame.origin.y 167 | } 168 | 169 | private func getSectionBy(_ touches: Set) -> Int? { 170 | guard let touch = touches.first else { return nil } 171 | let p = touch.location(in: self) 172 | return items.enumerated().filter { point(p, isIn: $0.element) }.compactMap { $0.offset }.first 173 | } 174 | 175 | private func touchesOccurred(_ touches: Set) { 176 | isTouching = true 177 | guard let section = getSectionBy(touches) else { return } 178 | guard let item = item(at: section), !(self.selectedItem?.isEqual(item) ?? false) else { return } 179 | delegate?.sectionIndexView(self, didSelect: section) 180 | NotificationCenter.default.post(name: SectionIndexView.touchesEndedNotification, object: self, userInfo: ["section": section]) 181 | } 182 | 183 | private func touchesEnded() { 184 | delegate?.sectionIndexViewToucheEnded(self) 185 | NotificationCenter.default.post(name: SectionIndexView.touchesEndedNotification, object: self) 186 | isTouching = false 187 | } 188 | 189 | // MARK: - UIView TouchesEvent 190 | 191 | override public func touchesBegan(_ touches: Set, with _: UIEvent?) { 192 | touchesOccurred(touches) 193 | } 194 | 195 | override public func touchesMoved(_ touches: Set, with _: UIEvent?) { 196 | touchesOccurred(touches) 197 | } 198 | 199 | override public func touchesEnded(_: Set, with _: UIEvent?) { 200 | touchesEnded() 201 | } 202 | 203 | override public func touchesCancelled(_: Set, with _: UIEvent?) { 204 | touchesEnded() 205 | } 206 | } 207 | 208 | public extension SectionIndexView { 209 | static let touchesOccurredNotification = Notification.Name("SectionIndexViewTouchesOccurredNotification") 210 | static let touchesEndedNotification = Notification.Name("SectionIndexViewTouchesEndedNotification") 211 | } 212 | -------------------------------------------------------------------------------- /Sources/SectionIndexView/UITableView+SectionIndexView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/0xcj/SectionIndexView 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | 23 | 24 | /// ┌─────────────────┐ 25 | /// │ │ 26 | /// │ ┌─┐│ ┌─┐ 27 | /// │ │A ││ │A │ ┌─┐ 28 | /// │ ├─┤│ ├─┤ │ A │-------> Item (SectionIndexViewItem) 29 | /// │ │B ││ │B │ └─┘ 30 | /// │ ├─┤│ ├─┤ 31 | /// │ │C ││ │C │ 32 | /// │ ├─┤│ ├─┤ 33 | /// │ │D ││ │D │ 34 | /// │ ├─┤│ ├─┤ 35 | /// │ │E ││ │E │---------------------------> SectionIndexView 36 | /// │ ┌─┐ ├─┤│ ├─┤ 37 | /// │ │G │ │F ││ │F │ 38 | /// │ └─┘ ├─┤│ ├─┤ 39 | /// │ │ │G ││ │G │ 40 | /// │ │ ├─┤│ ├─┤ 41 | /// │ ⇩ │H ││ │H │ 42 | /// │ Indicator (UIView) ├─┤│ ├─┤ 43 | /// │ │ I ││ │ I │ 44 | /// │ ├─┤│ ├─┤ 45 | /// │ │J ││ │J │ 46 | /// │ ├─┤│ ├─┤ 47 | /// │ │K ││ │K │ 48 | /// │ └─┘│ └─┘ 49 | /// │ │ 50 | /// │ │ 51 | /// │ │ 52 | /// └─────────────────┘ 53 | 54 | #if canImport(UIKit) 55 | 56 | import UIKit 57 | 58 | #endif 59 | 60 | //MARK: - SectionIndexViewConfiguration 61 | public final class SectionIndexViewConfiguration: NSObject { 62 | 63 | /// Configure this property to assure `SectionIndexView` has correct scrolling when your navigationBar not hidden and UITableView use ` contentInsetAdjustmentBehavior` or ` automaticallyAdjustsScrollViewInsets` to adjust content. 64 | /// This value should equal to UITableView’s adjustment content inset. 65 | /// 66 | /// let frame = CGRect.init(x: 0, y: 0, width: width, height: height) 67 | /// let tableView = UITableView.init(frame: frame, style: .plain) 68 | /// 69 | /// let navBarHeight = navigationController.navigationBar.frame.height 70 | /// let statusBarHeight = UIApplication.shared.statusBarFrame.size.height 71 | /// 72 | /// let configuration = SectionIndexViewConfiguration.init() 73 | /// configuration.adjustedContentInset = navBarHeight + statusBarHeight 74 | /// tableView.sectionIndexView(items: items, configuration: configuration) 75 | /// Default is 0. 76 | @objc public var adjustedContentInset: CGFloat = 0 77 | 78 | /// Configure the `item` size. 79 | /// Default is CGSize.init(width: 20, height: 15). 80 | @objc public var itemSize = CGSize.init(width: 20, height: 15) 81 | 82 | /// Configure the` indicator` always in centerY of `SectionIndexView`. 83 | /// Default is false. 84 | @objc public var isItemIndicatorAlwaysInCenterY = false 85 | 86 | /// Configure the `indicator` horizontal offset. 87 | /// Default is -20. 88 | @objc public var itemIndicatorHorizontalOffset: CGFloat = -20 89 | 90 | /// Configure the `SectionIndexView’s` location. 91 | /// Default is UIEdgeInsets.zero. 92 | @objc public var sectionIndexViewOriginInset = UIEdgeInsets.zero 93 | 94 | } 95 | 96 | //MARK: - UITableView Extension 97 | 98 | public extension UITableView { 99 | 100 | /// Set sectionIndexView. 101 | /// - Parameter items: items for sectionIndexView. 102 | @objc func sectionIndexView(items: [SectionIndexViewItem]) { 103 | let configuration = SectionIndexViewConfiguration.init() 104 | self.sectionIndexView(items: items, configuration: configuration) 105 | } 106 | 107 | /// Set sectionIndexView. 108 | /// - Parameters: 109 | /// - items: items for sectionIndexView. 110 | /// - configuration: configuration for sectionIndexView. 111 | @objc func sectionIndexView(items: [SectionIndexViewItem], configuration: SectionIndexViewConfiguration) { 112 | assert(self.superview != nil, "Call this method after setting tableView's superview.") 113 | self.sectionIndexViewManager = SectionIndexViewManager.init(self, items, configuration) 114 | } 115 | } 116 | 117 | private extension UITableView { 118 | private struct SectionIndexViewAssociationKey { 119 | static var manager = "SectionIndexViewAssociationKeyManager" 120 | } 121 | private var sectionIndexViewManager: SectionIndexViewManager? { 122 | set { 123 | objc_setAssociatedObject(self, &(SectionIndexViewAssociationKey.manager), newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 124 | } 125 | get { 126 | return objc_getAssociatedObject(self, &(SectionIndexViewAssociationKey.manager)) as? SectionIndexViewManager 127 | } 128 | } 129 | } 130 | 131 | 132 | //MARK: - SectionIndexViewManager 133 | private class SectionIndexViewManager: NSObject, SectionIndexViewDelegate, SectionIndexViewDataSource { 134 | private struct KVOKey { 135 | static var context = "SectionIndexViewManagerKVOContext" 136 | static var contentOffset = "contentOffset" 137 | } 138 | private var isOperated = false 139 | private weak var tableView: UITableView? 140 | private let indexView: SectionIndexView 141 | private let items: [SectionIndexViewItem] 142 | private let configuration: SectionIndexViewConfiguration 143 | 144 | init(_ tableView: UITableView, _ items: [SectionIndexViewItem], _ configuration: SectionIndexViewConfiguration) { 145 | self.tableView = tableView 146 | self.items = items 147 | self.indexView = SectionIndexView.init() 148 | self.configuration = configuration 149 | self.indexView.isItemIndicatorAlwaysInCenterY = configuration.isItemIndicatorAlwaysInCenterY 150 | self.indexView.itemIndicatorHorizontalOffset = configuration.itemIndicatorHorizontalOffset 151 | super.init() 152 | 153 | indexView.delegate = self 154 | indexView.dataSource = self 155 | self.setLayoutConstraint() 156 | tableView.addObserver(self, forKeyPath: KVOKey.contentOffset, options: .new, context: &KVOKey.context) 157 | } 158 | 159 | deinit { 160 | self.indexView.removeFromSuperview() 161 | self.tableView?.removeObserver(self, forKeyPath: KVOKey.contentOffset) 162 | } 163 | 164 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 165 | guard context == &KVOKey.context, keyPath == KVOKey.contentOffset else { return } 166 | self.tableViewContentOffsetChange() 167 | } 168 | 169 | private func setLayoutConstraint() { 170 | guard let tableView = self.tableView, let superview = tableView.superview else { return } 171 | superview.addSubview(self.indexView) 172 | self.indexView.translatesAutoresizingMaskIntoConstraints = false 173 | let size = CGSize.init(width: self.configuration.itemSize.width, height: self.configuration.itemSize.height * CGFloat(self.items.count)) 174 | let topOffset = self.configuration.sectionIndexViewOriginInset.bottom - self.configuration.sectionIndexViewOriginInset.top 175 | let rightOffset = self.configuration.sectionIndexViewOriginInset.right - self.configuration.sectionIndexViewOriginInset.left 176 | 177 | let constraints = [ 178 | self.indexView.centerYAnchor.constraint(equalTo: tableView.centerYAnchor, constant: topOffset), 179 | self.indexView.widthAnchor.constraint(equalToConstant: size.width), 180 | self.indexView.heightAnchor.constraint(equalToConstant: size.height), 181 | self.indexView.trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: rightOffset) 182 | ] 183 | NSLayoutConstraint.activate(constraints) 184 | } 185 | 186 | private func tableViewContentOffsetChange() { 187 | guard let tableView = self.tableView, !self.indexView.isTouching else { return } 188 | guard self.isOperated || tableView.isTracking else { return } 189 | guard let visible = tableView.indexPathsForVisibleRows else { return } 190 | guard let start = visible.first?.section, let end = visible.last?.section else { return } 191 | guard let topSection = (start.. Bool { 200 | let rect = tableView.rect(forSection: section) 201 | return tableView.contentOffset.y + self.configuration.adjustedContentInset < rect.origin.y + rect.size.height 202 | } 203 | 204 | //MARK: - SectionIndexViewDelegate, SectionIndexViewDataSource 205 | public func numberOfScetions(in sectionIndexView: SectionIndexView) -> Int { 206 | return self.items.count 207 | } 208 | 209 | public func sectionIndexView(_ sectionIndexView: SectionIndexView, itemAt section: Int) -> SectionIndexViewItem { 210 | return self.items[section] 211 | } 212 | 213 | public func sectionIndexView(_ sectionIndexView: SectionIndexView, didSelect section: Int) { 214 | guard let tableView = self.tableView, tableView.numberOfSections > section else { return } 215 | sectionIndexView.hideCurrentItemIndicator() 216 | sectionIndexView.deselectCurrentItem() 217 | sectionIndexView.selectItem(at: section) 218 | sectionIndexView.showCurrentItemIndicator() 219 | sectionIndexView.impact() 220 | self.isOperated = true 221 | tableView.panGestureRecognizer.isEnabled = false 222 | if tableView.numberOfRows(inSection: section) > 0 { 223 | tableView.scrollToRow(at: IndexPath.init(row: 0, section: section), at: .top, animated: false) 224 | } else { 225 | tableView.scrollRectToVisible(tableView.rect(forSection: section), animated: false) 226 | } 227 | } 228 | 229 | public func sectionIndexViewToucheEnded(_ sectionIndexView: SectionIndexView) { 230 | UIView.animate(withDuration: 0.3) { 231 | sectionIndexView.hideCurrentItemIndicator() 232 | } 233 | self.tableView?.panGestureRecognizer.isEnabled = true 234 | } 235 | } 236 | 237 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 077ECC4925A893CD002118E1 /* SectionIndexViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077ECC4525A893CD002118E1 /* SectionIndexViewItem.swift */; }; 11 | 077ECC4A25A893CD002118E1 /* UITableView+SectionIndexView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077ECC4625A893CD002118E1 /* UITableView+SectionIndexView.swift */; }; 12 | 077ECC4B25A893CD002118E1 /* SectionIndexView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077ECC4725A893CD002118E1 /* SectionIndexView.swift */; }; 13 | 077ECC4C25A893CD002118E1 /* SectionIndexViewItemIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077ECC4825A893CD002118E1 /* SectionIndexViewItemIndicator.swift */; }; 14 | 2360484C2438772400642930 /* data.json in Resources */ = {isa = PBXBuildFile; fileRef = 2360484B2438772400642930 /* data.json */; }; 15 | 2363E6B9243747DF009F06DD /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2363E6B8243747DF009F06DD /* Person.swift */; }; 16 | 238D003B2058280500665FD4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238D003A2058280500665FD4 /* AppDelegate.swift */; }; 17 | 238D003D2058280500665FD4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238D003C2058280500665FD4 /* ViewController.swift */; }; 18 | 238D00402058280500665FD4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 238D003E2058280500665FD4 /* Main.storyboard */; }; 19 | 238D00422058280500665FD4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 238D00412058280500665FD4 /* Assets.xcassets */; }; 20 | 238D00452058280500665FD4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 238D00432058280500665FD4 /* LaunchScreen.storyboard */; }; 21 | 2395825D205D344500886433 /* CusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2395825C205D344500886433 /* CusViewController.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 077ECC4525A893CD002118E1 /* SectionIndexViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SectionIndexViewItem.swift; path = ../Sources/SectionIndexView/SectionIndexViewItem.swift; sourceTree = ""; }; 26 | 077ECC4625A893CD002118E1 /* UITableView+SectionIndexView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UITableView+SectionIndexView.swift"; path = "../Sources/SectionIndexView/UITableView+SectionIndexView.swift"; sourceTree = ""; }; 27 | 077ECC4725A893CD002118E1 /* SectionIndexView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SectionIndexView.swift; path = ../Sources/SectionIndexView/SectionIndexView.swift; sourceTree = ""; }; 28 | 077ECC4825A893CD002118E1 /* SectionIndexViewItemIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SectionIndexViewItemIndicator.swift; path = ../Sources/SectionIndexView/SectionIndexViewItemIndicator.swift; sourceTree = ""; }; 29 | 2360484B2438772400642930 /* data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = data.json; sourceTree = ""; }; 30 | 2363E6B8243747DF009F06DD /* Person.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = ""; }; 31 | 238D00372058280500665FD4 /* IndexView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IndexView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 238D003A2058280500665FD4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 238D003C2058280500665FD4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | 238D003F2058280500665FD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | 238D00412058280500665FD4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 238D00442058280500665FD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 238D00462058280500665FD4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 2395825C205D344500886433 /* CusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CusViewController.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 238D00342058280500665FD4 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 077ECC4325A89394002118E1 /* Sources */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 077ECC4725A893CD002118E1 /* SectionIndexView.swift */, 56 | 077ECC4525A893CD002118E1 /* SectionIndexViewItem.swift */, 57 | 077ECC4825A893CD002118E1 /* SectionIndexViewItemIndicator.swift */, 58 | 077ECC4625A893CD002118E1 /* UITableView+SectionIndexView.swift */, 59 | ); 60 | name = Sources; 61 | sourceTree = ""; 62 | }; 63 | 238D002E2058280500665FD4 = { 64 | isa = PBXGroup; 65 | children = ( 66 | 238D00392058280500665FD4 /* SectionIndexViewDemo */, 67 | 077ECC4325A89394002118E1 /* Sources */, 68 | 238D00382058280500665FD4 /* Products */, 69 | ); 70 | sourceTree = ""; 71 | }; 72 | 238D00382058280500665FD4 /* Products */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 238D00372058280500665FD4 /* IndexView.app */, 76 | ); 77 | name = Products; 78 | sourceTree = ""; 79 | }; 80 | 238D00392058280500665FD4 /* SectionIndexViewDemo */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 238D003A2058280500665FD4 /* AppDelegate.swift */, 84 | 238D003C2058280500665FD4 /* ViewController.swift */, 85 | 2395825C205D344500886433 /* CusViewController.swift */, 86 | 2363E6B8243747DF009F06DD /* Person.swift */, 87 | 238D00412058280500665FD4 /* Assets.xcassets */, 88 | 238D003E2058280500665FD4 /* Main.storyboard */, 89 | 238D00432058280500665FD4 /* LaunchScreen.storyboard */, 90 | 238D00462058280500665FD4 /* Info.plist */, 91 | 2360484B2438772400642930 /* data.json */, 92 | ); 93 | path = SectionIndexViewDemo; 94 | sourceTree = ""; 95 | }; 96 | /* End PBXGroup section */ 97 | 98 | /* Begin PBXNativeTarget section */ 99 | 238D00362058280500665FD4 /* SectionIndexViewDemo */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = 238D00492058280500665FD4 /* Build configuration list for PBXNativeTarget "SectionIndexViewDemo" */; 102 | buildPhases = ( 103 | 238D00332058280500665FD4 /* Sources */, 104 | 238D00342058280500665FD4 /* Frameworks */, 105 | 238D00352058280500665FD4 /* Resources */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = SectionIndexViewDemo; 112 | productName = SectionIndexViewDemo; 113 | productReference = 238D00372058280500665FD4 /* IndexView.app */; 114 | productType = "com.apple.product-type.application"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | 238D002F2058280500665FD4 /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | LastSwiftUpdateCheck = 0920; 123 | LastUpgradeCheck = 1010; 124 | ORGANIZATIONNAME = ChenJian; 125 | TargetAttributes = { 126 | 238D00362058280500665FD4 = { 127 | CreatedOnToolsVersion = 9.2; 128 | LastSwiftMigration = 1130; 129 | ProvisioningStyle = Automatic; 130 | }; 131 | }; 132 | }; 133 | buildConfigurationList = 238D00322058280500665FD4 /* Build configuration list for PBXProject "SectionIndexViewDemo" */; 134 | compatibilityVersion = "Xcode 8.0"; 135 | developmentRegion = en; 136 | hasScannedForEncodings = 0; 137 | knownRegions = ( 138 | en, 139 | Base, 140 | ); 141 | mainGroup = 238D002E2058280500665FD4; 142 | productRefGroup = 238D00382058280500665FD4 /* Products */; 143 | projectDirPath = ""; 144 | projectRoot = ""; 145 | targets = ( 146 | 238D00362058280500665FD4 /* SectionIndexViewDemo */, 147 | ); 148 | }; 149 | /* End PBXProject section */ 150 | 151 | /* Begin PBXResourcesBuildPhase section */ 152 | 238D00352058280500665FD4 /* Resources */ = { 153 | isa = PBXResourcesBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | 238D00452058280500665FD4 /* LaunchScreen.storyboard in Resources */, 157 | 238D00422058280500665FD4 /* Assets.xcassets in Resources */, 158 | 238D00402058280500665FD4 /* Main.storyboard in Resources */, 159 | 2360484C2438772400642930 /* data.json in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 238D00332058280500665FD4 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 238D003D2058280500665FD4 /* ViewController.swift in Sources */, 171 | 238D003B2058280500665FD4 /* AppDelegate.swift in Sources */, 172 | 077ECC4C25A893CD002118E1 /* SectionIndexViewItemIndicator.swift in Sources */, 173 | 077ECC4925A893CD002118E1 /* SectionIndexViewItem.swift in Sources */, 174 | 077ECC4A25A893CD002118E1 /* UITableView+SectionIndexView.swift in Sources */, 175 | 2363E6B9243747DF009F06DD /* Person.swift in Sources */, 176 | 077ECC4B25A893CD002118E1 /* SectionIndexView.swift in Sources */, 177 | 2395825D205D344500886433 /* CusViewController.swift in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | 238D003E2058280500665FD4 /* Main.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 238D003F2058280500665FD4 /* Base */, 188 | ); 189 | name = Main.storyboard; 190 | sourceTree = ""; 191 | }; 192 | 238D00432058280500665FD4 /* LaunchScreen.storyboard */ = { 193 | isa = PBXVariantGroup; 194 | children = ( 195 | 238D00442058280500665FD4 /* Base */, 196 | ); 197 | name = LaunchScreen.storyboard; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXVariantGroup section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | 238D00472058280500665FD4 /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 214 | CLANG_WARN_BOOL_CONVERSION = YES; 215 | CLANG_WARN_COMMA = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 219 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 220 | CLANG_WARN_EMPTY_BODY = YES; 221 | CLANG_WARN_ENUM_CONVERSION = YES; 222 | CLANG_WARN_INFINITE_RECURSION = YES; 223 | CLANG_WARN_INT_CONVERSION = YES; 224 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 226 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | CODE_SIGN_IDENTITY = "iPhone Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | DEBUG_INFORMATION_FORMAT = dwarf; 237 | ENABLE_STRICT_OBJC_MSGSEND = YES; 238 | ENABLE_TESTABILITY = YES; 239 | GCC_C_LANGUAGE_STANDARD = gnu11; 240 | GCC_DYNAMIC_NO_PIC = NO; 241 | GCC_NO_COMMON_BLOCKS = YES; 242 | GCC_OPTIMIZATION_LEVEL = 0; 243 | GCC_PREPROCESSOR_DEFINITIONS = ( 244 | "DEBUG=1", 245 | "$(inherited)", 246 | ); 247 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 248 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 249 | GCC_WARN_UNDECLARED_SELECTOR = YES; 250 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 251 | GCC_WARN_UNUSED_FUNCTION = YES; 252 | GCC_WARN_UNUSED_VARIABLE = YES; 253 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 254 | MTL_ENABLE_DEBUG_INFO = YES; 255 | ONLY_ACTIVE_ARCH = YES; 256 | SDKROOT = iphoneos; 257 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 258 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 259 | }; 260 | name = Debug; 261 | }; 262 | 238D00482058280500665FD4 /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_ANALYZER_NONNULL = YES; 267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 269 | CLANG_CXX_LIBRARY = "libc++"; 270 | CLANG_ENABLE_MODULES = YES; 271 | CLANG_ENABLE_OBJC_ARC = YES; 272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_COMMA = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 278 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | CODE_SIGN_IDENTITY = "iPhone Developer"; 294 | COPY_PHASE_STRIP = NO; 295 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 296 | ENABLE_NS_ASSERTIONS = NO; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | GCC_C_LANGUAGE_STANDARD = gnu11; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 307 | MTL_ENABLE_DEBUG_INFO = NO; 308 | SDKROOT = iphoneos; 309 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 310 | VALIDATE_PRODUCT = YES; 311 | }; 312 | name = Release; 313 | }; 314 | 238D004A2058280500665FD4 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 319 | CODE_SIGN_STYLE = Automatic; 320 | DEVELOPMENT_TEAM = 76TH7FM7MJ; 321 | INFOPLIST_FILE = SectionIndexViewDemo/Info.plist; 322 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_BUNDLE_IDENTIFIER = SectionIndexViewDemo; 325 | PRODUCT_NAME = IndexView; 326 | PROVISIONING_PROFILE_SPECIFIER = ""; 327 | SWIFT_VERSION = 5.0; 328 | TARGETED_DEVICE_FAMILY = "1,2"; 329 | }; 330 | name = Debug; 331 | }; 332 | 238D004B2058280500665FD4 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 336 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 337 | CODE_SIGN_STYLE = Automatic; 338 | DEVELOPMENT_TEAM = 76TH7FM7MJ; 339 | INFOPLIST_FILE = SectionIndexViewDemo/Info.plist; 340 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_BUNDLE_IDENTIFIER = SectionIndexViewDemo; 343 | PRODUCT_NAME = IndexView; 344 | PROVISIONING_PROFILE_SPECIFIER = ""; 345 | SWIFT_VERSION = 5.0; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | 238D00322058280500665FD4 /* Build configuration list for PBXProject "SectionIndexViewDemo" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 238D00472058280500665FD4 /* Debug */, 357 | 238D00482058280500665FD4 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | 238D00492058280500665FD4 /* Build configuration list for PBXNativeTarget "SectionIndexViewDemo" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 238D004A2058280500665FD4 /* Debug */, 366 | 238D004B2058280500665FD4 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = 238D002F2058280500665FD4 /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /SectionIndexViewDemo/SectionIndexViewDemo/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ID":"1", 4 | "FirstNameLastName":"Marilyn Plumb" 5 | }, 6 | { 7 | "ID":"2", 8 | "FirstNameLastName":"Keira Evans" 9 | }, 10 | { 11 | "ID":"3", 12 | "FirstNameLastName":"Ronald Eyres" 13 | }, 14 | { 15 | "ID":"4", 16 | "FirstNameLastName":"Callie Raven" 17 | }, 18 | { 19 | "ID":"5", 20 | "FirstNameLastName":"Manuel Potts" 21 | }, 22 | { 23 | "ID":"6", 24 | "FirstNameLastName":"Ciara Sherry" 25 | }, 26 | { 27 | "ID":"7", 28 | "FirstNameLastName":"Mark Weston" 29 | }, 30 | { 31 | "ID":"8", 32 | "FirstNameLastName":"Mark Lyon" 33 | }, 34 | { 35 | "ID":"9", 36 | "FirstNameLastName":"Logan Whitehouse" 37 | }, 38 | { 39 | "ID":"10", 40 | "FirstNameLastName":"Rick Khan" 41 | }, 42 | { 43 | "ID":"11", 44 | "FirstNameLastName":"Nate Price" 45 | }, 46 | { 47 | "ID":"12", 48 | "FirstNameLastName":"Camila Russel" 49 | }, 50 | { 51 | "ID":"13", 52 | "FirstNameLastName":"Sasha Butler" 53 | }, 54 | { 55 | "ID":"14", 56 | "FirstNameLastName":"Leroy Morris" 57 | }, 58 | { 59 | "ID":"15", 60 | "FirstNameLastName":"Bree Mason" 61 | }, 62 | { 63 | "ID":"16", 64 | "FirstNameLastName":"Dorothy Clayton" 65 | }, 66 | { 67 | "ID":"17", 68 | "FirstNameLastName":"Adalie Stuart" 69 | }, 70 | { 71 | "ID":"18", 72 | "FirstNameLastName":"Miley Shields" 73 | }, 74 | { 75 | "ID":"19", 76 | "FirstNameLastName":"Ethan Kerr" 77 | }, 78 | { 79 | "ID":"20", 80 | "FirstNameLastName":"Clarissa Simpson" 81 | }, 82 | { 83 | "ID":"21", 84 | "FirstNameLastName":"Rocco Evans" 85 | }, 86 | { 87 | "ID":"22", 88 | "FirstNameLastName":"Gloria Chester" 89 | }, 90 | { 91 | "ID":"23", 92 | "FirstNameLastName":"Emerald Jones" 93 | }, 94 | { 95 | "ID":"24", 96 | "FirstNameLastName":"Cara Bell" 97 | }, 98 | { 99 | "ID":"25", 100 | "FirstNameLastName":"Benny Nurton" 101 | }, 102 | { 103 | "ID":"26", 104 | "FirstNameLastName":"Leslie Goodman" 105 | }, 106 | { 107 | "ID":"27", 108 | "FirstNameLastName":"Joseph Franks" 109 | }, 110 | { 111 | "ID":"28", 112 | "FirstNameLastName":"Stella Paterson" 113 | }, 114 | { 115 | "ID":"29", 116 | "FirstNameLastName":"Benny Porter" 117 | }, 118 | { 119 | "ID":"30", 120 | "FirstNameLastName":"Greta Jackson" 121 | }, 122 | { 123 | "ID":"31", 124 | "FirstNameLastName":"Mark Poole" 125 | }, 126 | { 127 | "ID":"32", 128 | "FirstNameLastName":"Lucas Kaur" 129 | }, 130 | { 131 | "ID":"33", 132 | "FirstNameLastName":"Ramon Neville" 133 | }, 134 | { 135 | "ID":"34", 136 | "FirstNameLastName":"William Todd" 137 | }, 138 | { 139 | "ID":"35", 140 | "FirstNameLastName":"Erin Neville" 141 | }, 142 | { 143 | "ID":"36", 144 | "FirstNameLastName":"Karla Sinclair" 145 | }, 146 | { 147 | "ID":"37", 148 | "FirstNameLastName":"Rick Cann" 149 | }, 150 | { 151 | "ID":"38", 152 | "FirstNameLastName":"Fiona Duvall" 153 | }, 154 | { 155 | "ID":"39", 156 | "FirstNameLastName":"Daron Yarwood" 157 | }, 158 | { 159 | "ID":"40", 160 | "FirstNameLastName":"Matt Donnelly" 161 | }, 162 | { 163 | "ID":"41", 164 | "FirstNameLastName":"John Tindall" 165 | }, 166 | { 167 | "ID":"42", 168 | "FirstNameLastName":"Liv Carson" 169 | }, 170 | { 171 | "ID":"43", 172 | "FirstNameLastName":"Elisabeth Redwood" 173 | }, 174 | { 175 | "ID":"44", 176 | "FirstNameLastName":"Bart Holmes" 177 | }, 178 | { 179 | "ID":"45", 180 | "FirstNameLastName":"Joseph Clifton" 181 | }, 182 | { 183 | "ID":"46", 184 | "FirstNameLastName":"Gina Gosling" 185 | }, 186 | { 187 | "ID":"47", 188 | "FirstNameLastName":"Parker Donovan" 189 | }, 190 | { 191 | "ID":"48", 192 | "FirstNameLastName":"Brad Denton" 193 | }, 194 | { 195 | "ID":"49", 196 | "FirstNameLastName":"Janelle Hewitt" 197 | }, 198 | { 199 | "ID":"50", 200 | "FirstNameLastName":"Harry Forth" 201 | }, 202 | { 203 | "ID":"51", 204 | "FirstNameLastName":"Nina Exton" 205 | }, 206 | { 207 | "ID":"52", 208 | "FirstNameLastName":"Leanne Gunn" 209 | }, 210 | { 211 | "ID":"53", 212 | "FirstNameLastName":"Shannon Vince" 213 | }, 214 | { 215 | "ID":"54", 216 | "FirstNameLastName":"Gladys Barrett" 217 | }, 218 | { 219 | "ID":"55", 220 | "FirstNameLastName":"Courtney Styles" 221 | }, 222 | { 223 | "ID":"56", 224 | "FirstNameLastName":"Melanie Reading" 225 | }, 226 | { 227 | "ID":"57", 228 | "FirstNameLastName":"Caydence Collis" 229 | }, 230 | { 231 | "ID":"58", 232 | "FirstNameLastName":"Ema Reese" 233 | }, 234 | { 235 | "ID":"59", 236 | "FirstNameLastName":"Kieth Raven" 237 | }, 238 | { 239 | "ID":"60", 240 | "FirstNameLastName":"Ronald Roberts" 241 | }, 242 | { 243 | "ID":"61", 244 | "FirstNameLastName":"Daria Snell" 245 | }, 246 | { 247 | "ID":"62", 248 | "FirstNameLastName":"William Wilde" 249 | }, 250 | { 251 | "ID":"63", 252 | "FirstNameLastName":"Sarah Hope" 253 | }, 254 | { 255 | "ID":"64", 256 | "FirstNameLastName":"Joyce Andrews" 257 | }, 258 | { 259 | "ID":"65", 260 | "FirstNameLastName":"Adalie Emerson" 261 | }, 262 | { 263 | "ID":"66", 264 | "FirstNameLastName":"Lara Edwards" 265 | }, 266 | { 267 | "ID":"67", 268 | "FirstNameLastName":"Phillip Miller" 269 | }, 270 | { 271 | "ID":"68", 272 | "FirstNameLastName":"Matt Callan" 273 | }, 274 | { 275 | "ID":"69", 276 | "FirstNameLastName":"Noemi Randall" 277 | }, 278 | { 279 | "ID":"70", 280 | "FirstNameLastName":"Maggie Brown" 281 | }, 282 | { 283 | "ID":"71", 284 | "FirstNameLastName":"Marvin Knight" 285 | }, 286 | { 287 | "ID":"72", 288 | "FirstNameLastName":"Tony Cameron" 289 | }, 290 | { 291 | "ID":"73", 292 | "FirstNameLastName":"Darlene Alexander" 293 | }, 294 | { 295 | "ID":"74", 296 | "FirstNameLastName":"Jack Whitehouse" 297 | }, 298 | { 299 | "ID":"75", 300 | "FirstNameLastName":"Molly Mcgregor" 301 | }, 302 | { 303 | "ID":"76", 304 | "FirstNameLastName":"Stella Rowan" 305 | }, 306 | { 307 | "ID":"77", 308 | "FirstNameLastName":"Maxwell Cowan" 309 | }, 310 | { 311 | "ID":"78", 312 | "FirstNameLastName":"Fred Devonport" 313 | }, 314 | { 315 | "ID":"79", 316 | "FirstNameLastName":"Barry Crawley" 317 | }, 318 | { 319 | "ID":"80", 320 | "FirstNameLastName":"Barry Ramsey" 321 | }, 322 | { 323 | "ID":"81", 324 | "FirstNameLastName":"Danny Knott" 325 | }, 326 | { 327 | "ID":"82", 328 | "FirstNameLastName":"Elijah Reynolds" 329 | }, 330 | { 331 | "ID":"83", 332 | "FirstNameLastName":"Jade Tyler" 333 | }, 334 | { 335 | "ID":"84", 336 | "FirstNameLastName":"Phillip Coleman" 337 | }, 338 | { 339 | "ID":"85", 340 | "FirstNameLastName":"Hayden Donovan" 341 | }, 342 | { 343 | "ID":"86", 344 | "FirstNameLastName":"Tyson Uttridge" 345 | }, 346 | { 347 | "ID":"87", 348 | "FirstNameLastName":"Gwen Judd" 349 | }, 350 | { 351 | "ID":"88", 352 | "FirstNameLastName":"Wendy Evans" 353 | }, 354 | { 355 | "ID":"89", 356 | "FirstNameLastName":"Analise Poole" 357 | }, 358 | { 359 | "ID":"90", 360 | "FirstNameLastName":"Caleb Santos" 361 | }, 362 | { 363 | "ID":"91", 364 | "FirstNameLastName":"Jules Niles" 365 | }, 366 | { 367 | "ID":"92", 368 | "FirstNameLastName":"Enoch Selby" 369 | }, 370 | { 371 | "ID":"93", 372 | "FirstNameLastName":"Mark Logan" 373 | }, 374 | { 375 | "ID":"94", 376 | "FirstNameLastName":"Trisha Quinnell" 377 | }, 378 | { 379 | "ID":"95", 380 | "FirstNameLastName":"Johnny Andersson" 381 | }, 382 | { 383 | "ID":"96", 384 | "FirstNameLastName":"Samara Harvey" 385 | }, 386 | { 387 | "ID":"97", 388 | "FirstNameLastName":"Sofie Dubois" 389 | }, 390 | { 391 | "ID":"98", 392 | "FirstNameLastName":"Javier Gates" 393 | }, 394 | { 395 | "ID":"99", 396 | "FirstNameLastName":"Aiden Vernon" 397 | }, 398 | { 399 | "ID":"100", 400 | "FirstNameLastName":"Gabriel Parker" 401 | }, 402 | { 403 | "ID":"101", 404 | "FirstNameLastName":"Jolene Bentley" 405 | }, 406 | { 407 | "ID":"102", 408 | "FirstNameLastName":"Tiffany Preston" 409 | }, 410 | { 411 | "ID":"103", 412 | "FirstNameLastName":"Johnathan Kirby" 413 | }, 414 | { 415 | "ID":"104", 416 | "FirstNameLastName":"Ruth Vass" 417 | }, 418 | { 419 | "ID":"105", 420 | "FirstNameLastName":"Penny Speed" 421 | }, 422 | { 423 | "ID":"106", 424 | "FirstNameLastName":"Angelica Jackson" 425 | }, 426 | { 427 | "ID":"107", 428 | "FirstNameLastName":"Jaylene Murphy" 429 | }, 430 | { 431 | "ID":"108", 432 | "FirstNameLastName":"Matt Villiger" 433 | }, 434 | { 435 | "ID":"109", 436 | "FirstNameLastName":"Isla Purvis" 437 | }, 438 | { 439 | "ID":"110", 440 | "FirstNameLastName":"Julian Emmott" 441 | }, 442 | { 443 | "ID":"111", 444 | "FirstNameLastName":"Rufus Wilson" 445 | }, 446 | { 447 | "ID":"112", 448 | "FirstNameLastName":"Shannon Spencer" 449 | }, 450 | { 451 | "ID":"113", 452 | "FirstNameLastName":"Camden Rose" 453 | }, 454 | { 455 | "ID":"114", 456 | "FirstNameLastName":"Erica Drake" 457 | }, 458 | { 459 | "ID":"115", 460 | "FirstNameLastName":"Ilona Mcneill" 461 | }, 462 | { 463 | "ID":"116", 464 | "FirstNameLastName":"Maxwell Calderwood" 465 | }, 466 | { 467 | "ID":"117", 468 | "FirstNameLastName":"Kieth Tait" 469 | }, 470 | { 471 | "ID":"118", 472 | "FirstNameLastName":"Chadwick Connor" 473 | }, 474 | { 475 | "ID":"119", 476 | "FirstNameLastName":"Tony Daniells" 477 | }, 478 | { 479 | "ID":"120", 480 | "FirstNameLastName":"Sydney Wooldridge" 481 | }, 482 | { 483 | "ID":"121", 484 | "FirstNameLastName":"Doug Howard" 485 | }, 486 | { 487 | "ID":"122", 488 | "FirstNameLastName":"Russel Glass" 489 | }, 490 | { 491 | "ID":"123", 492 | "FirstNameLastName":"Sonya Broomfield" 493 | }, 494 | { 495 | "ID":"124", 496 | "FirstNameLastName":"Johnathan Gilmore" 497 | }, 498 | { 499 | "ID":"125", 500 | "FirstNameLastName":"Benjamin Potts" 501 | }, 502 | { 503 | "ID":"126", 504 | "FirstNameLastName":"Rocco Avery" 505 | }, 506 | { 507 | "ID":"127", 508 | "FirstNameLastName":"Aiden Palmer" 509 | }, 510 | { 511 | "ID":"128", 512 | "FirstNameLastName":"Ryan Reese" 513 | }, 514 | { 515 | "ID":"129", 516 | "FirstNameLastName":"Ramon Fall" 517 | }, 518 | { 519 | "ID":"130", 520 | "FirstNameLastName":"Ilona Wheeler" 521 | }, 522 | { 523 | "ID":"131", 524 | "FirstNameLastName":"Danielle Long" 525 | }, 526 | { 527 | "ID":"132", 528 | "FirstNameLastName":"Alessandra Lloyd" 529 | }, 530 | { 531 | "ID":"133", 532 | "FirstNameLastName":"Anthony Clifford" 533 | }, 534 | { 535 | "ID":"134", 536 | "FirstNameLastName":"Stephanie Bishop" 537 | }, 538 | { 539 | "ID":"135", 540 | "FirstNameLastName":"Eden Cattell" 541 | }, 542 | { 543 | "ID":"136", 544 | "FirstNameLastName":"Henry Warden" 545 | }, 546 | { 547 | "ID":"137", 548 | "FirstNameLastName":"Elijah Holt" 549 | }, 550 | { 551 | "ID":"138", 552 | "FirstNameLastName":"Alexia Porter" 553 | }, 554 | { 555 | "ID":"139", 556 | "FirstNameLastName":"Trisha Villiger" 557 | }, 558 | { 559 | "ID":"140", 560 | "FirstNameLastName":"Nick Gaynor" 561 | }, 562 | { 563 | "ID":"141", 564 | "FirstNameLastName":"Nicholas Cooper" 565 | }, 566 | { 567 | "ID":"142", 568 | "FirstNameLastName":"Marvin Webster" 569 | }, 570 | { 571 | "ID":"143", 572 | "FirstNameLastName":"Camden Stewart" 573 | }, 574 | { 575 | "ID":"144", 576 | "FirstNameLastName":"Wade Ballard" 577 | }, 578 | { 579 | "ID":"145", 580 | "FirstNameLastName":"Bryon Vane" 581 | }, 582 | { 583 | "ID":"146", 584 | "FirstNameLastName":"Amy Jacobs" 585 | }, 586 | { 587 | "ID":"147", 588 | "FirstNameLastName":"Raquel Vincent" 589 | }, 590 | { 591 | "ID":"148", 592 | "FirstNameLastName":"Esmeralda Weatcroft" 593 | }, 594 | { 595 | "ID":"149", 596 | "FirstNameLastName":"Crystal Ellwood" 597 | }, 598 | { 599 | "ID":"150", 600 | "FirstNameLastName":"Ada Paterson" 601 | }, 602 | { 603 | "ID":"151", 604 | "FirstNameLastName":"Kirsten Knight" 605 | }, 606 | { 607 | "ID":"152", 608 | "FirstNameLastName":"Benny Thompson" 609 | }, 610 | { 611 | "ID":"153", 612 | "FirstNameLastName":"Rowan Sanchez" 613 | }, 614 | { 615 | "ID":"154", 616 | "FirstNameLastName":"Carol Sheldon" 617 | }, 618 | { 619 | "ID":"155", 620 | "FirstNameLastName":"Martin Jobson" 621 | }, 622 | { 623 | "ID":"156", 624 | "FirstNameLastName":"Lara Tanner" 625 | }, 626 | { 627 | "ID":"157", 628 | "FirstNameLastName":"Noah Sloan" 629 | }, 630 | { 631 | "ID":"158", 632 | "FirstNameLastName":"Gladys Notman" 633 | }, 634 | { 635 | "ID":"159", 636 | "FirstNameLastName":"Maya Atkinson" 637 | }, 638 | { 639 | "ID":"160", 640 | "FirstNameLastName":"Mona Tait" 641 | }, 642 | { 643 | "ID":"161", 644 | "FirstNameLastName":"Lauren Carson" 645 | }, 646 | { 647 | "ID":"162", 648 | "FirstNameLastName":"David Keys" 649 | }, 650 | { 651 | "ID":"163", 652 | "FirstNameLastName":"Lana Rehman" 653 | }, 654 | { 655 | "ID":"164", 656 | "FirstNameLastName":"Rylee Palmer" 657 | }, 658 | { 659 | "ID":"165", 660 | "FirstNameLastName":"Alexa Webster" 661 | }, 662 | { 663 | "ID":"166", 664 | "FirstNameLastName":"Janelle Moore" 665 | }, 666 | { 667 | "ID":"167", 668 | "FirstNameLastName":"Rick Samuel" 669 | }, 670 | { 671 | "ID":"168", 672 | "FirstNameLastName":"Martin Mcleod" 673 | }, 674 | { 675 | "ID":"169", 676 | "FirstNameLastName":"Remy Pierce" 677 | }, 678 | { 679 | "ID":"170", 680 | "FirstNameLastName":"Cara Pickard" 681 | }, 682 | { 683 | "ID":"171", 684 | "FirstNameLastName":"Sebastian Power" 685 | }, 686 | { 687 | "ID":"172", 688 | "FirstNameLastName":"Madelyn Stubbs" 689 | }, 690 | { 691 | "ID":"173", 692 | "FirstNameLastName":"Danny Summers" 693 | }, 694 | { 695 | "ID":"174", 696 | "FirstNameLastName":"Makenzie Jarrett" 697 | }, 698 | { 699 | "ID":"175", 700 | "FirstNameLastName":"Mark Osman" 701 | }, 702 | { 703 | "ID":"176", 704 | "FirstNameLastName":"Mike Price" 705 | }, 706 | { 707 | "ID":"177", 708 | "FirstNameLastName":"Scarlett Kent" 709 | }, 710 | { 711 | "ID":"178", 712 | "FirstNameLastName":"Mason Shaw" 713 | }, 714 | { 715 | "ID":"179", 716 | "FirstNameLastName":"Tyler Barrett" 717 | }, 718 | { 719 | "ID":"180", 720 | "FirstNameLastName":"Ema Curtis" 721 | }, 722 | { 723 | "ID":"181", 724 | "FirstNameLastName":"Harry Blackburn" 725 | }, 726 | { 727 | "ID":"182", 728 | "FirstNameLastName":"Alessia Tyrrell" 729 | }, 730 | { 731 | "ID":"183", 732 | "FirstNameLastName":"Percy Matthews" 733 | }, 734 | { 735 | "ID":"184", 736 | "FirstNameLastName":"Wendy Mills" 737 | }, 738 | { 739 | "ID":"185", 740 | "FirstNameLastName":"Alexander Robinson" 741 | }, 742 | { 743 | "ID":"186", 744 | "FirstNameLastName":"Rufus Yoman" 745 | }, 746 | { 747 | "ID":"187", 748 | "FirstNameLastName":"Darlene Mitchell" 749 | }, 750 | { 751 | "ID":"188", 752 | "FirstNameLastName":"Bridget Varley" 753 | }, 754 | { 755 | "ID":"189", 756 | "FirstNameLastName":"Marilyn Saunders" 757 | }, 758 | { 759 | "ID":"190", 760 | "FirstNameLastName":"Nate Thomson" 761 | }, 762 | { 763 | "ID":"191", 764 | "FirstNameLastName":"Willow Rowe" 765 | }, 766 | { 767 | "ID":"192", 768 | "FirstNameLastName":"Jacob Allcott" 769 | }, 770 | { 771 | "ID":"193", 772 | "FirstNameLastName":"Nate Thomson" 773 | }, 774 | { 775 | "ID":"194", 776 | "FirstNameLastName":"Carmen Paterson" 777 | }, 778 | { 779 | "ID":"195", 780 | "FirstNameLastName":"Rufus Abbey" 781 | }, 782 | { 783 | "ID":"196", 784 | "FirstNameLastName":"Cara Booth" 785 | }, 786 | { 787 | "ID":"197", 788 | "FirstNameLastName":"Jessica Simpson" 789 | }, 790 | { 791 | "ID":"198", 792 | "FirstNameLastName":"Joseph James" 793 | }, 794 | { 795 | "ID":"199", 796 | "FirstNameLastName":"Enoch Dillon" 797 | }, 798 | { 799 | "ID":"200", 800 | "FirstNameLastName":"Analise Cattell" 801 | }, 802 | { 803 | "ID":"201", 804 | "FirstNameLastName":"Ellen Rehman" 805 | }, 806 | { 807 | "ID":"202", 808 | "FirstNameLastName":"Adalind Wilton" 809 | }, 810 | { 811 | "ID":"203", 812 | "FirstNameLastName":"John Thompson" 813 | }, 814 | { 815 | "ID":"204", 816 | "FirstNameLastName":"Maxwell Ralph" 817 | }, 818 | { 819 | "ID":"205", 820 | "FirstNameLastName":"Judith Asher" 821 | }, 822 | { 823 | "ID":"206", 824 | "FirstNameLastName":"George Lakey" 825 | }, 826 | { 827 | "ID":"207", 828 | "FirstNameLastName":"Sloane Owen" 829 | }, 830 | { 831 | "ID":"208", 832 | "FirstNameLastName":"Aileen Anderson" 833 | }, 834 | { 835 | "ID":"209", 836 | "FirstNameLastName":"Ally Thomson" 837 | }, 838 | { 839 | "ID":"210", 840 | "FirstNameLastName":"Liam Ross" 841 | }, 842 | { 843 | "ID":"211", 844 | "FirstNameLastName":"Ramon Harrington" 845 | }, 846 | { 847 | "ID":"212", 848 | "FirstNameLastName":"Rufus Keys" 849 | }, 850 | { 851 | "ID":"213", 852 | "FirstNameLastName":"Alex Watson" 853 | }, 854 | { 855 | "ID":"214", 856 | "FirstNameLastName":"Abdul Lane" 857 | }, 858 | { 859 | "ID":"215", 860 | "FirstNameLastName":"Cherish Russell" 861 | }, 862 | { 863 | "ID":"216", 864 | "FirstNameLastName":"John Daniells" 865 | }, 866 | { 867 | "ID":"217", 868 | "FirstNameLastName":"Ronald Ianson" 869 | }, 870 | { 871 | "ID":"218", 872 | "FirstNameLastName":"Margaret Callan" 873 | }, 874 | { 875 | "ID":"219", 876 | "FirstNameLastName":"Harry Willis" 877 | }, 878 | { 879 | "ID":"220", 880 | "FirstNameLastName":"Tony Willson" 881 | }, 882 | { 883 | "ID":"221", 884 | "FirstNameLastName":"Bridget Gibson" 885 | }, 886 | { 887 | "ID":"222", 888 | "FirstNameLastName":"Barney Jenkins" 889 | }, 890 | { 891 | "ID":"223", 892 | "FirstNameLastName":"Tom Denton" 893 | }, 894 | { 895 | "ID":"224", 896 | "FirstNameLastName":"Jack Reese" 897 | }, 898 | { 899 | "ID":"225", 900 | "FirstNameLastName":"Maria Leigh" 901 | }, 902 | { 903 | "ID":"226", 904 | "FirstNameLastName":"Chadwick Lambert" 905 | }, 906 | { 907 | "ID":"227", 908 | "FirstNameLastName":"Harry Baxter" 909 | }, 910 | { 911 | "ID":"228", 912 | "FirstNameLastName":"Julian Faulkner" 913 | }, 914 | { 915 | "ID":"229", 916 | "FirstNameLastName":"Alexia Watt" 917 | }, 918 | { 919 | "ID":"230", 920 | "FirstNameLastName":"Harmony Holmes" 921 | }, 922 | { 923 | "ID":"231", 924 | "FirstNameLastName":"Elijah Phillips" 925 | }, 926 | { 927 | "ID":"232", 928 | "FirstNameLastName":"Rick Scott" 929 | }, 930 | { 931 | "ID":"233", 932 | "FirstNameLastName":"Clint Hamilton" 933 | }, 934 | { 935 | "ID":"234", 936 | "FirstNameLastName":"Bryon Curtis" 937 | }, 938 | { 939 | "ID":"235", 940 | "FirstNameLastName":"Anthony Fox" 941 | }, 942 | { 943 | "ID":"236", 944 | "FirstNameLastName":"Jacob Bell" 945 | }, 946 | { 947 | "ID":"237", 948 | "FirstNameLastName":"Gwenyth Willson" 949 | }, 950 | { 951 | "ID":"238", 952 | "FirstNameLastName":"Carla Lloyd" 953 | }, 954 | { 955 | "ID":"239", 956 | "FirstNameLastName":"Benjamin Lynn" 957 | }, 958 | { 959 | "ID":"240", 960 | "FirstNameLastName":"Claire Tutton" 961 | }, 962 | { 963 | "ID":"241", 964 | "FirstNameLastName":"Manuel Shields" 965 | }, 966 | { 967 | "ID":"242", 968 | "FirstNameLastName":"Brad Ventura" 969 | }, 970 | { 971 | "ID":"243", 972 | "FirstNameLastName":"Nick Dunbar" 973 | }, 974 | { 975 | "ID":"244", 976 | "FirstNameLastName":"Martin Harvey" 977 | }, 978 | { 979 | "ID":"245", 980 | "FirstNameLastName":"Gwen Lee" 981 | }, 982 | { 983 | "ID":"246", 984 | "FirstNameLastName":"Nate Rehman" 985 | }, 986 | { 987 | "ID":"247", 988 | "FirstNameLastName":"Vicky Potts" 989 | }, 990 | { 991 | "ID":"248", 992 | "FirstNameLastName":"Kurt Coleman" 993 | }, 994 | { 995 | "ID":"249", 996 | "FirstNameLastName":"Alexa Lloyd" 997 | }, 998 | { 999 | "ID":"250", 1000 | "FirstNameLastName":"Amelia Mcgee" 1001 | }, 1002 | { 1003 | "ID":"251", 1004 | "FirstNameLastName":"Doug Rodgers" 1005 | }, 1006 | { 1007 | "ID":"252", 1008 | "FirstNameLastName":"Aleksandra Ogilvy" 1009 | }, 1010 | { 1011 | "ID":"253", 1012 | "FirstNameLastName":"Kenzie Button" 1013 | }, 1014 | { 1015 | "ID":"254", 1016 | "FirstNameLastName":"Chad Holmes" 1017 | }, 1018 | { 1019 | "ID":"255", 1020 | "FirstNameLastName":"Chris Williams" 1021 | }, 1022 | { 1023 | "ID":"256", 1024 | "FirstNameLastName":"Nate Whitehouse" 1025 | }, 1026 | { 1027 | "ID":"257", 1028 | "FirstNameLastName":"Grace Palmer" 1029 | }, 1030 | { 1031 | "ID":"258", 1032 | "FirstNameLastName":"Elijah Mason" 1033 | }, 1034 | { 1035 | "ID":"259", 1036 | "FirstNameLastName":"Jayden Russell" 1037 | }, 1038 | { 1039 | "ID":"260", 1040 | "FirstNameLastName":"Sydney Hardwick" 1041 | }, 1042 | { 1043 | "ID":"261", 1044 | "FirstNameLastName":"Piper Tait" 1045 | }, 1046 | { 1047 | "ID":"262", 1048 | "FirstNameLastName":"Danny Flynn" 1049 | }, 1050 | { 1051 | "ID":"263", 1052 | "FirstNameLastName":"Evelynn Cunningham" 1053 | }, 1054 | { 1055 | "ID":"264", 1056 | "FirstNameLastName":"Rosa Gilbert" 1057 | }, 1058 | { 1059 | "ID":"265", 1060 | "FirstNameLastName":"Maggie Brown" 1061 | }, 1062 | { 1063 | "ID":"266", 1064 | "FirstNameLastName":"Henry Holt" 1065 | }, 1066 | { 1067 | "ID":"267", 1068 | "FirstNameLastName":"Jayden Thomas" 1069 | }, 1070 | { 1071 | "ID":"268", 1072 | "FirstNameLastName":"Ema James" 1073 | }, 1074 | { 1075 | "ID":"269", 1076 | "FirstNameLastName":"Scarlett Stone" 1077 | }, 1078 | { 1079 | "ID":"270", 1080 | "FirstNameLastName":"William Rose" 1081 | }, 1082 | { 1083 | "ID":"271", 1084 | "FirstNameLastName":"William Antcliff" 1085 | }, 1086 | { 1087 | "ID":"272", 1088 | "FirstNameLastName":"Marla Andersson" 1089 | }, 1090 | { 1091 | "ID":"273", 1092 | "FirstNameLastName":"Wade Hall" 1093 | }, 1094 | { 1095 | "ID":"274", 1096 | "FirstNameLastName":"Destiny Cork" 1097 | }, 1098 | { 1099 | "ID":"275", 1100 | "FirstNameLastName":"Joy Johnson" 1101 | }, 1102 | { 1103 | "ID":"276", 1104 | "FirstNameLastName":"Gemma Victor" 1105 | }, 1106 | { 1107 | "ID":"277", 1108 | "FirstNameLastName":"Elijah Goldsmith" 1109 | }, 1110 | { 1111 | "ID":"278", 1112 | "FirstNameLastName":"Estrella Stuart" 1113 | }, 1114 | { 1115 | "ID":"279", 1116 | "FirstNameLastName":"Danielle Lindsay" 1117 | }, 1118 | { 1119 | "ID":"280", 1120 | "FirstNameLastName":"Rae Truscott" 1121 | }, 1122 | { 1123 | "ID":"281", 1124 | "FirstNameLastName":"Mina Kelly" 1125 | }, 1126 | { 1127 | "ID":"282", 1128 | "FirstNameLastName":"Nate Veale" 1129 | }, 1130 | { 1131 | "ID":"283", 1132 | "FirstNameLastName":"Alice Dyson" 1133 | }, 1134 | { 1135 | "ID":"284", 1136 | "FirstNameLastName":"Iris Rowe" 1137 | }, 1138 | { 1139 | "ID":"285", 1140 | "FirstNameLastName":"Fred Bailey" 1141 | }, 1142 | { 1143 | "ID":"286", 1144 | "FirstNameLastName":"Beatrice Middleton" 1145 | }, 1146 | { 1147 | "ID":"287", 1148 | "FirstNameLastName":"Oliver Nielson" 1149 | }, 1150 | { 1151 | "ID":"288", 1152 | "FirstNameLastName":"Adalind Sloan" 1153 | }, 1154 | { 1155 | "ID":"289", 1156 | "FirstNameLastName":"Carmella Owen" 1157 | }, 1158 | { 1159 | "ID":"290", 1160 | "FirstNameLastName":"Rosalyn Vaughn" 1161 | }, 1162 | { 1163 | "ID":"291", 1164 | "FirstNameLastName":"Ryan Chapman" 1165 | }, 1166 | { 1167 | "ID":"292", 1168 | "FirstNameLastName":"Eduardo Harrison" 1169 | }, 1170 | { 1171 | "ID":"293", 1172 | "FirstNameLastName":"Erick Varndell" 1173 | }, 1174 | { 1175 | "ID":"294", 1176 | "FirstNameLastName":"Mark Corbett" 1177 | }, 1178 | { 1179 | "ID":"295", 1180 | "FirstNameLastName":"Sonya Pope" 1181 | }, 1182 | { 1183 | "ID":"296", 1184 | "FirstNameLastName":"Wade Jackson" 1185 | }, 1186 | { 1187 | "ID":"297", 1188 | "FirstNameLastName":"Ramon Gibbons" 1189 | }, 1190 | { 1191 | "ID":"298", 1192 | "FirstNameLastName":"Ramon Clark" 1193 | }, 1194 | { 1195 | "ID":"299", 1196 | "FirstNameLastName":"Lucas Allington" 1197 | }, 1198 | { 1199 | "ID":"300", 1200 | "FirstNameLastName":"Wendy Wright" 1201 | } 1202 | ] 1203 | --------------------------------------------------------------------------------