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

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.

Related

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.

Change the height of UISegmented Control using IB?

How can i change the height of UISegmented control. I'm using Swift 3.0 with xcode 8. Height property is disabled by default.
I found this:
https://stackoverflow.com/a/41889155/7652057
#IBDesignable class MySegmentedControl: UISegmentedControl {
#IBInspectable var height: CGFloat = 29 {
didSet {
let centerSave = center
frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: height)
center = centerSave
}
}
}
https://stackoverflow.com/a/37716960/7652057
One of three options from the link,
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let rect = CGRect(origin: segment.frame.origin, size: CGSize(width: segment.frame.size.width, height: 100))
segment.frame = rect
}
Ok, I was trying to do it for a long while and here is the solution.
Firstly, It is possible within IB but for that we need to have a good bunch of autolayout constraints.
I've placed that Segmented control in a UIVIew with all the edges pinned inside it.
Then I gave the desired height to that view and it worked.
Also.. thanks to all of your answer
It's very easy. You can access it programmaticly by using frame's height:
yourSegmentedControllOutlet.frame.size.height = yourHeight

UILabel subclass - text cut off in bottom despite label being correct height

I have a problem with UILabel subclass cutting off text in the bottom. Label is of proper height to fit the text, there is some space left in the bottom, but the text is still being cut off.
The red stripes are border added to label's layer.
I subclass the label to add edge insets.
override func sizeThatFits(size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.width += insets.left + insets.right
size.height += insets.top + insets.bottom
return size
}
override func drawTextInRect(rect: CGRect) {
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
However, in this particular case the insets are zero.
Turns out the problem was with
self.lineBreakMode = .ByClipping
changing it to
self.lineBreakMode = .ByCharWrapping
Solved the problem
I was facing the same issue with Helvetica Neue Condensed Bold font. Changing label's Baseline property from Align Baselines to Align Centers did the trick for me. You can change this easily in storyboard by selecting your label.
My problem was that the label's (vertical) content compression resistance priority was not high enough; setting it to required (1000) fixed it.
It looks like the other non-OP answers may be some sort of workaround for this same underlying issue.
TL'DR
Probably the property you are looking for is UILabel's baselineAdjustment.
It is needed because of an old UILabel's known bug. Try it:
label.baselineAdjustment = .none
Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".
Explanation
It's a bug
There is some discussions like this one about a bug on UILabel's text bounding box. What we observe here in our case is some version of this bug. It looks like the bounding box grows in height when we shrink the text through AutoShrink .minimumFontScale or .minimumFontSize.
As a consequence, the bounding box grows bigger than the line height and the visible portion of UILabel's height. That said, with baselineAdjustment property set to it's default state, .alignBaselines, text aligns to the cropped bottom and we could observe line clipping.
Understanding this behaviour is crucial to explain why set .alignCenters solve some problems but not others. Just center text on the bigger bounding box could still clip it.
Solution
So the best approach is to set
label.baselineAdjustment = .none
The documentation for the .none case said:
Adjust text relative to the top-left corner of the bounding box. This
is the default adjustment.
Since bonding box origin matches the label's frame, it should fix any problem for a one-lined label with AutoShrink enabled.
Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".
Documentation
You could read more here about UILabel's baselineAdjustmenton official documentation.
Happened for me when providing topAnchor and centerYAnchor for label at the same time.
Leaving just one anchor fixed the problem.
Other answers didn't help me, but what did was constraining the height of the label to whatever height it needed, like so:
let unconstrainedSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
label.heightAnchor.constraint(equalToConstant: label.sizeThatFits(unconstrainedSize).height).isActive = true
Also, sizeThatFits(_:) will return a 0 by 0 size if your label's text field is nil or equal to ""
I ran into this too, but wanted to avoid adding a height constraint. I'd already created a UILabel subclass that allowed me to add content insets (but for the purpose of setting tableHeaderView straight to a label without having to contain it in another view). Using the class I could set the bottom inset to solve the issue with the font clipping.
import UIKit
#IBDesignable class InsetLabel: UILabel {
#IBInspectable var topInset: CGFloat = 16
#IBInspectable var bottomInset: CGFloat = 16
#IBInspectable var leftInset: CGFloat = 16
#IBInspectable var rightInset: CGFloat = 16
var insets: UIEdgeInsets {
get {
return UIEdgeInsets(
top: topInset,
left: leftInset,
bottom: bottomInset,
right: rightInset
)
}
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
return addInsetsTo(size: super.intrinsicContentSize)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return addInsetsTo(size: super.sizeThatFits(size))
}
func addInsetsTo(size: CGSize) -> CGSize {
return CGSize(
width: size.width + leftInset + rightInset,
height: size.height + topInset + bottomInset
)
}
}
This could be simplified just for the font clipping to:
import UIKit
class FontFittingLabel: UILabel {
var inset: CGFloat = 16 // Adjust this
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: UIEdgeInsets(
top: 0,
left: 0,
bottom: inset,
right: 0
)))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(
width: size.width,
height: size.height + inset
)
}
}
I had a vertical UIStackView with a UILabel at the bottom. This UILabel was cutting off the letters that go below the baseline (q, g, y, etc), but only when nested inside a horizontal UIStackView. The fix was to add the .lastBaseline alignment modifier to the outer stack view.
lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [
aVerticalStackWithUILabelAtBottom, // <-- bottom UILabel was cutoff
UIView(),
someOtherView
])
stackView.axis = .horizontal
stackView.spacing = Spacing.one
stackView.alignment = .lastBaseline // <-- BOOM fixed it
stackView.isUserInteractionEnabled = true
return stackView
}()

Button with Image and Text vertically aligned using autolayout constraints

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.

Resources