I am using this class for center text below icon for UIButton:
#IBDesignable
class TopIconButton: UIButton {
override func layoutSubviews() {
super.layoutSubviews()
let kTextTopPadding:CGFloat = 3.0;
var titleLabelFrame = self.titleLabel!.frame;
let labelSize = titleLabel!.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude))
var imageFrame = self.imageView!.frame;
let fitBoxSize = CGSize(width: max(imageFrame.size.width, labelSize.width), height: labelSize.height + kTextTopPadding + imageFrame.size.height)
let fitBoxRect = self.bounds.insetBy(dx: (self.bounds.size.width - fitBoxSize.width)/2, dy: (self.bounds.size.height - fitBoxSize.height) / 2)
imageFrame.origin.y = fitBoxRect.origin.y
imageFrame.origin.x = fitBoxRect.midX - (imageFrame.size.width/2)
self.imageView!.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width
titleLabelFrame.size.height = labelSize.height
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2)
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding
self.titleLabel!.frame = titleLabelFrame
self.titleLabel!.textAlignment = .center
}
}
It's working fine. My problem is when I start using in UIStackView. I am creating something similar to UITabBar so I have 5 buttons next to each other. I want to middle button be in exact center of screen but it's always several pixels to side. I check my constraints for UIStackView and it's okay. I remove all buttons exect middle one and then it's in exact middle. I tried different options for UIStackView (Fill, Centered, Fill Equally, etc.). Nothing helps. Do anyone has any idea? Each button has different icon (with different size) and texts are different too (free, premium, etc. - sometimes bigger then icon sometimes smaller), is this relevant?
Example of my StackView (red line shows where is middle):
The image size is going to be relevant here. Once you get all 5 images to be the same exact size you won't experience this issue anymore. Using 'fill equally' after doing that and you will get the result you want
Related
I have a UILabel that is designed to expand in height when the width of the text's CGSize is greater than the width of the label. I accomplish that with this code:
func viewHeight(_ locationName: String) -> CGFloat {
let locationName = tappedLocation[0].name
var size = CGSize()
if let font = UIFont(name: ".SFUIText", size: 17.0) {
let fontAttributes = [NSAttributedStringKey.font: font]
size = (locationName as NSString).size(withAttributes: fontAttributes)
}
let normalCellHeight = horizontalStackViewHeightConstraint.constant
let extraLargeCellHeight = horizontalStackViewHeightConstraint.constant + 20.33
let textWidth = ceil(size.width)
let cellWidth = ceil(nameLabel.frame.width)
if textWidth > cellWidth {
return extraLargeCellHeight
} else {
return normalCellHeight
}
}
Lines = 0 and line break style = Word Wrap:
The label lives inside a vertical stackView, and is constrained to its top, leading and trailing edges and a stackView beneath it. The height of the label and the UIView properly expand in height when the CGSize width of the text is longer than the width of the label. All well and good.
However, the words do not wrap consistently. This behavior is intentional:
Bobby Mao's Chinese Kitchen & Bar:
XL cell. Width: 184.0,
Text width: 287.0
This behavior is not (why isn't "steak" on the prior line?):
Ruth's Chris Steak House:
XL cell. Width: 184.0,
Text width: 204.0
And neither is this (why didn't Gina wrap if it's over the label width parameter?):
Ristorante Mamma Gina:
XL cell. Width: 184.0,
Text width: 191.0
I have also set a temporary background color on my label to ensure that it does, in fact correspond to the intended width. The label in this example creates another line when the label's width is exceeded, but the text does not wrap:
I have read the other entries on Stack Overflow about word wrapping. I don't believe this is a duplicate. I do not have trouble creating two lines for my text. I don't have trouble with word wrapping occurring. I have trouble with how and when it is occurring.
I think the intent is clear... what am I missing?
The best way to describe my situations is with images. What I have is a view which contains several UILabels and UIImage. The red box is a UILabel and if the content is too big it should go to the second line.
From the storyboard I have a working case when the content fits but the problem is that I am not sure how to handle the case when the last (red box) should go to the second line. I am using autolayout and cartography.
If someone can point me to the right direction I will be very grateful.
First calcululate width of text as per your current label's position.
If text width is more than current label's width then see my answer from below link:
Auto Layout how to move the view2 from right to the bottom?
Calculate width:
func widthForView1(_ text:String, font:UIFont, height:CGFloat) -> CGFloat
{
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: your_label_width, height: your_lable_height))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.text = text
label.font = font
label.sizeToFit()
return label.frame.width
}
You cannot do that with constraints only. To change the entire position of the element on the screen, you need to do it programmatically.
Use of tag View can solve this issue. TagListView is an external library.
When u add a view as subclass of taglistView, its height automatically increases accordingly.
ADD this to pod file : pod 'TagListView'
func addTags() {
let str1 = "Hi"
tagListView.addTag(str1)
let str2 = "Helloo"
tagListView.addTag(str2)
let str3 = "How Are u ? "
tagListView.addTag(str2)
tagListView.isUserInteractionEnabled = false
}
I have a question about making an underline on UITextField.
I am trying to make an underline with bar on each end as shown below.
I tried the following and got this one. There is no bar on the right end.
extension UITextField {
func underline() {
let borderWidth = CGFloat(1.0)
let endBorderHeight = CGFloat(10.0)
let bottom = CALayer()
bottom.frame = CGRect(
x: 1,
y: self.frame.height - borderWidth,
width: self.frame.width - 2,
height: borderWidth)
bottom.borderWidth = borderWidth
bottom.borderColor = UIColor.lightGrayColor().CGColor
let leftEndBorder = CALayer()
leftEndBorder.frame = CGRect(
x: 0,
y: self.frame.height - endBorderHeight,
width: borderWidth,
height: endBorderHeight)
leftEndBorder.borderWidth = borderWidth
leftEndBorder.borderColor = UIColor.lightGrayColor().CGColor
print(bottom.frame.width)
let rightEndBorder = CALayer()
rightEndBorder.frame = CGRect(
x: self.frame.width - 1,
y: self.frame.height - endBorderHeight,
width: borderWidth,
height: endBorderHeight)
rightEndBorder.borderWidth = borderWidth
rightEndBorder.borderColor = UIColor.lightGrayColor().CGColor
self.layer.addSublayer(leftEndBorder)
self.layer.addSublayer(bottom)
self.layer.addSublayer(rightEndBorder)
self.layer.masksToBounds = true
}
}
I could make the bar on left side but having trouble making the right side because of the wrong x position of the rightEndBorder probably?
Can anyone tell me what I am doing wrong??
----- edit
I tried to set the x-position of the rightEndBorder to 200 and it gave me the following.
But if I tried to set it to 300, I don't see it anymore.
----- edit
Checked if the entire textfield was shown on the screen.
----- edit
It was that the leading and trailing constraints that changed the width of the textField I guess.
----- Solution
The problem was that I had leading and trailing constraints on the textField and those constraints changed the width after the unline was inserted. After searching google, I figured that I had to make the underline after the constraints were applied which is in the function viewDidLayoutSubviews().
Re-typing from comments section as an answer.
The problem seems to be either that the view is getting cut off (thus you will not see the right border) or the right end border is being shifted farther right after setting. Reason for suspecting this is from your picture where the right border is placed at x = 200 because it is well past half along the bottom border line but should be only be 40% of the way along the line.
Update:
The correct answer to this question was the constraints imposed caused the textfield width to change.
I have written a custom graph UIView subclass, and I use it to graph some basic data, and insert some user-defined data. As a final step, I'd like to add a UILabel on top of the graph with the user-defined data-point called out.
I highlight the point, and then create and add the UILabel:
if(graphPoints[i] == highlightPoint){
var point2 = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point2.x -= 8.0/2
point2.y -= 8.0/2
let circle2 = UIBezierPath(ovalInRect:
CGRect(origin: point2,
size: CGSize(width: 8.0, height: 8.0)))
highlightColor.setFill()
highlightColor.setStroke()
circle2.fill()
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
var pointLabel : UILabel = UILabel()
pointLabel.text = "Point = \(graphPoints[i])"
pointLabel.frame = CGRectMake(point2.x, point2.y, 100, 50)
self.addSubview(pointLabel)
} else {
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
}
This looks like it should work, but the UILabel is added twice. What am I doing wrong?
This code is probably in drawRect. You're doing subview-adding in drawRect which is incorrect. drawRect gets called at least twice as the view appears, and perhaps many times after that.
You should be overriding some early lifecycle method, like awakeFromNib() or the constructor. In that, construct your label and add it as a child view. This way the addSubView() happens once as it should. The label will be invisible having no contents, so don't worry about that.
In drawRect, just update all the necessary attributes of the label including the frame.
If you find you're actually needing lots of text "labels" that come and go, different quantity for every graph, you don't really want UILabels as subviews after all. Consider directly drawing text on screen with someString.drawAtPoint(...)
I'm writing an iOS app which has multiple labels in one view, like shown:
I would like these labels to be in the vertical centre of the view, with the middle of the collection of labels as the centre of the view.
I need to use auto layout for this, as the top label may be multiple lines, or may only be one depending on input, and will change height depending on this. This, along with the top label being a different size, means that I cannot simple have the middle label in the middle, and the others relative to that.
I'm looking for a solution either in code or IB.
EDIT: To clarify, I am looking to centre the middle of multiple labels, like so:
(The vertical middle might be slightly off)
*The image should read vertical middle
This is an old question of mine, but it maintains a fair number of views, and is quite a common use-case. I do not feel the other answer is a very efficient method to achieve this.
The easiest method to centre a collection of views is to place them within a UIView object which is itself centred.
To use the example above, the three UILabels would be within one UIView, with a 0 constraint between the top and bottom labels and the View. The view itself would then be set to be centred vertically.
I don't think there is a way to do this with Auto Layout, but you can code. I'm assuming you've already figured out the spacing of the labels, so I'll just help you center the whole thing.
var o1 = label1.frame.origin
var o4 = label4.frame.origin
var h4 = label4.frame.height
var w4 = label4.frame.width
var hc4 = o4.y + h4
var wc4 = o4.x + w4
var screen = UIScreen.mainScreen().bounds.size.height
var remainingScreen = (screen - (hc4 - o1.y))/2.0
var screenW = UIScreen.mainScreen().bounds.size.width
var remainingScreenW = (screenW - (wc4 - o1.x))/2.0
var moveH = remainingScreen - o1.y
var moveW = remainingScreenW - o1.x
var frame1 = label1.frame
var frame2 = label2.frame
var frame3 = label3.frame
var frame4 = label4.frame
label1.frame = CGRect(x: frame1.origin.x + moveW, y: frame1.origin.y + moveH, width: frame1.width, height: frame1.height)
label2.frame = CGRect(x: frame2.origin.x + moveW, y: frame2.origin.y + moveH, width: frame2.width, height: frame2.height)
label3.frame = CGRect(x: frame3.origin.x + moveW, y: frame3.origin.y + moveH, width: frame3.width, height: frame3.height)
label4.frame = CGRect(x: frame4.origin.x + moveW, y: frame4.origin.y + moveH, width: frame4.width, height: frame4.height)
This whole thing isn't very self explanatory, but I won't talk about what every line does. The overall idea is it finds the distance between the upper left and lower right corner of all the labels (so UL of label1 and LR of label4 [this only works if you've already set the spacing between labels and the width/height of each label]), then it finds the width/height (W/H) of the screen and subtracts the W/H of the label area, divides by two, giving the space between the top of the screen and the labels. Finally, it finds the distance the entire assembly needs to move by comparing the UL corner with where it should be, and combines the amount to be moved with the original location of all the labels.
Note: This code could be heavily condensed, it is just easier to view and read if it is written like this.