If I have a UIButton arranged using autolayout, its size adjusts nicely to fit its content.
If I set an image as button.image
, the instrinsic size again seems to account for this.
However, if I tweak the titleEdgeInsets
of the button, the layout does not account for this and instead truncates the button title.
How can I ensure that the intrinsic width of the button accounts for the inset?
https://i.stack.imgur.com/bFikp.png
Edit:
I am using the following:
[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
The goal is to add some separation between the image and the text.
titleEdgeInset
documentation: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text.
So by adding inset you are forcing the button to clip the text for sure
You can get this to work in Interface Builder (without writing any code), by using a combination of negative and positive Title and Content Insets.
https://i.stack.imgur.com/wnBnd.png
Update: Xcode 7 has a bug where you cannot enter negative values in the Right
Inset field, but you can use the stepper control next to it to decrease the value. (Thanks Stuart)
Doing this will add 8pt of spacing between the image and the title and will increase the intrinsic width of the button by the same amount. Like this:
https://i.stack.imgur.com/g5azb.png
You can solve this without having to override any methods or set an arbitrary width constraint. You can do it all in Interface Builder as follows.
Intrinsic button width is derived from the title width plus the icon width plus the left and right content edge insets.
If a button has both an image and text, they’re centered as a group, with no padding between.
If you add a left content inset, it’s calculated relative to the text, not the text + icon.
If you set a negative left image inset, the image is pulled out to the left but the overall button width is unaffected.
If you set a negative left image inset, the actual layout uses half that value. So to get a -20 point left inset, you must use a -40 point left inset value in Interface Builder.
So you provide a big enough left content inset to create space for both the desired left inset and the inner padding between the icon and the text, and then shift the icon left by doubling the amount of padding you want between the icon and the text. The result is a button with equal left and right content insets, and a text and icon pair that are centered as a group, with a specific amount of padding between them.
Some example values:
// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
UIButton
.
Why not override the intrinsicContentSize
method on UIView? For example:
- (CGSize) intrinsicContentSize
{
CGSize s = [super intrinsicContentSize];
return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}
This should tell the autolayout system that it should increase the size of the button to allow for the insets and show the full text. I'm not at my own computer, so I haven't tested this.
intrinsicContentSize
is a method on UIView, not UIButton, so you wouldn't be messing in any UIButton methods. Apple doesnt think it's a problem: "Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content." And the OP didn't say anything about different buttons, just the one.
intrinsicContentSize
is indeed a method on UIView and UIButton is a subclass of UIView so of course you can override this method; nothing in Apple's docs says that you should not. Simply make a UIButton subclass using Maarten's overridden method and change your UIButton in Interface Builder to be of type YourUIButtonSubclass and it will work perfectly.
intrinsicContentSize
for UIButton should add in the titleEdgeInsets, I'm going to file a bug with Apple.
You haven't specified how you're setting the insets, so I'm guessing that you're using titleEdgeInsets because I see the same effect you're getting. If I use contentEdgeInsets instead it works properly.
- (IBAction)ChangeTitle:(UIButton *)sender {
self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
[self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
And for Swift worked this:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
Love U Swift
This thread is a bit old, but I just ran into this myself and was able to solve it by using a negative inset. For example, substitute your desired padding values here:
UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
-desiredRightPadding,
-desiredTopPadding,
-desiredLeftPadding);
Combined with the right autolayout constraints, you end up with an auto-resizing button which contains an image and text! Seen below with desiredLeftPadding
set to 10.
https://i.stack.imgur.com/RVdm9.png
https://i.stack.imgur.com/s6lL8.png
You can see that the actual frame of the button doesn't encompass the label (since the label is shifted 10 points to the right, outside the bounds), but we've achieved 10 points of padding between the text and the picture.
I wanted to add a 5pt space between my UIButton icon and the label. This is how I achieved it:
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);
The way contentEdgeInsets, titleEdgeInsets and imageEdgeInsets relate to each other requires a little give and take from each inset. So if you add some insets to the title's left you have to add negative inset on the right and provide some more space (via a positive inset) on the content right.
By adding a right content inset to match the shift of the title insets my text doesn't go outside the bounds of the button.
For Swift 3 based on pegpeg's answer:
extension UIButton {
override open var intrinsicContentSize: CGSize {
let intrinsicContentSize = super.intrinsicContentSize
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
All above did not work for iOS 9+, what i did is:
Add a width constraint (for a minimum width when the button doesn't have any text. The button will auto scale if text is provided)
set the relation to Greater Than or Equal
https://i.stack.imgur.com/Zafor.png
Now to add a border around the button just use the method:
button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
The option is also available in interface builder. See the Inset. I set left and right to 3. Works like a charm.
https://i.stack.imgur.com/Nkmzv.png
The solution I use is to add a width constraint on the button. Then somewhere in initialization, after your text is set, update the width constraint like so:
self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;
Where 8 is whatever your inset is.
constant
of the constraint to the new value...and knowing when the intrinsic content size of the button changes is difficult without subclassing the button.
setNeedsUpdateConstraints
can be "manually" made after updating the button title or image. You can then override updateConstraints
and re-calculate buttonWidthConstraint
's constant from there. This is not necessarily the best approach but it works good enough for me. YMMV ;)
Success story sharing
Right
field.