Xcode Swift项目中单选按钮点击Selector函数未触发问题
Hey there, let's dig into why your radio button taps aren't firing the selector functions in your SortView subclass. I've gone through your code and spotted a few key issues that could be causing this, plus straightforward fixes to get things working.
Your Code Context
First, here's the relevant code you shared for reference:
SortView Implementation
class SortView: UIView { // MARK: - Properties lazy var driverSortRadioButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal) button.setDimensions(height: 25, width: 25) button.backgroundColor = .clear button.contentMode = .scaleAspectFill button.addTarget(self, action: #selector(handleDriverSortRadioButton), for: .touchUpInside) return button }() lazy var pickupTimeSortRadioButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal) button.setDimensions(height: 25, width: 25) button.backgroundColor = .clear button.contentMode = .scaleAspectFill button.addTarget(self, action: #selector(handlePickupTimeSortRadioButton), for: .touchUpInside) return button }() private let driverSortTitleLabel: UILabel = { let label = UILabel() label.text = "Sort by driver:" label.textAlignment = .left label.textColor = .white label.font = UIFont(name: "AvenirNext-DemiBold", size: 18) label.backgroundColor = .clear return label }() private let pickupTimeSortTitleLabel: UILabel = { let label = UILabel() label.text = "Sort by pickup time:" label.textAlignment = .left label.textColor = .white label.font = UIFont(name: "AvenirNext-DemiBold", size: 18) label.backgroundColor = .clear return label }() private let driverSortTextField: UITextField = { let tf = UITextField() tf.textColor = .black tf.textAlignment = .center tf.placeholder = "Louise" tf.font = UIFont(name: "AvenirNext-DemiBold", size: 15) tf.backgroundColor = UIColor(white: 0.95, alpha: 1.0) tf.setWidth(width: 150) tf.layer.cornerRadius = 5 return tf }() private let pickupTimeSortTextField: UITextField = { let tf = UITextField() tf.textColor = .black tf.textAlignment = .center tf.placeholder = "2:00pm" tf.font = UIFont(name: "AvenirNext-DemiBold", size: 15) tf.backgroundColor = UIColor(white: 0.95, alpha: 1.0) tf.setWidth(width: 150) tf.layer.cornerRadius = 5 return tf }() private enum radioButtonStates { case driver case pickupTime } private var radioButtonState = radioButtonStates.driver // MARK: - Lifecycle override init(frame: CGRect) { super.init(frame: frame) configureUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Selectors @objc func handleDriverSortRadioButton() { print("DEBUG: driver sort radio button clicked") if radioButtonState != .driver { pickupTimeSortRadioButton.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal) pickupTimeSortTextField.isEnabled = false pickupTimeSortTextField.text = "" radioButtonState = .driver driverSortRadioButton.setImage(UIImage(named: "Radio Button - Selected"), for: .normal) driverSortTextField.isEnabled = true driverSortTextField.text = "" } } @objc func handlePickupTimeSortRadioButton() { print("DEBUG: pickup time sort radio button clicked") if radioButtonState != .pickupTime { driverSortRadioButton.setImage(UIImage(named: "Radio Button - Unselected"), for: .normal) driverSortTextField.isEnabled = false driverSortTextField.text = "" radioButtonState = .pickupTime pickupTimeSortRadioButton.setImage(UIImage(named: "Radio Button - Selected"), for: .normal) pickupTimeSortTextField.isEnabled = true pickupTimeSortTextField.text = "" } } // MARK: - Helper Functions private func configureUI() { let driverSortStackView = UIStackView(arrangedSubviews: [driverSortRadioButton, driverSortTitleLabel, driverSortTextField]) driverSortStackView.axis = .horizontal driverSortStackView.distribution = .fill driverSortStackView.spacing = 5 let pickupTimeSortStackView = UIStackView(arrangedSubviews: [pickupTimeSortRadioButton, pickupTimeSortTitleLabel, pickupTimeSortTextField]) pickupTimeSortStackView.axis = .horizontal pickupTimeSortStackView.distribution = .fill pickupTimeSortStackView.spacing = 5 let stackView = UIStackView(arrangedSubviews:[driverSortStackView, pickupTimeSortStackView]) stackView.axis = .vertical stackView.distribution = .fill stackView.spacing = 10 self.addSubview(stackView) stackView.centerX(inView: self) stackView.centerY(inView: self) } }
Parent ViewController Setup
private lazy var sortView = SortView() // In viewDidLoad or similar view.addSubview(sortView) sortView.anchor(top:tableView.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 40, paddingLeft: 32, paddingRight: 32)
Root Cause Analysis
The most likely culprit here is incorrect layout constraints leading to zero or invalid frame size for your SortView and its buttons. Here's why:
- You're only setting
top,left, andrightconstraints forsortViewin the parent VC—you never define a height constraint. This meanssortViewhas an implicit height of 0, even if its internal stack view appears to render on screen. - Inside
SortView, you're centering the stack view usingcenterX(inView: self)andcenterY(inView: self)—but if the parent (SortView) has no height, the stack view's position and size become invalid, making the buttons unclickable (their actual tap area is outside the visible screen or non-existent).
Other minor checks to rule out:
- Ensure
isUserInteractionEnabledistrueforSortView, the stack views, and the buttons (default is true, but it's worth confirming). - Verify your button images exist in the asset catalog—missing images can sometimes cause unexpected layout issues.
Fixes to Get Taps Working
Let's implement these step-by-step fixes:
1. Add a Height Constraint to SortView
Update your parent VC's layout code to give sortView a clear height, or let it automatically size based on its content:
Option A: Fixed Height Constraint
view.addSubview(sortView) sortView.anchor(top: tableView.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 40, paddingLeft: 32, paddingRight: 32) // Add a fixed height (adjust based on your content) sortView.heightAnchor.constraint(equalToConstant: 100).isActive = true
Option B: Auto-Size to Content (Better Approach)
To make SortView automatically adjust its height to fit the internal stack view, first modify SortView to expose the stack view as a property:
// Inside SortView, replace the local stackView with a private property private let stackView: UIStackView = { let stack = UIStackView() stack.axis = .vertical stack.distribution = .fill stack.spacing = 10 return stack }() // Then update configureUI() private func configureUI() { let driverSortStackView = UIStackView(arrangedSubviews: [driverSortRadioButton, driverSortTitleLabel, driverSortTextField]) driverSortStackView.axis = .horizontal driverSortStackView.distribution = .fill driverSortStackView.spacing = 5 let pickupTimeSortStackView = UIStackView(arrangedSubviews: [pickupTimeSortRadioButton, pickupTimeSortTitleLabel, pickupTimeSortTextField]) pickupTimeSortStackView.axis = .horizontal pickupTimeSortStackView.distribution = .fill pickupTimeSortStackView.spacing = 5 stackView.addArrangedSubview(driverSortStackView) stackView.addArrangedSubview(pickupTimeSortStackView) self.addSubview(stackView) // Pin the stack view to the edges of SortView instead of centering stackView.fillSuperview(padding: UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)) }
Then in the parent VC, constrain sortView's height to match the stack view:
view.addSubview(sortView) sortView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ sortView.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 40), sortView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32), sortView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), sortView.heightAnchor.constraint(equalTo: sortView.stackView.heightAnchor, constant: 20) // Add padding if needed ])
2. Verify User Interaction is Enabled
Add these lines to SortView's init method to explicitly enable interaction (just to rule out any accidental overrides):
override init(frame: CGRect) { super.init(frame: frame) self.isUserInteractionEnabled = true driverSortRadioButton.isUserInteractionEnabled = true pickupTimeSortRadioButton.isUserInteractionEnabled = true configureUI() }
3. Test with Visual Debugging
Temporarily add background colors to SortView and the buttons to see their actual frames:
// In SortView's init self.backgroundColor = .blue // Remove after testing // In button setup button.backgroundColor = .red // Remove after testing
This will let you confirm if the buttons are actually within the bounds of SortView and have a valid tap area.
Final Notes
Once you implement these fixes, your button taps should start firing the selector functions correctly. The core issue was the missing height constraint on SortView—without it, the view (and its subviews) don't have a valid frame to receive touch events.
内容的提问来源于stack exchange,提问作者Stephen501

