UILabel textRectForBounds not working - ios

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.

Related

AVMakeRect returning working value when I use constraints

I am using AVMakeRect to get the image coordinates this is my code for that .
let x: CGRect = AVMakeRect(aspectRatio: image.size, insideRect: pointsView.imageView.frame)
This is output:
(50.9430284857571, 40.0, 272.113943028486, 484.0)
This is my constraints set up:
This is the image I used to get the output :
You can see there is no space on top of the image view but it is sending me the y value as 40 and x value as 50 .
What was wrong with my constraints and code ?
If you uses auto layout you need to change the constraints in viewDidLayoutSubviews based on our requirements.
Add top constraint outlet.
func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
topConstraint.constant = 50
}

Word Wrap Occurs Inconsistently in UILabel

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?

Swift 3 how to anchor bottom when width is exceeded

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

How to center the subviews of UIScrollView

I'm a beginner in creating a custom view. I'm trying to create a custom UIView with a scrollview and buttons that will look like this:
I'm adding a view(view with label of page number) inside of scrollView depending on the the number of pages. Is that how it should be?
Currently it looks like this:
My question is how can I center the subviews of scrollview? and next is what's wrong with this code? Why is that I can only see 1 label inside the view? and the other doesn't show up. How can I scroll to the selected page if the page number is not visible already in the scrollview?
Here's my code:
func addPageNumberViewWithCount(count: Int) {
var pageNumberViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
for i in 1...count {
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
label.center = pageNumberView.center
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
// add view inside scrollview
scrollView.addSubview(pageNumberView)
if i == count {
scrollView.contentSize = CGSizeMake(pageNumberViewX + pageNumberView.frame.width, 30)
}
}
}
Part of my answer will go to providing a solution to your question,and another part of my answer will go toward strongly suggesting that this not be the method you use to complete your desired tasks.
At this point, AutoLayout and Interface Builder have come a long way. Where they used to be difficult to use because of their inconsistency and unpredictability, they are now highly predictable and consistent as long as you understand the tools and how to use them.
Apple's suggested method for completing this task (which I mostly stand behind) is creating a .xib file (nib) to lay out the base components of the design, and to load the nib into the view or view controller whenever that design should be used. My question for you: have you tried this, or have you determined for some reason that this would be an unsatisfactory solution to your problem? AutoLayout exists to solve these problems not just in allowing you to achieve your desired solution in this one situation but to achieve it in other situations as well, with varying screen sizes and device types.
Now, if you were to simply ignore all of that and continue on your path, there would be a few good ways to handle your problem. One suggested solution I have:
1) Wrap your pageNumberView in another view. Constrain that view to the size of the scrollView. Doing this gives the scrollView content with which to base its scrollable content size, and gives the inner pageNumberView something to compare itself to.
2) Center the pageNumberView horizontally in its container (the new view that we just created).
Doing this, the page numbers should now center themselves in the container until they reach a size where they exceed the width of the scrollView. At that point, they will then continue to expand, making the area horizontally scrollable.
I can provide code examples of how you would do this, but frankly I would much prefer if you scrapped the idea of doing things this way and instead opted for the AutoLayout method at least, and perhaps even the Interface Builder method. I started out with iOS the same way you did, trying to do everything in code. It really isn't the best way to do things, at least with regard to iOS.
Edit: I've provided an example of how this would look in Interface Builder using UINib. I've populated the view with an example of 5 pages to show what it is like. I will see if I can make a GIF or something similar to show what each of the subviews look like.
For the OP, my suggestion would be this: Use this for reference, and go learn the constraints system. It is extremely unlikely that you will find success with iOS if you do not learn and utilize the constraints system. Coding in X values to a UIView's frame is only going to create a product with poor, inconsistent performance across devices, and will take much, much longer than it would to take the time to learn constraints.
Perhaps you should have a UICollectionView with a cell for each of these buttons. That's a better way of doing this, and you can lay it out again when the screen rotates and it changes width.
Those cells will layout offset to the left. You can solve that this way:
let pageNumberViewTotalWidth = 30 * count + (pageNumberViewDistance * count - 1)
self.collectionView.contentInset.left = (self.collectionView.frame.size.width - pageNumberViewTotalWidth) / 2
The labels aren't showing up because you're setting their frame's x to be the same as the page number view's x. It's frame should be relative to it's superview, in this case pageNumberView.
First Question of yours "how can I center the subviews of scrollview?"
Solution: lets suppose you have in total 50 pages and you want to show 5 pages at a time in the scrollview.
Then make 10 subviews of equal widths where each subview width will be equal to visible portion of the collection view that is
self.view.size.width - 2*(width of toggle button)
Then in each container view add 5 of your pageNumberView placed at equal distance
lets pageNumberViewWidth = container.width/5 - 2*margin
now pageNumberView frame will be (margin,0,pageNumberViewWidth,height)
In this way in each container view your pageNumberViews will be placed equally and it will look as if you have centred them.
Second Question "Why is that I can only see 1 label inside the view?"
Answer : Its because you are setting label frame incorrectly
let label = UILabel(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
Here label is the subview of pageNumberView So you have to set its frame according to its parent's view which is pageNumberView, so change it to
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
First time it was right because pageNumberViewX is 0 for first iteration after that it become some positive value which makes its frame shifted to right but its parent's width is small so its not visible to you.
Third Question : "How can I scroll to the selected page if the page number is not visible already in the scrollview?"
For this you need to find the frame of your selected page:
you can do that by using the offset that you used to create pageNumberView.
(width of each pageNumberView)*pageNumber = starting point of the required pageNumberView.
let frame : CGRect = CGRectMake(calculated offset above, 0,30, 30)
//where you want to scroll
self.scrollView.scrollRectToVisible(frame, animated:true)
I hope this will help you in solving your problem
Edit for first problem
func addPageNumberViewWithCount(count: Int) {
var containerViewX: CGFloat! = 0
let pageNumberViewDistance: CGFloat! = 10
let pageNumberViewPerSubview = 5
var numberOfSubview = count/pageNumberViewPerSubview
if(count % pageNumberViewPerSubview > 0){
numberOfSubview = numberOfSubview + 1
}
var pagesLeft = count
for i in 1...numberOfSubview {
var pageNumberViewX: CGFloat! = 0
let containerView : UIView = UIView(frame:CGRectMake(containerViewX,0,scrollView.frame.size.width,scrollView.frame.size.height))
if(pagesLeft < pageNumberViewPerSubview){
for k in 1...pagesLeft{
}
}
else{
for j in 1...pageNumberViewPerSubview{
let pageNumberView = UIView(frame: CGRectMake(pageNumberViewX, 0, 30, 30))
pageNumberView.backgroundColor = UIColor.redColor()
pageNumberView.layer.cornerRadius = pageNumberView.frame.height / 2
pageNumberView.layer.masksToBounds = true
pageNumberView.clipsToBounds = true
// add number label
let label = UILabel(frame: CGRectMake(0, 0, 30, 30))
label.text = "\(i)"
label.textAlignment = .Center
pageNumberView.addSubview(label)
// update x for next view
pageNumberViewX = pageNumberView.frame.origin.x + pageNumberView.frame.width + pageNumberViewDistance
containerView.addSubview(pageNumberView)
}
containerViewX = containerViewX + scrollView.frame.size.width
// add view inside scrollview
scrollView.addSubview(containerView)
pagesLeft = pagesLeft - pageNumberViewPerSubview
}
if i == count {
scrollView.contentSize = CGSizeMake(numberOfSubview*scrollView.frame.size.width, 30)
}
}
}

Dynamic height UITableViewCell

I have cells that consist of a title, date and a number of hashtags.
Here's the storyboard's screenshot:
Custom cell in storyboard
I've set the following in my ViewDidLoad:
tableView.rowHeight = UITableViewAutomaticDimension
But haven't given any estimatedHeight for the tableView which I'll explain why.
Here's my customCell:
#IBOutlet weak var title: UILabel!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var hashtagsView: UIView!
var item: Item? {
didSet {
configureCell()
}
}
func configureCell() {
if item = item {
title.text = item!.title
date.text = item!.date
// The part where I calculate the sizes of hashtags to fit them in hashtagsView
let totalWidth = CGRectGetWidth(hashtagsView.frame)
print("TotalWidth: \(totalWidth)")
.
.
.
.
print("Content: \(self.contentView.frame)")
print("HashtagsView: \(self.hashtagsView.frame)")
}
}
Here's the results:
With tableView.estimatedHeight = 150
TotalWidth: 240.0
Content: (0.0, 0.0, 240.0, 119.666666666667)
HashtagsView: (0.0, 0.0, 240.0, 128.0)
Without estimatedHeight
TotalWidth: 240.0
Content: (0.0, 0.0, 414.0, 43.6666666666667)
HashtagsView: (0.0, 0.0, 240.0, 128.0)
Recap
When there is an estimatedHeight, cell's contentView doesn't print a correct width, but displays the cell's contents well like nothing's wrong (except for the hashtagsView).
When there's not an estimatedHeight, cell's contentView does actually print the correct width, which lets me calculate the hashtags frames, but the cells display with their default 44 height.
Detailed Info
Since I don't know how many hashtags there is, I'm trying to use the blank UIView to add the UIButtons programmatically. And for calculation purposes of the hashtags' buttons, I need to have the "width" of the cell's contentView, or the hashtagsView's, but when I set tableView.estimatedHeight, cell's width will be some arbitrary number (e.g. 240 in 6s Plus Simulator). And I just can't get the hashtagsView's width, even though I have no auto-layout issues.
And when I don't give tableView.estimatedHeight an estimation, I get the following:
Custom cells without estimatedHeight
Updated - An update asked by #EarlGray in the comments
The hashtags are actually UIButtons I add them to the hashtagsView dynamically. I need to stretch the hashtagsView's height so it'll fit more than one line of hashtags.
I think I'll either need to subclass UIView and override layoutSubviews() to achieve fit vertical layout or add constraints to each subview (UIButtons) programmatically.
Doing what #VinodVishwanath said, setting estimatedRowHeight combined with explicit heights and vertical space constraints gives me this:
Which unables me to get the width of the hashtagsView. During the calculations of the UIBUttons' frames, I need the cell.contentView's width, but somehow, setting the estimatedRowHeight gives me the following coordinates for the cell.contentView.frame
Content: (0.0, 0.0, 240.0, 119.666666666667)
Which is incorrect, because it has to give 414, and that's why my hashtags start from half of the screen.
Commenting out tableView.estimatedRowHeight gives me the correct coordinates:
Content: (0.0, 0.0, 414.0, 43.6666666666667)
But messes my tableView like so:
Update #2 - Here's my constraints for the cell.contentView
ContentView's constraints
Update #3 - A breakpoint on my configureCell method
HashtagsView's superview returns nil!!
I don't get it, my UIView IBOutlet is connected, I double checked.
All of the contentView.subviews have incorrect frames. So does the superview-less hashtagsView.
But when I remove estimatedRowHeight, it suddenly considers hashtagsView as a subview of cell's contentView. Except for contentView, it's subviews frames' still return incorrect and negative values.
When you don't provide the estimated row-height, the cell height defaults to 44, and that's why you're getting a content height of 43.667.
You need to provide the estimated row height, which will be the height when there are no tags in the field provided. Then all that's needed to set the correct height dynamically is the right set of autolayout constraints to provide the contentView's intrinsic size.
This will happen when you set a vertical space constraint between all the subviews of contentView, including a topSpaceToSuperview and bottomSpaceToSuperview to enable the AutoLayout engine to calculate the intrinsic content size.
Edit
I have analysed your constraints from the screenshot, and here are the relevant constraints you have added to calculate the cell height:
Title.top = topMargin + 8
bottomMargin = Share Button.bottom
Link.centerY = Share Button.centerY
Hashtags View.top = Title.bottom
Date.top = Hashtags View.bottom + 15
Now here's a visual representation of these constraints:
TopMargin
|
Title
|
Hashtags View
|
Date
!!! broken link !!!
Link — Share Button
|
BottomMargin
The broken link prevents the Layout Engine from calculating the cell height. You need to fix the broken link in the vertical layout, for instance, by setting Date.centerY = shareButton.centerY, or by setting Date.top = HashtagsView.bottom.

Resources