I have a custom button with a right-aligned arrow icon.
When it's highlighted with a tap, only the arrow icon is highlighted. The text remains white instead of turning gray like the icon does.
This is the code (scoped in a subclass of UIButton):
let rightIcon = #imageLiteral(resourceName: "disclosureIndicator")
setTitleColor(.white, for: .normal)
setBackgroundImage(rightIcon, for: .normal)
guard let image = backgroundImage(for: .normal) else { return }
titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: image.size.width)
I've also overridden the backgroundRect property.
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
guard let image = backgroundImage(for: .normal) else {
return super.backgroundRect(forBounds: bounds)
}
return CGRect(
x: frame.width - (image.size.width + 20),
y: (frame.height / 4) + (image.size.height / 4),
width: image.size.width,
height: image.size.height)
}
I've also tried setting the button image (i.e. setImage(rightIcon, .normal)) instead of setting the button background image, but it didn't work.
I've also tried setting the highlight color for the icon and the title to gray, but that didn't work either.
I want both the text and the icon to be highlighted when the button is tapped, but I can't seem to find a way of achieving this. Is it genuinely impossible to do?
Setting the button type to System instead of Custom solved the issue. Now they both highlight like they should.
Related
I have a UIButton. And finally, I should get this:
No matter how I try, I can't get the desired result. I have read many questions regarding this topic. I tried to experiment with titleEdgeInsets and imageEdgeInsets, but it did not help. The fact is that when I set the image for the button, it takes up the entire content of the button and the text remains behind. Below how i set the title
let button = UIButton()
button.frame = CGRect(x: 150, y: 300, width: 100, height: 20)
button.backgroundColor = .blue
button.layer.borderColor = UIColor.green.cgColor
button.layer.borderWidth = 2
button.setImage(UIImage(named: "baseline_play_arrow_black_48.png"), for: .normal)
button.imageView?.backgroundColor = .red
button.imageView?.contentMode = .scaleAspectFit
button.setTitle("Play", for: .normal)
button.setTitleColor(.green, for: .normal)
button.titleLabel?.backgroundColor = .yellow
As an experiment I had tried in the playground. And code above gave the following result:
.
How can i get the desired result ?
I believe there are a few ways you can achieve this Ahmet, I will go over one idea.
The interesting challenge from your question is that you don't only want to left align the image.
You want to:
Left align the image with respect to the button title only
However, the image along with the text should be center aligned as a whole within the UIButton
I created the below extension which gets you close to your desired result with some comments to explain my thought process:
extension UIButton
{
func configureWithLeftImage(_ imageName: String)
{
// Retrieve the desired image and set it as the button's image
let buttonImage = UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal)
// Resize the image to the appropriate size if required
let resizedButtonImage = resizeImage(buttonImage)
setImage(resizedButtonImage, for: .normal)
// Set the content mode
contentMode = .scaleAspectFit
// Align the content inside the UIButton to be left so that
// image can be left and the text can be besides that on it's right
contentHorizontalAlignment = .left
// Set or compute the width of the UIImageView within the UIButton
let imageWidth: CGFloat = imageView!.frame.width
// Specify the padding you want between UIButton and the text
let contentPadding: CGFloat = 10.0
// Get the width required for your text in the button
let titleFrame = titleLabel!.intrinsicContentSize.width
// Keep a hold of the button width to make calculations easier
let buttonWidth = frame.width
// The UIImage and the Text combined should be centered so we need to calculate
// the x position of the image first.
let imageXPos = (buttonWidth - (imageWidth + contentPadding + titleFrame)) / 2
// Adjust the content to be centered
contentEdgeInsets = UIEdgeInsets(top: 0.0,
left: imageXPos,
bottom: 0.0,
right: 0.0)
}
// Make sure the image is sized properly, I have just given 50 x 50 as random
// Code taken from: https://www.createwithswift.com/uiimage-resize-resizing-an-uiimage/
private func resizeImage(_ image: UIImage?,
toSize size: CGSize = CGSize(width: 50, height: 50)) -> UIImage?
{
if let image = image
{
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
image.draw(in: CGRect(origin: CGPoint.zero, size: size))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return resizedImage
}
return nil
}
}
Then when you want to use it:
// Initialize a UIButton and set its frame
let customButton: UIButton = UIButton(type: .custom)
customButton.frame = CGRect(x: 100, y: 200, width: 150, height: 40)
// Customize the button as you wish
customButton.backgroundColor = .white
customButton.layer.cornerRadius = 5.0
customButton.setTitleColor(.black, for: .normal)
customButton.setTitle("смотрите", for: .normal)
// Call the function we just created to configure the button
customButton.configureWithLeftImage("play_icon")
// Finally add the button to your view
view.addSubview(customButton)
This is the end result produced which I believe is close to your desired goal:
This is the image I used as well inside the UIButton if you want to test it out
Update
The issue with your scenario was correctly identified by you in the comments, the size of your image was too large.
I have updated the above UIButton extension to include an image resize function which should solve the issue.
I have given random sizing for the UIImage inside the button, however you need to size it appropriate to your situation or dynamically calculate size of the image based on available space based on button size.
I also added some code for title insets to make final result better.
Now result works in PlayGround and iOS app:
App using your image
Playground using your image
Update 2.0
As Ahmet pointed out, there were issues calculating the right positions for the image and the title when the length of the string changes using titleInsets and imageInsets, sometimes leading to overlapping of the button and image.
Instead, Adjust the content inset instead as follows (updated in the extension above as well)
// The UIImage and the Text combined should be centered so we need to calculate
// the x position of the image first.
let imageXPos = (buttonWidth - (imageWidth + contentPadding + titleFrame)) / 2
// Adjust the content to be centered
contentEdgeInsets = UIEdgeInsets(top: 0.0,
left: imageXPos,
bottom: 0.0,
right: 0.0)
This will work if the text length of the button title is 1 or several characters
I have a problem in my navigation where my buttons are not displayed correctly. I want 2 rows of 3 buttons each. Each button has an image and a label. The label should be centered and below the image. But that's the tricky thing i can't seem to get working correctly because every time I start the emulator the first button looks okay and all the others are displayed correctly in therms of the image but not the label. I tried to mess with the size of the buttons but noting seems to be working.
I also tried to use the code from that question on stackoverflow but i didn't work out correctly. because the image wasn't displayed at all but the sizing was correct. Furthermore the label was only shown on the first button.
I'm using a IBDesignable to extend the UIButton class which I'm assigning to the Buttons
import UIKit
#IBDesignable
class VerticalButton: UIButton {
#IBInspectable public var padding: CGFloat = 20.0 {
didSet {
setNeedsLayout()
}
}
override var intrinsicContentSize: CGSize {
let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
if let titleSize = titleLabel?.sizeThatFits(maxSize), let imageSize = imageView?.sizeThatFits(maxSize) {
let width = ceil(max(imageSize.width, titleSize.width))
let height = ceil(imageSize.height + titleSize.height + padding)
return CGSize(width: width, height: height)
}
return super.intrinsicContentSize
}
override func layoutSubviews() {
if let image = imageView?.image, let title = titleLabel?.attributedText {
let imageSize = image.size
let titleSize = title.size()
titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + padding), right: 0.0)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + padding), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
super.layoutSubviews()
}
}
I'm guessing I made an error while calculating in the intrinsicContentSizeof the button.
I also double checked if the class was assigned to all the relevant button and that each button had a label. Beside that I also checked that there were no insets set via the interface builder.
So far I had no luck in finding a solution hence my question. I appreciate your help. If you need more information please don't hesitate to reach out. I will be happy to provide it.
Thanks
Thanks to #DonMag help I've found out that the issue was not with the code instead with the size of the images.
My scenario, I am trying to create centre circle button in UITabbarViewController. I tried many sample but not getting output like below Image. Please provide some code to achieve this. I am using storyboard for this.
sample Image
You need to do is your UITabbarViewController must contain five tabs in bottom tab bar that you can do by adding five view controller and make sure the middle tab contain no title and icon.
After that in onCreate method of your UITabbarViewController add this custom button Code -
let button = UIButton(type: .custom)
var toMakeButtonUp = 40
button.frame = CGRect(x: 0.0, y: 0.0, width: 65, height: 65)
button.setBackgroundImage(ADD, for: .normal)
button.setBackgroundImage(ADD, for: .highlighted)
let heightDifference: CGFloat = CGFloat(toMakeButtonUp)
if heightDifference < 0 {
button.center = tabBar.center
} else {
var center: CGPoint = tabBar.center
center.y = center.y - heightDifference / 2.0
button.center = center
}
button.addTarget(self, action: #selector(btnTouched), for:.touchUpInside)
view.addSubview(button)
FYI - toMakeButton is the margin from bottom you can set it accordingly.
and #selector(btnTouched) is the function defined in class which will perform on click on the button.
I have been trying to align an image on the left of a button while also placing some text in the center of a button. I have been following this stack overflow post: Left-align image and center text on UIButton.
I followed one of the answers that worked the best for me. Here is the code for that answer:
#IBDesignable class LeftAlignedButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
if let image = imageView?.image {
let margin = 30 - image.size.width / 2
let titleRec = titleRect(forContentRect: bounds)
let titleOffset = (bounds.width - titleRec.width - image.size.width - margin) / 2
contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
imageEdgeInsets = UIEdgeInsetsMake(0, margin, 0, 0)
titleEdgeInsets = UIEdgeInsetsMake(0, titleOffset, 0, 0)
}
}
}
Even though this gets me really close to my desired result it does not fully accomplish what I am looking for.
Here is what my current buttons look like:
As you can probably see the text in the google button, even though centered for that particular button does not correspond with how the facebook button's text is centered. Also, the images are a bit too big, but I don't know how to make them smaller.
Here is the result I am looking for:
To conclude, the questions I have are how do I properly center the google button's text so it corresponds with the design of the facebook button and how do I make the images smaller.
You can also try this one. Works well with different image widths and with different titles.
extension UIButton {
func moveImageLeftTextCenter(imagePadding: CGFloat = 30.0){
guard let imageViewWidth = self.imageView?.frame.width else{return}
guard let titleLabelWidth = self.titleLabel?.intrinsicContentSize.width else{return}
self.contentHorizontalAlignment = .left
imageEdgeInsets = UIEdgeInsets(top: 0.0, left: imagePadding - imageViewWidth / 2, bottom: 0.0, right: 0.0)
titleEdgeInsets = UIEdgeInsets(top: 0.0, left: (bounds.width - titleLabelWidth) / 2 - imageViewWidth, bottom: 0.0, right: 0.0)
}
}
Usage: myButton.moveImageLeftTextCenter()
#Rohan The issue is let margin = 30 - image.size.width / 2. You are using image width to find out the margin.The Images (facebook and google) size are different(I am assuming). So according to your code, label left margin must change with image width. So if you want to achieve You have to keep both image size similar according to your code.
I would like to add an image on the right side of the title something like this
|Button [image]|
|title |
And I am able to achieve this by this code
button.titleEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 0)
button.imageEdgeInsets = UIEdgeInsetsMake(0, 135, 0, 5)
But when the title is large, the title label and image intersect with each other.
How can I break the title label?
This extension works for me:
Extension:
extension UIButton {
func imageToRight() {
transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
}
}
Usage:
button.imageToRight()
Update for iOS 15
as described here: Meet the UIKit Button System
Create your button configuration
Choose from .plain(), .gray(), .tinted() or .filled()
var config = UIButton.Configuration.filled()
Choose the placement for your image
Choose from all, top, bottom, trailing, leading, none
config.imagePlacement = .trailing
Assign the configuration to your button
let myButton = UIButton(configuration: config, primaryAction: ...)
try this, it will never intersect title and image with each other. and i created a button in storyboard in centre.
#IBOutlet weak var myButton: UIButton!
let somespace: CGFloat = 10
self.myButton.setImage(UIImage(named: "cross"), forState: UIControlState.Normal)
self.myButton.imageEdgeInsets = UIEdgeInsetsMake(0, self.myButton.frame.size.width - somespace , 0, 0)
print(self.myButton.imageView?.frame)
self.myButton.titleEdgeInsets = UIEdgeInsetsMake(0, (self.myButton.imageView?.frame.width)! + somespace, 0, 10 )
and if you want multiline title for button change line break mode for button's title
self.myButton.titleLabel!.lineBreakMode = NSLineBreakMode.ByWordWrapping
Swift 3.0
In swift 3.0 you can easily do it in interface builder.
Select the UIButton -> Attributes Inspector -> go to size inspector and modify the image or title insets. and if you want image on button's right side simply select the force Right-to-left in semantic view utilities
.