Como evitar que o UIButton pisque ao atualizar o título

85

Quando chamo setTitle em um UIButton, o botão pisca no iOS 7. Tentei definir myButton.highlighted = NO, mas isso não impediu que o botão pisque.

[myButton setTitle:[[NSUserDefaults standardUserDefaults] stringForKey:@"elapsedLabelKey"] forState:UIControlStateNormal];

myButton.highlighted = NO;

Aqui está como eu configurei o cronômetro que atualizou os títulos:

- (void)actionTimer {
    if (myTimer == nil) {

        myTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0
                        target: self
                        selector: @selector(showActivity)
                        userInfo: nil
                        repeats: YES];
    }
}

Aqui está o método que realmente atualiza os títulos:

- (void)showActivity {

    NSString *sym = [[NSLocale currentLocale] objectForKey:NSLocaleCurrencySymbol];

    if (pauseInterval == nil) {

        // Update clock
        seconds = [[NSDate date] timeIntervalSinceDate:startInterval] - breakTime;

        // Update total earned
        secRate = rate.value / 60 / 60;
        total = secRate * seconds;
        [totalLabel setTitle:[NSString stringWithFormat:@"%@%.4f",sym,total] forState:UIControlStateNormal];

        days = seconds / (60 * 60 * 24);
        seconds -= days * (60 * 60 * 24);
        int hours = seconds / (60 * 60);
        fhours = (float)seconds / (60.0 * 60.0);
        seconds -= hours * (60 * 60);
        int minutes = seconds / 60;
        seconds -= minutes * 60;

        // Update the timer clock
        [elapsed setTitle:[NSString stringWithFormat:@"%.2i:%.2i:%.2i:%.2i",days,hours,minutes,seconds] forState:UIControlStateNormal];
    }
}
FierceMonkey
fonte
Não deveria estar piscando. Você está tentando fazer isso em um thread de segundo plano?
BergQuester,
Sim, está sendo definido em um cronômetro para atualizar o título do botão a cada segundo (o que eu acho que cria seu próprio tópico)
FierceMonkey
Os cronômetros só funcionam em uma thread separada se você configurá-los para isso. Podemos ver o método em que o título é atualizado e como você configura o cronômetro?
BergQuester,
Eu adicionei o código solicitado à pergunta principal
FierceMonkey

Respostas:

208

Defina o tipo de botão como UIButtonTypeCustom e ele parará de piscar

Quanlong
fonte
16
Esta realmente deveria ser a resposta preferida. A solução mais simples. E está funcionando.
Johan Karlsson
4
Acordado. Tentei muitas outras soluções e foi isso que aconteceu, por si só.
Churrasco Steve
2
Esta deve ser a resposta aceita. A solução mais simples até agora.
Machado
1
Funciona como um encanto! Obrigado
2
Acho que é uma solução muito boa sem escrever nenhum código para isso.
Chauyan
58

Uma abordagem melhor do [UIView setAnimationsEnabled:NO]que aquela que pode afetar outras animações é desativar apenas a animação do título específico.

Objective-C:

[UIView performWithoutAnimation:^{
  [myButton setTitle:text forState:UIControlStateNormal];
  [myButton layoutIfNeeded];
}];

Rápido:

UIView.performWithoutAnimation { 
    myButton.setTitle(text, for: .normal)
    myButton.layoutIfNeeded()
}
Cœur
fonte
Agora mesmo experimentei a solução Swift no iOS mais recente - funciona muito bem.
Ronen Rabinovici
1
Salvador do dia. Felicidades!
pkc456
Tentei fazer isso no Swift 3 e funciona sem o uso de layoutIfNeeded ()
user1898712
3
Esta deve ser a resposta aceita sem dúvida!
Julius
1
Esta é a melhor maneira de fazer isso. Ótima resposta!
termina
29

* Observe *

quando " buttonType " de _button é "UIButtonTypeSystem" , o código abaixo é inválido :

[UIView setAnimationsEnabled:NO];
[_button setTitle:@"title" forState:UIControlStateNormal];
[UIView setAnimationsEnabled:YES];

quando " buttonType " de _button é "UIButtonTypeCustom" , o código acima é válido .

shede333
fonte
19

Veja as respostas de Como parar a animação UIButton indesejada na mudança de título? :

[UIView setAnimationsEnabled:NO];
[elapsed setTitle:[NSString stringWithFormat:@"%.2i:%.2i:%.2i:%.2i",days,hours,minutes,seconds] forState:UIControlStateNormal];
[UIView setAnimationsEnabled:YES];
Marius
fonte
7
@FierceMonkey tente adicionar uma chamada para layoutIfNeeded: [elapsed layoutIfNeeded];. Isso removeu o piscar no meu caso.
Klaas de
1
layoutIfNeeded fez isso por mim.
Caulim Fogo
Parece ser uma solução bastante suja, mas layoutIfNeededparece estar funcionando
Przemysław Wrzesiński
7

Você pode simplesmente criar outra função para UIButton para facilitar o uso futuro.

extension UIButton {
    func setTitleWithoutAnimation(_ title: String?, for controlState: UIControlState) {
        UIView.performWithoutAnimation {
            self.setTitle(title, for: controlState)
            self.layoutIfNeeded()
        }
    }
}

É isso!

E apenas defina o título como faria antes, mas substitua setTitleporsetTitleWithoutAnimation

Daniel Tseng
fonte
6

O comportamento padrão "setTitle" é definitivamente odioso!

Minha solução é:

[UIView setAnimationsEnabled:NO];
[_button layoutIfNeeded]; 
[_button setTitle:[NSString stringWithFormat:@"what" forState:UIControlStateNormal];
[UIView setAnimationsEnabled:YES];

Além disso, no storyboard, na propriedade do botão, desmarco:

  • Inverte em destaque
  • Imagem Ajustada em Destaque

E verifique:

  • Desativado Ajustar Imagem

Testado e funcionando no iOS 8 também.

Atualizar - Swift

Eu sugiro que você crie seu botão personalizado e substitua o método setTitle .

class UnflashingButton: UIButton {
   
    override func setTitle(title: String?, forState state: UIControlState) {
        UIView.setAnimationsEnabled(false)
        self.layoutIfNeeded()
        super.setTitle(title, forState: state)
        UIView.setAnimationsEnabled(true)
    }

}
Luca Davanzo
fonte
Eu tentei essa abordagem (subclassificação, substituindo setTitle: forState: e desabilitando-habilitando UIView.setAnimationsEnabled). Não funciona para mim no iOS 9 e 10 beta. Funciona se eu apenas desativar as animações, mas elas
serão
2

Experimente isto, funciona em versões mais recentes do IOS:

class CustomButtonWithNoEffect : UIButton {
    override func setTitle(_ title: String?, for state: UIControlState) {
        UIView.performWithoutAnimation {
            super.setTitle(title, for: state)
            super.layoutIfNeeded()
        }
    }

}
Kárpáti András
fonte
0

Eu sou novo em codificação e Stackoverflow, então não tenho reputação o suficiente para comentar diretamente e expandir https://stackoverflow.com/users/4673064/daniel-tseng excelente resposta. Portanto, tenho que escrever minha própria nova resposta, e é esta:

extension UIButton {
    func setTitleWithoutAnimation(_ title: String?, for controlState: UIControlState) {
        UIView.performWithoutAnimation {
            self.setTitle(title, for: controlState)
            self.layoutIfNeeded()
        }
    }
}

Funciona muito bem, EXCETO:

Se todas as minhas chamadas posteriores no código para "setTitleWithoutAnimation" não especificarem um remetente, recebo essas mensagens estranhas relacionadas a CoreMedia ou CoreData, por exemplo, "Falha ao herdar permissões CoreMedia de 2526: (nulo)"

Isso é provavelmente muito básico para codificação e iOS, mas para mim, como um novo programador, me deixou em uma trilha de coelho por um tempo, como em: Hoje a extensão falhou ao herdar as permissões do CoreMedia , onde as pessoas tinham respostas interessantes, mas NÃO chegar à raiz do meu problema.

Então, finalmente descobri que precisava passar e fornecer parâmetros às minhas funções sem parâmetros, ou seja, eu precisava especificar um remetente. Esse remetente pode ser UIButton, Any ou AnyObject. Tudo isso funcionou, mas no final das contas parece haver um conflito entre adicionar uma extensão de botão com "self" e não dizer especificamente mais tarde que um botão a está usando.

Novamente, provavelmente básico, mas novo para mim, então achei que valeria a pena compartilhar.

Ian Hanson
fonte
0

Outro truque

@IBOutlet private weak var button: UIButton! {
    didSet {
        button.setTitle(title, for: .normal)
    }
}

Isso funciona apenas se você souber o valor do título sem fazer chamadas de API. O título do botão serádefinido antes de a vista ser carregada, por isso não verá a animação

TunaTheDog
fonte