Почему UIButton потребляет прикосновения, а не UIControl
- У меня есть несколько пользовательских кнопок в ячейке табличного представления.
- Эти кнопки содержатся в другом представлении, которое не занимает всю ячейку.
- Я хочу, чтобы кнопки всегда реагировали на нажатия (и потребляли нажатие, чтобы ячейка не выделялась одновременно).
- Я хочу, чтобы в моем представлении контейнера кнопок использовались касания, которых нет на самих кнопках (чтобы ячейка не была выбрана).
- Я хочу, чтобы где-нибудь в ячейке вне моего контейнера кнопок, чтобы выбрать ячейку, как обычно.
Для этого я прикрепил распознаватель жестов к представлению моего контейнера кнопок.
Это имеет желаемый эффект, пока мои кнопки UIButton
s (т.е. нажатие на кнопку само по себе вызывает TouchUpInside
событие на кнопке, касание в другом месте контейнера кнопок ничего не делает, а нажатие на любое другое место в ячейке, вне контейнера кнопок, вызывает выбор ячейки). Тем не менее, если я использую UIControl
вместо UIButton
тогда это уже не так - элемент управления никогда не реагирует на нажатие (контейнер кнопок всегда потребляет нажатие, а нажатие вне контейнера кнопок в ячейке вызывает выбор ячейки). Следует отметить, что если я не добавлю распознаватель жестов в свой контейнер кнопок, то элемент управления будет реагировать на нажатия так же, как UIButton
,
Мое единственное объяснение состоит в том, что UIButton
(который наследует от UIControl
) как-то добавляет дополнительную сенсорную обработку. В этом случае я хотел бы знать, что он делает и как я должен его подражать (мне нужно использовать UIControl
вместо UIButton
потому что моя кнопка имеет пользовательскую иерархию представлений, для которой я не хочу играть в UIButton
).
Код ниже для контроллера представления должен позволить любому воспроизвести проблему:
class ViewController: UITableViewController, UIGestureRecognizerDelegate {
lazy var containerView: UIView = {
let view: UIView = UIView()
view.backgroundColor = UIColor.redColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(self.buttonContainerView)
view.addConstraints([
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
return view
}()
lazy var buttonContainerView: UIView = {
let view: UIView = UIView()
view.backgroundColor = UIColor.blueColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(self.control)
view.addSubview(self.button)
view.addConstraints([
NSLayoutConstraint(item: self.control, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 0.5, constant: 0.0),
NSLayoutConstraint(item: self.control, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.5, constant: 0.0),
NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0)
])
return view
}()
lazy var control: UIControl = {
let view: UIControl = TestControl(frame: CGRectZero)
view.addTarget(self, action: Selector("controlTapped:"), forControlEvents: UIControlEvents.TouchUpInside)
return view
}()
lazy var button: UIButton = {
let view: UIButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
view.setTitle("Tap button", forState: UIControlState.Normal)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addTarget(self, action: Selector("buttonTapped:"), forControlEvents: UIControlEvents.TouchUpInside)
return view
}()
func controlTapped(sender: UIControl) -> Void {
println("Control tapped!")
}
func buttonTapped(sender: UIButton) -> Void {
println("Button tapped!")
}
var recogniser: UITapGestureRecognizer?
var blocker: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 200.0
self.containerView.layoutMargins = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
let recogniser: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tappedContainer:"))
recogniser.delegate = self
self.recogniser = recogniser
self.containerView.addGestureRecognizer(recogniser)
let blocker: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tappedBlocker:"))
blocker.delegate = self
self.blocker = blocker
self.buttonContainerView.addGestureRecognizer(blocker)
}
func tappedContainer(recogniser: UIGestureRecognizer) -> Void {
println("Tapped container!")
}
func tappedBlocker(recogniser: UIGestureRecognizer) -> Void {
println("Tapped blocker!")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier: String = "identifier"
let cell: UITableViewCell
if let queuedCell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(identifier) as? UITableViewCell {
cell = queuedCell
}
else {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: identifier)
cell.contentView.layoutMargins = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
cell.contentView.backgroundColor = UIColor.purpleColor()
cell.contentView.addSubview(self.containerView)
cell.contentView.addConstraints([
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
println("selected cell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
}
class TestControl: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
let view: UIControl = self
let label: UILabel = UILabel()
label.text = "Tap control"
label.userInteractionEnabled = false
view.layer.borderColor = UIColor.orangeColor().CGColor
view.layer.borderWidth = 2.0
view.setTranslatesAutoresizingMaskIntoConstraints(false)
label.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label)
view.addConstraints([
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 5.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: view, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.LessThanOrEqual, toItem: view, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
РЕДАКТИРОВАТЬ
Чтобы быть ясным, я не ищу альтернативное решение, которое "просто работает" - я хочу понять, что это за различие и что я должен делать, чтобы подражать ему, или, возможно, другой семантически правильный путь.
1 ответ
Мое единственное объяснение состоит в том, что UIButton (который наследуется от UIControl) каким-то образом добавляет дополнительную обработку касания.
Вы правы, что UIButton особенный. Некоторое время назад я провел некоторое исследование, отвечая на связанный вопрос, и причина, по которой события кнопки запуска упоминаются в Руководстве по обработке событий для iOS: раздел "Взаимодействие с другими элементами управления пользовательским интерфейсом" распознавателей жестов:
В iOS 6.0 и позже, управляющие действия по умолчанию предотвращают перекрывающееся поведение распознавателя жестов. Например, действие по умолчанию для кнопки - одно нажатие. Если к родительскому представлению кнопки подключен распознаватель жестов одним нажатием, а пользователь нажимает кнопку, метод действия кнопки получает событие касания вместо распознавателя жестов.
Затем он перечисляет примеры и одним нажатием пальца на UIButton
это один из них.
Способ блокировать распознаватели жестов, такие как элементы управления по умолчанию, заключается в TestControl
переопределение gestureRecognizerShouldBegin:
(см. Ссылку класса UIView). Если вы хотели подражать UIButton
Поведение, вы можете использовать что-то вроде:
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer {
if tapGestureRecognizer.numberOfTapsRequired == 1 && tapGestureRecognizer.numberOfTouchesRequired == 1 {
return false;
}
}
return true;
}