229

In iOS 7 my UIButton titles are animating in and out at the wrong time - late. This problem does not appear on iOS 6. I'm just using:

[self setTitle:text forState:UIControlStateNormal];

I would prefer this happens instantly and without a blank frame. This blink is especially distracting and draws attention away from other animations.

pkamb
  • 26,648
  • 20
  • 124
  • 157
exsulto
  • 2,375
  • 2
  • 14
  • 10
  • We're experiencing this as well. Not sure if it's a iOS7 bug or something we should fix. – Sway Sep 22 '13 at 22:50
  • Try,[self.button setHighlighted:NO]; – karthika Sep 23 '13 at 04:58
  • Thanks for these ideas. I tried setHighlighted:NO, but no luck there. I am able to reduce the blink by placing setTitle inside: [UIView animateWithDuration:0.0f animations:^{ ... }]; – exsulto Sep 23 '13 at 13:48
  • 1
    You can use this workaround in some cases: `self.button.titleLabel.text = text`. But this don't resize label frame and don't work with UIControlStates correctly – zxcat Oct 08 '13 at 11:58
  • That's a clever workaround. I'll play with this and see what happens, unfortunately I'm using UIControlStates. – exsulto Oct 09 '13 at 14:46
  • only answer from dubenko worked for me: stackoverflow.com/questions/18946490/how-to-stop-unwanted-uibutton-animation-on-title-change#23212964 – samir105 Oct 12 '14 at 05:21
  • Using custom UIButton type solved my problem. – Amit Tandel Dec 15 '17 at 09:05

24 Answers24

276

Use the performWithoutAnimation: method and then force layout to happen immediately instead of later on.

[UIView performWithoutAnimation:^{
  [self.myButton setTitle:text forState:UIControlStateNormal];
  [self.myButton layoutIfNeeded];
}];
Abhi Beckert
  • 30,929
  • 11
  • 77
  • 106
Snowman
  • 29,431
  • 43
  • 165
  • 290
166

This works for custom buttons:

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

For system buttons you need to add this before re-enabling animations (thank you @Klaas):

[_button layoutIfNeeded];
Liau Jian Jie
  • 6,585
  • 2
  • 13
  • 16
Jacob K
  • 2,601
  • 1
  • 13
  • 20
95

In Swift you can use :

UIView.performWithoutAnimation {
    self.someButtonButton.setTitle(newTitle, forState: .normal)
    self.someButtonButton.layoutIfNeeded()
}
Paulw11
  • 95,291
  • 12
  • 135
  • 153
  • 1
    This was by far the easiest method. And thank you for including a swift answer btw – simplexity Mar 10 '16 at 15:28
  • 1
    Best answer for Swift! – Nubaslon Jun 02 '17 at 11:08
  • Had an annoying bug where changing a UIButton title while offscreen would cause weird animation timings with interactivePopGestureRecognizer and this solved it. I still think it's a bug with the OS though – SparkyRobinson Aug 07 '17 at 02:05
  • Strange that .layoutIfNeeded() has to be called, but I tested it both ways in Swift 5 and it definitely still animates without it. – wildcat12 Nov 08 '19 at 14:08
  • 3
    Not really. If you don't call `layoutIfNeeded()` then the button is flagged as needing to be redrawn but this won't happen until the next layout pass, which will be outside the `performWithoutAnimation` – Paulw11 Nov 08 '19 at 18:57
91

Change button type to custom form interface builder.

enter image description here

This worked for me.

61

Please note :

when "buttonType" of _button is "UIButtonTypeSystem", below code is invalid

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

when "buttonType" of _button is "UIButtonTypeCustom", above code is valid.

shede333
  • 1,055
  • 9
  • 7
53

Starting in iOS 7.1 the only solution that worked for me was initializing the button with type UIButtonTypeCustom.

Norbert
  • 3,999
  • 6
  • 33
  • 57
  • This is the most sensible approach for anyone that doesn't require UIButtonTypeSystem. – DonnaLea May 23 '14 at 00:33
  • This ended up working best for me, I just created a CUSTOM button and made it look and highlight like a system button. Can hardly see the difference but you don't have that delay. – Travis M. Jun 27 '14 at 18:20
20

Swift 5

myButton.titleLabel?.text = "title"
myButton.setTitle("title", for: .normal)
Carter Randall
  • 313
  • 2
  • 6
18

so i find worked solution:

_logoutButton.titleLabel.text = NSLocalizedString(@"Logout",);
[_logoutButton setTitle:_logoutButton.titleLabel.text forState:UIControlStateNormal];

in first we change title for button, then resize button for this title

dubenko
  • 189
  • 1
  • 2
14

I’ve made a Swift extension to do this:

extension UIButton {
    func setTitleWithoutAnimation(title: String?) {
        UIView.setAnimationsEnabled(false)

        setTitle(title, forState: .Normal)

        layoutIfNeeded()
        UIView.setAnimationsEnabled(true)
    }
}

Works for me on iOS 8 and 9, with UIButtonTypeSystem.

(The code is for Swift 2, Swift 3 and Objective-C should be similar)

Xhacker Liu
  • 1,503
  • 1
  • 15
  • 25
13

Set the button type to UIButtonTypeCustom and it'll stop flashing

arunjos007
  • 3,749
  • 1
  • 22
  • 41
  • How can all those workaround "solutions" have so many upvotes when this simple answer must solve this issue 99% of the time... – Rob Apr 25 '19 at 07:19
10

UIButton with system type has implicit animation on setTitle(_:for:). You can fix it two different ways:

  1. Set the button type to custom, either from code or Interface Builder:
let button = UIButton(type: .custom)

enter image description here

  1. Disable animation from code:
UIView.performWithoutAnimation {
    button.setTitle(title, for: .normal)
    button.layoutIfNeeded()
}
sgl0v
  • 1,117
  • 8
  • 13
9

Set UIButton type as Custom. That should remove fade in and out animations.

Amit Tandel
  • 853
  • 7
  • 15
8

Usually simply setting the button type to Custom works for me, but for other reasons I needed to subclass UIButton and set the button type back to the default (System), so the blinking reappeared.

Setting UIView.setAnimationsEnabled(false) before changing the title and then to true again after that didn't avoid the blinking for me, no matter if I called self.layoutIfNeeded() or not.

This, and only this in the following exact order, worked for me with iOS 9 and 10 beta:

1) Create a subclass for UIButton (don't forget to set the custom class for the button in the Storyboard too).

2) Override setTitle:forState: as follows:

override func setTitle(title: String?, forState state: UIControlState) {

    UIView.performWithoutAnimation({

        super.setTitle(title, forState: state)

        self.layoutIfNeeded()
    })
}

In Interface Builder, you can leave the button type to System, no need to change it to Custom Type for this approach to work.

I hope this helps someone else, I've struggled for so long with the annoying blinking buttons that I hope to avoid it to others ;)

cdf1982
  • 704
  • 1
  • 15
  • 31
6

You can simply create Custom button and it will stop animate while changing the title.

        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setTitle:@"the title" forState:UIControlStateNormal];

you also can do it in Storyboard checkbox: select the button in storyboard -> select the attributes inspector (fourth from left side) -> in the 'Type' drop down menu, select 'Custom' instead of 'System' that was probably selected.

Good luck!

Mendy
  • 77
  • 1
  • 1
3

You can remove the animations from from the title label's layer:

    [[[theButton titleLabel] layer] removeAllAnimations];
Jacksonh
  • 377
  • 1
  • 6
3

Swift 4 version of Xhacker Liu answer

import Foundation
import UIKit
extension UIButton {
    func setTitleWithOutAnimation(title: String?) {
        UIView.setAnimationsEnabled(false)

        setTitle(title, for: .normal)

        layoutIfNeeded()
        UIView.setAnimationsEnabled(true)
    }
} 
faraz khonsari
  • 1,694
  • 17
  • 26
0

I've found that this workaround works with UIButtonTypeSystem as well but will only work if the button is enabled for some reason.

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

So you'll have to add these if you need the button to be disabled when setting its title.

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

(iOS 7, Xcode 5)

sCha
  • 1,291
  • 1
  • 10
  • 22
  • Just confirmed that this workaround no longer works on iOS 7.1. – sCha Mar 13 '14 at 17:13
  • don't suppose you've found a solution for 7.1? – George Green Mar 20 '14 at 15:24
  • @GeorgeGreen couldn't find any working solutions for **UIButtonTypeSystem**. I had to use **UIButtonTypeCustom**. – sCha Mar 21 '14 at 05:31
  • Since 7.1, you'll need to apply title changes to all states, setting it just for the normal state no longer applies. `[_button setTitle:@"title" forState:UIControlStateDisabled]` – Sam Apr 02 '14 at 16:30
0

Combining above great answers results in following workaround for UIButtonTypeSystem:

if (_button.enabled)
{
    [UIView setAnimationsEnabled:NO];
    [_button setTitle:@"title" forState:UIControlStateNormal];
    [UIView setAnimationsEnabled:YES];
}
else // disabled
{
    [UIView setAnimationsEnabled:NO];
    _button.enabled = YES;
    [_button setTitle:@"title" forState:UIControlStateNormal];
    _button.enabled = NO;
    [UIView setAnimationsEnabled:YES];
}
AppsolutEinfach
  • 1,229
  • 1
  • 13
  • 24
0

I got the ugly animation problem when changing button titles in view controllers within a UITabBarController. The titles that were originally set in the storyboard showed up for a short while before fading into their new values.

I wanted to iterate through all subviews and use the button titles as keys to get their localized values with NSLocalizedString, such as;

for(UIView *v in view.subviews) {

    if ([v isKindOfClass:[UIButton class]]) {
        UIButton *btn = (UIButton*)v;
        NSString *newTitle = NSLocalizedString(btn.titleLabel.text, nil);
        [btn setTitle:newTitle];
    }

}

I found out that what's triggering the animation is really the call to btn.titleLabel.text. So to still make use of the storyboards and have the components dynamically localized like this I make sure to set every button's Restoration ID (in Identity Inspector) to the same as the title and use that as key instead of the title;

for(UIView *v in view.subviews) {

    if ([v isKindOfClass:[UIButton class]]) {
        UIButton *btn = (UIButton*)v;
        NSString *newTitle = NSLocalizedString(btn.restorationIdentifier, nil);
        [btn setTitle:newTitle];
    }

}

Not ideal, but works..

Michael
  • 9
  • 1
0

You can actually set the title outside of an animation block, just be sure to call layoutIfNeeded() inside a performWithoutAnimation:

button1.setTitle("abc", forState: .Normal)
button2.setTitle("abc", forState: .Normal)
button3.setTitle("abc", forState: .Normal)
UIView.performWithoutAnimation {
    self.button1.layoutIfNeeded()
    self.button2.layoutIfNeeded()
    self.button3.layoutIfNeeded()
}

If you have a bunch of buttons, consider just calling layoutIfNeeded() on the super view:

button1.setTitle("abc", forState: .Normal)
button2.setTitle("abc", forState: .Normal)
button3.setTitle("abc", forState: .Normal)
UIView.performWithoutAnimation {
    self.view.layoutIfNeeded()
}
Senseful
  • 73,679
  • 56
  • 267
  • 405
0

The Xhacker Liu extension converted to Swift 3 :

extension UIButton {
    func setTitleWithoutAnimation(title: String?) {
        UIView.setAnimationsEnabled(false)

        setTitle(title, for: .normal)

        layoutIfNeeded()
        UIView.setAnimationsEnabled(true)
    }
}
Paweł
  • 1,081
  • 1
  • 11
  • 24
0

A convenient extension for animated button title change in Swift that plays nicely with the default implementation:

import UIKit

extension UIButton {
  /// By default iOS animated the title change, which is not desirable in reusable views
  func setTitle(_ title: String?, for controlState: UIControlState, animated: Bool = true) {
    if animated {
      setTitle(title, for: controlState)
    } else {
      UIView.setAnimationsEnabled(false)
      setTitle(title, for: controlState)
      layoutIfNeeded()
      UIView.setAnimationsEnabled(true)
    }
  }
}
Richard Topchii
  • 4,569
  • 3
  • 27
  • 70
-1

Maybe generating 2 animations and 2 buttons is a better solution, to avoid the problem that is appearing with animating and changing the text of a button?

I created a second uibutton and generated 2 animation, this solution works with no hickups.

    _button2.hidden = TRUE;
    _button1.hidden = FALSE;

    CGPoint startLocation = CGPointMake(_button1.center.x, button1.center.y - 70);
    CGPoint stopLocation  = CGPointMake(_button2.center.x, button2.center.y- 70);


    [UIView animateWithDuration:0.3 animations:^{ _button2.center = stopLocation;} completion:^(BOOL finished){_button2.center = stopLocation;}];
    [UIView animateWithDuration:0.3 animations:^{ _button1.center = startLocation;} completion:^(BOOL finished){_button1.center = startLocation;}];
coda
  • 1
-1

I got it to work with a combination of answers:

[[[button titleLabel] layer] removeAllAnimations];

    [UIView performWithoutAnimation:^{

        [button setTitle:@"Title" forState:UIControlStateNormal];

    }];
Jasper
  • 6,693
  • 3
  • 32
  • 43