I'm trying to make a UIButton that has a UIImage on either side (left and right) of the button's title. Ideally, the images would be pinned to the left and right sides of the button, and the title would be centered, but I can live with the images being right next to the title label I suppose. I have been able to add one image, which appears right before the title, but how would I add a second?
I know this is an old question but since no code was provided and someone asked for it I figured I would share my solution for this.
What I did was create a new class which subclasses a UIButton. I made sure that you can see all the changes you do instantly in your interface builder as well. So you can just drag in a UIButton. Specify the class to your own class and it will allow you to set the images and see it being drawn instantly
Here is what I did to achieve this
import UIKit
#IBDesignable
class AddProfilePictureView: UIButton {
#IBInspectable var leftHandImage: UIImage? {
didSet {
leftHandImage = leftHandImage?.withRenderingMode(.alwaysOriginal)
setupImages()
}
}
#IBInspectable var rightHandImage: UIImage? {
didSet {
rightHandImage = rightHandImage?.withRenderingMode(.alwaysTemplate)
setupImages()
}
}
func setupImages() {
if let leftImage = leftHandImage {
self.setImage(leftImage, for: .normal)
self.imageView?.contentMode = .scaleAspectFill
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: self.frame.width - (self.imageView?.frame.width)!)
}
if let rightImage = rightHandImage {
let rightImageView = UIImageView(image: rightImage)
rightImageView.tintColor = COLOR_BLUE
let height = self.frame.height * 0.2
let width = height
let xPos = self.frame.width - width
let yPos = (self.frame.height - height) / 2
rightImageView.frame = CGRect(x: xPos, y: yPos, width: width, height: height)
self.addSubview(rightImageView)
}
}
}
I would break it into two buttons within a collection view- it's very flexible and can work great for something like this.
Add a UIImageView with an image to the button by addSubview and another to the button by setImage, it can be adjusted with titleEdgeInsets and imageEdgeInsets.
if you need a code sample – let me know.
Hope it helps!
Swift 5, XCode 11.6
Please use the below code in your extension or in a custom UIButton class. I have used the below code in my custom class. The function will set left or right image respectively and align the text.
/// Sets a left and right image for a button
/// - Parameters:
/// - right: right image
/// - left: left image
func setImages(right: UIImage? = nil, left: UIImage? = nil) {
if let leftImage = left, right == nil {
setImage(leftImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: (bounds.width - 35), bottom: 5, right: 5)
titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: (imageView?.frame.width)!)
contentHorizontalAlignment = .left
}
if let rightImage = right, left == nil {
setImage(rightImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: (bounds.width - 35))
titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 10)
contentHorizontalAlignment = .right
}
if let rightImage = right, let leftImage = left {
setImage(rightImage, for: .normal)
imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: (bounds.width - 35))
titleEdgeInsets = UIEdgeInsets(top: 0, left: (imageView?.frame.width)!, bottom: 0, right: 10)
contentHorizontalAlignment = .left
let leftImageView = UIImageView(frame: CGRect(x: bounds.maxX - 30,
y: (titleLabel?.bounds.midY)! - 5,
width: 20,
height: frame.height - 10))
leftImageView.image?.withRenderingMode(.alwaysOriginal)
leftImageView.image = leftImage
leftImageView.contentMode = .scaleAspectFit
leftImageView.layer.masksToBounds = true
addSubview(leftImageView)
}
}
Usage:
vegaButton.setImage(right: myImage) // for right image
vegaButton.setImage(left: myImage) // for left image
vegaButton.setImage(right: myImage, left: myImage) // for both images.
Related
I'm trying to align vertically a text in a view.
For this, I call contentInset function, like this:
override func alignCenterVertical() {
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentInset = UIEdgeInsets(top: positiveTopOffset, left: 0, bottom: 0, right: 0)
}
But this only work when the value "positiveTopOffset" is negative. (reverse that I want)
I want exactly this comportment but with "positiveTopOffset" positive, like in this part of code here.
When I let the code like this, with "positiveTopOffset" positive, nothing append. Why?
thanks a lot!
I finally found the problem.
I have to add isScrollingEnable = true, like this:
override func alignCenterVertical() {
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
isScrollingEnabled = true
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentInset = UIEdgeInsets(top: positiveTopOffset, left: 0, bottom: 0, right: 0)
}
I have a custom button which works as a secure text toggle button - the image size is fine on larger iPhones, but on smaller Iphone8, iPhone 7 etc. The image is too big, see below.
I have tried adding constraints and modifying the CGrect of the button but this strangely has no effect on the size of the image.
I have tried a smaller image size but the image is just too small. Do I need different images for different device sizes or is there a way around this in code ?
Thanks
let button = UIButton(type: .custom)
//let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: self.frame.height))
setPasswordToggleImage(button)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
// Debug
//button.backgroundColor = .blue
//button.frame = CGRect(x: CGFloat(self.frame.size.width - 25), y: CGFloat(5), width: CGFloat(25), height: CGFloat(25))
button.addTarget(self, action: #selector(self.togglePasswordView), for: .touchUpInside)
self.rightView = button
self.rightViewMode = .always
button.alpha = 0.4
Creating correct assets for x1 x2 and x3 fixed the issue.
In my UIButton i want to show both the image and text. when i am adding the following code, only the image is showing but not the text. Can anyone help me to resolve the issue?
override func layoutSubviews() {
super.layoutSubviews()
guard imageView != nil else {
return
}
imageView?.frame.size.width = 25
imageView?.frame.size.height = 25
imageEdgeInsets.left = 0
titleEdgeInsets = UIEdgeInsets(top: 10, left: 25, bottom: 5, right: 0)
In the image 4 buttons are there. But here only the images are showing, not the text.
2- when i am trying the following code the output is
imageEdgeInsets = UIEdgeInsets(top: 5, left: (bounds.width - 25), bottom: 5, right: 0)
titleEdgeInsets = UIEdgeInsets(top: 10, left: 0, bottom: 5, right: (imageView?.frame.width)!)
But here i want the image should be on left side and the text will be on right side
You can subclass the UIButton and override imageRect(forContentRect, titleRect(forContentRect and intrinsicContentSize to get the desired effect
class MyButton: UIButton {
var titleFont: UIFont! = nil
var textSize: CGFloat = 0
override init(frame: CGRect) {
super.init(frame: frame)
self.titleFont = titleLabel?.font ?? .none
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.titleFont = titleLabel?.font ?? .none
}
override var intrinsicContentSize: CGSize {
CGSize(width: textSize + 40, height: 30)
//why height is 30? You want your imageView to be of height 25 and want top and bottom insets to be 5 each so 25 + 5 + 5 = 30
//Why textSize + 40? You want your button to take appropriate size of text + 5 left inset of image + 25 of imageView + 5 left inset of title + 5 right inset of title
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(x: 5, y: 2.5, width: 25, height: 25)
//why x is 5 because you want your image to have left inset of 5
//why y is 2.5 ? Your button height is 30, image height is 25 so (30 - 25) / 2 = 2.5
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if let string = self.title(for: .normal) {
textSize = string.widthOfString(usingFont: titleLabel!.font)
return CGRect(origin: CGPoint(x: 35, y: 0), size: CGSize(width: textSize + 35, height: 30))
//same explanation why x is 35 ? 5 (left inset of image) + 25 (image width) + 5 (left inset of text) = 35
}
return CGRect.zero
}
}
extension String {
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
How to use it?
Use it just like normal UIButton here is how I would use it in code
let myButton = MyButton()
myButton.backgroundColor = UIColor.red
myButton.setImage(.checkmark, for: .normal)
myButton.setTitle("Checked", for: .normal)
myButton.setTitleColor(.blue, for: .normal)
self.view.addSubview(myButton)
myButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([myButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
myButton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50)])
Output:
Though it seems like x and y coordinates are kind of hardcoded, they are not, they are relative values and will work with any kind of text (as long as button image width and height is 25)
I have a UIImageView, where I don't want the image to 'touch' the edge of the view, rather have some 'padding' around it. However, I have tried the following, and for some reason it doesnt change:
#IBOutlet weak var pictureOutletOne: UIImageView!
//set the image
pictureOutletOne.image = UIImage(named: itemOne)
//set the padding
pictureOutletOne.layoutMargins = UIEdgeInsetsMake(10, 10, 10, 10);
I have also tried:
pictureOutletOne.translatesAutoresizingMaskIntoConstraints = false
pictureOutletOne.layoutMargins = UIEdgeInsets(top: 10, left: 100, bottom: 10, right: 0)
I have read alot about this, but these are the solutions I have found, but they aren't working. Using Swift 3.
Thanks so much.
Swift 4.2 & 5
let imageView = UIImageView()
imageView.image = UIImage(named:
"image")?.withAlignmentRectInsets(UIEdgeInsets(top: -5, left: -5, bottom: -5,
right: -5))
Insets should be given in negative value
You can insert the imageView into a view and set constraints to sides, guaranteed approach :)
Override the alignmentRectInsets property in a new class:
class PaddedImageView: UIImageView {
override var alignmentRectInsets: UIEdgeInsets {
return UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
}
}
Swift 5.4 & Xcode 13
Here is a little helper extension I build:
extension UIImage {
func addPadding(_ padding: CGFloat) -> UIImage {
let alignmentInset = UIEdgeInsets(top: -padding, left: -padding,
bottom: -padding, right: -padding)
return withAlignmentRectInsets(alignmentInset)
}
}
let padding: CGFloat = 10
myImageView.contentMode = .scaleAspectFill
myImageView.image = UIImage(named: "myImage.png").resizableImage(withCapInsets: UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding), resizingMode: .stretch)
Swift 5
Adds padding to right and left of an image place in an image view.
let image = UIImage(systemName: "circle.fill")
let insets = UIEdgeInsets(top: 0, left: -15, bottom: 0, right: -15)
let imageView = UIImageView(image: image.withAlignmentRectInsets(insets))
Note: UIStackView honors alignment insets as contributors to an image view's intrinsic content size.
Use case example:
In my application, I have a vertical stack comprised of a small center-aligned UILabel stacked above a UIImageView in a UITableViewCell. Label width varies from cell to cell, varying respective vertical stack widths and shifting images' respective horizontal alignments. I.e. the images don't line up in the table.... By padding images with horizontal alignment insets, it forces vertical stack to have a consistent width greater than max expected label width, keeping images center-aligned vertically in the table.
Can I write like the code below on Storyboard?
let button = UIButton()
button.titleLabel.center = CGPoint(x:button.titleLabel.center.x, y:button.titleLabel.center.y + 30)
Look into UIEdgeInsets and the UIButton property titleEdgeInsets.
button.titleLabelInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 30)
Depending on your exact needs and code, this should do what you need. (FYI - there is a imageEdgeInsets also.)