Регистр UISegmentedControl касается выбранного сегмента

У меня есть сегментированный элемент управления, где пользователь может выбрать, как заказать список. Работает отлично.

Тем не менее, мне бы хотелось, чтобы при нажатии на уже выбранный сегмент порядок был инвертирован. У меня есть весь код на месте, но я не знаю, как зарегистрировать ответвления в этих сегментах. Кажется, единственное управляющее событие, которое вы можете использовать, это UIControlEventValueChanged, но это не работает (так как выбранный сегмент фактически не изменяется).

Есть ли решение для этого? И если так, что это?

Заранее спасибо!

19 ответов

Решение

Вы можете подкласс UISegmentedControl, а потом override setSelectedSegmentIndex:

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

Если вы используете IB, убедитесь, что вы установили класс вашего UISegmentedControl к вашему подклассу.

Теперь вы можете слушать то же самое UIControlEventValueChanged как обычно, за исключением того, что пользователь отменил выбор сегмента, вы увидите selectedSegmentIndex равно 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]);
    }
}

Я думаю, что это даже немного лучше, если вы используете -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event--- так как это поведение UISegmentedControl. Кроме того, кажется, вам не нужно перегружать -(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];
}

Хотел это сам. Взял решение Джулиана (спасибо!) И немного изменил.

Следующий подкласс UISegmentedControl просто запускает UIControlEventValueChanged событие, даже если значение не изменилось, что, очевидно, выглядит немного странно, но в моем случае работает нормально и делает вещи простыми.

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

Это работает на iOS 4 и 5:

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

Представленное текущее решение по-прежнему не работает, потому что setSelectedSegmentIndex никогда не вызывается, если только не будет затронут новый сегмент. По крайней мере, в моем случае это никогда не работало, хотя я использую iOS5, хотя, возможно, это решение работало в iOS4. Во всяком случае, это мое решение. Для этого требуется один дополнительный метод подкласса, а именно:

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

Удачи!

Это был довольно старый вопрос, поэтому я решил добавить довольно простое решение, которое использовал, когда столкнулся с этим в приложении для iOS 5+.

Все, что я сделал, это перетащил Распознаватель жестов касания на UISegmentedControl в моем файле.xib. Проверьте соединения для вашего нового распознавателя жестов, чтобы убедиться, что сегментированный элемент управления является единственным выходом gestRecognizer, а затем просто подключите его действие к методу, который вы хотите запустить в своем контроллере.

Этот метод должен срабатывать каждый раз, когда вы касаетесь сегментированного элемента управления, поэтому просто проверьте выбранный индекс, возможно, с помощью переменной экземпляра, чтобы увидеть, коснулся ли пользователь уже выбранного сегмента.

@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

На iOS8 каждый ответ здесь, кажется, либо не работает, либо вызывает изменение дважды. Я придумал очень простое решение - поэтому я хотел бы поделиться им.

В подклассе у меня есть только:

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

То, что он делает, это сбрасывает выбранный сегмент в -1 перед изменением сегментированного контрольного сегмента. Это произойдет в методе touchesEnded.

Swift 5

class ReselectableSegmentedControl: UISegmentedControl {
    // Captures existing selected segment on touchesBegan.
    var oldValue: Int!

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.oldValue = self.selectedSegmentIndex
        super.touchesBegan(touches, with: event)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)

        if self.oldValue == self.selectedSegmentIndex {
            self.sendActions(for: .valueChanged)
        }
    }
}

От сюда

Это работает:

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

В Swift 3 я мог представить как минимум 2 решения следующим образом. Для особого случая я разместил и третье решение, где выбранный сегмент работает как кнопка переключения.

Решение 1:

Подсказка: это решение работает только с мгновенными управляющими сегментами! Если вам нужно решение для стационарного контроля, выберите Решение 2

Идея состоит в том, чтобы зарегистрировать второе событие, которое запускается при касании:

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

Решение 2. Другой способ - переопределить сенсорные функции в UISegmentedControl и уволить valueChanged даже если индекс сегмента не изменился. Поэтому вы можете переопределить UISegmentedControl следующее:

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

Решение 3: Если ваш подход состоял в том, чтобы выбранный сегмент был кнопкой переключения, то решение 2 можно изменить, чтобы очистить код следующим образом:

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

Теперь мы могли бы очистить changeSelection функционировать следующим образом:

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

Вот мое решение. Я считаю самым элегантным, если вы хотите, чтобы событие ValueChanged срабатывало при каждом касании...

.час

@interface UISegmentedControlAllChanges : UISegmentedControl
@end

.m

@implementation UISegmentedControlAllChanges

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

@end

Первая идея, которая у меня была, это подключить Touch Up Inside или же Touch Down действия для вашего метода сортировки, но это не похоже на работу.

Вторая идея больше обойти, установить Momentary свойство на сегментированный контроль. Это тогда уволит Value Did Change действие каждый раз, когда он прослушивается.

Большая помощь! То, что я хочу сделать, - это установить одну или не заданные кнопки - и когда кнопка установлена, второй тап отменяет ее. Это моя модификация:

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

Сначала уведомить позволяет прочитать элемент управления, чтобы найти текущий параметр, прежде чем он будет сброшен.

Ниже кусок кода, который работал для меня. Повторное нажатие на тот же сегмент отменяет его выбор.

@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

Основываясь на ответе Боба де Граафа:

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

Обратите внимание на использование UIControlEventAllTouchEvents вместо UIControlEventValueChanged,

Также нет необходимости звонить -(void)setSelectedSegmentIndex:,

iOS 9 решение Переопределите UISegmentedControl с таким классом:

@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

Похоже, забавный вопрос, чтобы ответить. Эта схема распространяется на решения Стива Е и Йершуачу. Эта версия использует UITapGestureRecognizer для захвата всех касаний и устанавливает selectedSegmentIndex в -1; но он также передает все касания в UISegmentedControl, чтобы он мог обрабатывать любые обычные касания. Никаких подклассов не требуется.

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
}

Поскольку за настройку сегмента отвечает UISegmentedControl, он должен только сбрасывать состояние. Я немного изменяю предложения других парней для Swift 5:

//MARK: Custom segmentedControl
///  Every element works like a flipflop  [see](http://stackru.com/questions/17652773/how-to-deselect-a-segment-in-segmented-control-button-permanently-till-its-click)
@IBDesignable class GRSegmentedControl: UISegmentedControl {
  private let url = Bundle.main.url(forResource: "PlopChoiceCntrl", withExtension: "aiff")!
  private var soundID:SystemSoundID = 0

  override init(items: [Any]?) {
    AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
    super.init(items: items)
  }

  required init?(coder: NSCoder) {
    AudioServicesCreateSystemSoundID(url as CFURL, &soundID)
    super.init(coder: coder)
  }

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    AudioServicesPlaySystemSound(soundID)
    let previousSelectedSegmentIndex = self.selectedSegmentIndex
    super.touchesEnded(touches, with: event)
    if previousSelectedSegmentIndex == self.selectedSegmentIndex {
      if let touch = touches.first{
        let touchLocation = touch.location(in: self)
        if bounds.contains(touchLocation) {
          self.selectedSegmentIndex = UISegmentedControl.noSegment
        }
      }
    }
  }
}

Это должно работать:

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

Не забудьте подключить это IBAction к вашей сегментированной контрольной розетке Value Changed в инспекторе соединений.

Решение с выбранным ответом не работало для меня с текущей версией iOS v8.x. Но @Andy предложит решение, и я дополню:

Подкласс UISegmentedControlи перезаписать touchesEnded Метод в подклассе:

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

В классе, где вы собираетесь использовать UISegmentedControl, создайте iVar currentSelectedSegIndex. Добавьте UIControlEventAllTouchEvents рядом с вашими методами действий UIControlEventValueChanged:

NSInteger currentSelectedSegIndex;

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

Реализуйте два метода действия:

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

Я использую KVO, чтобы инвертировать уже выбранный сегмент для 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
Другие вопросы по тегам