UIButton provides several methods for changing appearance for particular state, e.g.
UIButton setTitle:(NSString *) forState:(UIControlState)
but what to do when there is no dedicated method?
for example I want to change center or frame of button image or label:
button.titleLabel.frame = CGRect(...)
The frame of label changes. But when user taps the button layout glitches because frames for other states had not been changed.
Is there any way better than handling all events and setting attributes in handler method?
Frame of the label is just an example - it could be many other things like background color of text label.
Related
I'm setting images for a UIButton's normal and highlighted states. However, since it's in a UIScrollView, unless I set the scroll view's delaysContentTouches to false, the button's highlighted state doesn't show unless the user long-presses the button.
Delaying content touches isn't ideal, since the user can't initiate a scroll gesture on top of the button.
However, it seem like somehow UIButton.showsTouchWhenHighlighted is able to show the highlighted state immediately upon tap without the need to set delaysContentTouches to false. Is there a way to be using UIButton to get the same behavior for its images?
Since it seems like UIButton.showsTouchWhenHighlighted and UIControl.State.highlighted have differing criteria for what it means to be "highlighted", I ended up making a custom UIButton which overrides setImage to simply save the images in custom properties and overrides touchesBegan/touchesEnded/touchesCancelled to call super.setImage and actually set the image (only ever setting .normal). I'm just using .normal and completely ignoring .highlighted, since state doesn't behave as I'd like (how showsTouchWhenHighlighted behaves).
I have this issue with a custom UIView where I have a UIButton subview, I want to set the button's text on initialization based on some condition like this:
- (void)awakeFromNib {
[super awakeFromNib];
//check for some conditions
self.testButton.titleLabel.text=#"Some Title";
}
Nothing happens and the button's text is the same as defined in the nib file, however if I change the implementation to:
- (void)awakeFromNib {
[super awakeFromNib];
//check for some conditions
[self.testButton setTitle:#"Some Title" forState:UIControlStateNormal];
}
It works as expected.
Can somebody please explain to me the difference between the two approaches? and when to use each?
EDIT:
the suggested answer doesn't explain my situation, I tested changing the button's text from another button's action like this:
- (IBAction)otherButtonClicked:(id)sender {
self.testButton.titleLabel.text=#"Some Title";
}
and the button's text changed. I just want to understand that behaviour.
Exact answer is
titleLabel
Do not use the label object to set the text color or the shadow color. Instead, use the setTitleColor:forState: and setTitleShadowColor:forState: methods of this class to make those changes.
The titleLabel property returns a value even if the button has not
been displayed yet. The value of the property is nil for system
buttons.
setTitle
Use this method to set the title for the button. The title you specify
derives its formatting from the button’s associated label object. If
you set both a title and an attributed title for the button, the
button prefers the use of the attributed title over this one.
At a minimum, you should set the value for the normal state. If a
title is not specified for a state, the default behavior is to use the
title associated with the UIControlStateNormal state. If the value for
UIControlStateNormal is not set, then the property defaults to a
system value.
Title label access is given to adjust other properties of the label such as font but setting titleLabel text does not work.
It is because UIButton class has inner implementation to set the text based on different states of the button like selected/highlighted etc which overrides label text.
The accepted answer is correct, I just add this here to elaborate a bit (comments are too short)
There's not much to explain here. The title is simply not meant to set the text at all. My guess is that the internal workings of UIButton make it save the text somewhere else as well (for different states). It uses a normal UILabel to eventually display that, because that's convenient and easy to understand. Setting the text of that does not change the button in all cases, which probably ultimately depends on the drawing cycle. I assume when it's drawn, laid out, or the like it "replaces" the label's text with its other saved variant at some point.
Now you might wonder why Apple did then expose the UILabel and thus seemed to make the text editable then.
Legacy is probably one aspect of this decision (IIRC you could once set the button's title that way). Although old code doesn't result in the desired behavior, it at least didn't crash immediately. Also, a lot of code simply wants to get the text and expects a label, that works perfectly fine as ever.
Second, designing it totally different seems overkill. To avoid that, they would have to use a subclass of UILabel which prevents you from setting the text or something and use that. Or skip it (and thus legacy support) completely and only offer the setTitle:forState: method. That seems like a bit much for a simple text container like a Label.
Ultimately it's a design choice made by Apple. You can't set the title text directly and there's no case in which you should do it any way other than by using setTitle:forState:
I have a button and multiple labels placed over the button in storyboard. For the button I specified a default and a highlight state. Also for the labels I specified the highlight color in storyboard.
However on button press the font color of the label does not change to white. Am I missing something? I would like to configure this behavior in storyboard, not programmatically. Is this possible? Or do I have to create a custom button?
The problem is that the UILabel does not get the touch events because it simply does not handle touch events by design, it's just for showing text.
You might want to create a subclass of a UIButton but this is not a good idea since it's kind of a cluster class.
Best way to do it is creating a custom button class by subclassing either UIControl or UIView. With the later you could add it in your storyboard by changing their class to one of your button subclasses. In the subclass make your customizations in the initWithCoder: method.
If you decided to choose the UIControl way of doing this. Look at setHighlighted: method:
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted: highlighted];
// Highlight your labels here
}
Useful link: UIControl Class Reference
I have a UICollectionView where each UICollectionViewCell has a UIButton as a subview. The UIButtons respond to taps no problem (their targets get fired), but the button itself does not change to the selected state (no change in look and feel of the button). I have a hunch it's because of the UICollectionViewCell not properly forwarding its touch events to the button, but I'm not sure. Even if that's so, how do I set things up so that the button's state changes properly in this scenario?
The UIScrollview (and thus UICollectionView too) has a property called delaysContentTouches, by default it is set to YES, change this to NO and your button should highlight like it is supposed to.
If i may suggest an alternative, the UICollectionView has an awesome delegate method called
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { }
which could handle the click to that cell... if you are looking for specific events to happen like seeing an image change to the state of the button, you could hard code that in... when they press the button do one thing, when they release the button do another thing....
according to the documentation as well
UIControlStateSelected
Selected state of a control. For many controls, this state has no effect on behavior or appearance. But other subclasses (for example, the UISegmentedControl class) may have different appearance depending on their selected state. You can retrieve and set this value through the selected property.
in laymens terms.. for a UIButton the "Selected State" does nothing...
if the the button is suppose to dim when it's clicked and it's not doing that, then you may have to do that programmatically if, but i'm not exactly sure what you are trying to do...
the dimming feature is with in the highlighted state
UIControlStateHighlighted
Highlighted state of a control. A control enters this state when a touch enters and exits during tracking and when there is a touch up event. You can retrieve and set this value through the highlighted property.
in laymens terms, you touch the button it's highlighted
to see if the button is changing states properly you can do something like this
[button addTarget:self action:#selector(functionToCall:) forControlEvents:UIControlEventAllTouchEvents];
NSLog(#"Selected: %i", button.selected);
NSLog(#"Highlighted: %i", button.highlighted);
NSLog(#"Normal State or not: %i", button.state);
the "functionToCall will be called when any type of touch even happens to the button and with in that function you could have those 3 NSLogs which will print to your console the different UIControlState values, this will show that the button is working properly and show that it may be a UIViewCollection error, if it's the UICollectionView... then you will have to programmatically dim the button :3
hope this helps !
I used this question:
How to create a UIButton with a black gradient for iPhone?
... to set up a color gradient as the background of my button and it looks great, but when I tap the button it doesn't visibly change and give the user any feedback that they successfully tapped the button.
How can I set up a second gradient for the highlighted state or something like that?
Well, you are setting a sublayer and not providing any content to the button's views. The sublayers are drawn directly, so the UIButton doesn't think it has any content to "highlight". If you want a different effect on your button when you select it, there are three simple approaches.
One is to make two images with gradients. A pressed and normal state. This is much simpler as you can design them as you want and its a simple one line, one time implementation.
The other option is adding selectors that get called when your button gets UIControlEventTouchDown and whichever other Control Event(s) you want to have it switch back under.
The last simple option is to subclass UIButton and use the UIResponder touch methods to determine when to manually switch between the different background types.