如何制作左右侧边带弧形、类PickerView效果的TableView
Hey there! Let's break down how to build that TableView that behaves like a UIPickerView, plus add those curved side effects you're looking for. Here's a step-by-step solution tailored to your needs:
UIPickerView's key behaviors are centered selection highlight, scroll snapping to rows, and a single-row focus. Here's how to replicate that:
Key Implementations:
- Fixed Row Height: Set a consistent row height so scrolling snaps cleanly.
- Centered Selection Detection: Track which row is in the center of the TableView as the user scrolls, then highlight it.
- Scroll Snapping: When the user stops dragging, auto-scroll to the nearest full row to keep the selected item centered.
Code Examples:
First, add these properties to your ViewController:
@IBOutlet weak var pickerLikeTableView: UITableView! private var selectedIndexPath: IndexPath? private let rowHeight: CGFloat = 50
Configure your TableView in viewDidLoad:
override func viewDidLoad() { super.viewDidLoad() pickerLikeTableView.rowHeight = rowHeight pickerLikeTableView.showsVerticalScrollIndicator = false pickerLikeTableView.delegate = self pickerLikeTableView.dataSource = self }
Implement scroll delegates to handle selection and snapping:
extension YourViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // Calculate the center point of the TableView let centerY = scrollView.contentOffset.y + scrollView.bounds.height / 2 let centerPoint = CGPoint(x: scrollView.bounds.midX, y: centerY) // Find the row at the center point if let indexPath = pickerLikeTableView.indexPathForRow(at: centerPoint) { guard selectedIndexPath != indexPath else { return } selectedIndexPath = indexPath // Reload only the changed rows to update highlighting pickerLikeTableView.reloadRows(at: [indexPath], with: .fade) } } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { // Calculate the target offset to snap to the nearest row let targetY = targetContentOffset.pointee.y let nearestRow = round(targetY / rowHeight) * rowHeight targetContentOffset.pointee.y = nearestRow } }
Update cell styling to highlight the selected row:
extension YourViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "PickerCell", for: indexPath) cell.textLabel?.text = "Option \(indexPath.row + 1)" // Customize highlight style if indexPath == selectedIndexPath { cell.textLabel?.font = .boldSystemFont(ofSize: 18) cell.textLabel?.textColor = .systemBlue cell.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1) } else { cell.textLabel?.font = .systemFont(ofSize: 16) cell.textLabel?.textColor = .label cell.backgroundColor = .clear } return cell } // Don't forget to implement numberOfRowsInSection! func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 // Replace with your data count } }
For the curved left/right edges, you have two solid approaches—pick whichever fits your design better:
Method 1: Use a Shape Mask
This masks the TableView to only show the curved shape, so the background behind it shows through the curved areas.
Add this helper method to your ViewController:
private func applyCurvedMask(to tableView: UITableView) { let cornerRadius: CGFloat = 30 // Adjust this to control curve intensity let maskPath = UIBezierPath(roundedRect: tableView.bounds, byRoundingCorners: [.topLeft, .bottomLeft, .topRight, .bottomRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) let maskLayer = CAShapeLayer() maskLayer.path = maskPath.cgPath tableView.layer.mask = maskLayer // Optional: Add a border around the curved shape let borderLayer = CAShapeLayer() borderLayer.path = maskPath.cgPath borderLayer.strokeColor = UIColor.systemGray.cgColor borderLayer.fillColor = UIColor.clear.cgColor borderLayer.lineWidth = 1 tableView.layer.addSublayer(borderLayer) }
Call it in viewDidLayoutSubviews to ensure it updates when the frame changes:
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() applyCurvedMask(to: pickerLikeTableView) // Adjust content inset so rows don't get cut off by the curve pickerLikeTableView.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) }
Method 2: Custom Curved Background View
If you want a solid curved background (instead of masking), create a custom view to draw the shape:
class CurvedBackgroundView: UIView { var cornerRadius: CGFloat = 30 { didSet { setNeedsDisplay() } } override func draw(_ rect: CGRect) { let path = UIBezierPath() // Draw left curved edge path.move(to: CGPoint(x: cornerRadius, y: 0)) path.addArc(withCenter: CGPoint(x: cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: .pi * 3/2, endAngle: .pi, clockwise: false) path.addLine(to: CGPoint(x: 0, y: bounds.height - cornerRadius)) path.addArc(withCenter: CGPoint(x: cornerRadius, y: bounds.height - cornerRadius), radius: cornerRadius, startAngle: .pi, endAngle: .pi/2, clockwise: false) // Draw right curved edge path.addLine(to: CGPoint(x: bounds.width - cornerRadius, y: bounds.height)) path.addArc(withCenter: CGPoint(x: bounds.width - cornerRadius, y: bounds.height - cornerRadius), radius: cornerRadius, startAngle: .pi/2, endAngle: 0, clockwise: false) path.addLine(to: CGPoint(x: bounds.width, y: cornerRadius)) path.addArc(withCenter: CGPoint(x: bounds.width - cornerRadius, y: cornerRadius), radius: cornerRadius, startAngle: 0, endAngle: .pi * 3/2, clockwise: false) path.close() // Fill the background color UIColor.white.setFill() path.fill() // Optional: Add border UIColor.systemGray.setStroke() path.lineWidth = 1 path.stroke() } }
Then set it as your TableView's background:
override func viewDidLoad() { super.viewDidLoad() // ... other TableView setup let curvedBackground = CurvedBackgroundView(frame: pickerLikeTableView.bounds) curvedBackground.cornerRadius = 30 pickerLikeTableView.backgroundView = curvedBackground pickerLikeTableView.backgroundColor = .clear pickerLikeTableView.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() pickerLikeTableView.backgroundView?.frame = pickerLikeTableView.bounds }
Quick Tips:
- Adjust the
cornerRadiusvalue to make the curves more or less pronounced. - The
contentInsetensures your TableView rows don't get hidden under the curved edges. - Test on different screen sizes to make sure the curves scale correctly (using
viewDidLayoutSubviewshelps with this).
内容的提问来源于stack exchange,提问作者Ishant IOS

