События щелчка в UINavigationBar, переопределенные распознавателем жестов
Вопрос в первую очередь был:
Когда у вас есть tableView, как реализовать это, пользователь может нажать NavigationBar, чтобы прокрутить весь путь до самого верха.
Решение:
- (void)viewDidLoad {
UITapGestureRecognizer* tapRecon = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(navigationBarDoubleTap:)];
tapRecon.numberOfTapsRequired = 2;
[navController.navigationBar addGestureRecognizer:tapRecon];
[tapRecon release];
}
- (void)navigationBarDoubleTap:(UIGestureRecognizer*)recognizer {
[tableView setContentOffset:CGPointMake(0,0) animated:YES];
}
Который работает как шарм!
Но Драрок указал на проблему:
Этот подход жизнеспособен, только если у вас нет кнопки назад или rightBarButtonItem. Их события щелчка переопределяются распознавателем жестов
Мой вопрос:
Как я могу получить приятную особенность, заключающуюся в том, что моя панель навигации кликабельна, но все еще может использовать кнопки "Назад" в моем приложении?
Так что либо найдите другое решение, которое не отменяет кнопку возврата, либо найдите решение, чтобы кнопка возврата снова заработала:)
6 ответов
UIGestureRecognizerDelegate имеет метод, который называется "gestRecognizer:shouldReceiveTouch". Если вы можете указать, является ли сенсорный экран кнопкой, просто верните "НЕТ", в противном случае верните "ДА", и все готово.
Вместо того, чтобы использовать вид местоположения, я решил это, проверив класс UITouch.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return (![[[touch view] class] isSubclassOfClass:[UIControl class]]);
}
Обратите внимание, что кнопки навигации имеют тип UINavigationButton
который не выставлен, следовательно проверка подкласса.
Этот метод входит в класс, который вы определяете как делегат распознавателя жестов. Если вы только начинаете работать с распознавателями жестов, обратите внимание, что делегат устанавливается отдельно от цели.
UIGestureRecognizer
также имеет атрибут @property(nonatomic) BOOL cancelsTouchesInView
, Из документации: A Boolean value affecting whether touches are delivered to a view when a gesture is recognized.
Так что если вы просто делаете
tapRecon.cancelsTouchesInView = NO;
это может быть еще более простым решением, в зависимости от вашего варианта использования. Вот как я делаю это в моем приложении.
При нажатии кнопки на панели навигации выполняется ее действие (по желанию), но UIGestureRecognizer
Действие выполняется также. Если это не беспокоит вас, то это будет самое простое решение, которое я могу придумать.
iOS7 версия:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
CGPoint point = [touch locationInView:touch.view];
UINavigationBar *naviagationBar = (UINavigationBar *)touch.view;
NSString *navigationItemViewClass = [NSString stringWithFormat:@"UINavigationItem%@%@",@"Button",@"View"];
for (id subview in naviagationBar.subviews) {
if (([subview isKindOfClass:[UIControl class]] ||
[subview isKindOfClass:NSClassFromString(navigationItemViewClass)]) &&
[subview pointInside:point withEvent:nil]) {
return NO;
}
}
return YES;
}
РЕДАКТИРОВАТЬ:
Что-то в углу жеста кнопки "Назад" по-прежнему перезаписывается, поэтому код pointInside:withEvent
:
CGRectContainsPoint((CGRect){ .origin = subview.frame.origin, .size = CGSizeMake(subview.frame.size.width + 16, subview.frame.size.height)}, point)
Xamarin.iOS не предоставляет оболочки C# для классов Objective-C в закрытом API, поэтому аккуратная проверка подклассов, предложенная @ben-flynn выше, здесь не сработает.
Несколько хакерский обходной путь - проверить Description
Поле зрения:
navigationTitleTap = new UITapGestureRecognizer (tap => DidTapNavigationTitle());
navigationTitleTap.ShouldReceiveTouch = (recognizer, touch) =>
touch.View.Subviews.Any(sv =>
// Is this the NavigationBar's title or prompt?
(sv.Description.StartsWith("<UINavigationItemView") || sv.Description.StartsWith("<UINavBarPrompt")) &&
// Was the nested label actually tapped?
sv.Subviews.OfType<UILabel>().Any(label =>
label.Frame.Contains(touch.LocationInView(sv))));
NavigationController.NavigationBar.AddGestureRecognizer (navigationTitleTap);
Фильтр коллекции Linq .OfType<T>
Это удобно при ловле для определенных типов в иерархии представлений.
Это сработало для меня, это основано на ответе Ставаша. Я использую свойство view распознавателя жестов, чтобы вернуть YES/NO в методе делегата.
Это старое приложение, поэтому очевидно, что это не ARC, не используются новые элементы макета или строки NSAttributed. Я оставляю это вам:p
- (void)viewDidLoad
{
...
CGRect r = self.navigationController.navigationBar.bounds;
UILabel *titleView = [[UILabel alloc] initWithFrame:r];
titleView.autoresizingMask =
UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
titleView.textAlignment = NSTextAlignmentCenter;
titleView.font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
titleView.text = self.title;
titleView.userInteractionEnabled = YES;
UITapGestureRecognizer *tgr =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(titleViewWasTapped:)];
tgr.numberOfTapsRequired = 1;
tgr.numberOfTouchesRequired = 1;
tgr.delegate = self;
[titleView addGestureRecognizer:tgr];
[tgr release];
self.navigationItem.titleView = titleView;
[titleView release];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
// This method is needed because the navigation bar with back
// buttons will swallow touch events
return (gestureRecognizer.view == self.navigationItem.titleView);
}
Затем вы используете распознаватель жестов как обычно
- (void)titleViewWasTapped:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateRecognized) {
return;
}
...
}