Get frame of the title Label inside the UIButton swift - ios

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

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

Changing UILabel title in custom UIButton without positional change

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

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.

Set stretching parameters for images programmatically in swift for iOS

So if we want to stretch only parts of an image, be it a regular image or a background image, we use the following settings in layout editor:
How do you set those programmatically?
I'm using Xcode 7.2.1
Specifying the cap insets of your image
You can set the stretch specifics by making use of the UIImage method .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode).
Declaration
func resizableImageWithCapInsets(capInsets: UIEdgeInsets, resizingMode: UIImageResizingMode) -> UIImage
Description
Creates and returns a new image object with the specified cap insets
and options.
A new image object with the specified cap insets and resizing mode.
Parameters
capInsets: The values to use for the cap insets.
resizingMode: The mode with which the interior of the image is
resized.
Example: custom stretching using the specified cap insets
As an example, let's try to---programmatically---stretch my (current) profile picture along its width, precisely at my right leg (left side from viewing point of view), and leave the rest of the image with its original proportions. This could be comparable to stretching the width of some button texture to the size of its content.
First, let's load our original image foo.png as an UIImage object:
let foo = UIImage(named: "foo.png") // 328 x 328
Now, using .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode), we'll define another UIImage instance, with specified cap insets (to the middle of my right leg), and set resizing mode to .Stretch:
/* middle of right leg at ~ |-> 0.48: LEG :0.52 <-| along
image width (for width normalized to 1.0) */
let fooWidth = foo?.size.width ?? 0
let leftCapInset = 0.48*fooWidth
let rightCapInset = fooWidth-leftCapInset // = 0.52*fooWidth
let bar = UIEdgeInsets(top: 0, left: leftCapInset, bottom: 0, right: rightCapInset)
let fooWithInsets = foo?.resizableImageWithCapInsets(bar, resizingMode: .Stretch) ?? UIImage()
Note that 0.48 literal above corresponds to the value you enter for X in the interface builder, as shown in the image in your question above (or as described in detail in the link provided by matt).
Moving on, we finally place the image with cap insets in an UIImageView, and let the width of this image view be larger than the width of the image
/* put 'fooWithInsets' in an imageView.
as per default, frame will cover 'foo.png' size */
let imageView = UIImageView(image: fooWithInsets)
/* expand frame width, 328 -> 600 */
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)
The resulting view stretches the original image as specified, yielding an unproportionally long leg.
Now, as long as the frame of the image has 1:1 width:height proportions (328:328), stretching will be uniform, as if only fitting any image to a smaller/larger frame. For any frame with width values larger than the height (a:1, ratio, a>1), the leg will begin to stretch unproportionally.
Extension to match the X, width, Y and height stretching properties in the Interface Builder
Finally, to thoroughly actually answer your question (which we've really only done implicitly above), we can make use of the detailed explanation of the X, width, Y and height Interface Builder stretching properties in the link provided by matt, to construct our own UIImage extension using (apparently) the same properties, translated to cap insets in the extension:
extension UIImage {
func resizableImageWithStretchingProperties(
X X: CGFloat, width widthProportion: CGFloat,
Y: CGFloat, height heightProportion: CGFloat) -> UIImage {
let selfWidth = self.size.width
let selfHeight = self.size.height
// insets along width
let leftCapInset = X*selfWidth*(1-widthProportion)
let rightCapInset = (1-X)*selfWidth*(1-widthProportion)
// insets along height
let topCapInset = Y*selfHeight*(1-heightProportion)
let bottomCapInset = (1-Y)*selfHeight*(1-heightProportion)
return self.resizableImageWithCapInsets(
UIEdgeInsets(top: topCapInset, left: leftCapInset,
bottom: bottomCapInset, right: rightCapInset),
resizingMode: .Stretch)
}
}
Using this extension, we can achieve the same horizontal stretching of foo.png as above, as follows:
let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
X: 0.48, width: 0, Y: 0, height: 0) ?? UIImage()
let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)
Extending our example: stretching width as well as height
Now, say we want to stretch my right leg as above (along width), but in addition also my hands and left leg along the height of the image. We control this by using the Y property in the extension above:
let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
X: 0.48, width: 0, Y: 0.45, height: 0) ?? UIImage()
let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
Yielding the following stretched image:
The extension obviously allows for a more versatile use of the cap inset stretching (comparable versatility as using the Interface Builder), but note that the extension, in its current form, does not include any user input validation, so it's up to the caller to use arguments in the correct ranges.
Finally, a relevant note for any operations covering images and their coordinates:
Note: Image coordinate axes x (width) and y (height) run as
x (width): left to right (as expected)
y (height): top to bottom (don't miss this!)

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