Swift4自定义UITableView滑动删除按钮方案求助(适配iOS11)
Hey there! I totally get your frustration—Apple overhauled the internal view hierarchy of UITableView's swipe actions starting in iOS 11, which broke that old subview-traversing hack you were using. Let's ditch the unstable private API approach and use Apple's officially supported methods to customize swipe delete buttons reliably in Swift 4 and later, including adding shadow effects to image-based buttons.
Why Your Old Code Stopped Working
In iOS 11, Apple replaced the old UITableViewCellActionButton structure with a new system built around UIContextualAction. This means your loop to find private subviews will no longer locate the button elements, and even if it did, relying on private view classes is always risky (Apple can change them at any time without warning).
Solution 1: Use UIContextualAction (iOS 11+, Official Approach)
This is the recommended way to create custom swipe actions. It's stable, flexible, and lets you customize colors, images, and even add shadows without hacking subviews.
Basic Custom Text Delete Button
Here's how to replicate your original text-based button customization:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { // Create the delete action let deleteAction = UIContextualAction(style: .destructive, title: "删除") { [weak self] (action, view, completionHandler) in // Handle your delete logic here self?.yourDataSourceArray.remove(at: indexPath.row) tableView.deleteRows(at: [indexPath], with: .automatic) completionHandler(true) } // Customize button background color deleteAction.backgroundColor = .white // Customize text color (access the titleLabel via valueForKey) if let titleLabel = deleteAction.value(forKey: "titleLabel") as? UILabel { titleLabel.textColor = SharedInstance.hexStringToUIColor(hex: Color.TodayActivityColor.Type_LEAVE) } // Configure the swipe actions let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) // Allow full swipe to trigger the first action (optional) configuration.performsFirstActionWithFullSwipe = true return configuration }
Image-Based Button with Shadow
If you want to use an image instead of text and add a shadow, you can directly access the action's imageView to modify its layer properties:
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let deleteAction = UIContextualAction(style: .destructive, title: "") { (action, view, completionHandler) in // Handle delete logic completionHandler(true) } // Set your image (use alwaysTemplate to match tint color) deleteAction.image = UIImage(named: "delete-icon")?.withRenderingMode(.alwaysTemplate) deleteAction.backgroundColor = .white deleteAction.imageView?.tintColor = SharedInstance.hexStringToUIColor(hex: Color.TodayActivityColor.Type_LEAVE) // Add shadow to the image view deleteAction.imageView?.layer.shadowColor = UIColor.black.cgColor deleteAction.imageView?.layer.shadowOffset = CGSize(width: 2, height: 2) deleteAction.imageView?.layer.shadowOpacity = 0.5 deleteAction.imageView?.layer.shadowRadius = 2 let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) configuration.performsFirstActionWithFullSwipe = true return configuration }
Solution 2: Fully Custom Button (For iOS 11-12 Compatibility)
If you need even more customization (like combining image + text) and need to support iOS 11-12 (where UIContextualAction doesn't have a backgroundView property), you can convert a custom UIView into an image and use it as the action's background:
private func createCustomDeleteAction(tableView: UITableView) -> UIContextualAction { let action = UIContextualAction(style: .destructive, title: "") { (action, view, completion) in // Handle your action logic here completion(true) } // Create your custom button view let customButtonView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: tableView.rowHeight)) customButtonView.backgroundColor = .white // Add image with shadow let deleteImageView = UIImageView(image: UIImage(named: "delete-icon")?.withRenderingMode(.alwaysTemplate)) deleteImageView.tintColor = SharedInstance.hexStringToUIColor(hex: Color.TodayActivityColor.Type_LEAVE) deleteImageView.frame = CGRect(x: 20, y: (customButtonView.frame.height - 24)/2, width: 24, height: 24) // Configure shadow deleteImageView.layer.shadowColor = UIColor.black.cgColor deleteImageView.layer.shadowOffset = CGSize(width: 1, height: 1) deleteImageView.layer.shadowOpacity = 0.6 deleteImageView.layer.shadowRadius = 1.5 customButtonView.addSubview(deleteImageView) // Add text label (optional) let deleteLabel = UILabel(frame: CGRect(x: 50, y: 0, width: 30, height: customButtonView.frame.height)) deleteLabel.text = "删除" deleteLabel.textColor = SharedInstance.hexStringToUIColor(hex: Color.TodayActivityColor.Type_LEAVE) deleteLabel.font = UIFont.systemFont(ofSize: 14) customButtonView.addSubview(deleteLabel) // Convert custom view to image UIGraphicsBeginImageContextWithOptions(customButtonView.bounds.size, false, UIScreen.main.scale) customButtonView.drawHierarchy(in: customButtonView.bounds, afterScreenUpdates: true) guard let customButtonImage = UIGraphicsGetImageFromCurrentImageContext() else { UIGraphicsEndImageContext() return action } UIGraphicsEndImageContext() // Set the image as the action's background action.backgroundColor = UIColor(patternImage: customButtonImage) return action } // Use it in your swipe configuration method func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let customDeleteAction = createCustomDeleteAction(tableView: tableView) let configuration = UISwipeActionsConfiguration(actions: [customDeleteAction]) configuration.performsFirstActionWithFullSwipe = true return configuration }
Key Takeaways
- Avoid private view hacks: Apple changes internal view structures with iOS updates, so these methods will break. Stick to official APIs like
UIContextualAction. UIContextualActionis flexible: It supports text, images, and custom colors out of the box.- Shadows are easy to add: For image actions, directly modify the
imageView's layer properties. For full custom views, build the view with shadows first, then convert it to an image.
内容的提问来源于stack exchange,提问作者Vishal N

