【发布时间】:2010-12-09 21:49:25
【问题描述】:
我有一个分段控件,用户可以在其中选择如何对列表进行排序。工作正常。
但是,我希望当点击已选择的片段时,顺序会颠倒。我有所有的代码,但我不知道如何在这些段上注册水龙头。您可以使用的唯一控制事件似乎是 UIControlEventValueChanged,但这不起作用(因为选定的段实际上并没有改变)。
有解决办法吗?如果是这样,它是什么?
提前致谢!
【问题讨论】:
我有一个分段控件,用户可以在其中选择如何对列表进行排序。工作正常。
但是,我希望当点击已选择的片段时,顺序会颠倒。我有所有的代码,但我不知道如何在这些段上注册水龙头。您可以使用的唯一控制事件似乎是 UIControlEventValueChanged,但这不起作用(因为选定的段实际上并没有改变)。
有解决办法吗?如果是这样,它是什么?
提前致谢!
【问题讨论】:
你可以继承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];
}
【讨论】:
if 语句的开头添加[self setSelectedSegmentIndex:UISegmentedControlNoSegment];(恕我直言,它确实应该使用花括号)
我自己想要这个。采用 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
【讨论】:
这适用于 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]?超级版会发生什么?
- (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); }
目前提出的解决方案仍然不起作用,因为除非真的点击了一个新段,否则永远不会调用 setSelectedSegmentIndex。至少在我的情况下这从未奏效,但我确实使用 iOS5,也许这个解决方案在 iOS4 中确实有效。无论如何,这是我的解决方案。 它需要一个额外的子类方法,如下:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self setSelectedSegmentIndex:self.selectedSegmentIndex];
[super touchesEnded:touches withEvent:event];
}
祝你好运!
【讨论】:
[self setSelectedSegmentIndex:self.selectedSegmentIndex]; 这一行。 [super ...] 调用使该行变得不必要。
这是一个相当老的问题,所以我想添加我在 iOS 5+ 应用程序中遇到此问题时使用的相当简单的解决方案。
我所做的只是将一个 Tap Gesture Recognizer 拖到我的 .xib 文件中的 UISegmentedControl 上。检查新手势识别器的连接,确保分段控件是唯一的手势识别器出口,然后将其操作连接到您要在控制器中触发的方法。
此方法应在您触摸分段控件时触发,因此只需使用实例变量检查选定的索引,以查看用户是否触摸了已选定的分段。
@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 方法中。
【讨论】:
斯威夫特 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)
}
}
}
来自here
【讨论】:
这行得通:
- (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 中,我至少可以想象以下两种解决方案。对于特殊情况,我还发布了第三种解决方案,其中选定的段用作切换按钮。
解决方案 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)")
}
}
【讨论】:
segmentedValues 是什么?
mySegmentedControl,解决方案 1 对我不起作用。
segmentedValues 实际上应该是 mySegmentedControl - 我已经解决了,谢谢提示
我的第一个想法是将Touch Up Inside 或Touch Down 操作连接到您的排序方法,但这似乎不起作用。
第二个想法更多的是变通,在分段控件上设置Momentary 属性。然后,每次点击它都会触发Value Did Change 动作。
【讨论】:
momentary 控件不会显示当前选择的任何段,这可能有点令人困惑。
帮助很大!我想要做的是选择一个或不设置按钮 - 当设置一个按钮时,第二次点击会取消它。这是我的修改:
- (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 first 让您在重置之前读取控件以找到当前设置。
【讨论】:
这是我的解决方案。如果您希望在每次触摸时触发 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
【讨论】:
根据 Bob de Graaf 的回答:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventAllTouchEvents];
[super touchesEnded:touches withEvent:event];
}
注意使用UIControlEventAllTouchEvents 而不是UIControlEventValueChanged。
也无需致电-(void)setSelectedSegmentIndex:。
【讨论】:
在对我有用的代码段下方。重复点击同一段将取消选择它。
@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
【讨论】:
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
【讨论】:
似乎是一个有趣的问题。该方案扩展了 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
}
【讨论】:
我正在使用 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
【讨论】:
从当前的 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];
实现两个动作方法:
-(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
}
【讨论】:
因为 UISegmentedControl 负责设置段,所以它应该只重置状态。我稍微修改了其他人对 swift 5 的建议:
//MARK: Custom segmentedControl
/// Every element works like a flipflop [see](http://stackoverflow.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
}
}
}
}
}
【讨论】: