UICollectionView .reloadData() only shows sections - ios

I have a collection view which has a section with a title and every section has some words. Words differ in size.
Because the words differ in size i've added the following, to prevent long words from being cut off: layout.estimatedItemSize = CGSize(width: 1.0, height: 1.0)
However after setting that and invoking reloadData(), the cells (words) do not get loaded only the sections (title).
But after scrolling all the sections that went out of screen will load their words. However when I don't use layout.estimatedItemSize it works correctly, but the words are cut off.
So my question is if there is another way to display those words (which are basically a small view with a label) without them being cut off. Or am I using estimatedSize wrongly?
As I read from the docs from apple itself: docs
The default value of this property is CGSizeZero. Setting it to any other value causes the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method.
I do set constraints dynamically and statically (StoryBoard),
my dynamic constraint is as following:
if prevCell != nil {
let constraint = NSLayoutConstraint(item: cell, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: prevCell, attribute: NSLayoutAttribute.trailing, multiplier: 1.0, constant: 5.0)
cell.addConstraint(constraint)
self.prevCell = cell
}
Things I have tried myself so far:
//before reload data invalidate the layout
self.collectionView!.collectionViewLayout.invalidateLayout()
self.collectionView!.reloadData()

After some more research found the correct way to handle this.
I forgot to override the preferredLayoutAttributesFitting in the cell's class. To make it work I just left the layout.estimatedItemSize = CGSize(width: 1.0, height: 1.0) in viewDidLoad of the collection view.
However somehow this is not enough because it wont be able to calculate the right size not until you scroll it out of screen. It probably has some logic behind it that I do not know of.
But when overriding the the preferredLayoutAttributesFitting as follow:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let updatedSize = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var updatedFrame = layoutAttributes.frame
updatedFrame.size.width = CGFloat(ceilf(Float(updatedSize.width)))
updatedFrame.size.height = CGFloat(ceilf(Float(updatedSize.height)))
layoutAttributes.frame = updatedFrame
return layoutAttributes
}

Related

Adding Right Aligning Constraints to the Programmatically Added Subview

I am trying to add a right aligned Segmented Control to the `UITableViewCell like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "mycell")
// ...
addSegmentedControlToCell(cell)
// ...
and then
func addSegmentedControlToCell(_ cell:UITableViewCell){
let items = ["One","Two"]
let unitControl = UISegmentedControl(items: items)
unitControl.selectedSegmentIndex = 0
let maxWd = cell.frame.size.width
let maxHt = cell.frame.size.height
let padding:CGFloat = 5
unitControl.frame = CGRect(x:maxWd/2, y:padding, width:maxWd/2, height: maxHt-padding*2)
unitControl.addTarget(self, action: #selector(SettingsTableViewController.unitControlValueDidChange(_:)), for: .valueChanged)
cell.addSubview(unitControl)
}
This works well on my default device iPhone 6. But when I run this app on a smaller width device, like iPhone 5, the programmatically added Segment Control, which receives 50% of cell width (cell.frame.size.width/2) seems much bigger that 50% of width and stretches to the right under the cell view port.
This is because of auto-layout and constraints as I see because iPhone 5 cell view gets resized. So I am trying to add a constraint to my new Segment Control, which fails with app crush. Note, I am not very good with programmatically adding constraints yet.
let widthContr = NSLayoutConstraint(item: unitControl,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: cell,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: 0.5)
cell.addConstraints([widthContr])
How to right align subview correctly (Segment Control) for correct cell size?
You can set constraints like this:
yourView.rightAnchor.constraint(equalTo: yourCell.rightAnchor, constant: -10).isActive = true
Or:
yourView.heightAnchor.constraint(equalToConstant: 50).isActive = true
But make sure that you include this code:
yourSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
You can do this in 3 ways.
1) You have written 0.5 constant in NSLayoutConstraint(this is a mistake).
You need to write constant is 1 and multiplier should be 0.5.
2) Or you should update frame of UISegmentControl in layoutSubviews() method of Custom Cell.
3) Or you should write cell.layoutSubviews() after UISegmentControl adding to Cell.

iOS - subivew can't be centered in UITableViewCell

It's really weird.
No matter how I set the constraints, it just ignors all of them after layouts.
I've tried to use cell.indicator.center = cell.center and cell.indicator.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] in the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
but still not worked.
Is there any special way to center a subview inside a UITableViewCell?
LoadingCell.xib
Screenshot(Simulator)
updated:
After trying #Vibha Singh's advice, I put these codes in my LoadingCell.
override func layoutSubviews() {
print("layoutSubviews")
print(center)
print(contentView.bounds)
indicator.center = contentView.center
}
And it printed these lines:
layoutSubviews
(207.0, 1055.16668891907)
(0.0, 0.0, 414.0, 44.6666666666667)
But the indicator is still not centered.
updated:
I fixed it by creating a new cell with an indicator.
Both of them have exactly the same constraints. The new one is centered as expected, but the old one is still positioned at left top.
updated:
I did use centerX and centerY as constraints at the first time. But it's not worked. So I have to try another way. That's why I use so many constraints in the first screenshot.
Here are the screenshots with two exactly the same xibs.
Both of them use the same codes to dequeue.
let dequeue = tableView.dequeueReusableCell(withIdentifier: SharedCell.loading.rawValue, for: indexPath)
if let cell = dequeue as? CenterLoadingCell {
cell.indicator.startAnimating()
}
else if let cell = dequeue as? LoadingCell {
cell.indicator.startAnimating()
}
return dequeue
The first one is named LoadingCell, which is not centered on the simulator.
The second one is named CenterLoadingCell, which I created after I asked this question. And this one is centered on the simulator.
You are confusing your layout setting big time by adding unnecessary constraints. Check this implementation.
With Custom Cell:
You can add indicator like
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityView.center = CGPoint(x: CGFloat(cell.bounds.midX), y: CGFloat(cell.bounds.midY))
cell.contentView.addSubview(activityView)
activityView.startAnimating()
Or you can set frame in below method of cell
override func layoutSubviews()
{
}
You can make it easily with constraints
func setConstraints(item:UIView , relatedTo:UIView , attr:NSLayoutAttribute , relatedBy:NSLayoutRelation , multiplier: CGFloat , constant : CGFloat)
{
relatedTo.addSubview(item)
item.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: item, attribute: attr, relatedBy: relatedBy, toItem: relatedTo, attribute: attr, multiplier: multiplier, constant: constant).isActive = true
}
and usage :
setConstraints(item: indicator, relatedTo:cell.contentView, attr:centerX, relatedBy:.equal, multiplier: 0 , constant : 0)

Swift - How changing textView height dynamically in constraint?

I want to change a text views's height in the middle of the table view cell by context. After textView I have another views, constant height provided by auto layout constraints.
Actually I will get first 150 characters from context to show, but I think using auto resize needed to prevent another screen sizes problem.
How Can I use auto dimension, Is there any way to assign table view row height like this?
let height = 4 + 17 + contextHeight + 4
in the storyboard, add an aspect ratio for the textview, Then check "Remove at build time" option.
In viewDidLayoutSubviews() :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let contentSize = self.TextViewTitle.sizeThatFits(self.TextViewTitle.bounds.size)
var frame = self.TextViewTitle.frame
frame.size.height = contentSize.height
self.TextViewTitle.frame = frame
aspectRatioTextViewConstraint = NSLayoutConstraint(item: self.TextViewTitle, attribute: .Height, relatedBy: .Equal, toItem: self.TextViewTitle, attribute: .Width, multiplier: TextViewTitle.bounds.height/TextViewTitle.bounds.width, constant: 1)
self.TextViewTitle.addConstraint(aspectRatioTextViewConstraint!)
}

textView height equal to content size without viewDidLayoutSubviews

I'm creating a viewController which contain 2 textViews a title and a fullText. At the moment i've created 2 textViews in the interface builder which is placed below each other and then created following code to change the height to equal to the content. However the issue is that it seem to be delayed, which gives a bad user experience. By delay i mean that it takes 0.5 or 1 sec before it resize? here is my code:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setHeightToContent(self.titleText!)
setHeightToContent(self.fullText!)
scrollView?.contentSize = CGSizeMake(self.view.frame.width, self.fullText!.frame.origin.y + self.fullText!.frame.height)
println(self.fullText!.frame.origin.y + self.fullText!.frame.height)
}
func setHeightToContent(theTextView: UITextView) {
let contentSize = theTextView.sizeThatFits(theTextView.bounds.size)
var frame = theTextView.frame
frame.size.height = contentSize.height
theTextView.frame = frame
var aspectRatioTextViewConstraint = NSLayoutConstraint(item: theTextView, attribute: .Height, relatedBy: .Equal, toItem: theTextView, attribute: .Width, multiplier: theTextView.bounds.height/theTextView.bounds.width, constant: 1)
theTextView.addConstraint(aspectRatioTextViewConstraint)
}
Make each of the text views self-sizing in accordance with its own content, and use constraints so that the scroll view's contentSize is configured automatically based on its content.

How to adjust the height of a textview to his content in SWIFT?

In my view controller, I have an UITextView. This textview is filled with a string. The string can be short or long. Depending on the length of the string, the height of the textview has to adjust.
I use storyboards and auto layout.
I have some troubles with the height of the textview.
Sometimes, the height is perfectly adjusted to the text. Sometimes, no, the text is cropped on the first line. Below, the textview is yellow. the blue is a containerview inside a scrollview. Purple is the place for a picture.
The texts are from my core data base, they are string attributes.
Between the screen 1 and 2, the only thing changed is the string.
If I print the strings in the console I have the correct texts :
my amazing new title
Another funny title for demo
The constraints of my textview :
I don't understand why I have 2 different displays.
EDIT
I tried the #Nate advice, in viewDidLoad, I added:
myTextViewTitle.text="my amazing new title"
myTextViewTitle.setContentHuggingPriority(1000, forAxis: UILayoutConstraintAxis.Vertical)
myTextViewTitle.setContentCompressionResistancePriority(1000, forAxis: UILayoutConstraintAxis.Vertical)
myTextViewTitle.setTranslatesAutoresizingMaskIntoConstraints(false)
let views = ["new_view": myTextViewTitle]
var constrs = NSLayoutConstraint.constraintsWithVisualFormat("|-8-[new_view]-8-|", options: NSLayoutFormatOptions(0), metrics: nil, views: views)
self.view.addConstraints(constrs)
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[new_view]", options: NSLayoutFormatOptions(0), metrics: nil, views: views))
self.myTextViewTitle.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[new_view(220#300)]", options: NSLayoutFormatOptions(0), metrics: nil, views: views))
No results. With or without height contraint added for my textview in interface builder...
EDIT 2
I need an UITextView and not an UIlabel, because of the back button, to use an exclusion path.
let exclusionPathBack:UIBezierPath = UIBezierPath(rect: CGRect(x:backButton.bounds.origin.x, y:backButton.bounds.origin.y, width:backButton.bounds.width+10, height:backButton.bounds.height))
myTextViewTitle.textContainer.exclusionPaths=[exclusionPathBack]
I found a solution.
I was focus on the height of my UITextView, then I read a post talking about the aspect ratio. I wanted to try and that worked!
So in the storyboard, I added an aspect ratio for the textview, and I checked "Remove at build time" option.
In viewDidLayoutSubviews(), I wrote :
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let contentSize = self.myTextViewTitle.sizeThatFits(self.myTextViewTitle.bounds.size)
var frame = self.myTextViewTitle.frame
frame.size.height = contentSize.height
self.myTextViewTitle.frame = frame
aspectRatioTextViewConstraint = NSLayoutConstraint(item: self.myTextViewTitle, attribute: .Height, relatedBy: .Equal, toItem: self.myTextViewTitle, attribute: .Width, multiplier: myTextViewTitle.bounds.height/myTextViewTitle.bounds.width, constant: 1)
self.myTextViewTitle.addConstraint(aspectRatioTextViewConstraint!)
}
It works perfectly.
First, disable UITextView's scrollable.
Two options:
uncheck Scrolling Enabled in .xib.
[TextView setScrollEnabled:NO];
Create a UITextView and connect it with IBOutlet (TextView).
Add a UITextView height constraint with default height, connect it with IBOutlet (TextViewHeightConstraint).
When you set your UITextView’s text asynchronously you should calculate the height of UITextView and set UITextView’s height constraint to it.
Sample code:
CGSize sizeThatFitsTextView = [TextView sizeThatFits:CGSizeMake(TextView.frame.size.width, MAXFLOAT)];
TextViewHeightConstraint.constant = sizeThatFitsTextView.height;
In Swift 3.0, where tvHeightConstraint is the height constraint of the subject TextView (if using for multiple textviews, would add it to the function inputs):
func textViewDidChange(textView: UITextView) {
let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
if size.height != tvHeightConstraint.constant && size.height > textView.frame.size.height{
tvHeightConstraint.constant = size.height
textView.setContentOffset(CGPoint.zero, animated: false)
}
}
H/T to #cmi and #Pablo Ruan
In swift 2.3:
func textViewDidChange(textView: UITextView) {
let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.max))
if size.height != TXV_Height.constant && size.height > textView.frame.size.height{
TXV_Height.constant = size.height
textView.setContentOffset(CGPointZero, animated: false)
}
}
Why not have an image for the back button and a UILabel for your text, and use autolayout to position them? And maybe a third view for the yellow background.
My simple solution to an extremely similar problem:
I used labels with the number of lines set to 0 instead of trying to use text fields or views. Then I clipped the leading and trailing edges to the labels' containers which constrain their widths in my case, with 0 as the constant for the constraints. The catch is to set the number of lines to 0 to allow the labels to grow or shrink according to their content. The text never gets chopped and the height of the labels never exceed their contents' for all of the different size classes I configured for the widths (the container widths in my case). Tested for IOS 8.0 and later (also make sure 'explicit' is unchecked next to 'Preferred Width' attributes for all lables for the label widths to adjust for different size classes) - works perfectly.
I have adapted your code and used it for both: layout subview and textViewDidChange. Thank you very much that made my day after an hour of troubleshooting...
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textViewDidChange(self.recordingTitleTV)
}
func textViewDidChange(_ textView: UITextView) {
let contentSize = textView.sizeThatFits(textView.bounds.size)
var frame = textView.frame
frame.size.height = contentSize.height
textView.frame = frame
let aspectRatioTextViewConstraint = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: textView, attribute: .width, multiplier: textView.bounds.height/textView.bounds.width, constant: 1)
textView.addConstraint(aspectRatioTextViewConstraint)
}

Resources