Ios 如何在分段控制按钮中永久取消选择分段,直到再次单击为止

Ios 如何在分段控制按钮中永久取消选择分段,直到再次单击为止,ios,objective-c,uibutton,uisegmentedcontrol,Ios,Objective C,Uibutton,Uisegmentedcontrol,我有一个带有4个段的UISegmentedControl。选择后,应保持选择状态。当再次单击同一段时,它应取消选择自身。 如何实现这一点?由于UISegmentedControl仅在选择了未选择的段时发送操作,因此您必须将UISegmentedControl子类化,以对其触摸处理进行微小更改。我使用这个类: @implementation MBSegmentedControl // this sends a value changed event even if we reselect the

我有一个带有4个段的
UISegmentedControl
。选择后,应保持
选择状态
。当再次单击同一段时,它应
取消选择自身

如何实现这一点?

由于
UISegmentedControl
仅在选择了未选择的段时发送操作,因此您必须将
UISegmentedControl
子类化,以对其触摸处理进行微小更改。我使用这个类:

@implementation MBSegmentedControl

// this sends a value changed event even if we reselect the currently selected segment
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSInteger current = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
    if (current == self.selectedSegmentIndex) {
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}

@end
现在,您将获得
UIControlEventValueChanged
事件,即使已选择该段。只需将当前索引保存在变量中,并在操作中进行比较即可。如果两个索引匹配,则必须取消选择被触摸的段

// _selectedSegmentIndex is an instance variable of the view controller

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _selectedSegmentIndex = self.segment.selectedSegmentIndex;
}

- (IBAction)segmentChanged:(UISegmentedControl *)sender {
    if (sender.selectedSegmentIndex == _selectedSegmentIndex) {
        NSLog(@"Segment %d deselected", sender.selectedSegmentIndex);
        sender.selectedSegmentIndex =  UISegmentedControlNoSegment;
        _selectedSegmentIndex = UISegmentedControlNoSegment;
    }
    else {
        NSLog(@"Segment %d selected", sender.selectedSegmentIndex);
        _selectedSegmentIndex = sender.selectedSegmentIndex;
    }
}

iOS 7改变了UISegmentedControl处理触摸的方式。选择的分段索引现在在
touchesend:
期间更改

因此,更新后的子类应如下所示:

@implementation MBSegmentedControl

+ (BOOL)isIOS7 {
    static BOOL isIOS7 = NO;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSInteger deviceSystemMajorVersion = [[[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."] objectAtIndex:0] integerValue];
        if (deviceSystemMajorVersion >= 7) {
            isIOS7 = YES;
        }
        else {
            isIOS7 = NO;
        }
    });
    return isIOS7;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
    if (![[self class] isIOS7]) {
        // before iOS7 the segment is selected in touchesBegan
        if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
            // if the selectedSegmentIndex before the selection process is equal to the selectedSegmentIndex
            // after the selection process the superclass won't send a UIControlEventValueChanged event.
            // So we have to do this ourselves.
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
    [super touchesEnded:touches withEvent:event];
    if ([[self class] isIOS7]) {
        // on iOS7 the segment is selected in touchesEnded
        if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
}

@end

Swift 2.2版本,修复了Grzegorz注意到的问题

class ReselectableSegmentedControl: UISegmentedControl {
    @IBInspectable var allowReselection: Bool = true

    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let previousSelectedSegmentIndex = self.selectedSegmentIndex
        super.touchesEnded(touches, withEvent: event)
        if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
            if let touch = touches.first {
                let touchLocation = touch.locationInView(self)
                if CGRectContainsPoint(bounds, touchLocation) {
                    self.sendActionsForControlEvents(.ValueChanged)
                }
            }
        }
    }
}
类可重新选择SegmentedControl:UISegmentedControl{
@IBInspectable var allowReselection:Bool=true
覆盖func touchesEnded(触摸:设置,withEvent事件:UIEvent?){
让previousSelectedSegmentIndex=self.selectedSegmentIndex
super.touchesend(touchs,withEvent:event)
如果AllowSelection&&previousSelectedSegmentIndex==self.selectedSegmentIndex{
如果让触摸=先触摸{
让touchLocation=touch.locationInView(自)
如果CGRectContainsPoint(边界、触摸位置){
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}

Swift 3.0将此修复更改为如下所示:

class MyDeselectableSegmentedControl: UISegmentedControl {
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let previousIndex = selectedSegmentIndex

        super.touchesEnded(touches, with: event)

        if previousIndex == selectedSegmentIndex {
            let touchLocation = touches.first!.location(in: self)

            if bounds.contains(touchLocation) {
                sendActions(for: .valueChanged)
            }
        }
    }
}
类MyDecelectableSegmentedControl:UISegmentedControl{
覆盖函数touchesend(touchs:Set,带有事件:UIEvent?){
让previousIndex=selectedSegmentIndex
super.touchesend(触摸,带有:事件)
如果previousIndex==selectedSegmentIndex{
让touchLocation=touchs.first!.location(in:self)
if bounds.contains(触摸位置){
sendActions(针对:.valueChanged)
}
}
}
}

以下是一个问题的解决方案,当您尝试通过点击UISegmentControl来取消选择,然后再点击外部,它仍然会取消选择

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint locationPoint = [[touches anyObject] locationInView:self];
CGPoint viewPoint = [self convertPoint:locationPoint fromView:self];
if ([self pointInside:viewPoint withEvent:event]) {
    int oldValue = self.selectedSegmentIndex;
    [super touchesEnded:touches withEvent:event];
    if (oldValue == self.selectedSegmentIndex)
    {
        [super setSelectedSegmentIndex:UISegmentedControlNoSegment];
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}
}

非常有帮助!非常感谢。我希望对我的项目的事件有更多的控制,所以我修改了@Matthias的答案,以发送一个定制的“值未更改”事件。我举了一个例子


我还加入了@Grzegorz的修复程序,因此如果用户将手指拖到分段控件之外,它会正常工作。

这里有一个独立于IOS版本的解决方案。它选择的是行为本身

@interface CustomSegmentedControl : UISegmentedControl
@end




@implementation CustomSegmentedControl{


BOOL _touchBegan;
BOOL _reactOnTouchBegan;
}



- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    _touchBegan = YES;

    NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
    [super touchesBegan:touches withEvent:event];
    if (_reactOnTouchBegan) {
        // before iOS7 the segment is selected in touchesBegan
        if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
}


-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    _touchBegan = NO;

    NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
    [super touchesEnded:touches withEvent:event];
    if (!_reactOnTouchBegan) {
        CGPoint locationPoint = [[touches anyObject] locationInView:self];
        CGPoint viewPoint = [self convertPoint:locationPoint fromView:self];
        if ([self pointInside:viewPoint withEvent:event]) {
            // on iOS7 the segment is selected in touchesEnded
            if (previousSelectedSegmentIndex == self.selectedSegmentIndex) {
                [self sendActionsForControlEvents:UIControlEventValueChanged];
            }
        }
    }
}


- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents {
    if(controlEvents == UIControlEventValueChanged){
        _reactOnTouchBegan = _touchBegan;
    }
    [super sendActionsForControlEvents:controlEvents];
}


@end

您可以通过以下方式完成此操作(谢谢您和):


我制作了一个开源(麻省理工学院许可)类(支持iOS 7+),其中包含了此功能(以及更多)。

参考@Matthias Bauch发布的答案。我不得不按照Xcode 7.3中的Swift 2.2做一些小改动:

class ReselectableSegmentedControl: UISegmentedControl {
    @IBInspectable var allowReselection: Bool = true
    override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
        let previousSelectedSegmentIndex = self.selectedSegmentIndex
        super.touchesEnded(touches, withEvent: event)
        if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
            if let touch = touches.first {
                let touchLocation = touch.locationInView(self)
                if CGRectContainsPoint(bounds, touchLocation) {
                    self.sendActionsForControlEvents(.ValueChanged)
                }
            }
        }
    }
}
类可重新选择SegmentedControl:UISegmentedControl{
@IBInspectable var allowReselection:Bool=true
覆盖func touchesEnded(触摸:设置,withEvent事件:UIEvent?){
让previousSelectedSegmentIndex=self.selectedSegmentIndex
super.touchesend(touchs,withEvent:event)
如果AllowSelection&&previousSelectedSegmentIndex==self.selectedSegmentIndex{
如果让触摸=先触摸{
让touchLocation=touch.locationInView(自)
如果CGRectContainsPoint(边界、触摸位置){
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}

swift3.1版本由@Kushal Ashok发布

class ReselectableSegmentedControl: UISegmentedControl {
    @IBInspectable var allowReselection: Bool = true

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let previousSelectedSegmentIndex = self.selectedSegmentIndex
        super.touchesEnded(touches, with: event)
        if allowReselection && previousSelectedSegmentIndex == self.selectedSegmentIndex {
            if let touch = touches.first {
                let touchLocation = touch.location(in: self)
                if bounds.contains(touchLocation) {
                    self.sendActions(for: .valueChanged)
                }
            }
        }
    }
}
类可重新选择SegmentedControl:UISegmentedControl{
@IBInspectable var allowReselection:Bool=true
覆盖函数touchesend(touchs:Set,带有事件:UIEvent?){
让previousSelectedSegmentIndex=self.selectedSegmentIndex
super.touchesend(触摸,带有:事件)
如果AllowSelection&&previousSelectedSegmentIndex==self.selectedSegmentIndex{
如果让触摸=先触摸{
让touchLocation=touch.location(in:self)
if bounds.contains(触摸位置){
self.sendActions(针对:.valueChanged)
}
}
}
}
}

关于@Stunner,这是我为实现目标所做的贡献。我更改了一些内容并添加了属性\u previousSelectedSegmentIndex;在@Stunner代码中,变量previousSelectedSegmentIndex无效:

@implementation STASegmentedControl
{
    NSInteger _previousSelectedSegmentIndex;
}

- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex
{
    [super setSelectedSegmentIndex: selectedSegmentIndex];

    _previousSelectedSegmentIndex = self.selectedSegmentIndex;
}

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

    CGPoint locationPoint = [[touches anyObject] locationInView:self];
    CGPoint viewPoint = [self convertPoint:locationPoint fromView:self];
    if (self.toggleableSegments) { // toggle selected segment on/off
        if ([self pointInside:viewPoint withEvent:event] && _previousSelectedSegmentIndex == self.selectedSegmentIndex) {
            self.selectedSegmentIndex = UISegmentedControlNoSegment;
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
    _previousSelectedSegmentIndex = self.selectedSegmentIndex;
}

当iOS7发布时,这个答案第二次帮助了我。我应该能够再次向上投票。您还可以通过添加
self.selectedSegmentIndex=UISegmentedControlNoSegment,将取消选择的代码封装在此子类中在<代码>[自发送操作ForControlEvents:UIControlEventValueChanged]之前该解决方案的问题是,当您开始触摸组件并将其拖动到组件外部以取消触摸时,它仍然会取消选择项目,这是一个非常好的解决方案!谢谢
[self pointInside:withEvent]
使用分段控件的可见维度。如果我使用它并触摸内部,然后在释放之前向外拖动一点,它会使片段呈现浅蓝色,就像我的手指仍然向下,并且不会发送事件。有没有办法知道控件的命中测试区域,以查看该点是否在该区域内?
@implementation STASegmentedControl
{
    NSInteger _previousSelectedSegmentIndex;
}

- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex
{
    [super setSelectedSegmentIndex: selectedSegmentIndex];

    _previousSelectedSegmentIndex = self.selectedSegmentIndex;
}

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

    CGPoint locationPoint = [[touches anyObject] locationInView:self];
    CGPoint viewPoint = [self convertPoint:locationPoint fromView:self];
    if (self.toggleableSegments) { // toggle selected segment on/off
        if ([self pointInside:viewPoint withEvent:event] && _previousSelectedSegmentIndex == self.selectedSegmentIndex) {
            self.selectedSegmentIndex = UISegmentedControlNoSegment;
            [self sendActionsForControlEvents:UIControlEventValueChanged];
        }
    }
    _previousSelectedSegmentIndex = self.selectedSegmentIndex;
}