Button with Image and Text vertically aligned using autolayout constraints - ios

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.

Related

How to align text in UIButton below UIImage because it is not showing up correctly

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.

Possible to left align button text but right align background or button image?

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

ios - Left Align Image and Place Text in Center of UIButton

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.

storyboard positioning text below image inside a button

I am able to set the image and text for button. But it is aligned horizontally. I want the image and text to be aligned vertically inside the button. i.e., text below the image
How to make these changes using storyboard?
What I want is this:
What I am getting now is this:
If you are looking for output like this
1) Select button and go to Attribute Inspector in your storyboard to get this result of Button size 50*50
2) Now set Title Insets of Button
I've written an extension that do the works for you, Swift 4 compatible.
public extension UIButton {
func alignTextBelow(spacing: CGFloat = 6.0) {
if let image = self.imageView?.image {
let imageSize: CGSize = image.size
self.titleEdgeInsets = UIEdgeInsetsMake(spacing, -imageSize.width, -(imageSize.height), 0.0)
let labelString = NSString(string: self.titleLabel!.text!)
let titleSize = labelString.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font])
self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width)
}
}
}
Where spacing is the distance between image and text.
Yes you can do it from storyboard without writing any logic also.
1) Select button and go to Attribute Inspector in your storyboard.
2) Assign Image to the button. (Don't use background Image)
3) Set Title text to that button.
4) Now you need to set edge and Inset so first select image from edge and set Inset as you need and then select title from edge and set inset as per your need.
Hope this helps.
You can easily and visually achieve the alignment using the Edge property of the Button from Property Window(Attribute Inspector) you can change the inset values of Title, and Image as per your need, see image below
Hope it helps.
Cheers.
Swift 4.2 compatible code is here , you just need to call this function
public extension UIButton
{
func alignTextUnderImage(spacing: CGFloat = 6.0)
{
if let image = self.imageView?.image
{
let imageSize: CGSize = image.size
self.titleEdgeInsets = UIEdgeInsets(top: spacing, left: -imageSize.width, bottom: -(imageSize.height), right: 0.0)
let labelString = NSString(string: self.titleLabel!.text!)
let titleSize = labelString.size(withAttributes: [NSAttributedString.Key.font: self.titleLabel!.font])
self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
}
}
a refined one
func alignTextUnderImage(spacing: CGFloat = 6.0) {
guard let image = imageView?.image, let label = titleLabel,
let string = label.text else { return }
titleEdgeInsets = UIEdgeInsets(top: spacing, left: -image.size.width, bottom: -image.size.height, right: 0.0)
let titleSize = string.size(withAttributes: [NSAttributedString.Key.font: label.font])
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
Setting Attribute Inspector in storyboard
Setup button size
Setup image's top
Setup title's top and left
Thank you #rbiard. Another workaround when you are developing a multilingual mobile app and when using UISemanticContentAttribute (right to left and left to right). This should work:
func alignTextBelow(spacing: CGFloat = 10.0) {
guard
let image = imageView?.image,
let label = titleLabel,
let string = label.text
else { return }
let titleSize = string.size(withAttributes: [NSAttributedString.Key.font: label.font])
//when the selected language is English
if L102Language.currentAppleLanguage() == "en" {
titleEdgeInsets = UIEdgeInsets(top: spacing, left: -image.size.width, bottom: -image.size.height, right: 0)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
} else {
//When selecting a right to left language. For example, Arabic
titleEdgeInsets = UIEdgeInsets(top: spacing, left: 0, bottom: -image.size.height, right: -image.size.width)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: -titleSize.width, bottom: 0, right: 0)
}
}
You cannot change layout directly in UIButton, but you can try to use these properties on UIButton:
UIButton *button = ...
button.titleEdgeInsets = UIEdgeInsetsMake(....);
button.imageEdgeInsets = UIEdgeInsetsMake(....);
If it won't help I'd recommend to subclass from UIResponder and make your own component and do your own layout as you needed it.

Button with Image and a label on bottom

I want to make a button which has an icon and a label on the bottom of it so like this:
What would be the best way to do this? Is it possible to drag an image view object and a label into a button?
By code you can do it like this Swift 4.2 compatible code is here , you just need to call function
public extension UIButton
{
func alignTextBelow(spacing: CGFloat = 6.0)
{
if let image = self.imageView?.image
{
let imageSize: CGSize = image.size
self.titleEdgeInsets = UIEdgeInsets(top: spacing, left: -imageSize.width, bottom: -(imageSize.height), right: 0.0)
let labelString = NSString(string: self.titleLabel!.text!)
let titleSize = labelString.size(withAttributes: [NSAttributedString.Key.font: self.titleLabel!.font])
self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
}
}
}
Cleaned up #Shahzaib Maqbool's answer. I had some crashes originally, fixed those by removing the force unwrapping. Tested on Swift 5.
extension UIButton {
func alignTextBelow(spacing: CGFloat = 6.0) {
guard let image = self.imageView?.image else {
return
}
guard let titleLabel = self.titleLabel else {
return
}
guard let titleText = titleLabel.text else {
return
}
let titleSize = titleText.size(withAttributes: [
NSAttributedString.Key.font: titleLabel.font
])
titleEdgeInsets = UIEdgeInsets(top: spacing, left: -image.size.width, bottom: -image.size.height, right: 0)
imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0, bottom: 0, right: -titleSize.width)
}
}
UIButton has two properties you may find of interest. They are titleEdgeInsets and imageEdgeInsets.
Use this property to resize and reposition the effective drawing rectangle for the button title. You can specify a different value for each of the four insets (top, left, bottom, right). A positive value shrinks, or insets, that edge—moving it closer to the center of the button. A negative value expands, or outsets, that edge
You could play around with these to get your desired layout.

Resources