Почему UINavigationBar крадет сенсорные события? - PullRequest
17 голосов
/ 31 января 2012

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

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

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

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

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

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

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

Ответы [ 12 ]

28 голосов
/ 15 мая 2012

Я заметил, что если вы установите userInteractionEnabled в положение OFF, панель навигации больше не будет "красть" касания.

Таким образом, вы должны создать подкласс UBavigationBar, а в 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];
}

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

16 голосов
/ 15 марта 2012

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

Кит в Apple Developer Technical Support, 18 мая 2010 г. (iPhone OS 3):

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

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

Также я обнаружил, что когда я касаюсь области под 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

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

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

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

3 голосов
/ 10 октября 2014

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

 -(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event
 {
    UIView *v = [super hitTest:point withEvent:event];
    return v == self? nil: v;
 }
2 голосов
/ 27 апреля 2017

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

Первое: добавьте в свое приложение (не важно, где вы вводите этот код) расширение для 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)
    }
}

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

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

Затем вам нужно создать метод tapNavigationBar в вашем контроллере вида следующим образом:

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)
}

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

1 голос
/ 25 сентября 2015

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

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

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 пунктов, где находится навигационная панель.,

Взгляните на экран, чтобы очистить его.

enter image description here

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

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

0 голосов
/ 24 мая 2019

У меня сработало следующее:

self.navigationController?.isNavigationBarHidden = true
0 голосов
/ 31 августа 2016

Дайте версию расширения согласно Барту Уайтли.Нет необходимости создавать подклассы.

@implementation UINavigationBar(Xxxxxx)

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

@end
0 голосов
/ 02 июня 2016

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

self.navigationController?.barHideOnTapGestureRecognizer.enabled = false

Вместо переопределения UIWindow, вы можете просто отключить распознаватель жестов, отвечающий за "область откоса"на UINavigationBar.

0 голосов
/ 24 января 2014

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

Я добавил hitTest: withEvent: код в мой подкласс 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];
    }
0 голосов
/ 14 мая 2013

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

Шаг 1. Подкласс 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, когда представление исчезнет или освободится, в viewDidAppear / viewDidDisappear / dealloc ViewControllers, поэтому _touchView в ChunyuWindow NULL, и это то же самое, что и UIWindow, без побочных эффектов.

...