Text bottom + center UILabel iOS Swift - ios

I have created an iOS application using Swift. Now I have one issue I want to make my text center + bottom in one label and center + top in another label. Is it possible to do?
I tried this
//Here learnItem is my label.
let constraintSize: CGSize = CGSizeMake(learnItem.frame.size.width, CGFloat(MAXFLOAT))
let textRect: CGRect = learnItem.text!.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: learnItem.font], context: nil)
learnItem.drawRect(textRect);
It doesn't affect anything. So maybe it is a wromg code. Also I saw
learnItem.textAlignment = .Center
There is option for center, justified, left, natural, right. But that is not what I want.
Please some one help me how to make my text center + bottom and center + top?

In order to do this you should override drawTextInRect method.
// MARK: - BottomAlignedLabel
#IBDesignable class BottomAlignedLabel: UILabel {
// MARK: Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawText(in rect: CGRect) {
guard text != nil else {
return super.drawText(in: rect)
}
let height = self.sizeThatFits(rect.size).height
let y = rect.origin.y + rect.height - height
super.drawText(in: CGRect(x: 0, y: y, width: rect.width, height: height))
}
}
For top alignment y should be 0.
If you're using storyboard the easiest way to use this code is just change class for your label in storyboard.

Related

Possible to get view size from within a class? (Swift)

If a subview of class MyClass is added to another view:
secondView = MyClass()
mainView.addSubview(secondView)
is it possible to get the subview's dimensions from within the class?
secondView = MyClass()
mainView.addSubview(secondView)
class MyClass: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// do whatever the view will need to show
// *** can I get the view's dimensions here? <----------------------***
// e.g., draw a diagonal line
// from top left corner to bottom right corner
// self.bounds and self.frame return (0, 0, 0, 0) here
}
}
Or do they need to be passed in somehow from the view creating it (mainView)?
You cannot get a view's size reliably within init. A view's size can change. Therefore you should either do any drawing you need in drawrect, or if you are using CALAyer, resize your layer in layoutSubviews where you will have the bounds of your view and you can then use that to set the frame of your sublayer or subviews.
https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews
https://developer.apple.com/documentation/uikit/uiview/1622529-drawrect
Here is a playground with a simple line example.
import UIKit
import Combine
import PlaygroundSupport
final class XView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
UIColor.red.setStroke()
let path = UIBezierPath()
path.lineWidth = 10
path.move(to: rect.origin)
path.addLine(to: .init(x: rect.maxX, y: rect.maxY))
path.stroke()
}
}
let v = XView()
v.frame = .init(origin: .zero, size: .init(width: 300, height: 300))
PlaygroundPage.current.liveView = XView()
If you want the fancy version see my GitHub project here:
https://github.com/joshuajhomann/pathmorpher/tree/master/final

Increasing height of button as text size increases in Swift

I am very new to autolayout and I have been struggling with one thing.
The Roll btn in the BottomView - I am trying to increase it's height according to the text size. As you can see, the height of the textlabel of btn in increasing, but the height of btn is not.
The constraints that I have applied:
1)Horizontallly and verticallly centered in superview
2)Leading and trailing from superview
3) I tried setting height constraint to greaterThanEqualTo a constant also. But that didnt work
4)I tried SizeToFit also. Didn't work
5)I have set link lineBreak as WordWrap and number of lines in titleLabel as 0. Still its not working
i used this solution to change height of uilabal dynamically
you can get this extension to get height and width of string
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(boundingBox.width)
}
}
since you are using storyboard set height and width constant then link them to viewcontroller script
YourContrainsHeight.constant = SomeString.height
YourContrainsWidth.constant = SomeString.width
When you modify the .titleLabel of a UIButton, auto-layout doesn't take your changes into account.
To get a properly auto-sizing multi-line button, you need to subclass it and return a valid .intrinsicContentSize.
Here is a quick example:
#IBDesignable
class MultilineButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
setNeedsLayout()
}
func commonInit() -> Void {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = .center
self.contentEdgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
The class is marked #IBDesignable so you can use it in Storyboard / Interface Builder get accurate results.
Here's a look (in Storyboard) of 3 instances of this button. First with a short title, so it doesn't need to wrap; next with a title that does wrap; and third with a title that has embedded newlines:
Note that this example explicitly sets the Content Edge Insets to 8-pts on all 4 sides. If you want to be able to change that for individual buttons, it will need a little editing.

How to get Width or Height of a UIView when I'm using "Equal width" constrain

I need help to solve this problem: I have a UITextFiled and I'm trying to apply a border at the bottom using this code:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.addSublayer(border)
}
The problem is that the result is not correct, the border goes outside the textfield because in the text Field I'm using the "Equal width constrain" and the Width at design time is not the same Width at "Didload()" time. There is a way to get the width to the textField after "Equal width constrain" correction?
A much better approach is to
subclass UITextField
create the "underline border" layer on initialization
change the frame of that layer in layoutSubviews()
Example:
#IBDesignable
class UnderlinedTextField: UITextField {
let underlineLayer: CALayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(underlineLayer)
underlineLayer.backgroundColor = UIColor.black.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
underlineLayer.frame = CGRect(x: 0, y: bounds.height - 2.0, width: bounds.width, height: 2)
}
}
Result (I gave the text field a background color of .cyan to make it easy to see):
It automatically resizes the "underline" when the field size changes - such as on device rotation:
Note that, by making it #IBDesignable, you can also see the underline layer during design-time.
This example uses a default color of black for the "underline" but you can change it via code just like any other property change, e.g.:
testField.underlineLayer.backgroundColor = UIColor.red.cgColor
Override bounds variable and call your border drawing in didSet. Your layer would be updated every time view changes bounds.
var border = CALayer()
init(frame: CGRect) {
super.init(farme: frame)
self.layer.addSublayer(border)
}
override var bounds: CGRect {
didSet {
addBottomBorderWithColor(color: .black, width: 2)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - width, width: self.frame.size.width, height: width)
self.layer.setNeedsLayout()
}
I found a possible solution by myself (not the perfect one).
Because the Constrains are probably applied after DidLoad() and after viewDidLayoutSubviews(), I called the function to add the border inside the function viewDidAppear(). Now it works even if the new borders are shown with a small delay.
The best way is sub-class a UITextFiled as described here.
Custom class that can be applied to every UITextField - Swift
In this case there the object is created correctly

UIView / UIControl ignoring frame rect. Always fullscreen

I am trying to build custom components in Swift 2.2, and obviously missing something essential. My views ignore the frame rect.
In the containing view, I add the view programmatically like this:
let arrowControl = ArrowControl(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
self.view.addSubview(arrowControl)
In fact, even if I programmatically add a UIView or UIControl, without subclassing it - then the view ignores the frame rect, and occupies the entire screen.
Here are the essential parts of my custom view:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
renderComponent(frame)
}
EDIT>>>>
If I change my example, and remove renderComponent, I still get the same problem.
override func layoutSubviews() {
super.layoutSubviews()
self.clipsToBounds = true
backgroundColor = UIColor.yellowColor()
// renderComponent(self.frame)
}
Or even doing the following ignores the frame rect, and gives me a full screen block of colour:
let test = UIView(frame: CGRect(x: 100 , y: 100, width: 300, height: 300))
test.backgroundColor = UIColor.redColor()
self.view.addSubview(test)
Doh! I worked it out. It turns out it was my own fault, as I'd implemented the following in the containing view:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
view.frame = UIScreen.mainScreen().bounds
}
}

Vertically align text within a UILabel (Note : Using AutoLayout)

I am Copying the same Question asked Before Question.
I have tried the solutions given and was not able to solve it since sizetofit was not effective when I use Autolayout.
The expected display is like below.
Edit
In my original answer I was using the paragraph style of the label. Turns out that for multi-line labels this actually prevents the label from being multi-line. As a result I removed it from the calculation. See more about this in Github
For those of you more comfortable with using Open Source definitely look at TTTAttributedLabel where you can set the label's text alignment to TTTAttributedLabelVerticalAlignmentTop
The trick is to subclass UILabel and override drawTextInRect. Then enforce that the text is drawn at the origin of the label's bounds.
Here's a naive implementation that you can use right now:
Swift
#IBDesignable class TopAlignedLabel: UILabel {
override func drawTextInRect(rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
var labelStringSize = stringTextAsNSString.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
} else {
super.drawTextInRect(rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.blackColor().CGColor
}
}
Swift 3
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawText(in: CGRect(x:0,y: 0,width: self.frame.width, height:ceil(labelStringSize.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
Objective-C
IB_DESIGNABLE
#interface TopAlignedLabel : UILabel
#end
#implementation TopAlignedLabel
- (void)drawTextInRect:(CGRect)rect {
if (self.text) {
CGSize labelStringSize = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.frame), CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName:self.font}
context:nil].size;
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),ceilf(labelStringSize.height))];
} else {
[super drawTextInRect:rect];
}
}
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
self.layer.borderWidth = 1;
self.layer.borderColor = [UIColor blackColor].CGColor;
}
#end
Since I used IBDesignable you can add this label to a storyboard and watch it go, this is what it looks like for me
If you're not restricted by having UILabel of fixed size, instead of aligning the text within a UILabel, simply use ≥ constraint on the given label to change the size of it.
It's the most elegant solution using Auto Layout. Don't forget to set numberOfLines to zero though.
You can use UITextView instead of UILabel:
Uncheck "Scrolling enabled"
Uncheck "Editable"
Uncheck "Selectable"
Set background color to ClearColor
I had the same problem, and this is how I solved it. I just edited the Baseline under Attribute Inspector for the Label. Set it to "Align Centers".
Instead, I changed the Bottom Space Constant to priority #250 and solved my problem. And my label has height constant with <= constant
You would do that by removing the minimum height.
If you need the minimum height to something else below the label then you would use a container view that resized based on the label contents but used a minimum.
Auto layout only work with edges/sizes of controller, not with controllers content.so its not a good idea to use auto layout to display your label text on top of first line.
according to me sizetofit is a best option to do so.
I used #Daniel Golasko's solution and whenever the text inside the UILabel was longer than the UILabel could contain, the text would start moving down instead of staying aligned to top.
I changed this line to make sure the text is aligned properly
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),MIN(ceilf(labelStringSize.height), self.frame.size.height))];
I had a similiar issue where there were 3 labels. The middle label could have much longer text than the other two, so its height could grow much larger.
Like this:
I set the middle label's bottom space constraint to be >= the bottom label.
That solved my problem.
Here's an improvement on the Swift 3 solution by Daniel Galasko (here you can also set the maximum line number without an offset on the top):
import UIKit
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelString = stringTextAsNSString.boundingRect(with: CGSize(width: frame.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
super.drawText(in: CGRect(x: 0, y: 0, width: frame.width, height: ceil(labelString.size.height) > frame.height ? frame.height : ceil(labelString.size.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
There is an easy solution for cases where the height of a label doesn't need to be constant: put your Label in a Stack View. Be sure to add leading and trailing constants to the Stack View. Here is a screenshot of how to do it in storyboard:
Swift 4
You should subclass UILabel and override text display rendering.
class UITopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
guard let string = text else {
super.drawText(in: rect)
return
}
let size = (string as NSString).boundingRect(
with: CGSize(width: rect.width, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin],
attributes: [.font: font],
context: nil).size
var rect = rect
rect.size.height = size.height.rounded()
super.drawText(in: rect)
}
}
You can try if button [button setContentVerticalAlignment:UIControlContentVerticalAlignmentTop];
Edit :
You can try with this if you want to use label only:
https://stackoverflow.com/a/11278660/1223897
For me, I didn't set the height constraint, the text always grows from the top of the label.
The constraints for this label are top, left, right.
By the way, my label has fixed line numbers, so no worries about the height.
#IBInspectable var alignTop: Bool = false
func setAlignTop() {
let text = self.text!
let lines = text.characters.split(separator: "\n").count
if lines < self.numberOfLines {
var newLines = ""
for _ in 0..<(self.numberOfLines - lines) {
newLines = newLines.appending("\n ")
}
self.text! = text.appending(newLines)
}
}
override var text: String? {
didSet {
if alignTop {
self.setAlignTop()
}
}
}
use this my class, you can change text alignment by contentMode.
supported case: .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight
Swift4
import Foundation
import UIKit
#IBDesignable
class UIAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let text = text as NSString? {
func defaultRect(for maxSize: CGSize) -> CGRect {
let size = text
.boundingRect(
with: maxSize,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [
NSAttributedStringKey.font: font
],
context: nil
).size
let rect = CGRect(
origin: .zero,
size: CGSize(
width: min(frame.width, ceil(size.width)),
height: min(frame.height, ceil(size.height))
)
)
return rect
}
switch contentMode {
case .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight:
let maxSize = CGSize(width: frame.width, height: frame.height)
var rect = defaultRect(for: maxSize)
switch contentMode {
case .bottom, .bottomLeft, .bottomRight:
rect.origin.y = frame.height - rect.height
default: break
}
switch contentMode {
case .right, .topRight, .bottomRight:
rect.origin.x = frame.width - rect.width
default: break
}
super.drawText(in: rect)
default:
super.drawText(in: rect)
}
} else {
super.drawText(in: rect)
}
}
}
In the Interface Builder, just make the height <= some value instead of =. This will enable to text to start at the top and expand the height as needed. For example, I have a label with a height proportional to the size of the main view. So my height constraint looks like this:
Height Constraint

Resources