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?
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.
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.
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:
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)
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.
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.
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
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.
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.
Related
I have added a view to my tableView header consisting of an imageView and a textView. The image view is left aligned in the top corner and the textview extends over the imageview to the right side of the screen like as follows.
The textView can have dynamic content and has an exclusion path set as follows:
let imagePath = UIBezierPath(rect: imageView.frame)
self.textView.textContainer.exclusionPaths = [imagePath]
I have disabled scrolling for the textview and have set the following constraints inside of the header view:
TextView: left - 8px, right - 8px, top - 0px, bottom - 8px
ImageView: left - 8px, width - 100px, height 100px, top - 8px, bottom - greater than or equal to 8px
I have added this code after my textView is populated with the dynamic text:
if let headerView = self.tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableView.tableHeaderView = headerView
}
}
Which adjusts the size of the header. However, when the textView has less text than the height of the image, the size of the view grows.
Example of three lines of text:
Example of six lines of text:
Example of enough text to pass imageview:
Does anyone know why this is happening?
I have a fix for this, because I just encountered this issue myself :)
You see, I was trying to do something very similar, where a UITextView's text should avoid a UIImageView to its left. My code was the following:
let ticketProfileExclusionPath = UIBezierPath(roundedRect: ticketProfilePicture.frame, cornerRadius: Constants.ProfilePictureExclusionRadius)
ticketContent.textContainer.exclusionPaths.append(ticketProfileExclusionPath)
And my results were not very good:
As you can see, the problem relies on the CGRect given to the ticketContent UITextView as its exclusion path, since the latter assumes the given CGRect is corrected to its own frame, not its superview's.
The fix is very simple and requires the use of an API present since the dawn of time (iOS 2.0):
let exclusionPathFrame = convert(ticketProfilePicture.frame, to: ticketContent).offsetBy(dx: Constants.SystemMargin, dy: Constants.Zero)
What we're doing here is converting the UIImageView's frame to the UITextView's coordinate system, therefore providing the correct exclusion path from the perspective of the UITextView. The added offset is simply to align all three of my UITableViewCell's text UIViews.
convert() is a UIView method.
As you can see, the text now wraps around the ticketProfilePicture UIImageView quite nicely.
Hope this helps.
For anyone using Auto Layout, make sure to add the exclusionPathFrame in viewDidLayoutSubviews().
You can calculate textview text height with content size.
let tempTextview = UITextView(frame: CGRect(x: 50, y: 50, width: 120, height: 250))
tempTextview.text = "Dynamic Text"
let height = tempTextview.contentSize.height
Add you extra top and bottom padding to this height.
height = height + padding
if imageview.frame.size.height > height
{
return imageview.frame.size.height
}
else
{
return height
}
I have a UILabel that I have layed out in a storyboard centred in the view. It has some initial text "tap to login".
I am changing the value of the label to be the user's name once they have logged in. Once I do this, the label is no longer centered as the UILabel has not changed size.
How can I do this with autolayout in interface builder?
see this scrrenshot
1 first select your label width constraint
2 set the relation Greater than or equal
3 set the default constant value here i set it 10
when you change the label text it change the size of label according to its text. hope it's help :)
Iam guessing the label is not getting height and width, consider giving those constraints and then when your text changes use this method to determine the height and width:
func labelSizeWithString(text: String, maxWidth : CGFloat,numberOfLines : Int) -> CGRect{
let label = UILabel(frame: CGRectMake(0, 0, maxWidth, CGFloat.max))
label.numberOfLines = numberOfLines
label.text = text
label.sizeToFit()
return label.frame
}
You can set the UILabel frame.width bigger, maybe the same width as the screen.
Then you set the
label.textAlignment = NSTextAlignmentCenter
1.showing initial text to label
2.After pressing button changing text, that showing in middle of screen 3.Look at constraints third image, two constraints given 1.top to view 2.horizontally middle in container
Another thing if u given fixed width for the label,change relation as more than equal.[Click width constraint ,see right side top there change the relation
I have a UIButton within a UIView with two constraints. Both constraints were set in Interface Builder so that the UIButton is centered vertically and horizontally in its superview.
I wanted to add a border to the UIButton and make it rounded. Having followed an answer on SO I was able to get the button partially rounded.
For some reason though, the button is not really round. I assume this is because of my height & width not being equal to each other. Each time I set the width/height of the button to be equal, the autolayout constraints reset it back to 40x34.
I am using the following code to create the rounded button.
self.startButton.clipsToBounds = true
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
Why does having my button centered vertically and horizontally prevent me from changing its size? I don't understand why I can't set the size of the button and have the constraints re-center it based on the size values I assign to it.
Edit
I have moved the code in to the viewDidLayoutSubviews and then modified the constraints so that both the height and width are constrained to 40x40.
override func viewDidLayoutSubviews() {
self.startButton.clipsToBounds = true
self.startButton.titleLabel?.text = "Start"
self.startButton.layer.cornerRadius = self.startButton.frame.height / 2
self.startButton.layer.borderWidth = 1
self.startButton.layer.borderColor = self.view.tintColor.CGColor
self.startButton.layer.shadowRadius = 6.0
self.startButton.layer.shadowColor = UIColor.blackColor().CGColor
self.startButton.layer.shadowOffset = CGSizeMake(0.0, 3.0)
self.startButton.layer.shadowOpacity = 0.65
}
This partially solves my problem; not fully though. If I do not assign a value to the titleLabel, then the button is a proper round button.
However, if I assign a value to the titleLabel, the button becomes a rounded rect and is not a circle.
The text does not appear in the button though, which confuses me. I need to have the button be a circle, scaling to fit the content it has as well. My button type is set to Custom. I don't know if that has any effect on this as well.
Edit 2
After doing some testing, I discovered i was setting the edge insets to the button. Once I removed the following:
self.startButton.contentEdgeInsets = UIEdgeInsets(top: 25, left: 25, bottom: 25, right: 25)
I got the desired effect with #Leo's answer.
You need to add constraints about height & width of your button.
Either fixed width & height,or aspect radio is ok.
If you use aspect radio,set up the corner radius in viewDidLayoutSubviews
How can I vertically center the content that I have inside a accessoryView from a UITableViewCell? The content is represented by a dynamically added UISwitch that is resized to 50% both width and height using:
switchView.transform = CGAffineTransformMakeScale(0.5, 0.5);
A picture with the problem is:
You cannot change the position of a accessoryView. Please refer to the following question:
Can a standard accessory view be in a different position within a UITableViewCell?
It is okay for you to apply transformation such as scale, and rotation to the accessory view. However, you are not able to apply translate transformation to the accessory view. For example,
switchView.transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(0.5, 0.5),10,10);
Only the scale part of the transformation is applied to the switchView. Looking through the apple documentation, I also found this following line of note:
... The accessory view appears in the right side of the cell.
I don't think apple do want you to customized the location of the accessory view. Based on your question, I also tried changing the centre and the frame of the accessoryView, I do notice that the position of the accessoryView is not moving at all.
In short, If you really want to change the size of the UISwitch and move it to the vertical align it in the table view cell, I think you have only one option: You will have to add UISwitch as a subview of the cell and then you will have to freedom to do whatever you want.
Set the appropriate constraint in interface builder: "Vertical Center in Container".
I would not recommend reducing the size of standard UI elements.
The best way I could do this is by subclassing UITableViewCell, and overriding layoutSubviews and positioning the accessoryView there:
- (void)layoutSubviews {
[super layoutSubviews];
self.accessoryView.frame = CGRectMake(self.accessoryView.frame.origin.x + (self.accessoryView.frame.size.width / 2), (self.frame.size.height - (self.accessoryView.frame.size.height / 2)) / 2, self.accessoryView.frame.size.width, f.size.height);
}
I would probably advise you not to resize Apple's prebuilt UI components, especially because Apple's Human Interface Guidelines suggest:
Give tappable controls a hit target of about 44 x 44 points.
The resized switches are smaller.
Nonetheless, I hope this helps!
iOS 15.5 / Swift 13.4.1
It's a little bit weird when I set a UIImageView to cell.accessoryView
let iconView = UIImageView(image: .sfSymbol(of: "lock.fill", size: 17, weight: .semibold))
iconView.bounds = .init(x: 0, y: 0, width: 32, height: 32)
iconView.contentMode = .scaleAspectFit
iconView.debugBorder() // Show 1pt border
self.accessoryView = iconView
But when I use an UIView as image view's container, add top/bottom/leading/trailing auto layout, then set container to cell.accessoryView, the image is vertically centered in cell:
let imageContainerView = UIView(frame: .init(x: 0, y: 0, width: 32, height: 32))
let imageView = UIImageView(image: .sfSymbol(of: "lock.fill", size: 17, weight: .semibold))
imageView.contentMode = .scaleAspectFit
imageContainerView.addSubview(imageView)
imageView.debugBorder() // Show 1pt border
imageView.labr.box(imageContainerView, padding: 0).done() // Auto Layout: fill container
self.accessoryView = imageContainerView
I'm using autolayout on iOS7 and I have a problem like this:
I'm putting a UILabel onto a UIView and I'm arranging my autolayout constraints so that the label's centerX = parent view's centerX. I'm not giving any width constraint to the label. When I set the label's text on runtime, the label is drawn just wide enough for the text to fit, there are no margins/paddings on the left and right sides. What I want is to have some padding on the left and right sides, so that the text doesn't begin just where the label begins. The hack to achieve this could be setting the text as #" text " but of course that's not the way to go :)
How can I achieve what I want?
You can extend UILabel and override the intrinsicContentSize by yourself. Please make sure you have set the textAlignment = NSTextAlignmentCenter as well.
-(CGSize)intrinsicContentSize{
CGSize contentSize = [super intrinsicContentSize];
return CGSizeMake(contentSize.width + 50, contentSize.height);
}
Swift 5.0
open override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + 16, height: size.height)
}
This probably only works when you only have just one line of text to display.
You can create a UILabel subclass and override intrinsicContent,
-(CGSize)intrinsicContentSize {
CGSize s = [super intrinsicContentSize];
s = CGSizeMake(s.width + 20, s.height);
return s;
}
This will add a padding of 20 points to the width. If you want your text in the middle, be sure to set the text alignment to center.
If you're using auto layout, you can set the horizontal constraints and use an NSDictionary in the metrics parameter to set this dynamically.
For instance, if you wanted to give a 10pt padding to the inner content of a UIButton, you could do something like the following:
NSDictionary *padding = #{ #"padding" : #(button.intrinsicContentSize.width + 20) };
NSArray *buttonHConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[button(==padding)]|" options:0 metrics:padding views:NSDictionaryOfVariableBindings(button)];
If you are trying to give the UILabel a different colour to its parent view, then you will need to enclose the UILabel in a UIView with the padding and the background colour you want.
If your UILabels background colour is the same as its parent view, then I don't understand the problem just use auto layout to specify how much space you want relative to the thing it is next to.