Swift 3 how to anchor bottom when width is exceeded - uiview

I am coding an app and I want to know how I can anchor a view to the bottom instead of right side of the previous view when total width in the same row exceeds device width.
Basically like this:
View 1: width=150 anchor to superview's leftAnchor
View 2: width=500 anchor to view1's rightAnchor
View 3: width=1000 anchor to view1's bottomAnchor because total width in same row
exceeds device width
#device width=1200
Any help is appreciated. Sorry for bad English, not a native speaker.
Ps. all heights are same, width is dynamic

The main challenge was I couldn't know the width of my views until they are constrained.
So I used swift's estimation.
let size = CGSize(width: UIScreen.main.bounds.width, height: 50)
let attributeHeight = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .subheadline)]
let estimatedHeight = NSString(string: _title).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributeHeight, context: nil)
self._estimatedWidth = estimatedHeight.width + 32
after that it was easy to anchor them according to my needs with a for loop

Related

iOS + Autolayout: Adding constraints to text of UILabel/Button, not frame

I have a UILabel and a UIButton, and in both cases the frame is a lot bigger than the text presented. I'd rather not change this if I can.
Now, I would like to add a spinner (UIActivityIndicatorView) right after the text, how do I do it? What I have right now is a constraint from the spinner's trailing edge to the button/label's trailing edge and a constant of -40. This constant if ok for english, but might be wrong for other languages.
Any ideas? Thanks!
Follow these steps :
Take leading constraint for UIActivityIndicator.
Calculate width of the text using this (create extension) :
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedStringKey.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
Set leading constraint of UIActivityIndicator
to width + 5 (+ 5 for leaving some gap).

Animate a layout constraint by changing its priority value

I have a label containing quite a lot of text. There's a toggle for collapsing and expanding the height of the label (here it's named "lire la suite") so it truncates the end of the text.
I have meticulously set the vertical content hugging priority and compression resistance so the intrinsic size has higher priority over the compression resistance.
The height constraint (the optional constraint directly at the right of the label) is set with a constant of 71, just the height for 4 lines. It never changes.
Then this same constraint has a priority switching between 747 and 749 so the following happens:
height constraint priority = 749:
compression resistance < constraint priority < hugging priority
Compression resistance collapses under the constraint priority, its height is 71 or less if its intrinsic size (hugging priority) is smaller.
height constraint priority = 747:
constraint priority < compression resistance < hugging priority
The greater compression resistance forces the height to follow its intrinsic size.
This works perfectly. My issue is that I can't figure out how to animate this constraint, since every solution animates the constant property and not the priority.
I would like to know if there's a solution or a workaround.
By experimenting with it, it seems that you cannot animate constraints using priorities, and you are stuck either with activating/deactivating constraints, or changing their constants.
I've had a similar task a couple of days ago. An easy but a bit naive approach is to drop the constraint and use only intrinsic content size - you can set the label.numberOfLines = 4 when it should be collapsed (thus the size won't expand over 4 lines), and label.numberOfLines = 0 when expanded. This is very easy and clean, however, I am not sure how that goes with animation.
A second approach is to use only the constraint and animate the constant. You have a height for 4 lines already, all you need is the height of the expanded label. You can use following extension on UILabel to calculate the height:
extension UILabel {
func heightNeeded() -> CGFloat {
self.layoutIfNeeded()
let myText = self.text! as NSString
let boundingRectangle = CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: boundingRectangle,
options: .usesLineFragmentOrigin,
attributes: [NSAttributedStringKey.font: self.font],
context: nil)
return labelSize.height
}
}
Then all you need to animate is the:
labelHeightConstraint.constant = label.heightNeeded()
Don't forget on how to animate that constant using autolayout, see for example my following answer to another SO question.

Layout for different iPhone screens

I'm building my first real-world app after going through some tutorials and I've come across a layout issue. It is quite simple to adjust UI layout to different screen size classes, but I haven't found any information on how to adjust layout within same size class.
For example, I have a label whose Top Space constraint is set to 40 pt form top of view. It looks neat on a large iPhone 8 Plus screen:
But on a smaller iPhone SE screen (which is confusingly of same size class) this constraint of 40 pt pushes the label halfway through to the center, leaving reasonably less useful space below it:
So I was wondering if there's a way to set different constraints for different iPhones: say, 40 pt for iPhone 8 Plus, 30 pt for iPhone 8 and 20 pt for iPhone SE. Same goes about positioning other views below the label: I want them more compact vertical-wise on a small iPhone screen, and having more space between them on large screen. I know this last part can be solved with a stack view, but it's not always convenient to use.
UPD. Here is a full layout of the view on 8 Plus screen:
It has 3 fixed constraints:
1. From 'Title' label to top of the view - 50 pt
2. From 'Percent' label to bottom of 'Title' label - 60 pt
3. From 'Details' label to bottom of the view - 80 pt.
I've used text autoshrink in all labels + height of each label is proportional to view's height. This made layout a bit more flexible, but still there's a noticible issue on small SE screen:
As you can see, 'Details' is squeezed to 'Percent' label. At this point it would be great to move 'Percent' label higher up and closer to 'Title', but unlike heights constraints cannot be set in proportion (not in IB at least) to Superview height.
One of the options I see is to put a blank view between top and mid labels, making its height proportional and setting 'Percent' label top constraint at 0 to this blank view. Not sure though using such a "crutch" is a good practice.
You may get your most satisfactory results by using a single UILabel and setting the Attributed Text, instead of trying to get multiple labels and font sizes to cooperate.
Try this:
Create a new View Controller
add a normal UILabel
set constraints to 85% of width and 80% of height, and centered both ways
connect the label to an IBOutlet
then:
class ScalingViewController: UIViewController {
#IBOutlet weak var theLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let titleFont = UIFont.systemFont(ofSize: 80.0, weight: UIFontWeightThin)
let pctFont = UIFont.systemFont(ofSize: 100.0, weight: UIFontWeightThin)
let paraFont = UIFont.systemFont(ofSize: 30.0, weight: UIFontWeightLight)
// for blank lines between Title and Percent and between Percent and Body
let blankLineFont = UIFont.systemFont(ofSize: 36.0, weight: UIFontWeightLight)
let sTitle = "Title"
let sPct = "78%"
let sBody = "A detailed text explaining the meaning of percentage above and what a person should do to make it lower or higher."
// create the Attributed String by combining Title, Percent and Body, plus blank lines
let attText = NSMutableAttributedString()
attText.append(NSMutableAttributedString(string: sTitle, attributes: [NSFontAttributeName: titleFont]))
attText.append(NSMutableAttributedString(string: "\n\n", attributes: [NSFontAttributeName: blankLineFont]))
attText.append(NSMutableAttributedString(string: sPct, attributes: [NSFontAttributeName: pctFont]))
attText.append(NSMutableAttributedString(string: "\n\n", attributes: [NSFontAttributeName: blankLineFont]))
attText.append(NSMutableAttributedString(string: sBody, attributes: [NSFontAttributeName: paraFont]))
// these properties can be set in Interface Builder... or set them here to make sure.
theLabel.textAlignment = .center
theLabel.numberOfLines = 0
theLabel.adjustsFontSizeToFitWidth = true
theLabel.minimumScaleFactor = 0.05
// set the label content
theLabel.attributedText = attText
}
}
This gives me these results for 7+, 6s and SE:
And, just for demonstration's sake, how it looks with additional text in the "body" paragraph:

UILabel textRectForBounds not working

I am trying to figure out the height of a long string "one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen" as would be needed to fit in a multi-line UILabel. The problem is that I am getting incorrect results from this method, even if I wait to call this method inside layoutSubviews()
let rect = label.textRectForBounds(
CGRect(x: 0,y: 0, width: label.bounds.width, height: 10000000.0),
limitedToNumberOfLines: 6)
When I set a breakpoint after this line, I get:
(lldb) po rect
▿ CGRect
▿ origin : CGPoint
- x : 0.0
- y : 0.0 { ... }
▿ size : CGSize
- width : 374.0
- height : 36.0
That 36.0 height is way to short -- that reprsents a single line, and I already have the label height set to be 65.0 so that two lines will display. When displayed, the UILabel is three lines high and the text gets cut off because the height is not big enough:
What am I doing wrong? Why can I not get the expected result of a height of 36.0*3=108.0?
For what it's worth, I get the same results if I call this method:
let rect = (label.text as NSString!).boundingRectWithSize(
CGSize(width: label.bounds.width, height: 100000000.0),
options: .UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: NSStringDrawingContext())
EDIT: The problem did end up being that the view had not been laid out yet, causing the width of the UILabel to be incorrect in the calculation above. See the last comment for details.
Sometimes when a view is a subview inside the view hierarchy, it's best to set height/width (using either frames or auto layout constraints) from the superview in viewWillLayoutSubviews() (or viewDidLayoutSubviews()).
Before these are executed the views don't have all the layout data they need.

Swift UILabel with horizontal lines on both sides

I am trying to create a UILabel that has 2 horizontal lines on the left and right side like this:
Does anyone know the best approach for doing this in Swift? The content text in the center will change so I want to make sure it can adapt. I'd really like to create some kind of reusable UIView class but I'm not sure where to start?
Thank you!
You can take two UIview of height 1 or 2 pixels of both side of the label. so it's look likes line!!
And you should set background color to black of that view!
Hope this will help :)
Take one UIView with height of 2. Set leading & Trailing according to Super View.
Now take one UILabel with background color white and put Vertically Center to line view.
Make both Center same.
Your work done.
For more help please refer below image.
You can use an extension on UILabel
public extension UILabel {
func drawLineOnBothSides(labelWidth: CGFloat, color: UIColor) {
let fontAttributes = [NSFontAttributeName: self.font]
let size = self.text?.size(attributes: fontAttributes)
let widthOfString = size!.width
let width = CGFloat(1)
let leftLine = UIView(frame: CGRect(x: 0, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
leftLine.backgroundColor = color
self.addSubview(leftLine)
let rightLine = UIView(frame: CGRect(x: labelWidth/2 + widthOfString/2 + 10, y: self.frame.height/2 - width/2, width: labelWidth/2 - widthOfString/2 - 10, height: width))
rightLine.backgroundColor = color
self.addSubview(rightLine)
}
}
This will add a horizontal line of width of 1.0 on the both side of your label. If you don't add text for your label, it will show two horizontal lines through center with some spaces in between them.
As others have mentioned, you can achieve this using three views:
Add a View to your scene to use as a container. I called this view "Lined Label Holder."
To that container, add two Views, one to produce the line on either side of the label.
Add the label in between the two views, and give it some text. Due to the "height = Test.height" constraint on the Lined Label Holder, The intrinsic height of this label is used to calculate the container's height.
The label is allowed to grow with added text and the lines will always start 5px away from the edges of the text and extended to the edges of the container, whose width can be set independently.
This image shows the required constraints:
Use one UIView with black background and height of 1px, set label background to white, align its text to center and align UILabel to center of UIView (there is no need for 2 views since label white background will cover UIView).
Not necessary 2 UIView's.
Take 1 UIView and give background black color.Add the constraints necessary with: height=2.
place 1 label on the center and give required constraints

Resources