web-dev-qa-db-ja.com

UISegmentedControlレジスタは、選択したセグメントをタップします

ユーザーがリストの順序を選択できるセグメント化されたコントロールがあります。正常に動作します。

ただし、既に選択されているセグメントをタップすると、順序が逆になります。すべてのコードが用意されていますが、それらのセグメントのタップを登録する方法がわかりません。使用できるコントロールイベントはUIControlEventValueChangedのみですが、機能していません(選択したセグメントが実際に変更されていないため)。

これに対する解決策はありますか?もしそうなら、それは何ですか?

前もって感謝します!

61
Pieter Jongsma

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]);
    }
}
51
Julian

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

自分でこれを望んでいました。ジュリアンのソリューションを取得し(ありがとう!)、わずかに修正しました。

次の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
17
Henrik N

これは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];
}
12
EricS

実際に新しいセグメントがタップされない限り、setSelectedSegmentIndexが呼び出されることはないため、現在のソリューションはまだ機能しません。少なくとも私の場合、これは機能しませんでしたが、iOS5を使用していますが、おそらくこのソリューションはiOS4でも機能しました。とにかく、これが私の解決策です。次の追加のサブクラスメソッドが1つ必要です。

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

幸運を!

11
Bob de Graaf

これはかなり古い質問だったので、iOS 5+アプリでこれに遭遇したときに使用したかなりシンプルなソリューションを追加すると思いました。

私がしたことは、Tap Gesture Recognizerを.xibファイルのUISegmentedControlにドラッグすることだけでした。新しいジェスチャーレコグナイザーの接続をチェックして、セグメント化されたコントロールが唯一のgestureRecognizerアウトレットであることを確認し、コントローラーで起動するメソッドにそのアクションを結び付けます。

このメソッドは、セグメント化されたコントロールに触れるたびに起動する必要があるため、選択されたインデックスをおそらくインスタンス変数でチェックして、ユーザーが既に選択されているセグメントに触れたかどうかを確認します。

@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

IOS8では、ここでのすべての回答は機能しないか、変更を2回トリガーします。私は非常に簡単な解決策を思いついた-それで私はそれを共有したいと思います。

サブクラスには次のものしかありません:

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

セグメント化されたコントロールセグメントを変更する前に、選択したセグメントを-1にリセットします。 touchesEndedメソッドで発生します。

6
yershuachu

これは動作します:

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

Swiftでは、少なくとも次の2つのソリューションを想像できます。特別な場合のために、選択したセグメントがトグルボタンとして機能する3番目のソリューションも投稿しました。

解決策1:

ヒント:このソリューションは、瞬間的なcontrolSegmentsでのみ機能します!固定制御のソリューションが必要な場合は、ソリューション2を選択してください

アイデアは、タッチアップ内部で起動する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:もう1つの方法は、セグメントインデックスが変更されていない場合でも、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)")
    }
}
4
hhamm

これが私の解決策です。 ValueChangedイベントをすべてのタッチで発生させたい場合、最もエレガントだと思います...

。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

私のために働いたコードの下。同じセグメントを繰り返しタップすると、選択が解除されます。

@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

答えるのは楽しい質問のようです。このスキームは、Steve Eとyershuachuのソリューションを拡張したものです。このバージョンでは、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
}
1
Jeff

ボブ・デ・グラーフの回答に基づく:

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

UIControlEventAllTouchEventsの代わりにUIControlEventValueChangedを使用していることに注意してください。

また、-(void)setSelectedSegmentIndex:を呼び出す必要もありません。

1
Andy

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
1
Gleb Tarasov

大きな助け!私がやりたいことは、ボタンを1つまたはまったく設定しないというオプションを使用することです。ボタンが設定されると、2回目のタップでボタンが設定解除されます。これは私の修正です:

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

Notifyでは、最初にコントロールを読み取って現在の設定を見つけてからリセットします。

1
David H

現在のiOS v8.xでは、選択した回答のソリューションは機能しませんでした。しかし、@ Andyは解決策を提供します。

UISegmentedControlをサブクラス化し、サブクラスのtouchesEndedメソッドを上書きします。

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

UISegmentedControlを使用する予定のクラスで、iVar currentSelectedSegIndexを作成します。 UIControlEventValueChangedアクションメソッドの横にUIControlEventAllTouchEventsを追加します。

NSInteger currentSelectedSegIndex;

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

2つのアクションメソッドを実装します。

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

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
0
eXhausted