Je ne veux pas UIButton
ou quelque chose comme ça. Je veux directement sous-classe UIControl
et créer mon propre contrôle très spécial.
Mais pour une raison quelconque, aucune des méthodes que je substitue n’est jamais appelée. Le travail cible-action fonctionne et les cibles reçoivent les messages d'action appropriés. Cependant, dans ma sous-classe UIControl
, je dois capturer les coordonnées tactiles, et le seul moyen de le faire semble être de surcharger ces types:
- (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;
}
Ils ne sont jamais appelés, même si la UIControl
est instanciée avec l'initialiseur désigné par UIView
, initWithFrame:
.
Tous les exemples que je peux trouver utilisent toujours une UIButton
ou UISlider
comme base pour le sous-classement, mais je veux aller plus près de UIControl
car c'est la source de ce que je veux: coordonnées tactiles rapides et non retardées.
Je sais que cette question est ancienne, mais j'avais le même problème et je pensais que je devrais donner mes 2 cents.
Si votre contrôle comporte des sous-vues, il est possible que beginTrackingWithTouch
, touchesBegan
, etc. ne soit pas appelé car ces sous-vues avalent les événements tactiles.
Si vous ne voulez pas que ces sous-vues gèrent les contacts, vous pouvez définir userInteractionEnabled
à NO
, afin que les sous-vues transmettent simplement l'événement. Ensuite, vous pouvez remplacer touchesBegan/touchesEnded
et gérer tous vos contacts.
J'espère que cela t'aides.
C’est plus d’un moyen de sous-classe UIControl
. Lorsqu'une vue parent doit réagir à des événements tactiles ou obtenir d'autres données du contrôle, cela se fait généralement à l'aide de (1) cibles ou (2) du modèle de délégué avec des événements tactiles remplacés. Pour être complet, je montrerai également comment (3) faire la même chose avec un identificateur de geste. Chacune de ces méthodes se comportera comme l'animation suivante:
Il vous suffit de choisir l'une des méthodes suivantes.
Une sous-classe UIControl
prend en charge les cibles déjà intégrées. Si vous n'avez pas besoin de transmettre beaucoup de données au parent, cette méthode est probablement celle que vous souhaitez.
MyCustomControl.Swift
import UIKit
class MyCustomControl: UIControl {
// You don't need to do anything special in the control for targets to work.
}
ViewController.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)"
}
}
}
Remarques
didDragInsideControl:withEvent:
signifient que deux paramètres sont passés à la méthode didDragInsideControl
. Si vous oubliez d'ajouter deux points ou si vous n'avez pas le nombre correct de paramètres, vous obtiendrez un crash.TouchDragInside
. Transmettre d'autres données
Si vous avez une valeur dans votre contrôle personnalisé
class MyCustomControl: UIControl {
var someValue = "hello"
}
auquel vous souhaitez accéder dans la méthode d’action cible, vous pouvez alors transmettre une référence au contrôle. Lorsque vous définissez la cible, ajoutez deux points après le nom de la méthode d'action. Par exemple:
myCustomControl.addTarget(self, action: #selector(touchedDown), forControlEvents: UIControlEvents.TouchDown)
Notez qu'il s'agit de touchedDown:
(avec deux points) et non de touchedDown
(sans deux points). Les deux points signifient qu'un paramètre est transmis à la méthode d'action. Dans la méthode d'action, spécifiez que le paramètre est une référence à votre sous-classe UIControl
. Avec cette référence, vous pouvez obtenir des données de votre contrôle.
func touchedDown(control: MyCustomControl) {
trackingBeganLabel.text = "Tracking began"
// now you have access to the public properties and methods of your control
print(control.someValue)
}
Le sous-classement UIControl
nous donne accès aux méthodes suivantes:
beginTrackingWithTouch
est appelé lorsque le doigt touche pour la première fois dans les limites du contrôle.continueTrackingWithTouch
est appelé à plusieurs reprises lorsque le doigt glisse sur le contrôle et même en dehors des limites du contrôle.endTrackingWithTouch
est appelé lorsque le doigt s’éloigne de l’écran.Si vous avez besoin d'un contrôle spécial des événements tactiles ou si vous avez beaucoup de communication de données à faire avec le parent, cette méthode peut alors être plus efficace que l'ajout de cibles.
Voici comment faire:
MyCustomControl.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()
}
}
ViewController.Swift
Voici comment le contrôleur de vue est configuré pour être le délégué et répondre aux événements tactiles de notre contrôle personnalisé.
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"
}
}
Remarques
Il n'est pas nécessaire d'utiliser un délégué avec ces méthodes si elles ne sont utilisées que dans le contrôle personnalisé lui-même. J'aurais pu simplement ajouter une déclaration print
pour montrer comment les événements sont appelés. Dans ce cas, le code serait simplifié pour
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")
}
}
L'ajout d'un identificateur de geste peut être fait sur n'importe quelle vue et cela fonctionne également sur une UIControl
. Pour obtenir des résultats similaires à l'exemple du haut, nous allons utiliser un UIPanGestureRecognizer
. Ensuite, en testant les différents états lorsqu'un événement est déclenché, nous pouvons déterminer ce qui se passe.
MyCustomControl.Swift
import UIKit
class MyCustomControl: UIControl {
// nothing special is required in the control to make it work
}
ViewController.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"
}
}
}
Remarques
action: "gestureRecognized:"
. Les deux points signifient que les paramètres sont transmis.J'ai longtemps et longuement cherché une solution à ce problème et je ne pense pas qu'il en existe une. Cependant, en examinant de plus près la documentation, je pense que le fait que begintrackingWithTouch:withEvent:
et continueTrackingWithTouch:withEvent:
soient censés être appelés est peut-être un malentendu ...
UIControl
documentation indique:
Vous souhaiterez peut-être prolonger une
UIControl
sous-classe pour deux raisons fondamentales:Observer ou modifier l’envoi de messages d'action aux cibles pour événements particuliers Pour ce faire, remplacez
sendAction:to:forEvent:
, évaluez le sélecteur passé, objet cible ou Masque de bits «Remarque» et procédez comme suit Champs obligatoires.Pour fournir un comportement de suivi personnalisé (par exemple, pour changer l'apparence en surbrillance ) Pour ce faire, remplacez un ou toutes les méthodes suivantes:
beginTrackingWithTouch:withEvent:
,continueTrackingWithTouch:withEvent:
,endTrackingWithTouch:withEvent:
.
La partie critique de ceci, ce qui n’est pas très clair à mon avis, est qu’il est dit que vous voudrez peut-être étendre une sous-classe UIControl
- NON, vous voudrez peut-être étendre directement UIControl
. Il est possible que beginTrackingWithTouch:withEvent:
et continuetrackingWithTouch:withEvent:
ne soient pas censés être appelés en réponse à des effleurements et que les sous-classes directes UIControl
soient supposées les appeler afin que leurs - sous-classes puissent surveiller le suivi.
Donc, ma solution à cela est de remplacer touchesBegan:withEvent:
et touchesMoved:withEvent:
et de les appeler comme suit. Notez que le multi-touch n'est pas activé pour ce contrôle et que je ne me soucie pas des touches finies et des événements annulés, mais si vous voulez être complet/complet, vous devriez probablement également les implémenter.
- (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];
}
Notez que vous devriez également envoyer tous les messages UIControlEvent*
pertinents pour votre contrôle en utilisant sendActionsForControlEvents:
- ceux-ci peuvent être appelés à partir des super méthodes, je ne l’ai pas testé.
Je pense que vous avez oublié d'ajouter [super] appels aux touchesBegan/touchesEnded/touchesMoved . Des méthodes telles que
(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
ne fonctionne pas si vous écrasez les touchesBegan/touchesEnded comme ceci:
- (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");
}
Mais! Tout fonctionne bien si les méthodes ressemblent à:
- (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");
}
L'approche la plus simple que j'utilise souvent consiste à étendre UIControl, mais à utiliser la méthode héritée addTarget pour recevoir des rappels pour les différents événements. L'essentiel est d'écouter à la fois l'expéditeur et l'événement afin de pouvoir obtenir plus d'informations sur l'événement réel (tel que l'emplacement de l'événement).
Donc, il suffit simplement de sous-classer UIControl puis dans la méthode init (assurez-vous que votre initWithCoder est également configuré si vous utilisez des nibs), ajoutez ce qui suit:
[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
Bien entendu, vous pouvez choisir l'un des événements de contrôle standard, y compris UIControlEventAllTouchEvents. Notez que le sélecteur va passer deux objets. Le premier est le contrôle. La seconde est l’information sur l’événement. Voici un exemple d'utilisation de l'événement tactile pour basculer un bouton selon que l'utilisateur a appuyé à gauche et à droite.
- (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
}
}
}
Certes, il s’agit de contrôles assez simples et vous risquez de ne pas vous donner suffisamment de fonctionnalités, mais cela a fonctionné pour tous les besoins de contrôle personnalisé jusqu’à présent, et il est très facile à utiliser car je me soucie généralement de la même contrôler les événements déjà pris en charge par UIControl (retouches, glisser, etc.)
Échantillon de code du contrôle personnalisé ici: UISwitch personnalisé (remarque: cela ne s’enregistre pas avec le boutonPrimé: forEvent: sélecteur, mais vous pouvez le comprendre à partir du code ci-dessus)
J'avais des problèmes avec un UIControl qui ne répondait pas à beginTrackingWithTouch
et continueTrackingWithTouch
.
J'ai trouvé que mon problème était quand je faisais initWithFrame:CGRectMake()
j'ai fait le cadre trop petit (la zone qui réagit) et je n'avais qu'un point ou deux points où cela fonctionnait. J'ai fait le cadre de la même taille que le contrôle et puis chaque fois que j'ai appuyé n'importe où dans le contrôle, il a répondu.
J'ai trouvé un article lié de 2014 https://www.objc.io/issues/13-architecture/behaviors/ .
Ce qui est intéressant, c’est que son approche consiste à utiliser IB et à encapsuler la logique de gestion des événements dans un objet désigné (ils l’appellent comportements), supprimant ainsi la logique de votre view/viewController, ce qui la rend plus légère.