UISegmentedControl在选定的分段上注册点击

66

我有一个分段控件,用户可以使用它来选择如何对列表进行排序。工作得很好。

但是,当已经选择的片段被点击时,我希望排序被反转。 我已经将所有代码放在了正确的位置,但我不知道如何注册那些片段的点击事件。似乎你可以使用的唯一控制事件是UIControlEventValueChanged,但这并没有起作用(因为所选的片段实际上没有改变)。

是否有解决方案?如果有,是什么呢?

谢谢!

20个回答

51

你可以创建 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]);
    }
}

对于iOS5.1,这个解决方案与Bob De Graaf的解决方案完美结合。 - smile.al.d.way

32

我认为如果您使用-(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];
}

1
你可能想在 if 语句的开头添加 [self setSelectedSegmentIndex:UISegmentedControlNoSegment]; (在我看来,这个 if 语句应该使用花括号)。 - Warpspace
3
请参考这个回答以获取iOS 7更新版本:https://dev59.com/eGMm5IYBdhLWcg3wZ-MX#17652998 - Klaas

20

我本来也想要这个功能。采用了Julian的解决方案(谢谢!)并进行了一些修改。

下面是一个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

这似乎是我正在寻找的技巧 - 我有一个分段控件,其中一个部分需要在每次触摸时触发模态视图的出现。谢谢。 - petert
对于单个分段控件(如petert所提到的),请参见http://gist.github.com/401670。 - Henrik N
2
这在iOS 6.1中对我不起作用。那个方法只被调用一次,但在响应触摸时却没有被调用。看起来苹果正在绕过自己的设置器和获取器。 - Vincent Bernier

12

这在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];
}

这是怎么工作的?为什么要调用[super touchesBegan:touches withEvent:event]?在super版本中会发生什么? - ladookie
1
当活动段被点击时,它会发送一个UIControlEventValueChanged消息。调用[super touchesBegan:touches withEvent:event]会执行正常的系统提供的事件处理代码--如果用户点击非活动段,则会更改值并在该情况下发送该事件。 - EricS
我喜欢这个。但是要使选择变为未选中状态,需要在我的代码主体部分(而不是在此子类中)进行设置,在Change IBAction内:- (IBAction)addSSslotSelectorChange:(id)sender { if(slotSelector.selectedSegmentIndex == [scratch_format.slot intValue]) { slotSelector.selectedSegmentIndex = -1; } scratch_format.slot = [NSNumber numberWithInt:slotSelector.selectedSegmentIndex]; NSLog(@"Slot changed to: %i",slotSelector.selectedSegmentIndex); } - CommaToast

11

目前提供的解决方案仍然无效,因为除非真的点击了新段,否则不会调用 setSelectedSegmentIndex。至少在我的情况下从未起作用,我使用的是 iOS5,也许这个解决方案在 iOS4 上有效。无论如何,这是我的解决方案。

它需要一个额外的子类方法,如下所示:

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

祝你好运!


1
谢谢。这个方法在iOS 5中有效,而另一种方法适用于iOS 3和4。 - EricS
要让它在 iOS 5 上运行,您需要添加 [self sendActionsForControlEvents:UIControlEventValueChanged];。 - Chris Chen
对我来说,touchesBegan起作用了。虽然它不能完全产生切换行为,但我在我的代码中进行了自定义处理。谢谢。 - smile.al.d.way
我认为这行代码[self setSelectedSegmentIndex:self.selectedSegmentIndex];是不必要的。[super ...]调用使得该行代码变得多余。 - TyR

9

这是一个相当老的问题,所以我想添加一个非常简单的解决方案,当我在 iOS 5+ 应用程序中遇到此问题时使用了它。

我只是将一个 Tap Gesture Recognizer 拖到我的 .xib 文件中的 UISegmentedControl 上。检查您的新手势识别器的连接,确保分段控件是唯一的 gestureRecognizer outlet,然后将其操作连接到您希望在控制器中触发的方法即可。

这种方法应该在任何时候都会触发,因此只需使用实例变量检查所选索引,以查看用户是否触摸了已经选择的分段。

@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

由于手势识别器不愿意在拖动到分段控件时接受它,因此该功能在iOS5.1上无法正常工作。 - user387184
它仍然有效。即使它不允许您将手势识别器拖动到分段控件上,您可以将其放置在xib的顶层(我将其放置在对象列表中视图之上),并在连接选项卡中将手势连接到那里的分段控件。 - Steve E
1
这对我非常有效!@user387184,你必须将它拖到你的xib中,然后右键单击它并从“New referencing outlet collection”拖动到分段控件并单击弹出窗口“gestureRecognizer”。 - malhal

8
在iOS8上,这里的每个答案似乎要么不起作用,要么会触发两次变化。我想出了一个非常简单的解决方案-所以我想分享一下。
在子类中,我只有:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    [self setSelectedSegmentIndex:UISegmentedControlNoSegment];
}

它的作用是在改变分段控件选项之前将所选段重置为-1。这会发生在touchesEnded方法中。

优秀,最佳答案。 - mojuba
对我来说工作正常。 :) - sanjeet
在我的iOS10上可以运行。 - Alper
现在它不再这样了...非常奇怪的行为。 - Alper
1
聪明的技巧!在iOS 13.x中运行良好。 - DrMickeyLauer

8

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

来源于这里


6
这是可行的:
- (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];
    }
}

4
在Swift 3中,我可以想象至少有以下两种解决方案。对于一个特殊情况,我还发布了第三个解决方案,其中所选的段落作为切换按钮。
解决方案1:
提示:此解决方案仅适用于短暂的controlSegments!如果您需要解决稳定控件的问题,请选择解决方案2。
思路是注册第二个事件,在touch-up-inside上触发:
// 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)")
    }
}

在解决方案1中,segmentedValues是什么? - Jonny
如果你指的是mySegmentedControl,那么解决方案1对我不起作用。 - Jonny
segmentedValues 实际上应该是 mySegmentedControl - 我已经修复了,感谢提示。 - hhamm
同时,Solution1仅适用于瞬时SegmentControl。 - hhamm
在Xcode 9,Swift 4中,无论是solution1还是solution2都对我不起作用。 - lozflan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接