如何使用Swift 3/4制作自定义UITableView索引栏或扩展索引栏占满屏幕
Hey Marco, let's tackle your UITableView index bar needs—either expanding it to full screen or building a custom version that fits your layout. The default system index bar is restricted by Apple's private components, so we'll focus on a reliable custom solution that gives you full control, while aligning with your existing code logic.
1. Replace Default Index with a Full-Screen Custom Index
Since we can't directly modify the system index bar's height to cover the entire screen, we'll hide the default one and build our own. Here's how to integrate it with your existing code:
Step 1: Update Your Section Index Method
First, disable the system index bar by returning nil in your existing method:
override func sectionIndexTitles(for tableView: UITableView) -> [String]? { // Hide system index bar—we'll use our custom one instead return nil }
Step 2: Add Custom Index View Setup
Add these properties and methods to your view controller to create and manage the full-screen index:
private var customIndexView: UIView! private var indexLabels: [UILabel] = [] override func viewDidLoad() { super.viewDidLoad() // Initialize custom index view setupCustomIndexView() // Hook into search controller changes searchController.delegate = self } private func setupCustomIndexView() { // Get your existing index titles using your method guard let indexTitles = setIndexTitlesForTV(basedOn: title!, searchController) else { customIndexView?.isHidden = true return } // Create the main index container view if customIndexView == nil { customIndexView = UIView() customIndexView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(customIndexView) // Pin to right edge, full safe area height, fixed width NSLayoutConstraint.activate([ customIndexView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8), customIndexView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), customIndexView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), customIndexView.widthAnchor.constraint(equalToConstant: 30) ]) } // Clear existing labels (for refresh scenarios) customIndexView.subviews.forEach { $0.removeFromSuperview() } indexLabels.removeAll() // Create vertical stack to arrange index titles let stackView = UIStackView() stackView.axis = .vertical stackView.alignment = .center stackView.distribution = .equalSpacing stackView.translatesAutoresizingMaskIntoConstraints = false customIndexView.addSubview(stackView) NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: customIndexView.topAnchor, constant: 16), stackView.bottomAnchor.constraint(equalTo: customIndexView.bottomAnchor, constant: -16), stackView.leadingAnchor.constraint(equalTo: customIndexView.leadingAnchor), stackView.trailingAnchor.constraint(equalTo: customIndexView.trailingAnchor) ]) // Add each index title as a tappable label for title in indexTitles { let label = UILabel() label.text = title label.font = UIFont.systemFont(ofSize: 12) label.textColor = .darkGray label.isUserInteractionEnabled = true let tapGesture = UITapGestureRecognizer(target: self, action: #selector(indexLabelTapped(_:))) label.addGestureRecognizer(tapGesture) stackView.addArrangedSubview(label) indexLabels.append(label) } customIndexView.isHidden = false } // Handle index label taps to scroll to the corresponding section @objc private func indexLabelTapped(_ gesture: UITapGestureRecognizer) { guard let label = gesture.view as? UILabel, let title = label.text, let indexTitles = setIndexTitlesForTV(basedOn: self.title!, searchController), let sectionIndex = indexTitles.firstIndex(of: title) else { return } let indexPath = IndexPath(row: 0, section: sectionIndex) tableView.scrollToRow(at: indexPath, at: .top, animated: true) } // Update highlighted index label when tableView scrolls override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "contentOffset", object as? UITableView == tableView else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } guard let visibleSections = tableView.indexPathsForVisibleRows?.map({ $0.section }), let currentSection = visibleSections.first, let indexTitles = setIndexTitlesForTV(basedOn: self.title!, searchController), currentSection < indexTitles.count else { return } // Reset all label colors indexLabels.forEach { $0.textColor = .darkGray } // Highlight the current section's index label indexLabels[currentSection].textColor = .systemBlue } // Clean up observer deinit { tableView.removeObserver(self, forKeyPath: "contentOffset") }
Step 3: Sync with Search Controller
Add UISearchControllerDelegate conformance to handle hiding/showing the custom index when searching:
extension YourViewController: UISearchControllerDelegate { func didPresentSearchController(_ searchController: UISearchController) { customIndexView.isHidden = true } func didDismissSearchController(_ searchController: UISearchController) { // Refresh index titles after search ends setupCustomIndexView() } }
2. Styling the Default Index Bar (Limited Control)
If you only need minor style tweaks (not full screen), you can adjust the system index's appearance without replacing it:
// Set index title color tableView.sectionIndexColor = .systemBlue // Set index background color tableView.sectionIndexBackgroundColor = .clear // Note: You can't modify the height of the default index bar safely (uses private APIs)
This custom solution will give you a full-screen index bar that works seamlessly with your existing search logic.
内容的提问来源于stack exchange,提问作者marco

