objective c - Permitindo a interação com um UIView sob outro UIView



objective-c ios (13)

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
}

https://src-bin.com

Existe uma maneira simples de permitir a interação com um botão em um UIView que fica sob outro UIView - onde não há objetos reais do UIView superior na parte superior do botão?

Por exemplo, no momento eu tenho um UIView (A) com um objeto no topo e um objeto na parte inferior da tela e nada no meio. Isso fica no topo de outro UIView que possui botões no meio (B). No entanto, parece que não consigo interagir com os botões no meio de B.

Eu posso ver os botões em B - defini o plano de fundo de A para clearColor - mas os botões em B parecem não receber toques apesar do fato de não haver objetos de A na parte superior desses botões.

EDIT - Eu ainda quero poder interagir com os objetos no topo do UIView

Certamente há uma maneira simples de fazer isso?


Answer #1

Implementação do Swift 4 para solução baseada em HitTest

let hitView = super.hitTest(point, with: event)
if hitView == self { return nil }
return hitView

Answer #2

Acho que estou um pouco atrasado para essa festa, mas adicionarei essa possível solução:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView != self) return hitView;
    return [self superview];
}

Se você usar esse código para substituir uma função hitTest padrão do UIView, ele ignorará APENAS a visão em si. Qualquer subvisualização dessa visualização retornará seus resultados normalmente, e quaisquer ocorrências que tenham sido direcionadas para a visualização em si serão passadas para sua supervisualização.

-Cinza


Answer #3

Apenas riffing na resposta aceita e colocando isso aqui para minha referência. A resposta aceita funciona perfeitamente. Você pode estendê-lo dessa forma para permitir que as subvisualizações de sua visualização recebam o toque ou para passá-lo para qualquer visualização que esteja atrás de nós:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // If one of our subviews wants it, return YES
    for (UIView *subview in self.subviews) {
        CGPoint pointInSubview = [subview convertPoint:point fromView:self];
        if ([subview pointInside:pointInSubview withEvent:event]) {
            return YES;
        }
    }
    // otherwise return NO, as if userInteractionEnabled were NO
    return NO;
}

Nota: Você nem precisa fazer recursão na árvore de pointInside:withEvent: , porque cada pointInside:withEvent: tratará disso para você.


Answer #4

Derivada da excelente e principalmente infalível resposta de Stuart, e da implementação útil de Segev, aqui está um pacote do Swift 4 que você pode colocar em qualquer projeto:

extension UIColor {
    static func colorOfPoint(point:CGPoint, in view: UIView) -> UIColor {

        var pixel: [CUnsignedChar] = [0, 0, 0, 0]

        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)

        let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

        context!.translateBy(x: -point.x, y: -point.y)

        view.layer.render(in: context!)

        let red: CGFloat   = CGFloat(pixel[0]) / 255.0
        let green: CGFloat = CGFloat(pixel[1]) / 255.0
        let blue: CGFloat  = CGFloat(pixel[2]) / 255.0
        let alpha: CGFloat = CGFloat(pixel[3]) / 255.0

        let color = UIColor(red:red, green: green, blue:blue, alpha:alpha)

        return color
    }
}

E então com hitTest:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard UIColor.colorOfPoint(point: point, in: self).cgColor.alpha > 0 else { return nil }
    return super.hitTest(point, with: event)
}

Answer #5

Embora muitas das respostas aqui funcionem, estou um pouco surpreso ao ver que a resposta mais conveniente, genérica e infalível não foi dada aqui. @Ash chegou mais perto, exceto que há algo estranho acontecendo com o retorno da super visão ... não faça isso.

Esta resposta é tirada de uma resposta que eu dei a uma pergunta semelhante, here .

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

[super hitTest:point withEvent:event] retornará a visão mais profunda na hierarquia dessa visão que foi tocada. Se hitView == self (ou seja, se não houver subvisualização sob o ponto de contato), retorne nil , especificando que essa visualização não deve receber o toque. A maneira como a cadeia de respostas funciona significa que a hierarquia de vistas acima desse ponto continuará a ser percorrida até que seja encontrada uma visualização que responda ao toque. Não retorne a super visão, já que não cabe a essa visão se sua super visão deve aceitar toques ou não!

Esta solução é:

  • conveniente , porque não requer referências a quaisquer outras visualizações / subvisões / objetos;
  • genérico , porque se aplica a qualquer visualização que atue apenas como um contêiner para subvisualizações tangíveis, e a configuração das subvisualizações não afeta a maneira como ele funciona (como acontece se você substituir o pointInside:withEvent: para retornar uma área pointInside:withEvent: em particular).
  • infalível , não há muito código ... e o conceito não é difícil de entender.

Eu uso isso com freqüência suficiente para que eu tenha abstraído-lo em uma subclasse para salvar subclasses de vista sem sentido para uma substituição. Como bônus, adicione uma propriedade para configurá-la:

@interface ISView : UIView
@property(nonatomic, assign) BOOL onlyRespondToTouchesInSubviews;
@end

@implementation ISView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self && onlyRespondToTouchesInSubviews) return nil;
    return hitView;
}
@end

Então, enlouqueça e use essa visão onde quer que você use um UIView simples. Configurá-lo é tão simples quanto configurar onlyRespondToTouchesInSubviews para YES .


Answer #6

Eu acho que o caminho certo é usar a cadeia de visão embutida na hierarquia da visão. Para suas subvisualizações que são colocadas na visualização principal, não use o UIView genérico, mas, em vez disso, subclique o UIView (ou uma de suas variantes como UIImageView) para fazer MYView: UIView (ou qualquer supertipo que você desejar, como UIImageView). Na implementação do YourView, implemente o método toquesBegan. Este método será então chamado quando essa visão for tocada. Tudo o que você precisa ter nessa implementação é um método de instância:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ;
{   // cannot handle this event. pass off to super
    [self.superview touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]; }

este toquesBegan é uma api responder, então você não precisa declará-lo em sua interface pública ou privada; é uma daquelas api mágicas que você só precisa saber. Esta self.superview irá borbulhar a requisição eventualmente para o viewController. No viewController, então, implemente este toques em Vegetarian para manipular o toque.

Observe que o local dos toques (CGPoint) é ajustado automaticamente em relação à visão abrangente para você, pois é retornado para a cadeia da hierarquia da vista.


Answer #7

Eu nunca construí uma interface de usuário completa usando o toolkit de UI, então não tenho muita experiência com isso. Aqui está o que eu acho que deveria funcionar.

Todo UIView, e este o UIWindow, tem uma propriedade subviews , que é um NSArray contendo todas as subvisualizações.

A primeira subvisualização adicionada a uma exibição receberá o índice 0 e o próximo índice 1 e assim por diante. Você também pode substituir addSubview: com insertSubview: atIndex: ou insertSubview:aboveSubview: e os métodos que podem determinar a posição de sua subvisualização na hierarquia.

Portanto, verifique seu código para ver qual visualização você adiciona primeiro à sua interface do usuário. Isso será 0, o outro será 1.
Agora, de uma das suas subvisualizações, para alcançar outra, você faria o seguinte:

UIView * theOtherView = [[[self superview] subviews] objectAtIndex: 0];
// or using the properties syntax
UIView * theOtherView = [self.superview.subviews objectAtIndex:0];

Deixe-me saber se isso funciona para o seu caso!

(abaixo deste marcador está minha resposta anterior):

Se as visões precisarem se comunicar, elas deverão fazê-lo por meio de um controlador (isto é, usando o popular modelo MVC ).

Quando você cria uma nova visão, você pode se certificar de que ela se registra com um controlador.

Portanto, a técnica é garantir que suas visualizações sejam registradas com um controlador (que pode armazená-las por nome ou o que você preferir em um dicionário ou matriz). Você pode fazer com que o controlador envie uma mensagem para você ou pode obter uma referência para a exibição e se comunicar diretamente com ela.

Se a sua visualização não tiver um link para o controlador (o que pode ser o caso), você poderá usar métodos singletons e / ou class para obter uma referência ao seu controlador.


Answer #8

Há algo que você pode fazer para interceptar o toque em ambas as visualizações.

Vista do topo:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   // Do code in the top view
   [bottomView touchesBegan:touches withEvent:event]; // And pass them on to bottomView
   // You have to implement the code for touchesBegan, touchesEnded, touchesCancelled in top/bottom view.
}

Mas essa é a ideia.


Answer #9

Implementação personalizada de pointInside: withEvent: de fato parecia ser o caminho a percorrer, mas lidar com coordenadas codificadas parecia estranho para mim. Então acabei verificando se o CGPoint estava dentro do botão CGRect usando a função CGRectContainsPoint ():

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    return (CGRectContainsPoint(disclosureButton.frame, point));
}

Answer #10

Só quero postar isso, porque eu tive um problema semelhante, passei uma quantidade substancial de tempo tentando implementar respostas aqui sem qualquer sorte. O que acabei fazendo:

 for(UIGestureRecognizer *recognizer in topView.gestureRecognizers)
 {
     recognizer.delegate=self;
     [bottomView addGestureRecognizer:recognizer];   
 }
 topView.abView.userInteractionEnabled=NO; 

e implementando UIGestureRecognizerDelegate :

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

Vista inferior era um controlador de navegação com número de segues e eu tinha uma espécie de porta em cima dela que poderia fechar com um gesto de pan. A coisa toda foi incorporada em outro VC. Trabalhou como um encanto. Espero que isto ajude.


Answer #11

Ultimamente eu escrevi uma aula que me ajudará com isso. Usá-lo como uma classe personalizada para um UIButton ou UIView passará por eventos de toque que foram executados em um pixel transparente.

Essa solução é um pouco melhor do que a resposta aceita, porque você ainda pode clicar em um UIButton que está sob um UIView semitransparente, enquanto a parte não transparente do UIView ainda responderá aos eventos de toque.

Como você pode ver no GIF, o botão Girafa é um retângulo simples, mas os eventos de toque em áreas transparentes são passados ​​para o UIButton amarelo embaixo.

Link para a turma


Answer #12

Você precisa definir upperView.userInteractionEnabled = NO; , caso contrário, a vista superior irá interceptar os toques.

A versão do Construtor de Interface é uma caixa de seleção na parte inferior do painel Exibir Atributos chamado "Interação do Usuário Habilitada". Desmarque e você deve estar pronto para ir.





cocoa-touch