Swift - How changing textView height dynamically in constraint? - ios

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!)
}

Related

How to change aspectRatio of IBOutlet for a view programmatically?

Okay here is my constraints.
The view is aligned centreX and centreY and has leading, trailing, top & bottom as >= 0.
It also has another constraint of aspectRatio.
Now I have created the #IBOutlet of aspectRation constraint.
#IBOutlet weak var contentViewAspectRatio: NSLayoutConstraint!
The problem is I don't know how to change the aspectRatio programmatically.
if value {
//Change aspect ratio to 16/9
} else {
//Change aspect ratio to 19.5/9
}
Any kind of help would be appreciated.
Changing the aspect ratio means changing the multiplier of the constraint which is a read-only so you need to deactivate that constraint and create a new 1 with a new multiplier
let newCon = contentViewAspectRatio.addConstraintWithMul(0.3)
parentViewOfConstraint.removeConstraint(contentViewAspectRatio)
parentViewOfConstraint.addConstraint(newCon)
view.layoutIfNeeded()
contentViewAspectRatio = newCon
extension NSLayoutConstraint {
func addConstraintWithMul(_ multiplier: CGFloat) -> NSLayoutConstraint {
return NSLayoutConstraint(item: self.firstItem!, attribute: self.firstAttribute, relatedBy: self.relation, toItem: self.secondItem, attribute: self.secondAttribute, multiplier: multiplier, constant: self.constant)
}
}
Another option is to create 2 constraints with 2 different aspects and play with their active / priority state in case you have a limited and know number of aspects

Make UITextView height dynamic, issue with constraint multiplier

I have a UICollectionView which contains five cells. In there, I have a UIImageView, two UILabels and a UITextView. I want to change the height of the textview based on the text it contains, so afterwards I can set the height of the entire cell based on the height of the UITextView and the labels above them. Let me demonstrate with a screenshot.
So, as you can tell, the red background shows the height of the UITextView is not right. I set up the UITextView like this:
let commentTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = "This is just some text to act as a description"
textView.textContainerInset = UIEdgeInsets(top: 0, left: -4, bottom: 0, right: 0)
textView.font = UIFont.systemFont(ofSize: 16.0)
textView.textColor = UIColor.darkGray
textView.isEditable = false
textView.isSelectable = false
textView.backgroundColor = UIColor.red
textView.frame.size.width = (UIScreen.main.bounds.width - 16 - 16 - 60 - 8)
let numberOfLines = (textView.contentSize.height / textView.font!.lineHeight)
var textViewHeight = (textView.font?.lineHeight)! * floor(numberOfLines)
textView.frame.size.height = textViewHeight
return textView
}()
This does not create the wrong height, I think. I think the problem can be found in my constraints, which has a height constraint (if I delete it, the UITextView disappears). If I change the multiplier (currently set at 0.3), I have different heights, but I want this to be dynamically. So in my opinion, I would need to set a dynamic variable inside the multiplier, but I have no idea how to compose it. Could anyone help? Here are my constraints:
// top constraints
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .top, relatedBy: .equal, toItem: workoutLabel, attribute: .bottom, multiplier: 1, constant: 2)])
// left constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .left, relatedBy: .equal, toItem: profilePictureImageView, attribute: .right, multiplier: 1, constant: 16)])
// right constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .right, relatedBy: .equal, toItem: self.commentTextView.superview, attribute: .right, multiplier: 1, constant: -16)])
// height constraint
addConstraints([NSLayoutConstraint(item: commentTextView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0.3, constant: 1)])
Cheers guys!
You could consider another way about dynamic height of UITextView. It's known about intrinsic content size. You could implement dynamic height with that tech.
Originally answered here.
Don't change or give any frame to your UITextView. Just give it leading, trailing, top & bottom constraints. Then if your cell is capable of having automatic size then you don't need to calculate anything for your text view.
When using Auto-Layout for dynamically sizing cells, you don't really need to implement sizeThatFits(...). If the constraints are setup correctly, then you only need to disable the scrolling of the UITextView.
From code:
yourTextView.scrollEnabled = false
From IB:
Select your Text View and open Attributes inspector, then
Now if you are facing problems with making your cell to have dynamic size then please look at this answer.
As mentioned yesterday, I had the textview adapt its size based on the answer of #nayem - thank you!
However, I faced another problem with making the cell height dynamic. I have looked into different solutions, got nothing to work, except when I calculate the height myself and set this as the height to be used. This works on all devices in Simulator, just wondering if this is the right approach. Code below, constraints are set on top, left and right.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let dummyText = "Just a long text to see what will happen to the cell. Will it adapt or not? Let's find out!"
let rectWidth = view.frame.width - 32 - 60 - 16
let rect = NSString(string: dummyText).boundingRect(with:CGSize(width: rectWidth, height: 1000), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)
let variableHeight = 16 + 20 + 2 + 20 + 2 + 16 + rect.height
return CGSize(width: view.frame.width, height: variableHeight)
}
Is this the best way to do this? Will this cause problems later on?

UIViews overlapping when adding multiple views dynamically to a tableViewCell in swift

I want to add some n views inside a table cell (lets say each view is a row). Based on the designerExpertiseList count, i have created a view for each row and added 1 image view and 1 label.
But when i scroll, the data for cells is not correct. If i long press on a cell, i can see a different view overlapped with the one visible now. Please check the attached screenshots
1st time when the view is loaded : http://i.stack.imgur.com/M8itL.png
After i scroll down, scroll up again and long press: http://i.stack.imgur.com/AuTG0.png
And when i scroll up, the data which was correct the first time for few cell, even that is getting messed up. I even tried to add these dynamic views only once, on First Render.
There are the global declarations:
let rowHeight:CGFloat = 20.0
let imageWidth:CGFloat = 15.0
let imageHeight:CGFloat = 15.0
let labelX:CGFloat = 30.0
let labelHeight:CGFloat = 20.0
var firstRender:[Bool] = [Bool]()
code inside tableView cellForRowAtIndexPath method:
self.designer = AppController.getElementFromDesignersList(indexPath.section)
cell.designerName.text = designer.getFirstName() + " " + designer.getLastName()
cell.location.text = designer.getAddress()
// Add more ROWS of expertise if exist! Getting only 1st expertise now, if it exists
let expertiseList = self.designer.getExpertiseList()
if self.firstRender[indexPath.section] {
var i:Int = 0
for e in expertiseList {
let v = UIView(frame: CGRectMake(0, CGFloat(i)*rowHeight, cell.frame.width, rowHeight))
v.backgroundColor = UIColor.yellowColor()
let im = UIImageView(frame: CGRectMake(0, CGFloat(i)*imageHeight, imageWidth, imageHeight ))
//print("expertise image path: ", e.getImagePath())
im.af_setImageWithURL(
NSURL(string: e.getImagePath())!,
placeholderImage: UIImage(named: "default_galary_demo2")!
)
im.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(im)
// Adding constraints
NSLayoutConstraint(item: im, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: im, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: v, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0).active = true
im.widthAnchor.constraintEqualToAnchor(nil, constant: imageWidth).active = true
im.heightAnchor.constraintEqualToAnchor(nil, constant: imageHeight).active = true
// cell.frame.width - im.frame.width - 50
let label = UILabel(frame: CGRectMake(0, CGFloat(i)*labelHeight, UIScreen.mainScreen().bounds.width - imageWidth, labelHeight))
label.font = UIFont(name: "OpenSans", size: 12)
print("expertise dump: ", dump(e.getExpertiseValuesList()))
//print("expertise str: ", e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(","))
label.text = e.getExpertiseValuesList().map({"\($0.getName())"}).joinWithSeparator(",")
//label.translatesAutoresizingMaskIntoConstraints = false
v.addSubview(label)
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: v, attribute: .CenterY, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: im, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 10.0).active = true
cell.designerExpertiseView.addSubview(v)
i += 1
}
self.firstRender[indexPath.section] = false
I don't think it correct to render cell only when if self.firstRender[indexPath.section] because when user scroll table view, the cells out of screen will be reused to show others different indexPaths.
Another thing, you create constraints, but not use them
You could use UIStackViews to handle the constraints for you. You would:
Greatly reduce the boilerplate-constraint code in your class;
Simplify adding subviews;
Allow for Adaptive Layouts.
Add a Vertical UIStackViewFor into your UITableViewCell. For more complex layouts, you can either embed more UIStackViews into the Vertical stack view or mix AutoLayout and UIStackViews.
I've quickly tried to recreate your layout and I've added a UIStackView. This implementation uses AutoLayout for some fixed components (like the profile picture) and the stack view to handle UILabels added programatically.
I hope this helps!
As it seems, adding views programmatically to cell using .addSubView() won't go well with reusable cell. It happens since cells are reused when go out of view, but when become visible again, the subviews are being added again.
The solution/workaround I used was to add another placeholder view from storyboard. And i removed the view from superview using .removeFromSuperview() when not needed.
I know this method is not good
Ex: Lets say I put 10 views in a cell ( assuming 10 is maximum views i need), but many of them might not be needed based on the data i get from server. So i will remove most of them from the view.
I am still looking for a better/actual solution for this issue.

How do I set the height to a tableFooterView loaded from a xib

I have a UITableView, I'm adding tableFooterView loaded from a xib.
var footer = NSBundle.mainBundle().loadNibNamed("AFooterView", owner: self, options: nil).first as! AFooterView
self.tableView.tableFooterView = footer
This works fine, but I need to be able to set the height for this footer. The xib only has a UIImageView centered vertically and horizontally so it will adapt whatever the height of the view is.
I have no clue how to do this with AutoLayout? What would be the right path to follow?
I'm sorry for the confusion, here's the updated answer:
I know you can do this by setting the frame height, but it might also work with auto layout just by re-assigning the footer view after your imageView has finished loading.
// either let auto layout calculate the frame, or set the frame yourself
// I set the width to an arbitrary size but it doesn't seem to matter,
// it will automatically be adjusted by the tableview when you assign it
CGFloat width = 100;
CGFloat height = 500;
footerView.frame = CGRectMake(0, 0, width, height);
// this is the "trick": re-assign the footerView after its size has been updated
// so that the tableView will show it correctly
tableView.tableFooterView = footerView;
For more information, see Resizing a UITableView’s tableHeaderView
The original answer talked about section footers, not table view footers
If you use Autolayout and load Header or Footer View from xib, you should add constraints:
func setupTableFooterView() {
// Create your footer view from XIB and set constraints (in my case it is historyToolBarView of class HistoryToolBarView)
let view = Bundle.main.loadNibNamed("HistoryToolBarView", owner: self, options: nil)?.first
historyToolBarView = view as! HistoryToolBarView
historyToolBarView.translatesAutoresizingMaskIntoConstraints = false
historyToolBarView.addConstraints(
[NSLayoutConstraint.init(item: self.historyToolBarView,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1.0,
constant: 60),
NSLayoutConstraint.init(item: self.historyToolBarView,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .notAnAttribute,
multiplier: 1.0,
constant: UIScreen.main.bounds.size.width)])
// Create a container of your footer view called footerView and set it as a tableFooterView
let footerView = UIView(frame: CGRect.init(x: 0, y: 0, width: tableView.frame.width, height: 60))
footerView.backgroundColor = UIColor.green
tableView.tableFooterView = footerView
// Add your footer view to the container
footerView.addSubview(historyToolBarView)
}

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.

Resources