Domanda

Ho un controllo segmentato in cui l'utente può selezionare come ordinare un elenco. Funziona bene.

Tuttavia, vorrei che quando si toccasse un segmento già selezionato, l'ordine venisse invertito. Ho tutto il codice in atto, ma non so come registrare i tocchi su quei segmenti. Sembra che l'unico evento di controllo che puoi utilizzare sia UIControlEventValueChanged, ma che non funziona (poiché il segmento selezionato non sta effettivamente cambiando).

C'è una soluzione per questo? E se è così, che cos'è?

Grazie in anticipo!

È stato utile?

Soluzione

Puoi sottoclassare UISegmentedControl , quindi sovrascrivere setSelectedSegmentIndex:

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

Se si utilizza IB, assicurarsi di impostare la classe del UISegmentedControl sulla sottoclasse.

Ora puoi ascoltare lo stesso UIControlEventValueChanged come faresti normalmente, tranne se l'utente ha deselezionato il segmento, vedrai un selectedSegmentIndex uguale a 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]);
    }
}

Altri suggerimenti

Penso che sia ancora un po 'meglio se usi - (void) touchesBegan: (NSSet *) tocca withEvent: (UIEvent *) event --- poiché questo è il comportamento di UISegmentedControl. Inoltre, sembra che non sia necessario sovraccaricare -(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];
}

Lo volevo anch'io. Ha preso la soluzione di Julian (grazie!) E leggermente modificata.

La seguente sottoclasse UISegmentedControl innesca semplicemente l'evento UIControlEventValueChanged anche quando il valore non è cambiato, il che ovviamente legge un po 'strano, ma funziona bene nel mio caso e mantiene le cose semplici.

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

Funziona su iOS 4 e 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 soluzione attuale presentata non funziona ancora, perché setSelectedSegmentIndex non viene mai chiamato a meno che non venga toccato un nuovo segmento. Almeno nel mio caso questo non ha mai funzionato, io uso iOS5 però, forse questa soluzione ha funzionato in iOS4. Comunque, questa è la mia soluzione. È necessario un metodo di sottoclasse aggiuntivo, che è il seguente:

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

Buona fortuna!

Questa era una domanda piuttosto vecchia, quindi ho pensato di aggiungere la soluzione piuttosto semplice che ho usato quando mi sono imbattuto in questo in un'app iOS 5+.

Tutto quello che ho fatto è stato trascinare un Riconoscimento gesti Tap su UISegmentedControl nel mio file .xib. Controlla le connessioni per il tuo nuovo riconoscimento dei gesti per assicurarti che il controllo segmentato sia l'unica presa di gestoRecognizer e quindi collega la sua azione al metodo che desideri attivare nel controller.

Questo metodo dovrebbe attivarsi ogni volta che tocchi il controllo segmentato, quindi controlla l'indice selezionato con forse una variabile di istanza per vedere se l'utente ha toccato il segmento già selezionato.

@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

Su iOS8 ogni risposta qui sembra non funzionare o innescare il cambiamento due volte. Ho trovato una soluzione molto semplice, quindi vorrei condividerla.

Nella sottoclasse ho solo:

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

Ciò che fa è ripristinare il segmento selezionato su -1 prima di cambiare il segmento di controllo segmentato. Accadrà con il metodo touchesEnded.

Funziona:

- (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];
    }
}

In Swift 3 ho potuto immaginare almeno 2 soluzioni come segue. Per un caso speciale ho pubblicato anche una terza soluzione, in cui il segmento selezionato funziona come pulsante di attivazione / disattivazione.

Soluzione 1:

Suggerimento: questa soluzione funziona solo su segmenti di controllo momentanei! Se hai bisogno di una soluzione per i controlli fissi, scegli Soluzione 2

L'idea è di registrare un secondo evento che si accende al ritocco interno:

// 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")
        }
    }
}

Soluzione 2: L'altro modo è quello di sovrascrivere le funzioni touch in UISegmentedControl e attivare il valueChanged anche se l'indice di segmento non è cambiato. Pertanto è possibile ignorare UISegmentedControl come segue:

// 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")
        }
    }
}

Soluzione 3: Se il tuo approccio fosse quello di fare in modo che il segmento selezionato fosse un pulsante di attivazione, la Soluzione 2 potrebbe essere modificata per ripulire il codice in questo modo:

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
        }
    }
}

Ora potremmo ripulire la funzione changeSelection come segue:

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

Ecco la mia soluzione. Il più elegante penso se vuoi che l'evento ValueChanged si attivi ad ogni tocco ...

.h

@interface UISegmentedControlAllChanges : UISegmentedControl
@end

.m

@implementation UISegmentedControlAllChanges

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

@end

La prima idea che ho avuto è stata collegare le azioni Touch Up Inside o Touch Down al tuo metodo di ordinamento, ma questo non sembra funzionare.

La seconda idea è più una soluzione, imposta la proprietà Momentary sul controllo segmentato. Questo genererà quindi un'azione Value Did Change ogni volta che viene toccato.

Grande aiuto! Quello che voglio fare è avere l'opzione di uno o nessun pulsante impostato - e quando viene impostato un pulsante, un secondo tocco lo disattiva. Questa è la mia modifica:

- (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]; 
  }
}

Notifica prima ti consente di leggere il controllo per trovare l'impostazione corrente prima che venga ripristinata.

Basato sulla risposta di Bob de Graaf:

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

Nota l'uso di UIControlEventAllTouchEvents invece di UIControlEventValueChanged .

Inoltre non è necessario chiamare - (void) setSelectedSegmentIndex: .

Sotto il pezzo di codice che ha funzionato per me. Il tocco ripetuto sullo stesso segmento lo deselezionerà.

@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

Soluzione iOS 9. Sostituisci UISegmentedControl con tale 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

Sembra una domanda divertente a cui rispondere. Questo schema si espande sulle soluzioni di Steve E e yershuachu. Questa versione utilizza un UITapGestureRecognizer per catturare tutti i tocchi e che imposta selectedSegmentIndex su -1; ma passa anche tutti i tocchi a UISegmentedControl in modo che possa gestire qualsiasi tocco normale. Non è richiesta alcuna sottoclasse.

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
}

Sto usando KVO per invertire il segmento già selezionato per 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

La soluzione della risposta selezionata non ha funzionato per me a partire dall'attuale iOS v8.x. Ma @Andy offre una soluzione e io completerò:

Sottoclasse il UISegmentedControl e sovrascrivi il metodo touchesEnded nella sottoclasse:

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

Nella classe in cui si intende utilizzare UISegmentedControl, creare un iVar currentSelectedSegIndex. Aggiungi UIControlEventAllTouchEvents accanto ai tuoi metodi di azione UIControlEventValueChanged:

NSInteger currentSelectedSegIndex;

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

Implementa i due metodi di azione:

-(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
}

Questo dovrebbe funzionare:

- (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];
}

Ricorda di collegare questo IBAction alla tua presa di controllo segmentata Value Changed in Inspections Connections .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top