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

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

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

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

Спасибо заранее!

Ответ 1

Вы можете UISegmentedControl подкласс 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]);
    }
}

Ответ 2

Я думаю, что даже немного лучше, если вы используете -(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];
}

Ответ 3

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

Следующий подкласс 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

Ответ 4

Это работает как на 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];
}

Ответ 5

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

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

Удачи!

Ответ 6

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

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

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

@implementation MyViewController

@synthesize selectedSegment;
@synthesize segmentedControl;

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

    selectedSegment = [segmentedControl selectedSegmentIndex];

}

@end

Ответ 7

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

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

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

То, что он делает, - это reset выбранный сегмент до -1 перед изменением сегментированного сегмента управления. Это произойдет при помощи метода toucedEnded.

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

.h

@interface UISegmentedControlAllChanges : UISegmentedControl
@end

ого

@implementation UISegmentedControlAllChanges

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

@end

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

На основании ответа Боб де Граафа:

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

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

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

Ответ 14

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

@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

Ответ 15

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

Ответ 16

Я использую 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

Ответ 17

Выбранное решение для ответа не сработало для меня с текущего 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
}

Ответ 18

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

- (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 в Инспекторе соединений.

Ответ 19

Похоже, забавный вопрос, чтобы ответить. Эта схема распространяется на решения Стива Е и Йершуачу. В этой версии используется 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
}