I am using this code to create a button with an image. And I want the image size to be the same as the button size. I am using this code for that:
func buttonImage() {
var configuration = UIButton.Configuration.plain()
configuration.image = UIImage(named: "someImage")!
configuration.baseBackgroundColor = .red
configuration.contentInsets = .zero
let button = UIButton(configuration: configuration)
button.backgroundColor = .red
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.widthAnchor.constraint(equalToConstant: 300).isActive = true
button.heightAnchor.constraint(equalToConstant: 300).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
but I have this result:
All works fine if I use single scale
with 135x135 without @3x suffix instead of individual scales
with 3 images 45x45, 90x90, 135x135 in my assets.
How to fix it with using configuration
?
A UIButton
using UIButton.Configuration
does not scale the image... it uses the image at its point size.
If we have:
and we run this code:
guard let imgA = UIImage(named: "someImage"),
let imgB = UIImage(named: "someImageU")
else { fatalError() }
print("imgA size:", imgA.size)
print("imgB size:", imgB.size)
we will get this output:
imgA size: (45.0, 45.0)
imgB size: (135.0, 135.0)
Whether the device has a screen scale of 1, 2 or 3, the point size will always be the same, and UIKit will load the corresponding @1, @2 or @3 image.
If you are using a png (or other format) image in your Asset Catalog, you can either save multiple sizes and do something like this:
var btnImage: UIImage!
if true {
if UIScreen.main.scale == 1 {
btnImage = UIImage(named: "someImage45x45")
} else if UIScreen.main.scale == 2 {
btnImage = UIImage(named: "someImage90x90")
} else {
btnImage = UIImage(named: "someImage135x135")
}
}
Or, scale the UIImage
on-the-fly before assigning it as the configuration's .image
property...
Or, create a UIButton
without using UIButton.Configuration
Edit
Some clarification...
As I said, when using UIButton.Configuration
, the image point size will be used.
If you want the button frame to be 300 x 300
points, and you want the image to be 135 x 135
points, the images in your should be:
(Actually, no devices have @1x
screen scale, so you only need the @2x
and @3x
images.)
That's fine, as long as you always want the button's image to be 135 x 135
points.
Suppose, though, that you decide you want you button to be 200 x 200
points (2/3rds of 300), and thus your button's image should be 90 x 90
points (2/3rds of 135)?
What you can do is resize the image on-the-fly.
Keep in mind, though... if you have PNG images in your Asset Catalog, you will get bit-scaled images which may get "fuzzy" or otherwise lose detail.
That's where using SVG files can be very helpful.
Quick example -- using Sketch for Mac, I exported this as a 45x45 PNG:
and here is the SVG code:
<svg width="45px" height="45px" viewBox="0 0 45 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard">
<rect id="Rectangle" fill="#32C5FF" x="0" y="0" width="45" height="45"></rect>
<path d="M22.5,5.43914852 L26.5842329,18.8785356 L40.627947,18.609873 L29.1084277,26.6472083 L33.7036874,39.9205528 L22.5,31.4485121 L11.2963126,39.9205528 L15.8915723,26.6472083 L4.37205299,18.609873 L18.4157671,18.8785356 L22.5,5.43914852 Z" id="Star" stroke="#E02020" stroke-width="2" fill="#F7B500"></path>
</g>
</g>
</svg>
When adding the SVG to assets, select Preserve Vector Data...
Then, using this resizing code:
guard let imgA = UIImage(named: "starPNG45x45"),
let imgB = UIImage(named: "starSVG45x45")
else { fatalError() }
print("imgA size:", imgA.size)
print("imgB size:", imgB.size)
let targetPointSize: CGFloat = 135.0
let targetImageSize: CGSize = .init(width: targetPointSize, height: targetPointSize)
// by default, UIGraphicsImageRenderer uses the screen-scale
var rndr = UIGraphicsImageRenderer(size: targetImageSize)
let rImgA = rndr.image { ctx in
imgA.draw(in: .init(origin: .zero, size: targetImageSize))
}
let rImgB = rndr.image { ctx in
imgB.draw(in: .init(origin: .zero, size: targetImageSize))
}
The resulting images (on an iPhone 15 Pro with 3x screen scale)...
PNG:
SVG:
Note that the generated images will each have a point size of 135
, but a pixel size of 405
.
If we run that code on a device with 2x screen scale, the resulting images will still have a point size of 135
, but a pixel size of 270
.