Як я можу натиснути кнопку за прозорим UIView?


177

Скажімо, у нас є контролер перегляду з одним підвидом. субпредмет займає центр екрана з полями 100 пікселів з усіх боків. Потім ми додаємо купу дрібниць, щоб натиснути всередині цього перегляду. Ми використовуємо лише підперегляд, щоб скористатися новим кадром (x = 0, y = 0 всередині підпогляду насправді 100,100 в батьківському поданні).

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

Як я можу це зробити? Схоже, що дотики починаються, але кнопки не працюють.


1
Я думав, що прозорі (альфа 0) UIView не повинні реагувати на торкаються подій?
Evadne Wu

1
Я написав невеликий клас саме для цього. (Додано приклад у відповідях). Рішення є дещо кращим, ніж прийнята відповідь, оскільки ви все одно можете натиснути кнопку, UIButtonяка знаходиться під напівпрозорою, UIViewтоді як непрозора частина UIViewзаповіту все одно буде реагувати на дотики дотику.
Сегев

Відповіді:


315

Створіть спеціальний вигляд для свого контейнера та замініть поле pointInside: повідомлення, яке поверне помилкове значення, коли точка не знаходиться в придатному дитячому представленні, наприклад:

Швидкий:

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

Мета C:

@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

Використання цього виду як контейнера дозволить будь-якому з його дітей отримувати дотики, але сам погляд буде прозорим для подій.


4
Цікаво. Мені потрібно більше зануритися у ланцюжок реагування.
Шон Кларк Хесс

1
вам також потрібно перевірити, чи вигляд також видимий, тоді вам також потрібно перевірити, чи бачать підперегляди перед їх тестуванням.
Ледачий кодер

2
на жаль, цей трюк не працює для tabBarController, для якого погляд неможливо змінити. Хтось має ідею зробити цей погляд прозорим і для подій?
cyrilchampier

1
Ви також повинні перевірити альфа перегляду. Досить часто я приховую вигляд, встановлюючи альфа на нуль. Вид з нульовою альфа повинен діяти як прихований вид.
Джеймс Ендрюс

2
Я зробив швидку версію: можливо, ви можете включити її у відповідь на видимість gist.github.com/eyeballz/17945454447c7ae766cb
eyeballz

107

Я також використовую

myView.userInteractionEnabled = NO;

Не потрібно підклас. Добре працює.


76
Це також відключить взаємодію з користувачем для будь-яких
підзаписів

Як згадував @pixelfreak, це не ідеальне рішення для всіх випадків. Випадки, коли взаємодія на підглядах цього виду все ще бажана, вимагає, щоб прапор залишався увімкненим.
Augusto Carmo

20

Від Apple:

Переадресація подій - це техніка, яку використовують деякі програми. Ви переадресовуєте події, використовуючи методи обробки подій іншого об'єкта-респондента. Хоча це може бути ефективною технікою, слід використовувати її обережно. Класи фреймворку UIKit не розроблені для отримання дотиків, які не пов'язані з ними .... Якщо ви хочете умовно пересилати дотики до інших респондентів у вашій заявці, всі ці відповіді повинні бути примірниками ваших власних підкласів UIView.

Найкраща практика яблук :

Не надсилайте явно події в ланцюжку відповідей (через nextResponder); натомість виклик реалізації суперкласу та дозвольте UIKit обробляти обхід ланцюга відповідей.

замість цього ви можете перекрити:

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

у вашому підкласі UIView та поверніть НІ, якщо ви хочете, щоб цей дотик був надісланий у відповідь ланцюжком (IE для переглядів позаду вашого перегляду, у ньому нічого немає).


1
Було б дивовижно мати посилання на документи, які ви цитуєте, разом із самими цитатами.
morgancodes

1
Я додав посилання на відповідні місця в документації для вас
чорновий косур

1
~~ Чи можете ви показати, як має виглядати це переоцінка? ~~ Знайшов відповідь в іншому запитанні, як це виглядатиме; це буквально просто повертається NO;
kontur

Це, мабуть, має бути прийнятою відповіддю. Це найпростіший і рекомендований спосіб.
Еквадор

8

Набагато простішим способом є "Увімкнути скасування" Включеної взаємодії користувачів у програмі створення інтерфейсу. "Якщо ви використовуєте табло"

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


6
як зазначали інші у подібній відповіді, це не допомагає в цьому випадку, оскільки це також вимикає події, що торкаються в нижньому погляді. Іншими словами: кнопку під цим поданням не можна торкнутись, незалежно від того, ввімкнути або вимкнути цей параметр.
auco

6

Спираючись на те, що Джон розмістив, ось приклад, який дозволить подій із сенсорним проходженням пройти через усі підгляди представлення, крім кнопок:

-(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;
}

Потрібно перевірити, чи перегляд видимий, і чи бачать підгляди.
Ледачий кодер

Ідеальне рішення! Ще простішим підходом було б створити UIViewпідклас, PassThroughViewякий просто переосмислює, -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }якщо ви хочете перенести будь-які сенсорні події під перегляди.
auco

6

Нещодавно я написав клас, який допоможе мені просто в цьому. Використання його як спеціальний клас для UIButtonабоUIView передаватиме сенсорні події, виконані на прозорому пікселі.

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

GIF

Як ви бачите в GIF, кнопка Giraffe - це простий прямокутник, але події, що торкаються на прозорих ділянках, передаються в жовтий колір UIButton.

Посилання на клас


1
Хоча це рішення може працювати, краще розмістити відповідні частини вашого рішення у межах відповіді.
Коен.

4

Рішення, яке проголосувало вгорі, для мене не повністю працювало, я думаю, це було тому, що я мав TabBarController в ієрархію (як вказує один із коментарів), він насправді передавав штрихи до деяких частин інтерфейсу, але він псувався з моїм здатність Tableview на перехоплення подій дотику, то , що , нарешті , зробив це було перевизначення Трасування з точки зору я хочу , щоб ігнорувати штрихи і нехай підвидів цього виду з ними впоратися

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

3

Швидкий 3

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

2

Відповідно до "Посібника з програмування додатків iPhone":

Відключення доставки сенсорних подій. За замовчуванням представлення отримує сенсорні події, але ви можете встановити його властивість userInteractionEnabled на "НІ", щоб відключити доставку подій. Перегляд також не отримує події, якщо він прихований або якщо він прозорий .

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

Оновлено : Видалений приклад - перечитайте питання ...

Чи є у вас обробка жестами на видах, які можуть обробляти крани, перш ніж кнопка отримає його? Чи працює кнопка, коли у вас немає прозорого перегляду?

Будь-які зразки коду непрацюючого коду?


Так, я писав у своєму запитанні, що звичайні штрихи працюють внизу, але UIB-кнопки та інші елементи не працюють.
Шон Кларк Хесс

1

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

Врешті-решт я створив низку прозорих поглядів на предмет, що торкається, щоб вони не прикривали його. Трохи хак для мого питання це спрацювало чудово.


1

Отримуючи поради з інших відповідей і читаючи документацію Apple, я створив цю просту бібліотеку для вирішення вашої проблеми:
https://github.com/natrosoft/NATouchThroughView
Це дозволяє легко малювати погляди в Builder інтерфейсу, який повинен передавати дотики до основний погляд.

Я думаю, що метод перекручування є надмірним і дуже небезпечним у виробничому коді, тому що ви безпосередньо псуєтесь із базовою реалізацією Apple і вносите зміни в застосуванні, що може спричинити непередбачувані наслідки.

Існує демонстраційний проект, і, сподіваємось, README добре працює, пояснюючи, що робити. Для звернення до ОП ви б змінили чіткий UIView, який містить кнопки для класу NATouchThroughView в Interface Builder. Потім знайдіть чіткий UIView, який накладає меню, яке ви хочете мати можливість натискання. Змініть цей UIView на клас NARootTouchThroughView в Interface Builder. Це навіть може бути кореневим UIView вашого контролера перегляду, якщо ви маєте намір ці штрихи перейти до базового контролера перегляду. Перегляньте демонстраційний проект, щоб побачити, як він працює. Це дійсно досить просто, безпечно і неінвазивно


1

Я створив категорію для цього.

трохи метод закружляє, і вид золотий.

Заголовок

//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

розташований тут

Після цього ви просто імпортуєте 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

метод passthroughPointInside добре працює для мене (навіть без використання будь-якого з матеріалів, що задаються через прозорий або swizz - просто перейменуйте passthroughPointInside на pointInside), велике спасибі.
Бенджамін Піет

Що таке propertyValueForKey?
Скотч

1
Хм. Ніхто цього не вказував. Я створив спеціальний словник вказівника, який містить властивості в розширенні класу. Не бачу, чи можу я його включити сюди.
Ледачий кодер

Відомі способи закрутки - це делікатне хакерське рішення для рідкісних випадків та розваг для розробників. Це, безумовно, не безпечно для магазину додатків, тому що цілком ймовірно зламати та зламати додаток із наступним оновленням ОС. Чому б просто не переоцінити pointInside: withEvent:?
auco

0

Якщо ви не можете зайнятися використанням категорії або підкласу UIView, ви також можете просто перенести кнопку вперед, щоб вона була перед прозорим поданням. Це не завжди можливо в залежності від вашої заявки, але це працювало для мене. Ви завжди можете повернути кнопку знову або приховати.


2
Привіт, Ласкаво просимо, щоб стек переповнення. Лише натяк для вас як нового користувача: можливо, ви хочете бути обережними зі своїм тоном. Я б уникав фрази на кшталт "якщо ти не можеш турбуватися"; це може здатися поблажливим
номістично

Дякую за голови вгору. Це було більше з ледачої точки зору, ніж поблажливість, але точка зору.
Похитувався Саяг

0

Спробуйте це

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 

0

Спробуйте встановити backgroundColorсвою transparentViewяк UIColor(white:0.000, alpha:0.020). Тоді ви можете отримати сенсорні події в touchesBegan/ touchesMovedметодах. Розмістіть код нижче десь переглянуто ваше перегляд:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true

0

Я використовую це замість переопределення точки методу (всередині: CGPoint, з: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.