UISegmentedControl registar toques no segmento seleccionado
-
06-07-2019 - |
Pergunta
Eu tenho um controle segmentado, onde o usuário pode selecionar como encomendar uma lista. Funciona bem.
No entanto, gostaria que, quando um segmento já selecionado é aproveitado, a ordem se inverte. Eu tenho todo o código no lugar, mas eu não sei como registrar as torneiras nesses segmentos. Parece que o único evento de controle que você pode usar é UIControlEventValueChanged, mas que não está funcionando (uma vez que o segmento selecionado não está realmente mudando).
Existe uma solução para isso? E se assim for, o que é?
Agradecemos antecipadamente!
Solução
Você pode subclasse UISegmentedControl
, e depois override setSelectedSegmentIndex:
- (void) setSelectedSegmentIndex:(NSInteger)toValue {
if (self.selectedSegmentIndex == toValue) {
[super setSelectedSegmentIndex:UISegmentedControlNoSegment];
} else {
[super setSelectedSegmentIndex:toValue];
}
}
Se estiver usando IB, certifique-se de definir a classe de seu UISegmentedControl
a sua subclasse.
Agora você pode ouvir a mesma UIControlEventValueChanged
como faria normalmente, exceto se o usuário desmarcada do segmento, você verá uma 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]);
}
}
Outras dicas
Eu acho que é até um pouco melhor se você usar -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
--- como este é o comportamento de UISegmentedControl. Além disso, parece que você não precisa sobrecarregar o -(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];
}
Procurado isso mesmo. Tomou solução de Julian (graças!) E ligeiramente modificado.
O UISegmentedControl seguinte subclasse simplesmente dispara o evento UIControlEventValueChanged
mesmo quando o valor não se alterou, o que obviamente lê um pouco estranho, mas funciona bem no meu caso e mantém as coisas 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
Isso funciona em ambos iOS 4 e 5:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
int oldValue = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if ( oldValue == self.selectedSegmentIndex )
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
A solução atual ainda apresentada não trabalho, porque setSelectedSegmentIndex nunca é chamado a não ser realmente um novo segmento é aproveitado. Pelo menos no meu caso, isso nunca funcionou, eu uso iOS5 embora, talvez esta solução operavam em iOS4. De qualquer forma, esta é a minha solução. Ele precisa de um método da subclasse extra, que é o seguinte:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self setSelectedSegmentIndex:self.selectedSegmentIndex];
[super touchesEnded:touches withEvent:event];
}
Boa sorte!
Esta foi uma pergunta muito velho, então eu pensei que eu iria adicionar a solução bastante simples que eu usei quando eu corri para este em um iOS 5+ aplicativo.
Tudo que eu fiz foi arrastar um Tap Gesto reconhecedor para o UISegmentedControl no meu arquivo .xib. Verifique as ligações para o seu novo gesto de reconhecimento para garantir que o controle segmentado é a única saída GestureRecognizer e depois é só conectar-se a sua ação para o método que você deseja fogo em seu controlador.
Este método deve disparar a qualquer momento que você tocar o controle segmentado, de modo que basta verificar o índice selecionado talvez com uma variável de instância para ver se o usuário tocou o segmento já selecionado.
@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
Em iOS8 cada resposta aqui parece ou não trabalho ou mudança gatilho duas vezes. Eu vim com uma solução muito simples - então eu gostaria de compartilhá-lo
.Em subclasse eu só tenho:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self setSelectedSegmentIndex:UISegmentedControlNoSegment];
}
O que acontece é a repor segmento seleccionado a -1 antes de mudar de segmento de controlo segmentada. Ela vai acontecer no método touchesEnded.
Isso 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];
}
}
Swift 3 eu poderia imaginar pelo menos 2 soluções como segue. Para um caso especial que eu postei também uma terceira solução, onde o segmento selecionado funciona como botão de alternância.
Solução 1:
Dica: Esta solução só funciona em controlSegments momentâneas! Se você precisa de uma solução para controles estacionárias, escolha Solução 2
A idéia é registrar um segundo evento que dispara acima em 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")
}
}
}
Solução 2:
A outra maneira é para substituir as funções de toque em UISegmentedControl
e para disparar o valueChanged
mesmo se o índice do segmento não mudou. Portanto, você poderia substituir o UISegmentedControl
da seguinte forma:
// 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")
}
}
}
Solução 3: Se a sua abordagem era ter o segmento selecionado ser um botão de alternância de Solução 2 pode ser alterado para limpar o código como este:
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
}
}
}
Agora nós podemos limpar a função changeSelection
da seguinte forma:
func changeSelection(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case UISegmentedControlNoSegment:
print("none")
default:
print("selected: \(sender.selectedSegmentIndex)")
}
}
Aqui está a minha solução. O mais elegante que eu acho que se você deseja que o evento ValueChanged ao fogo em todos os toques ...
.h
@interface UISegmentedControlAllChanges : UISegmentedControl
@end
.m
@implementation UISegmentedControlAllChanges
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventValueChanged];
[super touchesEnded:touches withEvent:event];
}
@end
A primeira ideia que tive foi de arame até os Touch Up Inside
ou Touch Down
ações para o seu método de classificação, mas isso não parece trabalho.
A segunda idéia é mais um trabalho em torno, defina a propriedade Momentary
no controle segmentado. Este, então, disparar uma ação Value Did Change
cada vez que é tocada.
Big ajuda! O que eu quero fazer é ter a opção de um ou nenhum botão set - e quando um botão é definida, um segundo toque desactiva-o. Esta é a minha modificação:
- (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 primeiro permite que você leia o controle para encontrar a configuração atual antes que seja reiniciado.
Com base na resposta de Bob de Graaf:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventAllTouchEvents];
[super touchesEnded:touches withEvent:event];
}
Observe o uso de UIControlEventAllTouchEvents
vez de UIControlEventValueChanged
.
Além disso, não há necessidade de -(void)setSelectedSegmentIndex:
chamada.
Abaixo o pedaço de código que fez o trabalho para mim. tap repetido no mesmo segmento vai desmarcá-lo.
@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 solução. Substituir UISegmentedControl com essa classe:
@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
Parece uma pergunta divertida de resposta. Este esquema expande soluções yershuachu de Steve E de e. Esta versão utiliza um UITapGestureRecognizer para capturar todos os toques e que define o selectedSegmentIndex para -1; mas também passa em todos os toques para o UISegmentedControl para que ele possa lidar com quaisquer toques normais. Não é necessária nenhuma subclasse.
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
}
Eu estou usando KVO para inverter segmento já selecionados 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
A solução da resposta selecionada não funcionou para mim como de corrente v8.x. iOS Mas @Andy oferecer uma solução e vou completar:
subclasse o UISegmentedControl
e método touchesEnded
de substituição na subclasse:
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self sendActionsForControlEvents:UIControlEventAllTouchEvents];
[super touchesEnded:touches withEvent:event];
}
Na classe onde você pretende usar o UISegmentedControl, criar um iVar currentSelectedSegIndex. Adicione os UIControlEventAllTouchEvents ao lado de seus métodos de ação UIControlEventValueChanged:
NSInteger currentSelectedSegIndex;
[aSegmentedController addTarget:self action:@selector(aSegmentedControllerSelection:) forControlEvents:UIControlEventValueChanged];
[aSegmentedController addTarget:self action:@selector(aSegmentedControllerAllTouchEvent:) forControlEvents:UIControlEventAllTouchEvents];
Implementar os dois métodos de ação:
-(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
}
Isso deve 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];
}
Lembre-se conectar este IBAction
ao seu Value Changed
tomada de controle segmentada nos Conexões Inspector .