I currently have 3 objects called MenuButton that's a child of UIButton. These are defined in a row as shown in the image.
I've defined the highlight states in my MenuButton -didUpdateFocusInContext:withAnimationCoordinator method:
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
if (context.nextFocusedView == self)
{
[self setTitleColor:[UIColor whiteColor]
forState:UIControlStateNormal];
}
else if (context.previouslyFocusedView == self)
{
[self setTitleColor:[UIColor colorWithRed:25.0/255.0
green:23.0/255.0
blue:16.0/255.0
alpha:1.0]
forState:UIControlStateNormal];
}
}
I've also set canBecomeFocused in the MenuButton object via:
- (BOOL)canBecomeFocused
{
return YES;
}
I've set my initial focus button using:
[_camerasButton setNeedsFocusUpdate]
When I swipe right on the Simulator remote, I'm not able to get it to focus on Deliveries.
Can someone please explain how I can use UIFocusGuide to implement this correctly in Objective-C?
UPDATE 1
Based on JustinVoss' suggestion on the first comment, I set breakpoint and did a _whyIsThisViewNotFocusable on the debug console. Received this hint:
(lldb) po [(UIView*)0x7ff68054f7c0 _whyIsThisViewNotFocusable]
ISSUE: This view may not be focusable because other views or focus guides are occluding it.
I'm defining my MenuButton frames as shown:
MenuButton *deliveriesButton = [MenuButton buttonWithType:UIButtonTypeCustom];
[deliveriesButton setTitle:#"Deliveries" forState:UIControlStateNormal];
[deliveriesButton setTitleColor:[UIColor colorWithRed:25.0/255.0
green:23.0/255.0
blue:16.0/255.0
alpha:1.0]
forState:UIControlStateNormal];
[deliveriesButton.titleLabel setFont:[Constants lightFontOfSize:39]];
[deliveriesButton sizeToFit];
deliveriesButton.center = CGPointMake(viewWidth/2,
menuBarHeight / 2);
[self.view addSubview:deliveriesButton];
_deliveriesButton = deliveriesButton;
Using the solution referenced by Justin Voss in the comments above, I went through the UIViews in my UIViewController and set:
view.userInteractionEnabled = NO;
on any view that I did not need to have a selection on (including background UIImageView objects). This resolved the problem.
Hope this helps someone else
I have below code to create simple NSButton in a separate function
-(void)myFunction
{
NSButton *btn = [self createButton:#"Button_Name"];
if(some condition )
{
[btn setEditable:YES];
}
}
- (NSButton*)createButton:(NSString *)buttonName
{
NSButton *btn = [[NSButton alloc] initWithFrame:NSMakeRect(20, 0, 20, 20)];
[btn setButtonType:NSSwitchButton];
[btn setImagePosition:NSImageOnly];
[btn setTarget:self];
[btn setTitle: buttonName];
return btn;
}
In my same It is working fine.I am using this code in a Big project.Will It work normally or will cause some problem.Is this a correct way?
Few things I would like to bring in your notice:
You pass buttonName and buttonTitle but never uses it.
You create an object of type NSButton but your object name is against the convention, by reading btnCell someone will expect it to be NSButtonCell.
In the above code I cant see any reference to the newly created button and even you are not adding it to any view. (I hope in your real Big project you are not missing those.)
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.
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];
}];
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];
In Swift you can use :
UIView.performWithoutAnimation {
self.someButtonButton.setTitle(newTitle, forState: .normal)
self.someButtonButton.layoutIfNeeded()
}
Change button type to custom form interface builder.
This worked for me.
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.
Starting in iOS 7.1 the only solution that worked for me was initializing the button with type UIButtonTypeCustom.
Swift 5
myButton.titleLabel?.text = "title"
myButton.setTitle("title", for: .normal)
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
UIButton with system type has implicit animation on setTitle(_:for:). You can fix it two different ways:
Set the button type to custom, either from code or Interface Builder:
let button = UIButton(type: .custom)
Disable animation from code:
UIView.performWithoutAnimation {
button.setTitle(title, for: .normal)
button.layoutIfNeeded()
}
Set the button type to UIButtonTypeCustom and it'll stop flashing
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)
Set UIButton type as Custom. That should remove fade in and out animations.
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 ;)
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!
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)
}
}
You can remove the animations from from the title label's layer:
[[[theButton titleLabel] layer] removeAllAnimations];
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()
}
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)
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];
}
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..
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)
}
}
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)
}
}
}
I got it to work with a combination of answers:
[[[button titleLabel] layer] removeAllAnimations];
[UIView performWithoutAnimation:^{
[button setTitle:#"Title" forState:UIControlStateNormal];
}];
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;}];
I'm working on a matching program for iPad and when a user selects a button an image is "uncovered" and then when the user selects a second button,another image is uncovered. I then programmatically check for a match and if not, revert both button images back to their initial state.
This is working fine except when a match is NOT made, the switch happens so fast that you do not have time to see what you have "uncovered". I tried to make it sleep but the image doesn't ever swap to the uncovered state... Thoughts?
The code for this is as follows:
//Take action on the tap of one of the buttons
if(isFirstSelection)
{
firstSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:firstSelection];
tempImage = [tempImageItem tileImage];
firstSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
tempButton = sender;
isFirstSelection = NO;
}else{
secondSelection = [(UIButton *)sender tag];
tempImageItem = [tileArray objectAtIndex:secondSelection];
tempImage = [tempImageItem tileImage];
secondSelectionName = [[NSString alloc] initWithString:[tempImageItem tileName]];
[(UIButton *)sender setImage:tempImage forState:UIControlStateNormal];
//Two game pieces have been removed so check to see if they are a match
if([firstSelectionName isEqualToString:secondSelectionName])
{
//Match found
//do something
}else{
**//NO MATCH FOUND
[NSThread sleepForTimeInterval:3];
//Display the checker board pieces again
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];**
}
//Reset isFirstSelection Flag to YES for next selection
isFirstSelection = YES;
}
From what i understand you want to put the button's original image after 3 seconds so the user has the time to see what happened.
You should look at the NSTimer class to trigger that code
[(UIButton *)sender setImage:[UIImage imageNamed:#"originalImage"] forState:UIControlStateNormal];
in 3 seconds from now.
For "run this code in X seconds" I prefer:
[self performSelector:#selector(spinWheel:) withObject:[NSNumber numberWithUnsignedInt:0] afterDelay:delay];
no need to muck around with timer objects.
I have a situation here please help me out in this,
1) I have a Table with custom cells
2) Each cell has 2 search bars and 2 lables.
what I was attempting is suppose a user beginEditing a searchBar a popover should appear pointing that search bar.
I have implemented this but popover is not appearing over the desired searchBar moreover height of popover is also too long sometime
if (searchBar.tag==10) {
NSLog(#"Display date popover");
CGRect pickerFrame = CGRectMake(0,0,300,200);
UIViewController *tempDateViewController=[[UIViewController alloc] init];
UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:pickerFrame];
[datePicker addTarget:self action:#selector(pickerChanged:) forControlEvents:UIControlEventValueChanged];
[tempDateViewController.view addSubview:datePicker];
if(!currentPopover)
{
currentPopover=[[UIPopoverController alloc] initWithContentViewController:tempDateViewController];
}
else {
[currentPopover setContentViewController:tempDateViewController animated:YES];
}
tempDateViewController.contentSizeForViewInPopover=CGSizeMake(320, 300);
[datePicker release];
[tempDateViewController release];
[currentPopover presentPopoverFromRect:searchBar.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
Please help me to solve this one.
Thanx in advance.
The searchBar is inside a cell which is in a table view (which may be in some other view depending on your layout). The problem most likely is that self.view is not the direct parent of searchBar where you are calling this. So using the frame of the searchBar in self.view will give unexpected results.
Instead of using self.view and trying to figure out where the searchBar is relative to that, you can use the searchBar itself for the "inView" and for the "rect", use the searchBar's bounds:
[currentPopover presentPopoverFromRect:searchBar.bounds inView:searchBar
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Next, to fix the height of the popover, try setting the popover's popoverContentSize instead of the view controller's contentSizeForViewInPopover:
//tempDateViewController.contentSizeForViewInPopover=CGSizeMake(320, 300);
currentPopover.popoverContentSize = CGSizeMake(320, 300);
Finally, a separate issue but, the minimum height for a datepicker should be 216, not 200:
CGRect pickerFrame = CGRectMake(0,0,300,216);