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!
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]);
}
}
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];
}
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
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];
}
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!
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
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.
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];
}
}
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)")
}
}
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
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
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.
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:
.
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
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.
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 .
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
}
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
}
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