iphone - how - swift 4 segmented control example
UISegmentedControl registrar taps en el segmento seleccionado (18)
Aquí está mi solución. El más elegante, creo, si quieres que el evento ValueChanged se dispare con cada toque ...
.h
@interface UISegmentedControlAllChanges : UISegmentedControl
@end
.metro
@implementation UISegmentedControlAllChanges
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventValueChanged];
[super touchesEnded:touches withEvent:event];
}
@end
Tengo un control segmentado donde el usuario puede seleccionar cómo ordenar una lista. Funciona bien.
Sin embargo, me gustaría que cuando se toca un segmento ya seleccionado, el orden se invierta. Tengo todo el código en su lugar, pero no sé cómo registrar los toques en esos segmentos. Parece que el único evento de control que puedes usar es UIControlEventValueChanged, pero eso no funciona (ya que el segmento seleccionado no está cambiando).
¿Existe alguna solucion para esto? Y si es así, ¿qué es?
¡Gracias por adelantado!
Basado en la respuesta de Bob de Graaf:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventAllTouchEvents];
[super touchesEnded:touches withEvent:event];
}
Tenga en cuenta el uso de UIControlEventAllTouchEvents
lugar de UIControlEventValueChanged
.
Tampoco hay necesidad de llamar -(void)setSelectedSegmentIndex:
Creo que es incluso un poco mejor si usa -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
--- ya que este es el comportamiento de UISegmentedControl. Además, parece que no necesita sobrecargar el -(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];
}
Debajo de la pieza de código que funcionó para mí. La pulsación repetida en el mismo segmento la deseleccionará.
@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
En Swift 3 podría imaginar al menos 2 soluciones de la siguiente manera. Para un caso especial, también publiqué una tercera solución, donde el segmento seleccionado funciona como botón de alternar.
Solución 1:
Sugerencia: ¡Esta solución solo funciona con controles de tiempo momentáneos! Si necesita una solución para controles estacionarios, elija la Solución 2
La idea es registrar un segundo evento que se active en el retoque interno:
// 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")
}
}
}
Solución 2: La otra forma es anular las funciones táctiles en UISegmentedControl
y disparar el valor valueChanged
incluso si el índice de segmento no ha cambiado. Por lo tanto, podría anular el UISegmentedControl
siguiente manera:
// 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")
}
}
}
Solución 3: si su enfoque era que el segmento seleccionado fuera un botón de alternancia, la Solución 2 podría cambiarse para limpiar el código de esta manera:
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
}
}
}
Ahora podríamos limpiar la función changeSelection
siguiente manera:
func changeSelection(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case UISegmentedControlNoSegment:
print("none")
default:
print("selected: /(sender.selectedSegmentIndex)")
}
}
En iOS8, todas las respuestas aquí parecen no funcionar o desencadenar el cambio dos veces. Se me ocurrió una solución muy simple, así que me gustaría compartirla.
En la subclase solo tengo:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self setSelectedSegmentIndex:UISegmentedControlNoSegment];
}
Lo que hace es restablecer el segmento seleccionado a -1 antes de cambiar el segmento de control segmentado. Ocurrirá en el método touchesEnded.
Esta era una pregunta bastante antigua, así que pensé que agregaría la solución bastante simple que usé cuando me encontré con esto en una aplicación iOS 5+.
Todo lo que hice fue arrastrar un Tap Gesture Recognizer al UISegmentedControl en mi archivo .xib. Verifique las conexiones de su nuevo reconocedor de gestos para asegurarse de que el control segmentado sea la única salida de Reconocimiento de gestos y luego conecte su acción al método que desea activar en su controlador.
Este método debería activarse cada vez que toque el control segmentado, así que solo revise el índice seleccionado con quizás una variable de instancia para ver si el usuario tocó el segmento ya seleccionado.
@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
Esto debería funcionar:
- (IBAction)segmentedControlValueChanged:(id)sender { UISegmentedControl *sc = (UISegmentedControl*) sender; switch ([sc selectedSegmentIndex]) { case 0: NSLog(@"First option!"); break; case 1: NSLog(@"Second option!"); break; case UISegmentedControlNoSegment: NSLog(@"No option..."); break; default: NSLog(@"No selected option :( "); } //this resets the segmented control (mySC) value to act as a button. [self.mySC setSelectedSegmentIndex:UISegmentedControlNoSegment]; }
Recuerde conectar esta IBAction
a su salida de control segmentado. Value Changed
en el Inspector de conexiones .
Esto funciona tanto en iOS 4 como en 5:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
int oldValue = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if ( oldValue == self.selectedSegmentIndex )
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
Esto funciona:
- (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];
}
}
Estoy usando KVO para invertir el segmento ya seleccionado para 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
Gran ayuda Lo que quiero hacer es tener la opción de configurar uno o ningún botón, y cuando se configura un botón, una segunda pulsación lo desactiva. Esta es mi modificación:
- (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];
}
}
Notificar primero le permite leer el control para encontrar la configuración actual antes de que se restablezca.
La primera idea que tuve fue conectar las acciones de Touch Up Inside
o Touch Down
a tu método de clasificación, pero esto no parece funcionar.
La segunda idea es más bien una solución, establezca la propiedad Momentary
en el control segmentado. Esto activará una acción de Value Did Change
cada vez que se toca.
La solución actual presentada todavía no funciona, porque nunca se llama a setSelectedSegmentIndex a menos que realmente se toque un nuevo segmento. Al menos en mi caso esto nunca funcionó, aunque sí uso iOS5, quizás esta solución funcionó en iOS4. De todos modos, esta es mi solución. Necesita un método de subclase adicional, que es el siguiente:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self setSelectedSegmentIndex:self.selectedSegmentIndex];
[super touchesEnded:touches withEvent:event];
}
¡Buena suerte!
La solución de la respuesta seleccionada no me funcionó a partir de la versión actual de iOS v8.x. Pero @Andy ofrece una solución y completaré:
Subclase el UISegmentedControl
y sobrescriba el método touchesEnded
en la subclase:
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventAllTouchEvents];
[super touchesEnded:touches withEvent:event];
}
En la clase en la que pretende utilizar UISegmentedControl, cree un iVar currentSelectedSegIndex. Agregue los UIControlEventAllTouchEvents al lado de sus métodos de acción UIControlEventValueChanged:
NSInteger currentSelectedSegIndex;
[aSegmentedController addTarget:self action:@selector(aSegmentedControllerSelection:) forControlEvents:UIControlEventValueChanged];
[aSegmentedController addTarget:self action:@selector(aSegmentedControllerAllTouchEvent:) forControlEvents:UIControlEventAllTouchEvents];
Implementar los dos métodos de acción:
-(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
}
Quería esto yo mismo. Tomó la solución de Julian (¡gracias!) Y la modificó ligeramente.
La siguiente subclase UISegmentedControl simplemente activa el evento UIControlEventValueChanged
incluso cuando el valor no cambió, lo que obviamente se lee un poco raro, pero funciona bien en mi caso y mantiene las cosas simples.
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
Solución iOS 9. Reemplace UISegmentedControl con dicha clase:
@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
Puede subclase UISegmentedControl
y, a continuación, override setSelectedSegmentIndex:
- (void) setSelectedSegmentIndex:(NSInteger)toValue {
if (self.selectedSegmentIndex == toValue) {
[super setSelectedSegmentIndex:UISegmentedControlNoSegment];
} else {
[super setSelectedSegmentIndex:toValue];
}
}
Si usa IB, asegúrese de establecer la clase de su UISegmentedControl
en su subclase.
Ahora puede escuchar el mismo UIControlEventValueChanged
como lo haría normalmente, excepto que si el usuario deseleccionó el segmento, verá UISegmentedControlNoSegment
selectedSegmentIndex
igual a 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]);
}
}