web-dev-qa-db-fra.com

Le registre UISegmentedControl tape sur le segment sélectionné

J'ai un contrôle segmenté où l'utilisateur peut choisir comment commander une liste. Fonctionne bien.

Cependant, j'aimerais que lorsqu'un segment déjà sélectionné soit exploité, l'ordre soit inversé. J'ai tout le code en place, mais je ne sais pas comment enregistrer les taps sur ces segments. Il semble que le seul événement de contrôle que vous puissiez utiliser soit UIControlEventValueChanged, mais cela ne fonctionne pas (car le segment sélectionné ne change pas réellement).

Y a-t-il une solution pour cela? Et si oui, c'est quoi?

Merci d'avance!

61
Pieter Jongsma

Vous pouvez sous-classer UISegmentedControl, puis override setSelectedSegmentIndex:

- (void) setSelectedSegmentIndex:(NSInteger)toValue {
    if (self.selectedSegmentIndex == toValue) {
        [super setSelectedSegmentIndex:UISegmentedControlNoSegment];
    } else {
        [super setSelectedSegmentIndex:toValue];        
    }
}

Si vous utilisez IB, assurez-vous de définir la classe de votre UISegmentedControl dans votre sous-classe.

Vous pouvez maintenant écouter la même UIControlEventValueChanged que vous le feriez normalement, sauf si l'utilisateur désélectionne le segment, vous verrez une selectedSegmentIndex égale à UISegmentedControlNoSegment:

-(IBAction) valueChanged: (id) sender {
    UISegmentedControl *segmentedControl = (UISegmentedControl*) sender;
    switch ([segmentedControl selectedSegmentIndex]) {
        case 0:
            // do something
            break;
        case 1:
            // do something
            break;
        case UISegmentedControlNoSegment:
            // do something
            break;
        default:
            NSLog(@"No option for: %d", [segmentedControl selectedSegmentIndex]);
    }
}
51
Julian

Je pense que c'est même un peu mieux si vous utilisez -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event--- car c'est le comportement de UISegmentedControl. De plus, il semble que vous n’ayez pas besoin de surcharger la -(void)setSelectedSegmentIndex:(NSInteger)toValue

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    
    NSInteger current = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
    if (current == self.selectedSegmentIndex) 
        [self sendActionsForControlEvents:UIControlEventValueChanged];
}
31
davidav

Voulu cela moi-même. Pris la solution de Julian (merci!) Et modifié légèrement.

La sous-classe UISegmentedControl suivante déclenche simplement l'événement UIControlEventValueChanged même lorsque la valeur ne change pas, ce qui se lit évidemment un peu bizarre, mais fonctionne bien dans mon cas et simplifie les choses.

AKSegmentedControl.h

#import <UIKit/UIKit.h>

@interface AKSegmentedControl : UISegmentedControl {
}

@end

AKSegmentedControl.m

#import "AKSegmentedControl.h"

@implementation AKSegmentedControl

- (void)setSelectedSegmentIndex:(NSInteger)toValue {
  // Trigger UIControlEventValueChanged even when re-tapping the selected segment.
  if (toValue==self.selectedSegmentIndex) {
    [self sendActionsForControlEvents:UIControlEventValueChanged];
  }
  [super setSelectedSegmentIndex:toValue];        
}

@end
17
Henrik N

Cela fonctionne à la fois sur iOS 4 et 5:

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  int oldValue = self.selectedSegmentIndex;
  [super touchesBegan:touches withEvent:event];
  if ( oldValue == self.selectedSegmentIndex )
    [self sendActionsForControlEvents:UIControlEventValueChanged];
}
12
EricS

La solution actuelle présentée ne fonctionne toujours pas, car setSelectedSegmentIndex n'est jamais appelé, sauf si un nouveau segment est exploité. Au moins dans mon cas, cela n'a jamais fonctionné. J'utilise iOS5, cependant, cette solution a peut-être fonctionné sous iOS4. Quoi qu’il en soit, c’est ma solution… .. Il nécessite une méthode de sous-classe supplémentaire, qui est la suivante:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{    
    [self setSelectedSegmentIndex:self.selectedSegmentIndex];
    [super touchesEnded:touches withEvent:event];
}

Bonne chance!

11
Bob de Graaf

C'était une question assez ancienne et j'ai donc pensé ajouter la solution plutôt simple que j'utilisais lorsque je rencontrais cela dans une application iOS 5+.

Tout ce que j'ai fait est de faire glisser un outil de reconnaissance de gestes tactiles sur UISegmentedControl dans mon fichier .xib. Vérifiez les connexions de votre nouveau programme de reconnaissance de mouvements pour vous assurer que le contrôle segmenté est la seule sortie de gestureRecognizer, puis connectez simplement son action à la méthode que vous souhaitez déclencher dans votre contrôleur.

Cette méthode devrait se déclencher chaque fois que vous touchez le contrôle segmenté, il vous suffit donc de vérifier l'index sélectionné avec éventuellement une variable d'instance pour voir si l'utilisateur a touché le segment déjà sélectionné.

@implementation MyViewController

@synthesize selectedSegment;
@synthesize segmentedControl;

// connected to Tap Gesture Recognizer's action
- (IBAction)segmentedControlTouched:(id)sender
{
    NSLog(@"Segmented Control Touched");
    if (selectedSegment == [segmentedControl selectedSegmentIndex]) {
        // Do some cool stuff
    }

    selectedSegment = [segmentedControl selectedSegmentIndex];

}

@end
9
Steve E

Sur iOS8, chaque réponse semble ne pas fonctionner ou déclencher un changement deux fois. Je suis venu avec une solution très simple - je voudrais donc le partager.

Dans la sous-classe, je n'ai que:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    [self setSelectedSegmentIndex:UISegmentedControlNoSegment];
}

Cela revient à réinitialiser le segment sélectionné sur -1 avant de modifier le segment de contrôle segmenté. Cela se produira dans la méthode touchEnded.

6
yershuachu

Cela marche:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    int oldValue = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
    if (oldValue == self.selectedSegmentIndex)
    {
        [super setSelectedSegmentIndex:UISegmentedControlNoSegment];
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}
5
Jason Shehane

Dans Swift 3 Je pourrais imaginer au moins deux solutions comme suit. Pour un cas particulier, j'ai également posté une troisième solution, où le segment sélectionné fonctionne comme un bouton bascule.

Solution 1:

Astuce: Cette solution ne fonctionne que sur les segments de contrôle momentanés! Si vous avez besoin d’une solution pour les commandes fixes, choisissez la solution 2.

L'idée est d'enregistrer un deuxième événement qui se déclenche à la retouche:

// this solution works only for momentary segment control:
class Solution1ViewController : UIViewController {
    var current:Int = UISegmentedControlNoSegment
    @IBOutlet weak var mySegmentedControl: UISegmentedControl! {
        didSet {
            guard mySegmentedControl != nil else { return }

            self.mySegmentedControl!.addTarget(
                self,
                action:#selector(changeSelection(sender:)),
                for: .valueChanged)

            self.mySegmentedControl!.addTarget(
                self,
                action:#selector(changeSelection(sender:)),
                for: .touchUpInside)
        }
    }

    func changeSelection(sender: UISegmentedControl) {
        if current == sender.selectedSegmentIndex {
            // user hit the same button again!
            print("user hit the same button again!")
        }
        else {
            current = sender.selectedSegmentIndex
            // user selected a new index
             print("user selected a new index")
        }
    }
}

Solution 2: L’autre méthode consiste à remplacer les fonctions tactiles dans UISegmentedControl et à déclencher la valueChanged même si l’index de segment n’a pas changé. Par conséquent, vous pouvez remplacer la UISegmentedControl comme suit:

// override UISegmentControl to fire event on second hit:
class Solution2SegmentControl : UISegmentedControl
{
    private var current:Int = UISegmentedControlNoSegment
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        current = self.selectedSegmentIndex
        super.touchesBegan(touches, with: event)
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        if current == self.selectedSegmentIndex {
            self.sendActions(for: .valueChanged)
        }
    }
}

// solution2 works with stationary (default) segment controls
class Solution2ViewController : UIViewController {
    var current:Int = UISegmentedControlNoSegment

    @IBOutlet weak var mySegmentedControl: UISegmentedControl! {
        didSet {
            guard mySegmentedControl != nil else { return }

            self.mySegmentedControl!.addTarget(
                self,
                action:#selector(changeSelection(sender:)),
                for: .valueChanged)
        }
    }

    func changeSelection(sender: UISegmentedControl) {
        if current == sender.selectedSegmentIndex {
            // user hit the same button again!
            print("user hit the same button again!")
        }
        else {
            current = sender.selectedSegmentIndex
            // user selected a new index
            print("user selected a new index")
        }
    }
}

Solution 3: .__ Si votre approche voulait que le segment sélectionné soit un bouton bascule, la solution 2 pourrait être modifiée pour nettoyer le code de la manière suivante:

class MyToggleSegmentControl : UISegmentedControl {
    /// you could enable or disable the toggle behaviour here
    var shouldToggle:Bool = true

    private var current:Int = UISegmentedControlNoSegment
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        current = self.selectedSegmentIndex
        super.touchesBegan(touches, with: event)
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        if shouldToggle, current == self.selectedSegmentIndex {
            self.selectedSegmentIndex = UISegmentedControlNoSegment
        }
    }
}

Nous pouvons maintenant nettoyer la fonction changeSelection comme suit:

func changeSelection(sender: UISegmentedControl) {
    switch sender.selectedSegmentIndex {
    case UISegmentedControlNoSegment:
        print("none")
    default:
        print("selected: \(sender.selectedSegmentIndex)")
    }
}
3
hhamm

Voici ma solution. Le plus élégant, je pense, si vous voulez que l’événement ValueChanged se déclenche à chaque touche ...

.h

@interface UISegmentedControlAllChanges : UISegmentedControl
@end

.m

@implementation UISegmentedControlAllChanges

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{    
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    [super touchesEnded:touches withEvent:event];
}

@end
2
Clement M

Ci-dessous le code qui a fonctionné pour moi. Appuyez plusieurs fois sur le même segment pour le désélectionner.

@implementation WUUnselectableSegmentedControl
{
    NSUInteger _selectedIndexBeforeTouches;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    _selectedIndexBeforeTouches = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    if (_selectedIndexBeforeTouches == self.selectedSegmentIndex)
    {
        // Selection didn't change after touch - deselect
        self.selectedSegmentIndex = UISegmentedControlNoSegment;
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}

@end
1
kelin

Ma première idée a été de relier les actions Touch Up Inside ou Touch Down à votre méthode de tri, mais cela ne semble pas fonctionner.

La deuxième idée est plus une solution de contournement, définissez la propriété Momentary sur le contrôle segmenté. Cela déclenchera alors une action Value Did Change à chaque fois que vous appuyez dessus. 

1
rjstelling

Basé sur la réponse de Bob de Graaf:

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self sendActionsForControlEvents:UIControlEventAllTouchEvents];
    [super touchesEnded:touches withEvent:event];
}

Notez l'utilisation de UIControlEventAllTouchEvents au lieu de UIControlEventValueChanged.

De plus, il n'est pas nécessaire d'appeler -(void)setSelectedSegmentIndex:.

1
Andy

solution iOS 9. Substituez UISegmentedControl avec cette classe:

@interface SegmentedControl()

@property (nonatomic) NSInteger previousCurrent;

@end

@implementation SegmentedControl

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.previousCurrent = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];

    if (self.selectedSegmentIndex == self.previousCurrent) {
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }

    self.previousCurrent = NSNotFound;
}

@end
1
Gleb Tarasov

Grande aide! Ce que je veux faire, c’est de pouvoir choisir entre un ou plusieurs boutons - et quand un bouton est activé, un deuxième appui le désactive. Ceci est ma modification:

- (void)setSelectedSegmentIndex:(NSInteger)toValue
{
  // Trigger UIControlEventValueChanged even when re-tapping the selected segment.
  if (toValue==self.selectedSegmentIndex) {
    [super setSelectedSegmentIndex:UISegmentedControlNoSegment]; // notify first
    [self sendActionsForControlEvents:UIControlEventValueChanged]; // then unset
  } else {
    [super setSelectedSegmentIndex:toValue]; 
  }
}

Notifier d’abord vous permet de lire le contrôle pour trouver le paramètre actuel avant sa réinitialisation.

1
David H

Cela devrait fonctionner:

- (IBAction)segmentedControlValueChanged:(id)sender
{
    UISegmentedControl *sc = (UISegmentedControl*) sender;
    switch ([sc selectedSegmentIndex])
    {
       case 0:
            NSLog(@"First option!");
            break;
       case 1:
            NSLog(@"Second option!");
            break;
       case UISegmentedControlNoSegment:
            NSLog(@"No option...");
            break;
        default:
            NSLog(@"No selected option :( ");
    }

    //this resets the segmented control (mySC) value to act as a button.
    [self.mySC setSelectedSegmentIndex:UISegmentedControlNoSegment];
}

N'oubliez pas de connecter cette IBAction à votre sortie de contrôle segmentée Value Changed dans Inspecteur de connexions .

0
Pedro Trujillo

Cela semble être une question amusante à répondre. Ce schéma s'étend aux solutions de Steve E et de yershuachu. Cette version utilise un UITapGestureRecognizer pour capturer toutes les touches et qui définit le selectedSegmentIndex sur -1; mais il transmet également toutes les manipulations à UISegmentedControl afin qu’il puisse gérer toutes les manipulations normales. Aucun sous-classement n'est requis.

UISegmentedControl *mySegControl;

- (void)viewDidLoad
{
    [super viewDidLoad];

    [mySegControl addTarget:self action:@selector(segmentAction:)
           forControlEvents:UIControlEventValueChanged];
    // allow the a seg button tap to be seen even if already selected
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]
                                          initWithTarget:self action:@selector(unitsSegTap:)];
    tapGesture.cancelsTouchesInView = NO; // pass touches through for normal taps
    [mySegControl addGestureRecognizer:tapGesture];

    // continue setup ...
}

- (void)segTap:(UITapGestureRecognizer *)sender
{
    mySegControl.selectedSegmentIndex = -1;
}

// called for UIControlEventValueChanged
- (void)segmentAction:(UISegmentedControl *)sender
{
    NSInteger index = sender.selectedSegmentIndex;
    NSLog(@"unitsSegmentAction %d",(int)index);
    // process the segment
}
0
Jeff

La solution de la réponse sélectionnée ne fonctionnait pas pour moi à partir de la version actuelle iOS v8.x. Mais @Andy propose une solution et je vais compléter:

Sous-classe la méthode UISegmentedControl et écraser touchesEnded dans la sous-classe:

-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self sendActionsForControlEvents:UIControlEventAllTouchEvents];
    [super touchesEnded:touches withEvent:event];
}

Dans la classe où vous avez l'intention d'utiliser UISegmentedControl, créez un iVar currentSelectedSegIndex. Ajoutez les événements UIControlEventAllTouchEvents à côté de vos méthodes d'action UIControlEventValueChanged:

NSInteger currentSelectedSegIndex;

[aSegmentedController addTarget:self action:@selector(aSegmentedControllerSelection:) forControlEvents:UIControlEventValueChanged];
[aSegmentedController addTarget:self action:@selector(aSegmentedControllerAllTouchEvent:) forControlEvents:UIControlEventAllTouchEvents];

Implémentez les deux méthodes d'action:

-(void)aSegmentedControllerAllTouchEvent:(MyUISegmentedControl *)seg
{
    seg.selectedSegmentIndex = UISegmentedControlNoSegment;
}
-(void)aSegmentedControllerSelection:(MyUISegmentedControl *)seg
{
    if (currentSelectedSegIndex == seg.selectedSegmentIndex)
    {
        seg.selectedSegmentIndex = UISegmentedControlNoSegment;
        currentSelectedSegIndex = NSIntegerMax;
        NSLog(@"%s Deselected.", __PRETTY_FUNCTION__);
        // do your stuff if deselected
        return;
    }
    NSLog(@"%s Selected index:%u", __PRETTY_FUNCTION__, seg.selectedSegmentIndex);
    currentSelectedSegIndex = seg.selectedSegmentIndex;
    //do your stuff if selected index
}
0
user523234

J'utilise KVO pour inverser le segment déjà sélectionné pour iOS8.

#import "QCSegmentedControl.h"

static void *QCSegmentedControlContext = &QCSegmentedControlContext;

@implementation QCSegmentedControl

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self registerKVO];
    }

    return self;
}

- (void)dealloc {
    [self removeObserver:self forKeyPath:@"selectedSegmentIndex"];
}

#pragma mark -

- (void)registerKVO {
    [self addObserver:self
           forKeyPath:@"selectedSegmentIndex"
              options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
              context:QCSegmentedControlContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    if (context == QCSegmentedControlContext) {
        NSNumber *new = change[NSKeyValueChangeNewKey];
        NSNumber *old = change[NSKeyValueChangeOldKey];
        if (new.integerValue == old.integerValue) {
            self.selectedSegmentIndex = UISegmentedControlNoSegment;
        }
    }
}

@end
0
eXhausted