) {
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 |  |  | 
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 |
--------------------------------------------------------------------------------