Как предотвратить UINavigationBar от блокировки касаний на просмотр

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *view = [super hitTest:point withEvent:event];

    if (view.tag == 399)
    {
        return view;
    }
    else
    {
        return nil;
    }
}

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

Ответ 1

Подкласс UINavigationBar и переопределить - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event так, чтобы он возвращал NO для прямоугольника, где вид, который вы хотите получить касания, и YES в противном случае.

Например:

Подкласс UINavigationBar.h:

@property (nonatomic, strong) NSMutableArray *viewsToIgnoreTouchesFor; 

Подкласс UINavigationBar.m:

- (NSMutableArray *)viewsToIgnoreTouchesFor
{
    if (!_viewsToIgnoreTouchesFor) {
        _viewsToIgnoreTouchesFor = [@[] mutableCopy];
    }
    return _viewsToIgnoreTouchesFor;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL pointInSide = [super pointInside:point withEvent:event];
    for (UIView *view in self.viewsToIgnoreTouchesFor) {

        CGPoint convertedPoint = [view convertPoint:point fromView:self];
        if ([view pointInside:convertedPoint withEvent:event]) {
            pointInSide = NO;
            break;
        }
    }

    return pointInSide;
}

В полноэкранном режиме просмотраController, где у вас есть вид за навигационной панелью, добавьте эти строки в viewDidLoad

UINavigationBarSubclass *navBar = 
(UINavigationBarSubclass*)self.navigationController.navigationBar;
[navBar.viewsToIgnoreTouchesFor addObject:self.buttonBehindBar];

Обратите внимание: это не будет посылать касания к навигационной панели, то есть если вы добавите представление, которое находится за кнопками на navBar, кнопки на navBar не получат штрихов.

Swift:

var viewsToIgnore = [UIView]()

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    let ignore = viewsToIgnore.first {
        let converted = $0.convert(point, from: self)
        return $0.point(inside: converted, with: event)
    }
    return ignore == nil && super.point(inside: point, with: event)
}

Подробнее о pointInside:withEvent:

см. documentation.

Также, если pointInside:withEvent: не работает так, как вам нужно, возможно, стоит попробовать выполнить код выше в hitTest:withEvent:.

Ответ 2

Здесь версия, которая не требует установки определенных представлений, которые вы хотите включить внизу. Вместо этого он пропускает любое касание, кроме случаев, когда это касание происходит в пределах UIControl или ракурса с UIGestureRecognizer.

import UIKit

/// Passes through all touch events to views behind it, except when the
/// touch occurs in a contained UIControl or view with a gesture
/// recognizer attached
final class PassThroughNavigationBar: UINavigationBar {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard nestedInteractiveViews(in: self, contain: point) else { return false }
        return super.point(inside: point, with: event)
    }

    private func nestedInteractiveViews(in view: UIView, contain point: CGPoint) -> Bool {

        if view.isPotentiallyInteractive, view.bounds.contains(convert(point, to: view)) {
            return true
        }

        for subview in view.subviews {
            if nestedInteractiveViews(in: subview, contain: point) {
                return true
            }
        }

        return false
    }
}

fileprivate extension UIView {
    var isPotentiallyInteractive: Bool {
        guard isUserInteractionEnabled else { return false }
        return (isControl || doesContainGestureRecognizer)
    }

    var isControl: Bool {
        return self is UIControl
    }

    var doesContainGestureRecognizer: Bool {
        return !(gestureRecognizers?.isEmpty ?? true)
    }
}

Ответ 3

Быстрое решение для вышеупомянутого ответа Цель C.

class MyNavigationBar: UINavigationBar {

    var viewsToIgnoreTouchesFor:[UIView] = []

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var pointInside = super.point(inside: point, with: event)

        for each in viewsToIgnoreTouchesFor {
            let convertedPoint = each.convert(point, from: self)
            if each.point(inside: convertedPoint, with: event) {
                pointInside = false
                break
            }
        }
        return pointInside
    }
}

Теперь установите представления, касания которых вы хотите захватить, под панелью навигации, как показано ниже, из метода viewDidLoad или любого другого подходящего места в коде

if let navBar = self.navigationController?.navigationBar as? MyNavigationBar {
    navBar.viewsToIgnoreTouchesFor = [btnTest]
}

Счастливого прикосновения!