У меня есть UISegmentedControl
с 4 сегментами. Когда он выбран, он должен поддерживать selected state
. Когда один и тот же сегмент снова щелкнут, он должен deselect itself
.
Как достичь этого?
Как отменить выбор сегмента в Сегментированной кнопке управления до тех пор, пока не будет снова нажата
Ответ 1
Так как 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. Выбранный параметр SegmentIndex теперь изменяется во время touchesEnded:
.
Итак, обновленный подкласс должен выглядеть следующим образом:
@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, исправлена проблема, которую заметил Гжегорж.
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)
}
}
}
}
}
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)
}
}
}
}
Ответ 2
Вот исправление проблемы, когда при попытке отменить выбор, нажав на 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];
}
}
}
Ответ 3
Вы можете сделать это со следующим (спасибо ответ Grzegorz и ответ Маттиаса):
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSInteger previousSelectedSegmentIndex = self.selectedSegmentIndex;
[super touchesEnded:touches withEvent:event];
CGPoint locationPoint = [[touches anyObject] locationInView:self];
CGPoint viewPoint = [self convertPoint:locationPoint fromView:self];
if ([self pointInside:viewPoint withEvent:event] && previousSelectedSegmentIndex == self.selectedSegmentIndex) {
self.selectedSegmentIndex = UISegmentedControlNoSegment;
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
Я сделал класс с открытым исходным кодом (MIT Licensed) STASegmentedControl (поддерживает iOS 7+), у которого есть эта функция, испеченная в (и более).
Ответ 4
Очень полезно! Спасибо! Я хотел немного больше контролировать события для своего проекта, поэтому я адаптировал @Matthias ответ для отправки пользовательского события "Значение без изменений". Я привел пример GitHub.
Я также включил исправление @Grzegorz, так что он ведет себя правильно, если пользователь перетаскивает свой палец за пределы сегментированного элемента управления.
Ответ 5
Вот решение, которое не зависит от версии 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
Ответ 6
Относительно ответа, опубликованного @Matthias Bauch. Я должен был внести небольшие изменения в соответствии с Swift 2.2 в Xcode 7.3:
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)
}
}
}
}
}
Ответ 7
версия быстрого 3.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)
}
}
}
}
}
Ответ 8
В отношении @Stunner это мой вклад в достижение цели. Я что-то изменил и добавил свойство _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;
}