2010-06-15 3 views
143

Предположим, у нас есть контроллер вида с одним подзором. subview занимает центр экрана с полями 100 px со всех сторон. Затем мы добавляем кучу маленьких вещей, чтобы щелкнуть внутри этого подсмотра. Мы используем только subview, чтобы воспользоваться новым фреймом (x = 0, y = 0 внутри subview фактически 100 100 в родительском представлении).Как я могу нажать кнопку за прозрачным UIView?

Тогда представьте, что у нас есть что-то позади, как меню. Я хочу, чтобы пользователь мог выбрать какой-либо из «мелочей» в подвью, но если там ничего нет, я хочу, чтобы прикосновения проходили через него (поскольку фон все равно ясен) кнопкам позади него.

Как это сделать? Похоже, что прикосновения к нему идут, но кнопки не работают.

+1

Я думал, что прозрачный (альфа 0) UIViews не должен реагировать на события касания? –

+0

Я написал для этого небольшой класс. (Добавлен пример в ответах). Решение там несколько лучше, чем принятый ответ, потому что вы все равно можете щелкнуть 'UIButton', который находится под полупрозрачным' UIView', в то время как непрозрачная часть 'UIView' будет реагировать на события касания. – Segev

ответ

260

Создать настраиваемое представление для вашего контейнера и переопределить pointInside: сообщение вернуть NO, если точка не находится в допустимом зрения ребенка, как это:

@interface PassthroughView : UIView 
@end 

@implementation PassthroughView 
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 
    for (UIView *view in self.subviews) { 
     if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) 
      return YES; 
    } 
    return NO; 
} 
@end 

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

Edit: Вот Swift версия

class PassThroughView: UIView { 
    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool { 
     for subview in subviews { 
      if !subview.hidden && subview.userInteractionEnabled && subview.pointInside(convertPoint(point, toView: subview), withEvent: event) { 
       return true 
      } 
     } 
     return false 
    } 
} 

Swift 3:

class PassThroughView: UIView { 
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 
     for subview in subviews { 
      if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) { 
       return true 
      } 
     } 
     return false 
    } 
} 
+3

Интересно. Мне нужно больше погрузиться в цепочку ответчиков. –

+1

вам нужно проверить, видимо ли это вид, тогда вам также нужно проверить, видимы ли видимые объекты перед их тестированием. –

+0

Я использую приведенный выше пример в своем производственном коде и правильно игнорирует скрытые виды. Метод pointInside: теперь уже проверяет скрытый флаг, насколько я могу судить. –

0

Насколько я знаю, вы должны сделать это, переопределив метод hitTest:. Я попробовал, но не смог заставить его работать правильно.

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

1

Согласно 'iPhone Application Programming Guide':

Выключение поставки сенсорных событий. По умолчанию в представлении поступают события , но вы можете установить для свойства userInteractionEnabled значение NO , чтобы отключить доставку событий. Вид также не принимает события, если он скрыт или если он прозрачный.

http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

Обновлено: Удален пример - перечитать вопрос ...

У вас есть какая-либо обработка жеста мнений, которые могут быть обработками отводов до кнопки получает? Работает ли кнопка, когда у вас нет прозрачного представления?

Любые примеры кода нерабочего кода?

+0

Да, я писал в своем вопросе, что нормальные штрихи работают под представлениями, но UIButtons и другие элементы не работают. –

84

Я также использую

myView.userInteractionEnabled = NO; 

Нет необходимости подкласса. Работает отлично.

+53

Это также отключит взаимодействие пользователя для любые подпункты – pixelfreak

17

From Apple:

экспедиторская Событие это метод, используемый некоторыми приложениями. Вы пересылаете события касания, вызывая методы обработки событий другого объекта-ответчика. Хотя это может быть эффективным методом, вы должны использовать его с осторожностью. Классы инфраструктуры UIKit не предназначены для получения касаний, которые не привязаны к ним. Если вы хотите условно переадресовать касания другим респондентам в вашем приложении, все эти респонденты должны быть экземплярами ваших собственных подклассов UIView.

Apples Best Practise:

ли явно не посылать события вверх по цепочке ответчик (через nextResponder); вместо этого вызовите реализацию суперкласса и позвольте UIKit обрабатывать цепочку обратных цепочек.

вместо этого вы можете переопределить:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 

в вашем UIView подкласса и вернуть NO, если вы хотите, что прикосновение к отправке на иммунокомпетентные цепь (И.Е. с видом за ваш взгляд с ничего в нем).

+1

Было бы замечательно иметь ссылку на документы, которые вы цитируете, вместе с самими цитатами. – morgancodes

+1

Я добавил ссылки на соответствующие места в документации для вас – jackslash

+1

~~ Не могли бы вы показать, как должно выглядеть это переопределение? ~~ Нашел [ответ в другом вопросе] (http://stackoverflow.com/a/ 12355957/999162) о том, как это будет выглядеть; это буквально просто возвращает «НЕТ»; – kontur

5

Опираясь на то, что писал Джон, вот пример, который позволит событию прикосновения пройти через все подвиды одного обзора для кнопок, за исключением:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
{ 
    // Allow buttons to receive press events. All other views will get ignored 
    for(id foundView in self.subviews) 
    { 
     if([foundView isKindOfClass:[UIButton class]]) 
     { 
      UIButton *foundButton = foundView; 

      if(foundButton.isEnabled && !foundButton.hidden && [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event]) 
       return YES; 
     }   
    } 
    return NO; 
} 
+0

Необходимо проверить, видимо ли вид, и видны ли подзоны. –

+0

Идеальное решение! Еще более простой подход заключался бы в создании подкласса 'UIView'' PassThroughView', который просто переопределяет точку '- (BOOL) pointInside: (CGPoint) с событиемEvent: (UIEvent *) {return YES; } ', если вы хотите пересылать любые события касания под представлениями. – auco

2

Я создал категорию, чтобы сделать это.

немного метод swizzling и вид золотой.

Заголовок

//UIView+PassthroughParent.h 
@interface UIView (PassthroughParent) 

- (BOOL) passthroughParent; 
- (void) setPassthroughParent:(BOOL) passthroughParent; 

@end 

Файл реализации

#import "UIView+PassthroughParent.h" 

@implementation UIView (PassthroughParent) 

+ (void)load{ 
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:)); 
} 

- (BOOL)passthroughParent{ 
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"]; 
    if (passthrough) return passthrough.boolValue; 
    return NO; 
} 
- (void)setPassthroughParent:(BOOL)passthroughParent{ 
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"]; 
} 

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{ 
    // Allow buttons to receive press events. All other views will get ignored 
    if (self.passthroughParent){ 
     if (self.alpha != 0 && !self.isHidden){ 
      for(id foundView in self.subviews) 
      { 
       if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event]) 
        return YES; 
      } 
     } 
     return NO; 
    } 
    else { 
     return [self passthroughPointInside:point withEvent:event];// Swizzled 
    } 
} 

@end 

Вам нужно будет добавить мой Swizz.h и Swizz.m

расположен Here

После этого, вы просто импортируйте UIView + PassthroughParent.h в свой {Project} -Prefix.pch файл, и каждое представление будет иметь эту способность.

каждый вид займет точки, но ни одно пустое место не будет.

Я также рекомендую использовать чистый фон.

myView.passthroughParent = YES; 
myView.backgroundColor = [UIColor clearColor]; 

EDIT

Я создал свой собственный мешок собственности, и не был включен ранее.

Заголовочный файл

// NSObject+PropertyBag.h 

#import <Foundation/Foundation.h> 

@interface NSObject (PropertyBag) 

- (id) propertyValueForKey:(NSString*) key; 
- (void) setPropertyValue:(id) value forKey:(NSString*) key; 

@end 

Файл Реализация

// NSObject+PropertyBag.m 

#import "NSObject+PropertyBag.h" 



@implementation NSObject (PropertyBag) 

+ (void) load{ 
    [self loadPropertyBag]; 
} 

+ (void) loadPropertyBag{ 
    @autoreleasepool { 
     static dispatch_once_t onceToken; 
     dispatch_once(&onceToken, ^{ 
      Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc)); 
     }); 
    } 
} 

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag 
- (id) propertyValueForKey:(NSString*) key{ 
    return [[self propertyBag] valueForKey:key]; 
} 
- (void) setPropertyValue:(id) value forKey:(NSString*) key{ 
    [[self propertyBag] setValue:value forKey:key]; 
} 
- (NSMutableDictionary*) propertyBag{ 
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; 
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]]; 
    if (propBag == nil){ 
     propBag = [NSMutableDictionary dictionary]; 
     [self setPropertyBag:propBag]; 
    } 
    return propBag; 
} 
- (void) setPropertyBag:(NSDictionary*) propertyBag{ 
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; 
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]]; 
} 

- (void)propertyBagDealloc{ 
    [self setPropertyBag:nil]; 
    [self propertyBagDealloc];//Swizzled 
} 

@end 
+0

Метод passthroughPointInside работает хорошо для меня (даже без использования каких-либо прорывных или swizz-вещей - просто переименуйте passthroughPointInside в pointInside), спасибо большое. –

+0

Что такое свойствоValueForKey? – Skotch

+1

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

1

Принимая советы от других ответов и читать на документации Apple, я создал эту простую библиотеку для решения вашей проблемы:
https://github.com/natrosoft/NATouchThroughView
Это облегчает рисование представлений в Interface Builder, которые должны передавать штрихи в базовое представление.

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

Существует демонстрационный проект, и, надеюсь, README хорошо справляется, объясняя, что делать. Чтобы обратиться к OP, вы измените прозрачный UIView, содержащий кнопки в классе NATouchThroughView в Interface Builder. Затем найдите ясный UIView, который накладывает меню, которое вы хотите использовать. Измените этот UIView на класс NARootTouchThroughView в Interface Builder. Это может быть даже корневой UIView вашего контроллера вида, если вы намерены передать эти штрихи в основной контроллер представления. Просмотрите демонстрационный проект, чтобы узнать, как он работает. Это действительно довольно просто, безопасно и неинвазивно

6

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

Это решение несколько лучше, чем принято отвечать, потому что вы все еще можете нажать на UIButton, который под полупрозрачный UIView, а не прозрачная часть UIView все равно будет реагировать на событие нажатия.

GIF

Как вы можете видеть в GIF, кнопка Жираф является простой прямоугольник, но сенсорные события на прозрачные участки передаются на желтом UIButton внизу.

Link to class

0

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

+2

Привет, добро пожаловать в переполнение стека. Просто подсказка для вас как нового пользователя: вы можете быть осторожны с вашим тоном. Я бы избегал фразы типа «если вы не можете беспокоиться»; это может показаться снисходительным – nomistic

+0

Спасибо за головы. Это было скорее с ленивой точки зрения, чем снисходительно, но с точки зрения. –

5

Более простой способ - «Отменить проверку» взаимодействия с пользователем. Включено в построителе интерфейса.«Если вы используете раскадровку»

enter image description here

+1

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

0

Swift 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 
    for subview in subviews { 
     if subview.frame.contains(point) { 
      return true 
     } 
    } 
    return false 
} 
0

Top голосовавших раствор полностью не работает для меня, я думаю, это потому, что я имел TabBarController в иерархии (как отмечается в одном из замечаний), он фактически проходил по прикосновениям к некоторым частям пользовательского интерфейса, но он возился с способностью моего tableView перехватывать события касания, что, в конце концов, было переопределяющим hitTest в представлении, которое я хочу игнорировать Touc hes и позволить подсмотрам этого вида обрабатывать их

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ 
    UIView *view = [super hitTest:point withEvent:event]; 
    if (view == self) { 
     return nil; //avoid delivering touch events to the container view (self) 
    } 
    else{ 
     return view; //the subviews will still receive touch events 
    } 
} 
Смежные вопросы