Возможно ли, чтобы UIButton (или любой другой элемент управления в этом отношении) получал события касания, когда фрейм UIButton находится за пределами его родительского фрейма? Потому что, когда я пытаюсь это сделать, мой UIButton, похоже, не может получать какие-либо события. Как мне обойти это?
Взаимодействие за пределами UIView
Ответы (9)
да. Вы можете переопределить метод hitTest:withEvent:
, чтобы вернуть представление для большего набора точек, чем содержит это представление. См. справочник по классам UIView.
Изменить: Пример:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat radius = 100.0;
CGRect frame = CGRectMake(-radius, -radius,
self.frame.size.width + radius,
self.frame.size.height + radius);
if (CGRectContainsPoint(frame, point)) {
return self;
}
return nil;
}
Редактировать 2: (После уточнения:) Чтобы кнопка считалась находящейся в границах родителя, вам необходимо переопределить pointInside:withEvent:
в родительском элементе, чтобы включить рамку кнопки.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(self.view.bounds, point) ||
CGRectContainsPoint(button.view.frame, point))
{
return YES;
}
return NO;
}
Обратите внимание, приведенный здесь код для переопределения pointInside не совсем корректен. Как Summon поясняет ниже, сделайте следующее:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
return YES;
return [super pointInside:point withEvent:event];
}
Обратите внимание, что вы, скорее всего, сделаете это с self.oversizeButton
в качестве IBOutlet в этом подклассе UIView; затем вы можете просто перетащить рассматриваемую «кнопку большого размера» в соответствующий специальный вид. (Или, если по какой-то причине вы часто делали это в проекте, у вас был бы специальный подкласс UIButton, и вы могли бы просмотреть список подпредставлений для этих классов.) Надеюсь, это поможет.
pointInside:withEvent:
родителя, чтобы включить рамку кнопки. Я добавлю пример кода к моему ответу выше.
- person jnic; 25.03.2011
touchesBegan
и т. д., поскольку UIViewController
является подклассом UIResponder
. Представления, инициализированные непосредственно из пера, все еще могут быть пользовательскими подклассами. Просто установите поле «Класс» вашего представления в «Инспекторе удостоверений» в качестве своего собственного подкласса и, после загрузки, отобразите свой наконечник соответствующим образом.
- person jnic; 05.04.2012
hitTest:withEvent:
и pointInside:withEvent:
области не вызываются для меня, когда я нажимаю кнопку, которая находится за пределами родительских границ. Что может быть не так?
- person iosdude; 13.06.2017
@jnic, я работаю над iOS SDK 5.0, и чтобы ваш код работал правильно, мне пришлось сделать следующее:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
return YES;
}
return [super pointInside:point withEvent:event]; }
Представление контейнера в моем случае представляет собой UIButton, и все дочерние элементы также являются UIButton, которые могут перемещаться за пределы родительского UIButton.
Лучший
В родительском представлении вы можете переопределить метод проверки попадания:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];
if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
return [_myButton hitTest:translatedPoint withEvent:event];
}
return [super hitTest:point withEvent:event];
}
В этом случае, если точка попадает в пределы вашей кнопки, вы перенаправляете звонок туда; если нет, вернитесь к исходной реализации.
В моем случае у меня был подкласс UICollectionViewCell
, который содержал класс UIButton
. Я отключил clipsToBounds
в ячейке, и кнопка была видна за пределами ячейки. Однако кнопка не получала событий касания. Мне удалось обнаружить сенсорные события на кнопке, используя ответ Джека: https://stackoverflow.com/a/30431157/3344977< /а>
Вот версия Swift:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let translatedPoint = button.convertPoint(point, fromView: self)
if (CGRectContainsPoint(button.bounds, translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, withEvent: event)
}
return super.hitTest(point, withEvent: event)
}
Свифт 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let translatedPoint = button.convert(point, from: self)
if (button.bounds.contains(translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, with: event)
}
return super.hitTest(point, with: event)
}
Почему это происходит?
Это связано с тем, что когда ваше подпредставление находится за пределами границ вашего суперпредставления, события касания, которые фактически происходят в этом подпредставлении, не будут доставлены в это подпредставление. Однако он БУДЕТ доставлен в супервизор.
Независимо от того, обрезаются ли подпредставления визуально, события касания всегда учитывают прямоугольник границ суперпредставления целевого представления. Другими словами, сенсорные события, происходящие в части представления, которая находится за пределами прямоугольника границ его суперпредставления, не доставляются в это представление. Ссылка
Что вам нужно сделать?
Когда ваше суперпредставление получает событие касания, упомянутое выше, вам нужно явно указать UIKit, что мое подпредставление должно получать это событие касания.
Что насчет кода?
В своем супервизоре реализуйте func hitTest(_ point: CGPoint, with event: UIEvent?)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
// convert the point into subview's coordinate system
let subviewPoint = self.convert(point, to: subview)
// if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
return super.hitTest(point, with: event)
}
Увлекательная ловушка: вы должны перейти к "самому высокому слишком маленькому супервизору"
Вы должны подняться «вверх» к «самому высокому» виду, за пределами которого находится проблемный вид.
Типичный пример:
Скажем, у вас есть экран S с представлением контейнера C. Представление контроллера представления контейнера — это V. (Напомним, что V будет находиться внутри C и иметь такой же размер.) V имеет подпредставление (возможно, кнопку) B. B — это проблема вид, который на самом деле находится за пределами V.
Но обратите внимание, что B также находится за пределами C.
В этом примере вам нужно применить решение override hitTest
на самом деле к C, а не к V. Если вы примените его к V - он ничего не сделает.
Быстрый:
Где targetView — это представление, в котором вы хотите получить событие (которое может быть полностью или частично расположено за пределами фрейма своего родительского представления).
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
return closeButton
}
return super.hitTest(point, withEvent: event)
}
Для меня (как и для других) ответом было не переопределить метод hitTest
, а скорее метод pointInside
. Мне пришлось сделать это только в двух местах.
Суперпредставление Это представление, согласно которому, если бы clipsToBounds
было установлено значение true, вся эта проблема исчезла бы. Итак, вот мой простой UIView
в Swift 3:
class PromiscuousView : UIView {
override func point(inside point: CGPoint,
with event: UIEvent?) -> Bool {
return true
}
}
Вспомогательное представление Ваш пробег может отличаться, но в моем случае мне также пришлось перезаписать метод pointInside
для подпредставления, которое решило, что касание выходит за пределы допустимого. Мой находится в Objective-C, поэтому:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
WEPopoverController *controller = (id) self.delegate;
if (self.takeHitsOutside) {
return YES;
} else {
return [super pointInside:point withEvent:event];
}
}
Этот ответ (и несколько других на тот же вопрос) может помочь прояснить ваше понимание.
свифт 4.1 обновлен
Чтобы получить сенсорные события в том же UIView, вам просто нужно добавить следующий метод переопределения, он будет идентифицировать конкретное представление, нажатое в UIView, которое находится за пределами границ.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
if btnAngle.bounds.contains(pointforTargetview) {
return self
}
return super.hitTest(point, with: event)
}
Я знаю, что это очень старый вопрос, но все же нам нужны эти решения в виде обновленной версии, поэтому я поделюсь новой версией решения этой проблемы.
Предположим, что мой mainView
кадр равен CGRect(0.0, 0.0, 250, 250.0)
, и я хочу добавить кнопку размером ширины и высоты 50 в качестве subView
моего mainView
(в верхний левый угол), его кадр будет выглядеть как CGRect(-50, -50, 50, 50)
в mainView
, в этом случае мы не можем взаимодействовать с этой кнопкой, и вот пример решения этой проблемы
В вашем MainView (представление контейнера кнопок) переопределите hitTest
function, как указано в других ответах, но здесь измените порядок подпредставлений и преобразуйте точку попадания в эту точку подпредставления, затем проверьте результат только с помощью hitTest
еще раз, иначе проверьте, содержит ли ваше представление это point верните mainView
, иначе верните nil, чтобы не влиять на другие виды
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return self.superview }
for member in subviews.reversed() {
let subPoint = member.convert(point, from: self)
guard let result = member.hitTest(subPoint, with: event) else { continue }
return result
}
return self.bounds.contains(point) ? self : nil
}
Я протестировал и использовал этот код в своем собственном приложении, и он работает хорошо.