Почему UINavigationBar крадет события касания?

У меня есть пользовательский UIButton с UILabel, добавленным как subview. Кнопка выполняет заданный селектор только тогда, когда я касаюсь его около 15 точек ниже верхней границы. И когда я нажимаю выше этой области, ничего не происходит.

Я узнал, что это не вызвано неправильным созданием кнопки и метки, потому что после того, как я сдвигаю кнопку ниже примерно на 15 пикселей, она работает правильно.

ОБНОВЛЕНИЕ Я забыл сказать, что кнопка, расположенная под UINavigationBar и 1/3 верхней части кнопки, не получает сенсорных событий.

Изображение было здесь

Вид с 4 кнопками расположен под навигационной панелью. И когда прикоснитесь к «Баскетболу» в верхней части, BackButton получит событие касания, а при касании «Фортепиано» сверху, тогда rightBarButton (если существует) получит прикосновение. Если не существует, ничего не произошло.

Я не нашел эту документацию в приложениях.

Также я нашел эту тему связанной с моей проблемой, но ответа тоже нет.

Я заметил, что если вы установите userInteractionEnabled в положение OFF, NavigationBar больше не «крадет» штрихи.

Таким образом, вы должны подclassифицировать свой UINavigationBar и в своем CustomNavigationBar сделать это:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if ([self pointInside:point withEvent:event]) { self.userInteractionEnabled = YES; } else { self.userInteractionEnabled = NO; } return [super hitTest:point withEvent:event]; } 

Информация о подclassе UINavigationBar вы можете найти здесь .

Я узнал ответ здесь (Apple Developer Forum).

Кейт в технической поддержке Apple Developer, 18 мая 2010 года (iPhone OS 3):

Я рекомендую вам избегать использования сенсорного интерфейса в такой непосредственной близости от навигационной панели или панели инструментов. Эти области обычно известны как «факторы отслоения», что облегчает пользователям возможность совершать сенсорные события на кнопках без сложностей с точными касаниями. Это также относится к UIButton, например.

Но если вы хотите захватить событие касания до того, как панель навигации или панель инструментов его получит, вы можете подclassифицировать UIWindow и переопределить: – (void) событие sendEvent: (UIEvent *);

Также я узнал, что когда я касаюсь области под UINavigationBar, location.y определяется как 64, хотя это не так. Поэтому я сделал это:

CustomWindow.h

 @interface CustomWindow: UIWindow @end 

CustomWindow.m

 @implementation CustomWindow - (void) sendEvent:(UIEvent *)event { BOOL flag = YES; switch ([event type]) { case UIEventTypeTouches: //[self catchUIEventTypeTouches: event]; perform if you need to do something with event for (UITouch *touch in [event allTouches]) { if ([touch phase] == UITouchPhaseBegan) { for (int i=0; i<[self.subviews count]; i++) { //GET THE FINGER LOCATION ON THE SCREEN CGPoint location = [touch locationInView:[self.subviews objectAtIndex:i]]; //REPORT THE TOUCH NSLog(@"[%@] touchesBegan (%i,%i)", [[self.subviews objectAtIndex:i] class],(NSInteger) location.x, (NSInteger) location.y); if (((NSInteger)location.y) == 64) { flag = NO; } } } } break; default: break; } if(!flag) return; //to do nothing /*IMPORTANT*/[super sendEvent:(UIEvent *)event];/*IMPORTANT*/ } @end 

В classе AppDelegate я использую CustomWindow вместо UIWindow.

Теперь, когда я касаюсь области под панелью навигации, ничего не происходит.

Мои кнопки по-прежнему не получают сенсорных событий, потому что я не знаю, как отправить это событие (и изменить координаты) на мой взгляд с помощью кнопок.

Подclass UINavigationBar и добавьте этот метод. Это приведет к тому, что краны будут пропущены, если они не постукивают подвью (например, кнопка).

  -(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *v = [super hitTest:point withEvent:event]; return v == self? nil: v; } 

Я просто хотел поделиться другой перспективой решения этой проблемы. Это не проблема по дизайну, но она призвана помочь пользователю вернуться или перейти. Но нам нужно плотно положить вещи в навигационную панель или под ней, и все выглядит грустно.

Сначала давайте посмотрим на код.

 class MyNavigationBar: UINavigationBar { private var secondTap = false private var firstTapPoint = CGPointZero override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { if !self.secondTap{ self.firstTapPoint = point } defer{ self.secondTap = !self.secondTap } return super.pointInside(firstTapPoint, withEvent: event) } 

}

Возможно, вы видите, почему я занимаюсь обработкой второго касания. Рецепт решения.

Хит-тест вызывается дважды для вызова. В первый раз сообщается о фактической точке в окне. Все идет хорошо. На втором проходе это происходит.

Если система видит навигационную панель, а точка попадания составляет около 9 пикселей на стороне Y, она пытается уменьшить это постепенно до уровня ниже 44 пунктов, где находится панель навигации.

Посмотрите на экран, чтобы быть ясным.

введите описание изображения здесь

Таким образом, theres механизм, который будет использовать соседнюю логику для второго прохода hittest. Если мы сможем узнать его второй проход, а затем позвоним супер с первой тестовой точкой. Работа выполнена.

Вышеприведенный код делает это точно.

Решение для меня было следующим:

Во-первых: добавьте в свое приложение (не имеет значения, где вы вводите этот код) расширение для UINavigationBar например: Следующий код просто отправляет уведомление с точкой и событием при нажатии navigationBar панели.

 extension UINavigationBar { open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tapNavigationBar"), object: nil, userInfo: ["point": point, "event": event as Any]) return super.hitTest(point, with: event) } } 

Затем в вашем конкретном controllerе просмотра вы должны прослушать это уведомление, добавив эту строку в свой viewDidLoad :

 NotificationCenter.default.addObserver(self, selector: #selector(tapNavigationBar), name: NSNotification.Name(rawValue: "tapNavigationBar"), object: nil) 

Затем вам нужно создать метод tapNavigationBar в вашем controllerе представления так:

 func tapNavigationBar(notification: Notification) { let pointOpt = notification.userInfo?["point"] as? CGPoint let eventOpt = notification.userInfo?["event"] as? UIEvent? guard let point = pointOpt, let event = eventOpt else { return } let convertedPoint = YOUR_VIEW_BEHIND_THE_NAVBAR.convert(point, from: self.navigationController?.navigationBar) if YOUR_VIEW_BEHIND_THE_NAVBAR.point(inside: convertedPoint, with: event) { //Dispatch whatever you wanted at the first place. } } 

PD: Не забудьте удалить наблюдение в deinit так:

 deinit { NotificationCenter.default.removeObserver(self) } 

Вот и все … Это немного «сложно», но это хороший способ обхода без подclassа и получения уведомления в любое время, когда navigationBar панель используется.

Есть две вещи, которые могут вызвать проблемы.

  1. Вы пытались setUserInteractionEnabled:NO для метки.

  2. Вторая вещь, я думаю, может работать, кроме того, после добавления ярлыка поверх кнопки вы можете отправить ярлык назад (это может работать, но не уверен, хотя)

    [button sendSubviewToBack:label];

Пожалуйста, дайте мне знать, работает ли код 🙂

Ваши ярлыки огромны. Они начинаются с {0,0} (левый верхний угол кнопки), распространяются по всей ширине кнопки и имеют высоту всего изображения. Проверьте данные frame и повторите попытку.

Кроме того, у вас есть возможность использовать свойство titleLabel . Возможно, вы UILabel заголовок позже, и он попадает на этот ярлык, а не на ваш собственный UILabel . Это объясняет, почему текст (принадлежащий кнопке) будет работать, в то время как метка будет охватывать остальную часть кнопки (не пропуская краны).

titleLabel – свойство только для чтения, но вы можете настроить его так же, как ваш собственный ярлык (кроме, возможно, frame ), включая цвет текста, шрифт, тень и т. д.

Это решило мою проблему ..

Я добавил hitTest: withEvent: code к моему подclassу navbar.

  -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { int errorMargin = 5;// space left to decrease the click event area CGRect smallerFrame = CGRectMake(0 , 0 - errorMargin, self.frame.size.width, self.frame.size.height); BOOL isTouchAllowed = (CGRectContainsPoint(smallerFrame, point) == 1); if (isTouchAllowed) { self.userInteractionEnabled = YES; } else { self.userInteractionEnabled = NO; } return [super hitTest:point withEvent:event]; } 

Расширение решения Александра:

Шаг 1. Подclass UIWindow

 @interface ChunyuWindow : UIWindow { NSMutableArray * _views; @private UIView *_touchView; } - (void)addViewForTouchPriority:(UIView*)view; - (void)removeViewForTouchPriority:(UIView*)view; @end // .m File // #import "ChunyuWindow.h" @implementation ChunyuWindow - (void) dealloc { TT_RELEASE_SAFELY(_views); [super dealloc]; } - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { if (UIEventSubtypeMotionShake == motion && [TTNavigator navigator].supportsShakeToReload) { // If you're going to use a custom navigator implementation, you need to ensure that you // implement the reload method. If you're inheriting from TTNavigator, then you're fine. TTDASSERT([[TTNavigator navigator] respondsToSelector:@selector(reload)]); [(TTNavigator*)[TTNavigator navigator] reload]; } } - (void)addViewForTouchPriority:(UIView*)view { if ( !_views ) { _views = [[NSMutableArray alloc] init]; } if (![_views containsObject: view]) { [_views addObject:view]; } } - (void)removeViewForTouchPriority:(UIView*)view { if ( !_views ) { return; } if ([_views containsObject: view]) { [_views removeObject:view]; } } - (void)sendEvent:(UIEvent *)event { if ( !_views || _views.count == 0 ) { [super sendEvent:event]; return; } UITouch *touch = [[event allTouches] anyObject]; switch (touch.phase) { case UITouchPhaseBegan: { for ( UIView *view in _views ) { if ( CGRectContainsPoint(view.frame, [touch locationInView:[view superview]]) ) { _touchView = view; [_touchView touchesBegan:[event allTouches] withEvent:event]; return; } } break; } case UITouchPhaseMoved: { if ( _touchView ) { [_touchView touchesMoved:[event allTouches] withEvent:event]; return; } break; } case UITouchPhaseCancelled: { if ( _touchView ) { [_touchView touchesCancelled:[event allTouches] withEvent:event]; _touchView = nil; return; } break; } case UITouchPhaseEnded: { if ( _touchView ) { [_touchView touchesEnded:[event allTouches] withEvent:event]; _touchView = nil; return; } break; } default: { break; } } [super sendEvent:event]; } @end 

Шаг 2: Назначьте экземпляр ChunyuWindow экземпляру AppDelegate

Шаг 3: Внедрение touchesEnded:widthEvent: для view с помощью кнопок, например:

 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded: touches withEvent: event]; UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView: _buttonsView]; // a subview contains buttons for (UIButton* button in _buttons) { if (CGRectContainsPoint(button.frame, point)) { [self onTabButtonClicked: button]; break; } } } 

Шаг 4: вызовите ChunyuWindow addViewForTouchPriority когда появится представление, о котором мы заботимся, и вызовите removeViewForTouchPriority когда представление исчезает или dealloc, в viewDidAppear / viewDidDisappear / dealloc ViewControllers, поэтому _touchView в ChunyuWindow является NULL, и это то же самое, что и UIWindow , не имеющих побочных эффектов.

Альтернативное решение, которое работало для меня, основано на ответе, предоставленном Александром:

 self.navigationController?.barHideOnTapGestureRecognizer.enabled = false 

Вместо того, чтобы переопределять UIWindow , вы можете просто отключить распознавателя жестов, ответственного за «область slop» на UINavigationBar .

Дайте дополнительную версию в соответствии с Бартом Уайтли. Нет необходимости в подclassе.

 @implementation UINavigationBar(Xxxxxx) - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *v = [super hitTest:point withEvent:event]; return v == self ? nil: v; } @end 
  • Как распределить приложение ios без проводов без управления UDID и перекомпиляции
  • Как добавить 1 день к NSDate?
  • Как передать объект с помощью NSNotificationCenter
  • Как отслеживать причину SIGABRT
  • Прокрутка двумя пальцами с помощью UIScrollView
  • Как обрабатывать дублируемую символьную ошибку от сторонних библиотек?
  • iPhone: как передавать данные между несколькими Viewcontrollers в приложении Tabbar
  • iOS: конвертировать UTC NSDate в локальный часовой пояс
  • Эквивалент карты NSArray
  • Что такое Swift-эквивалент ответаSoSelector?
  • iOS 8 Снимок снимка, который не был отображен, приводит к созданию пустого моментального снимка
  • Давайте будем гением компьютера.