Como subclassificar correctamente o UIControl?

Não quero nada disso. Quero subclasse directamente e fazer o meu próprio controlo muito especial.

Mas, por alguma razão, nenhum dos métodos que eu sobreponho é chamado. O material de Acção-alvo funciona, e os alvos recebem mensagens de Acção adequadas. No entanto, dentro da minha subclasse tenho de apanhar coordenadas de toque, e a única maneira de o fazer parece estar a sobrepor-se a estes tipos:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"begin touch track");
    return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"continue touch track");
    return YES;
}
Eles nunca são chamados, mesmo que ... o UIControl está instanciado com o inicializador designates de UIView, initWithFrame:.

todos os exemplos que consigo encontrar usam sempre um UIButton ou UISlider como base para subclassificação, mas quero aproximar-me de UIControl uma vez que essa é a fonte para o que eu quero: coordenadas de Toque rápidas e não marcadas.

Author: Jayesh Thanki, 2009-08-07

7 answers

Sei que esta pergunta é antiga, mas tinha o mesmo problema e achei que devia dar a minha opinião.

Se o seu controlo tiver alguma visão negativa, beginTrackingWithTouch, touchesBegan, etc pode não ser chamado porque essas visões estão engolindo os eventos de toque.

Se não quiser que essas visualizações manipulem os toques, pode definir userInteractionEnabled para NO, para que as visualizações passem simplesmente o evento. Então você pode anular touchesBegan/touchesEnded e gerenciar todos os seus toques lá.

Espero que isto ajude.
 79
Author: pixelfreak, 2011-09-25 01:53:57

Swift

Estas são mais do que uma forma de subclasse. Quando uma visão pai precisa reagir a eventos de toque ou obter outros dados do controle, isso é geralmente feito usando (1) alvos ou (2) o padrão de delegado com eventos de toque sobrepostos. Para completar, também mostrarei como (3) Fazer a mesma coisa com um reconhecedor de gestos. Cada um destes métodos se comportará como a seguinte animação:

enter image description here

Só precisas de escolher um dos seguinte.


Método 1: Adicionar um alvo

A subclasse a UIControl tem suporte para alvos já incorporados. Se você não precisa passar um monte de dados para o pai, este é provavelmente o método que você quer.

Eustomcontrol.swift

import UIKit
class MyCustomControl: UIControl {
    // You don't need to do anything special in the control for targets to work.
}

Vercontrolador.swift

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add the targets
        // Whenever the given even occurs, the action method will be called
        myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown)
        myCustomControl.addTarget(self, action: #selector(didDragInsideControl(_:withEvent:)),
                              forControlEvents: UIControlEvents.TouchDragInside)
        myCustomControl.addTarget(self, action: #selector(touchedUpInside), forControlEvents: UIControlEvents.TouchUpInside)
    }

    // MARK: - target action methods

    func touchedDown() {
        trackingBeganLabel.text = "Tracking began"
    }

    func touchedUpInside() {
        trackingEndedLabel.text = "Tracking ended"
    }

    func didDragInsideControl(control: MyCustomControl, withEvent event: UIEvent) {

        if let touch = event.touchesForView(control)?.first {
            let location = touch.locationInView(control)
            xLabel.text = "x: \(location.x)"
            yLabel.text = "y: \(location.y)"
        }
    }
}

Notas

    Não há nada de especial nos nomes dos métodos de Acção. Podia ter-lhes chamado qualquer coisa. Só tenho de ser cuidado para soletrar o nome do método exactamente como eu fiz onde adicionei o alvo. Caso contrário, vais ter um acidente.
  • os dois pontos em didDragInsideControl:withEvent: significa que dois parâmetros estão a ser passados para o método didDragInsideControl. Se você se esquecer de adicionar um cólon ou se você não tiver o número correto de Parâmetros, você vai ter um acidente.
  • graças a esta resposta para a ajuda com o evento TouchDragInside.

Transmissão de outros dados

Se TEM algum valor no seu costume controlo

class MyCustomControl: UIControl {
    var someValue = "hello"
}

Que você quer acessar no método de ação alvo, então você pode passar em uma referência ao controle. Quando estiver a definir o alvo, adicione dois pontos após o nome do método de Acção. Por exemplo:

myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown) 

Repare que é touchedDown: (com dois pontos) e não touchedDown (Sem dois pontos). O cólon significa que um parâmetro está sendo passado para o método de ação. No método de ação, especifique que o parâmetro é uma referência à sua subclasse UIControl. Com essa referência, você pode obter dados do seu controle.

func touchedDown(control: MyCustomControl) {
    trackingBeganLabel.text = "Tracking began"

    // now you have access to the public properties and methods of your control
    print(control.someValue)
}

Método 2: delegar padrões e anular eventos de Toque

Subclassing UIControl dá-nos acesso aos seguintes métodos:

  • Chama-se quando o dedo toca pela primeira vez dentro dos limites do controlo.
  • continueTrackingWithTouch é chamado repetidamente como o dedo desliza através do controle e mesmo fora dos limites do controle.
  • endTrackingWithTouch chama-se quando o dedo levanta ecra.

Se você precisa de um controle especial dos eventos de toque ou se você tem um monte de comunicação de dados para fazer com o pai, então este método pode funcionar melhor do que adicionar metas.

Aqui está como fazê-lo.

Eustomcontrol.swift

import UIKit

// These are out self-defined rules for how we will communicate with other classes
protocol ViewControllerCommunicationDelegate: class {
    func myTrackingBegan()
    func myTrackingContinuing(location: CGPoint)
    func myTrackingEnded()
}

class MyCustomControl: UIControl {

    // whichever class wants to be notified of the touch events must set the delegate to itself
    weak var delegate: ViewControllerCommunicationDelegate?

    override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {

        // notify the delegate (i.e. the view controller)
        delegate?.myTrackingBegan()

        // returning true means that future events (like continueTrackingWithTouch and endTrackingWithTouch) will continue to be fired
        return true
    }

    override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {

        // get the touch location in our custom control's own coordinate system
        let point = touch.locationInView(self)

        // Update the delegate (i.e. the view controller) with the new coordinate point
        delegate?.myTrackingContinuing(point)

        // returning true means that future events will continue to be fired
        return true
    }

    override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {

        // notify the delegate (i.e. the view controller)
        delegate?.myTrackingEnded()
    }
}

Vercontrolador.swift

É assim que o controlador de visualização é configurado para ser o delegado e responder a eventos de toque do nosso costume controlo.
import UIKit
class ViewController: UIViewController, ViewControllerCommunicationDelegate {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        myCustomControl.delegate = self
    }

    func myTrackingBegan() {
        trackingBeganLabel.text = "Tracking began"
    }

    func myTrackingContinuing(location: CGPoint) {
        xLabel.text = "x: \(location.x)"
        yLabel.text = "y: \(location.y)"
    }

    func myTrackingEnded() {
        trackingEndedLabel.text = "Tracking ended"
    }
}

Notas

  • para saber mais sobre o padrão dos delegados, veja esta resposta.
  • Não é necessário utilizar um delegado com estes métodos se estes só estiverem a ser utilizados dentro do próprio controlo aduaneiro. Eu poderia ter apenas Adicionado uma declaração para mostrar como os eventos estão sendo chamados. Nesse caso, o código seria simplificado para

    import UIKit
    class MyCustomControl: UIControl {
    
        override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            print("Began tracking")
            return true
        }
    
        override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
            let point = touch.locationInView(self)
            print("x: \(point.x), y: \(point.y)")
            return true
        }
    
        override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
            print("Ended tracking")
        }
    }
    

Método 3: Usar um gesto Reconhecedor A adição de um reconhecedor de gestos pode ser feita em qualquer visão e também funciona em um {[[10]}. Para obter resultados semelhantes ao exemplo no topo, usaremos um UIPanGestureRecognizer. Depois, testando os vários estados quando um evento é disparado, podemos determinar o que está acontecendo.

Eustomcontrol.swift

import UIKit
class MyCustomControl: UIControl {
    // nothing special is required in the control to make it work
}

Vercontrolador.swift

import UIKit
class ViewController: UIViewController {

    @IBOutlet weak var myCustomControl: MyCustomControl!
    @IBOutlet weak var trackingBeganLabel: UILabel!
    @IBOutlet weak var trackingEndedLabel: UILabel!
    @IBOutlet weak var xLabel: UILabel!
    @IBOutlet weak var yLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        // add gesture recognizer
        let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureRecognized(_:)))
        myCustomControl.addGestureRecognizer(gestureRecognizer)
    }

    // gesture recognizer action method
    func gestureRecognized(gesture: UIPanGestureRecognizer) {

        if gesture.state == UIGestureRecognizerState.Began {

            trackingBeganLabel.text = "Tracking began"

        } else if gesture.state == UIGestureRecognizerState.Changed {

            let location = gesture.locationInView(myCustomControl)

            xLabel.text = "x: \(location.x)"
            yLabel.text = "y: \(location.y)"

        } else if gesture.state == UIGestureRecognizerState.Ended {
            trackingEndedLabel.text = "Tracking ended"
        }
    }
}

Notas

  • Não se esqueça de adicionar o cólon após o nome do método de acção em action: "gestureRecognized:". O cólon significa que os parâmetros estão a ser passados para dentro.
  • se você precisa obter dados do controle, você pode implementar o padrão de delegado como no método 2 acima.
 30
Author: Suragch, 2017-05-23 12:18:25
Procurei muito por uma solução para este problema e acho que não há solução. No entanto, ao examinar mais de perto a documentação, acho que pode ser um mal-entendido que begintrackingWithTouch:withEvent: e {[2] } devem ser chamados de todo...

UIControl a documentação diz:

É melhor estender um UIControl subclasse por duas razões básicas:

Para observar ou modificar a expedição de mensagens de Acção para os objectivos para eventos particulares para fazer isso, substituir sendAction:to:forEvent:, avalie o selector de passagem, objecto-alvo ou "Nota" bit mask e proceder como necessario.

Para fornecer um comportamento de rastreamento personalizado (por exemplo, para alterar o realce aparência) para fazer isso, sobreponha um ou todos os seguintes métodos: beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent:, endTrackingWithTouch:withEvent:.

A parte crítica disto, que não é muito clara em minha opinião, é que diz que você pode querer estender uma subclasse UIControl - Não você pode querer estender UIControl diretamente. É possível que beginTrackingWithTouch:withEvent: e continuetrackingWithTouch:withEvent: não devam ser chamados em resposta a toques e que UIControl subclasses diretas devam chamá-los para que suas subclasses possam monitorar rastreamento.

Então a minha solução para isto é anular touchesBegan:withEvent: e {15]} e chamá-los de lá da seguinte forma. Note que o Multi-touch não está habilitado para este controle e que eu não me importo com toques terminados e toques cancelados eventos, mas se você quiser ser completo / completo você provavelmente deve implemente esses também.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesBegan:touches withEvent:event];
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self beginTrackingWithTouch:touch withEvent:event];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesMoved:touches withEvent:event];
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self continueTrackingWithTouch:touch withEvent:event];
}

Note que você também deve enviar quaisquer mensagens UIControlEvent* que sejam relevantes para o seu controlo usando sendActionsForControlEvents: - estas podem ser chamadas a partir dos super métodos, Eu não testei.

 20
Author: jhabbott, 2013-04-22 07:31:37

A abordagem mais simples que muitas vezes uso é estender o UIControl, mas fazer uso do método adtarget herdado para receber chamadas para os vários eventos. A chave é ouvir tanto o remetente quanto o evento para que você possa descobrir mais informações sobre o evento real (como a localização de onde o evento ocorreu).

Por isso, basta subclasse o UIControl e , em seguida, no método init (certifique-se que o seu codificador init também está configurado se estiver a usar o nibs), adicione o seguinte:

[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
É claro que pode escolher qualquer um dos eventos de controlo padrão, incluindo UIControlEventAllTouchEvents. Repare que o selector vai passar por dois objectos. O primeiro é o controlo. A segunda é a informação sobre o evento. Aqui está um exemplo de usar o evento touch para comutar um botão, dependendo se o usuário pressionou à esquerda e à direita.
- (IBAction)buttonPressed:(id)sender forEvent:(UIEvent *)event
{
    if (sender == self.someControl)
    {        
        UITouch* touch = [[event allTouches] anyObject];
        CGPoint p = [touch locationInView:self.someControl];
        if (p.x < self. someControl.frame.size.width / 2.0)
        {
            // left side touch
        }
        else
        {
            // right side touch
        }
    }
}
Certo, isto é para controles muito simplistas e você pode chegar a um ponto em que isso não lhe dará o suficiente. funcionalidade, mas isso tem funcionado para todos os meus propósitos para controles personalizados até este ponto e é realmente fácil de usar, uma vez que eu tipicamente me importo com os mesmos eventos de controle que o UIControl já suporta (toque, arrasto, etc...)

Amostra de código do controlo personalizado aqui: UISwitch personalizado (nota: isto não se regista com o selector buttonPressed: forEvent:, mas você pode descobrir isso a partir do código acima)

 10
Author: Duane Homick, 2019-02-06 11:11:10
Acho que te esqueceste de adicionar chamadas ao touchesBegan/touchesEnded/touchesMoved. Métodos como
(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event    
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event

Não está a funcionar se substituir o touchesBegan / touchesEnded desta forma:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Moved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Ended");
}
Mas! Todos funcionam bem se os métodos forem como:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesBegan:touches withEvent:event];
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesMoved:touches withEvent:event];
     NSLog(@"Touches Moved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesEnded:touches withEvent:event];
   NSLog(@"Touches Ended");
}
 9
Author: tt.Kilew, 2011-09-18 08:01:10
Estava a ter problemas com um UIControl que não respondia a {[[0]}e continueTrackingWithTouch. Descobri que o meu problema era quando o fiz.fiz a moldura para pequena (a área que reage) e tinha apenas um par de pontos onde funcionava. Eu fiz a moldura do mesmo tamanho do controle e então sempre que eu pressionei em qualquer lugar no controle ela respondeu.
 4
Author: digitalmasters, 2013-05-16 14:48:07

Obj C

Encontrei um artigo relacionado de 2014 https://www.objc.io/issues/13-architecture/behaviors/.

O interessante é que a sua abordagem é fazer uso do IB e encapsular a lógica de manipulação de eventos dentro de um objecto designado( eles chamam-lhe comportamentos), removendo assim a lógica do seu ponto de vista/vercontrolador tornando-a mais leve.

 0
Author: supergegs, 2016-05-07 11:05:12