Changing UILabel title in custom UIButton without positional change - ios

I have a UIButton with the label on the left side and UIImageView on the right side. The button is used to open a UIPicker. When a value is picked in the picker the same value is shown in button title. When the title changes (or, more accurately, when to uilabel has a width that screws the UI up) the title and icon is moved and the UI does not look good.
When a title with too long text is used the word is clipped and when it's too short the alignment is messed up.
I've tried changing the label frame so it can be constant, whatever the text, and left aligning the text so the jumping stops. I added adjustsFontSizeToFitWidth = true which kind of works, but with a longer title the text will get too small. I've also tried recreating/rerendering the button when the title changes but all attempts fail.
lazy var sortButton = { () -> UIButton in
let btn = UIButton()
btn.addTarget(self, action: #selector(sortButtonPressed), for: .touchUpInside)
btn.setTitle(NSLocalizedString("Sortera", comment: ""), for: .normal)
btn.titleLabel?.text = btn.titleLabel?.text?.uppercased()
btn.setImage(UIImage(named: "ios-down"), for: .normal)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitleColor(Colors.FILTER_BUTTON_TEXT_COLOR, for: .normal)
btn.titleLabel?.adjustsFontSizeToFitWidth = true
btn.titleLabel?.font = UIFont(name: Fonts.AkzidenzGroteskProMd, size: 16)
btn.backgroundColor = Colors.BUTTON_BACKGROUND_GRAY
btn.imageView?.contentMode = .scaleAspectFit
btn.imageEdgeInsets = UIEdgeInsets(top: 16, left: (btn.titleLabel?.frame.size.width)! - buttonInsideOffset/2, bottom: 16, right: -(btn.titleLabel?.frame.size.width)! + buttonInsideOffset/2)
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(btn.titleLabel?.frame.size.width)! + buttonInsideOffset, bottom: 0, right: (btn.titleLabel?.frame.size.width)! - buttonInsideOffset)
return btn
}()
I want all button to like like this, whatever the title text:
However, when the text is too small it looks like this:
or when it's too long:

You can do it in many way but the simplest way :
Take a UIView and then others two-element (a label a imageView) set in this View and make it look like button then set constraint as you want. Then addTarget to label and do all functionality to that that target selector method.

If you dont want button image to be shifted to the right or left then you have to constraint independent of the button's title label
btn.imageView?.frame = CGRect(x: 0, y: 0, width: 20, height: 20) // Or any size you want
// NB: I ommited left insets intentionally
btn.imageEdgeInsets.top = 16
btn.imageEdgeInsets.bottom = 16
btn.imageEdgeInsets.right = 16
Then constraint your label dependent to the imageView position, that way only label size will change without affecting the position of image.
btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(btn.titleLabel?.frame.size.width)! + buttonInsideOffset, bottom: 0, right: btn.imageView?.frame.width + 10)
Lastly, Since the frame is fixed size, I think you need to limit the font scaling factor to the min size you want and truncate the tail when that size is reached. If you don't want to truncate tail then you have to enable multiline titleLabel (Which I think you don't want this).
btn.titleLabel?.minimumScaleFactor = 0.5 // Or whatever minimum scale you wish
btn.titleLabel?.lineBreakMode = NSLineBreakMode.byTruncatingTail // Since button size is fixed and you want to limit font size then the best option is to truncate tail

Related

'imageEdgeInsets' was deprecated in iOS 15.0

I'm getting the warning:
'imageEdgeInsets' was deprecated in iOS 15.0
When setting UIButton imageEdgeInsets like so:
button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
I tried setting imageEdgeInsets like so:
UIImage(named: "filter")?.withRenderingMode(.alwaysTemplate).withAlignmentRectInsets(UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
Both where top, left, right and bottom are positive or negative values, no luck. Anyone who knows the version of
imageEdgeInsets
That's not deprecated in iOS 15?
All help is appreciated,
Kind regards :)
iOS 15 Apple introduced 3 new options to control padding and insets.
.titlePadding : Padding between the title and subtitle labels.
.imagePadding : Padding between the button’s image and text.
.contentInsets: Padding from the button’s content area to its bounds.
Using the above option you can manage and set your button style according.
You can check this article for more. Source and Image
So your code should be like this
var configuration = UIButton.Configuration.filled()
configuration.title = "Title"
configuration.image = UIImage(systemName: "swift")
configuration.titlePadding = 10
configuration.imagePadding = 10
configuration.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
Swift now comes default with different types of buttons that you can setup up from iOS 15 onwards. Use UIButton.Configuration to set up your button. For your case, you can use,
config.imagePadding = 5
An example can be seen below:
var filled = UIButton.Configuration.filled()
filled.title = "Filled button"
filled.buttonSize = .large
filled.subtitle = "With subtitle even"
filled.image = UIImage(systemName: "bubble.left.fill")
filled.imagePlacement = .trailing
filled.imagePadding = 5
let button = UIButton(configuration: filled, primaryAction: nil)
Image and Code credits to nemecek.be

CustomView UIBarButtonItem works for all phones other than iPhone 6

I created a UIBarButtonItem with a custom view using the following code (I specify the size of the profileImageButton frame to have dimensions 40x40).
let profileImageButton = UIButton(type: .custom)
profileImageButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
profileImageButton.setImage(imageCache.getProfileImage(), for: .normal)
profileImageButton.layer.cornerRadius = 20
profileImageButton.layer.masksToBounds = true
profileImageButton.addTarget(self, action: #selector(self.openSettings), for: .touchUpInside)
let settingsButton = UIBarButtonItem(customView: profileImageButton)
On most devices, the view has a 40x40 frame. However, when I run it on the iPhone 6s, it looks a bit rectangular. So I printed out its frame in the debug hierarchy feature of Xcode. The result I get is: frame = (0 0; 50 44), which I assume means that the frame has dimensions 50x44. Therefore, my question is why doesn't the iPhone 6s keep the specified 40x40 size?
In iOS 11, the size of a UIBarButtonItem with a custom view is determined by the internal constraints of the custom view. You have not provided any internal constraints, so all bets are off; you have not put yourself in charge of the bar button item's size. To do so, give profileImageButton a width constraint and a height constraint.
Adding onto matt's answer, I needed to also add these three lines of code for use in iOS 11:
profileImageButton.translatesAutoresizingMaskIntoConstraints = false
profileImageButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
profileImageButton.widthAnchor.constraint(equalToConstant: 40).isActive = true

Get frame of the title Label inside the UIButton swift

I am trying to create a UIButton that contains a UIImage and text. So far I have managed to do that but I can't make it look pretty because the button width is half of the screen size (and this means the proportions are not right on big screens when I set the image)
Is there a way to position the Image exactly before the text Label in the button? Now I am doing this:
economicsButton.imageEdgeInsets = UIEdgeInsets(top: 15,left: 10,bottom: 15,right: 30)
economicsButton.titleEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 0)
Try this
let somespace: CGFloat = 10
self.economicsButton.setImage(UIImage(named: "cross"), forState: UIControlState.Normal)
self.economicsButton.imageEdgeInsets = UIEdgeInsetsMake(0, self.economicsButton.frame.size.width - somespace , 0, 0)
print(self.economicsButton.imageView?.frame)
self.economicsButton.titleEdgeInsets = UIEdgeInsetsMake(0,(self.economicsButton.imageView?.frame.width)! + somespace, 0, 10 )
check the original answer Align image on right side

Swift 2.1 - Making dynamic buttons with width relative to text length

I need to make buttons programmatically based on the number of fetched values and each button needs to have width that is relative to their text length.
My current implementation has a fixed width with X position that also increments in fixed length.
What is the process of achieving this so I can have buttons like attached screenshot?
for var i = 0; i < self.category.count; i++ {
let frame1 = CGRect(x: 0 + (i * 45), y: 20, width: 80, height: 40 )
let button = UIButton(frame: frame1)
button.setTitle("\(category[i].name!)", forState: .Normal)
button.backgroundColor = UIColor.blackColor()
self.categoryScrollView.addSubview(button)
}
After you set text on button, use
button.sizeToFit()
then read the width of the button and add an appropriate number to it (this number will be same for all buttons regardless of the text length and will give you desired results.
Alternatively, you can add padding to button; in which case you only need to do the first step.

UIButton sizing wrong with image and title inset

It does not seem like UIButton instrinsicSize and/or sizeToFit takes a title left edge inset into account, or something is messed up with my expectations.
To demonstrate, I have two Custom type buttons in a view, both with the title "Button". I want to add an image to the button to the left of a title.
var image = UIImage(named: "circledPlay")
image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
self.button1.setImage(image, forState: UIControlState.Normal)
self.button1.invalidateIntrinsicContentSize()
self.button1.sizeToFit()
self.button2.setImage(image, forState: UIControlState.Normal)
self.button2.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 0)
self.button2.invalidateIntrinsicContentSize()
self.button2.sizeToFit()
The result is as follows:
Note the second button is being truncated.
So my question would be if anybody has seen this before (and hopefully has a solution) or am I confused and this is behaving as expected (and hopefully knows the right way to do this)?
As it says in the documentation, for titleEdgeInsets: "The button does not use this property to determine intrinsicContentSize and sizeThatFits:".
So, setting the titleEdgeInsets just moves the title label, but doesn't affect the size of the button. If you want the button to have more padding around the content, set the contentEdgeInsets as well. I don't think you need to call either sizeToFit, or invalidateIntrinsicContentSize (but 'm not sure about that).
You can use the contentEdgeInsets and titleEdgeInsets to implement that.
button2.setImage(image, for: .normal)
button2.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
button2.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 10)
button2.titleEdgeInsets = UIEdgeInsetsMake(0, 10, 0, -10)
button2.setTitle("Button", for: .normal)
button2.sizeToFit()

Resources