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.
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.
Hi I have a UIButton created in Storyboard where I would like to left align the title but right align the image. So far, I've been unable to find a way to do it. I know it is possible to add a separate UIImage but for purposes of simplifying autolayout I was hoping to do it all with one button.
Is such a thing possible either in storyboard or with code? Thanks in advance for any suggestions
overwrite this in subclass:
- (CGRect)titleRectForContentRect:(CGRect)contentRect;
- (CGRect)imageRectForContentRect:(CGRect)contentRect;
then use this subclass in storyboard
My UIButton extension methods, in Swift. Sorry about it's not obj-c. My projects are almost written by Swift now.
extension UIButton {
// ## Usage
// let btn: UIButton = //... UIButton
//
// ... configure `image` and `text` before calling following method.
// btn.myImageRightAligned()
// Let image of `UIButton` aligned right.
public func myImageRightAligned(_ spacing: CGFloat = 2.0) {
let button = self
guard (button.imageView != nil && button.imageView?.image != nil) else {
return
}
guard (button.titleLabel != nil && button.titleLabel?.text != nil) else {
return
}
let imageSize = button.imageView!.image!.size
let title = button.titleLabel!.text! as NSString
let titleSize = title.size(attributes: [NSFontAttributeName: button.titleLabel!.font])
button.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -(imageSize.width + spacing), bottom: 0.0, right: imageSize.width)
button.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: titleSize.width, bottom: 0.0, right: -(titleSize.width + spacing))
self.setNeedsLayout()
}
}
You can change button image offset (changing position) from element inspector in storyboard, i don't get the meaning of aligning the background as it covers the whole button there is no left and right in this
I m very new to IOS development and AutoLayout .
I am facing issues to align the Image and Text inside UIbutton using Storyboard. I had tried to achieve it with TitleEdgeinset and ImageEdge insets accordingly to place the Title ( text ) vertically centered below the Image. But the issue is I have 3 similar buttons which are Vertically stacked ( StackView) and the Text is dynamically set since we have localized strings ( includes Arabic rtl ) .
The image and text moves according to the text length. Is there any ways that I can achieve to make all the buttons with image and text vertically alligned.Also, different screen resolutions are not currently working if using edge insets. Appreciate your help . Thanks in advance.
Few days ago, I solved similar problem,try this
private func adjustImageAndTitleOffsetsForButton (button: UIButton) {
let spacing: CGFloat = 6.0
let imageSize = button.imageView!.frame.size
button.titleEdgeInsets = UIEdgeInsetsMake(0, -imageSize.width, -(imageSize.height + spacing), 0)
let titleSize = button.titleLabel!.frame.size
button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0, 0, -titleSize.width)
}
call this method for each button, like
self.adjustImageAndTitleOffsetsForButton(yourButton)
Combining Ajay's and Matej's answer:
import Foundation
import UIKit
class VerticalButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.contentHorizontalAlignment = .left
}
override func layoutSubviews() {
super.layoutSubviews()
centerButtonImageAndTitle()
}
private func centerButtonImageAndTitle() {
let titleSize = self.titleLabel?.frame.size ?? .zero
let imageSize = self.imageView?.frame.size ?? .zero
let spacing: CGFloat = 6.0
self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing),left: 0, bottom: 0, right: -titleSize.width)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0)
}
}
I've modified Ajay's answer because my images weren't centered:
func centerButtonImageAndTitle(button: UIButton) {
let spacing: CGFloat = 5
let titleSize = button.titleLabel!.frame.size
let imageSize = button.imageView!.frame.size
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0)
button.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: -imageSize.width/2, bottom: 0, right: -titleSize.width)
}
This is a solution that worked for me. Just set the button class to HorizontallyCenteredButton in storyboard and set the title and image top and bottom insets according to your needs (to place the image higher than title) and the button will adjust horizontal insets automatically so that the image is centered above title.
class HorizontallyCenteredButton: LocalizedButton {
override func awakeFromNib() {
super.awakeFromNib()
self.contentHorizontalAlignment = .left
}
override func layoutSubviews() {
super.layoutSubviews()
self.centerButtonImageAndTitle()
}
func centerButtonImageAndTitle() {
let size = self.bounds.size
let titleSize = self.titleLabel!.frame.size
let imageSize = self.imageView!.frame.size
self.imageEdgeInsets = UIEdgeInsets(top: self.imageEdgeInsets.top, left: size.width/2 - imageSize.width/2, bottom: self.imageEdgeInsets.bottom, right: 0)
self.titleEdgeInsets = UIEdgeInsets(top: self.titleEdgeInsets.top, left: -imageSize.width + size.width/2 - titleSize.width/2, bottom: self.titleEdgeInsets.bottom, right: 0)
}
}
To align all the buttons vertically, first select the buttons, then click the button on the bottom-right of the storyboard titled "Align", and finally select "Vertical Centers" in the menu that appears. That should do the trick.
I created a view, then put a VStack with image and text, as well as button.
Make sure the VStack constraints are 0 on all 4 sides so it covers the button :)
this is an easy one, so apologies if you have already tried it!
you don't need to set anything for insets, just make sure that the 3 buttons are aligned with horizontal centres ...
select the controls you want to align, and click on the button highlighted below (bottom right on the xCode screen) and select the Vertical Centres option to align the three with each other, or select Horizontally in Container to put them in the middle of your view.
I am trying to create a grey space between the header view (with the two buttons) and the dynamic tableview as shown here:
This is what I was able to create so far:
Now there is a line between the header view and the dynamic table view. However, I would like to have a grey space as shown in the picture above. I tried using Grouped instead of Plain. And I hypothesize that this is a way to achieve my goal because it does add grey to my view like so but only at the bottom:
So I figured if I can add some padding unto my headerview, that cause the grey to show since I think the background is grey now. So I tried writing some code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let headerView = taskListTableView.tableHeaderView
headerView!.setNeedsLayout()
headerView!.layoutIfNeeded()
let cGPoint = CGPoint(x: 0.0, y: 0.0)
let cGHeight = CGSize.init(width: 0, height: 10)
var frame = headerView!.frame
headerView!.bounds = CGRect(origin: cGpoint, size: cGHeight)
frame.size.height = CGFloat(30.0)
headerView!.frame = frame
taskListTableView.tableHeaderView = headerView
}
But this code is won't compile. Am I on the right track? What else could I do to achieve this effect?