Dynamic number of elements in TableViewCell - ios

I need to add labels one after another vertically and their number is dynamic. For now it works fine with this code:
let numUnitsLabel = UILabel(frame: CGRectZero)
numUnitsLabel.text = units
numUnitsLabel.font = UIFont.boldSystemFontOfSize(28)
numUnitsLabel.textColor = UIColor.redColor()
numUnitsLabel.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(numUnitsLabel)
let topConstraint = NSLayoutConstraint(item: numUnitsLabel, attribute: .Top, relatedBy: .Equal, toItem: cell.contentView, attribute: .Top, multiplier: 1, constant: 8 * CGFloat(i+1) + CGFloat(i*25) + CGFloat(i*20) - 5)
let rightConstraint = NSLayoutConstraint(item: numUnitsLabel, attribute: .Trailing, relatedBy: .Equal, toItem: cell.contentView, attribute: .Trailing, multiplier: 1, constant: -10)
cell.contentView.addConstraints([topConstraint, rightConstraint])
The cell is drawn fine. But when I add new item in the list and call tableView.reloadData the old labels are "remembered" and they are over new ones. The newly added object is put in the first place in the array and they are like one over another... Maybe my approach is bad, but if you have any suggestions please advice me.

Remember that cells are re-used, so however a cell was prepared previously, it still has all of that state. In particular, its view hierarchy (added subviews) remain. The reason for this is generally a layout of a Table Cell stays the same, and only its content is meant to be re-set in cellForRowAtIndexPath.
You're using an anti-pattern, first of all, which is why you're running into difficulty.
If you really want to clear out and re-layout the cell, remove all subviews explicitly, every time, e.g.:
https://techfuzionwithsam.wordpress.com/2014/12/23/what-is-the-best-way-to-remove-all-subviews-from-parent-viewsuper-view/
I suspect though, (admittedly this isn't CodeReview) that your design is flawed. Generally table cells don't need to grow in height with dynamic count of internal elements. You may need nested tables (!!), really just a single multi-line UITextView in each, or a table structured by sections with custom section header views.

Related

Is there a difference between the constraints instantiated in these two ways?

I'm rather new to iOS programming.
I was wondering if there is any functional difference between making a constraint like this
cell_image_view.heightAnchor.constraint(equalToConstant: 60).isActive = true
As opposed to doing it this way
var my_constraint = NSLayoutConstraint(item: cell_image_view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 60)
cell_image_view.addConstraint(my_constraint)
Anyone have any insights into this problem? Thanks.
heightAnchor is a UIView property that returns an NSLayoutDimension anchor, which is a subclass of NSLayoutAnchor.
If you search in the Xcode docs under, you'll find the following:
NSLayoutAnchor: A factory class for creating layout constraint objects
using a fluent API.
Layout anchors are "Syntactic sugar" for creating NSLayoutConstraint objects. Assuming you have them set up to create the same constraints, the results are the same.

Swift4 issues : change Nslayoutconstraint programmatically

I would like to change position of UITextfield txtAmount NSLayoutConstraint programmatically from its top to bottom of collection view to bottom of image view. All views are embed in a ui view.
Old constraint is dragged and mapped from storyboard to view controller.
New constraint constr is to be created programmatically.
When it comes to implementation and execution, it says
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled.
'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
Would you please tell me any guidelines for such modification ? I embed the UI elements in an embedded UIView because of scrollview I have used.
let constr = NSLayoutConstraint(item: txtAmount, attribute: .top, relatedBy: .equal, toItem: imageView, attribute: .bottom , multiplier: 1, constant: 0)
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
txtAmount.addConstraint(constr)
txtAmount.removeConstraint(constraintPo)
you are adding constraint to the txtAmount whereas you should've added it to the view which actually contains txtAmount and another view referenced in this constraint, i.e. imageView. Let's name this view which contains them superview.
let constr = NSLayoutConstraint(item: txtAmount, attribute: .top, relatedBy: .equal, toItem: imageView, attribute: .bottom , multiplier: 1, constant: 0)
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
txtAmount.removeConstraint(constraintPo)
superview.addConstraint(constr)
But this is not recommended way since iOS 8. As the docs say, you should set the constraint isActive (read this) property to true instead, and iOS will add them to relevant views:
IETypeList.removeFromSuperview()
uiviewType.removeFromSuperview()
constraintPo.isActive = false
constr.isActive = true

How to implement class swizzling swift?

i have one view.xib file and it's having small container(container view) which holds all the controls like button/textfield, all the events of controls is handle by parent view.xib class file.
My requirement is at one place i need to add/show parent i.e view.xib completed screen. At one more place i need to add only container view. when i add/show container view only, control's associated events/methods doesn't works.
So i thought, if i can change class of container view with parent view.xib's class, my work can be done.
so either suggest me some other solutions or class swizzaling if it's possible to handle in this way.
basically i am adding container view on uitableview cell's container(view) my code for same is as below
if let questionContainerView = cellQuestionView.viewQuestionContainer {
// let questionContainerView = cellQuestionView
cell.viewQuestionContainer.addSubview(questionContainerView)
questionContainerView.translatesAutoresizingMaskIntoConstraints = false
cell.viewQuestionContainer.addConstraint(NSLayoutConstraint(item: questionContainerView, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: cell.viewQuestionContainer, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 0))
cell.viewQuestionContainer.addConstraint(NSLayoutConstraint(item: questionContainerView, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: cell.viewQuestionContainer, attribute: NSLayoutAttribute.right, multiplier: 1, constant: 0))
cell.viewQuestionContainer.addConstraint(NSLayoutConstraint(item: questionContainerView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: cell.viewQuestionContainer, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0))
cell.viewQuestionContainer.addConstraint(NSLayoutConstraint(item: questionContainerView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: cell.viewQuestionContainer, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0))
cellQuestionView.layoutIfNeeded()
}
I believe what you are trying to do is create a re-usable UIView with your control buttons, that you can use in different view controllers. There is a great tutorial on Youtube entitled iOS Basically: Reusable UIView - Programming in Swift (Part 1).
Basically, you will want to create a .Xib file dedicated to the view that you want to re-use, with all the actions handled by that custom view class. Every time you will want your custom view, you will need to instantiate it from the Xib file and manually add it as a subview onto the container view.
Good luck and happy coding!
-- edit --
Your updated code shows that this will be part of a UITableView and you are trying to add your custom view on top of a UITableViewCell. You should instead instantiate a custom UITableViewCell and register it as reusable with the table view. This tutorial should guide you on doing just that: Custom UITableViewCell Tutorial - TableView with Images and Text in Swift
You can define your #IBAction in your custom table cell, and attach your button selectors to your action in your custom cell's nib.
Cheers!

Xcode swift view wrap content

I'm an Android developer trying my hand at Xcode and it's been unpleasant so far. What I'm trying to do is have a custom view that has three sub views:
UIImageView (for an icon)
UILabel (for the title)
UILabel (for the content)
I want it such that the content label's height grows and shrinks to wrap the text it contains (like Android's wrap_content). And then, I want the custom view to also grow and shrink to wrap all three sub views.
However, I cannot, for the life of me, figure out how these auto layouts/constraints work.
01) How would I make my UILabel's height grow/shrink to match its contained text?
02) How would I make my custom view's height grow/shrink to match its contained sub views?
override func layoutSubviews() {
super.layoutSubviews()
img_icon = UIImageView()
txt_title = UILabel()
txt_content = UILabel()
img_icon.backgroundColor = Palette.white
img_icon.image = icon
txt_title.text = title
txt_title.textAlignment = .Center
txt_title.font = UIFont(name: "Roboto-Bold", size:14)
txt_title.textColor = Palette.txt_heading1
txt_content.text = content
txt_content.textAlignment = .Center
txt_content.font = UIFont(name: "Roboto-Regular", size:12)
txt_content.textColor = Palette.txt_dark
txt_content.numberOfLines = 0
txt_content.preferredMaxLayoutWidth = self.frame.width
txt_content.lineBreakMode = NSLineBreakMode.ByWordWrapping
self.backgroundColor = Palette.white
addSubview(img_icon)
addSubview(txt_title)
addSubview(txt_content)
/*snip img_icon and txt_title constraints*/
let txt_content_x = NSLayoutConstraint(item: txt_content, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0)
let txt_content_y = NSLayoutConstraint(item: txt_content, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 80)
let txt_content_w = NSLayoutConstraint(item: txt_content, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1, constant: 0)
let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
txt_content.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
txt_content_x,
txt_content_y,
txt_content_w,
txt_content_h
])
}
I understand that, in the above code I've tried, I have the height set to a constant 40. This is only because I don't know how to achieve what I want.
[EDIT]
I've tried setting the height constraint to greater than or equal to but it just crashes Xcode.
[EDIT]
It crashes Xcode if I try to view it but works perfectly fine in the simulator. Question now is, why?
My height constraint is now:
let txt_content_h = NSLayoutConstraint(item: txt_content, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 40)
It works in the simulator and has the desired behaviour. However, if I open the storyboard that contains the view, it crashes. It's definitely that line of code because changing it back to .Equal resolves the crash.
[EDIT]
My temporary fix is:
#if TARGET_INTERFACE_BUILDER
//use .Equal for height constraint
#else
//use .GreaterThanOrEqual for height constraint
#endif
This way, it doesn't crash Xcode and still renders the way I want it on the simulator.
[EDIT]
I removed the pre-processor check because I realized there's no actual thing like that defined and it still works now. I swear I've changed nothing else.
I am this close to giving up on iOS development because the interface builder keeps crashing Xcode without a reason when everything works in the simulator. Then, I do some nonsense edits and it works fine again.
01) How would I make my UILabel's height grow/shrink to match its contained text?
Just set top, left and right-constraint to the labels superview. Set the property number of lines to 0. Then it will start wrapping text.
02) How would I make my custom view's height grow/shrink to match its contained sub views?
By using interface builder this is much easier to achieve.
My suggestion to you is to start with your constraints in storyboard. You will not need to compile your code to see what the constraints will result in. Also you will get warnings and errors directly in the interface builder.
If you WANT to use programmatic constraints, my suggestion is to start using a framework for it. For example: https://github.com/SnapKit/SnapKit
You can use a trick with constraints to achieve wrap-content. For example :
let maximumWidth = frame / 4 //For example
yourView.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true
The "maximumWidth" depends on your UI and your design and you can change it.
Also, you should set "lineBreakMode" in StoryBoard or in code like :
yourBtn.titleLabel?.lineBreakMode = .byCharWrapping //For UIButton or
yourTxt.textContainer.lineBreakMode = .byCharWrapping //For UITextView
Often clean will do a lot of good when code jams for no reason ar all, cmd-shift-k if i remember correctly
I understand there is no direct application of wrap content in iOS just like we have in Android and thats a big problem, I resolved it through manual anchors like this.
create a function with where in you calculate the height of the view using
mainView.contentSize.height
and then set anchors based on the total height to the enclosing view, call this function inside
override func viewWillLayoutSubviews()
And this would work, the viewWillLayoutSubviews() is a lifecycle method and whenever you override you have to do
super.viewWillLayoutSubviews()
This worked in my case, might work with yours too, if there is a better approach please do comment.

How to setup layout constraints for a horizontal alignment of UIImageView's

I am trying to implement something like the image shown (http://i.stack.imgur.com/jKmGO.png) below using auto layout in Swift, but every time I end up with some issues. Can anyone please tell me the best way to implement auto layout for this kind of arrangement in horizontal grid for 3,4,5 or more image views.
Try the following steps
1) first select all of them and add constraint EQUAL WIDTH to images
2) set the leading space between left margin and first image view( let it be d)
3) Similarly set trailing space between the right margin and last imageview (let it be d)
4) Now add constraint HORIZONTAL DISTANCE(d) between all the image view e.g.
LEFT MARGIN-d-IMAGE1-d-IMAGE2-d-IMAGE3-d-IMAGE4-d-RIGHT MARGIN
Checkout Stanford Cs193p lecture no 8. Something similar is considered which was a basic calculator with equal width and height of keys.
Try code below (for horizontal arrangement)
var arrayOfImages = [image1,image2,image3,image4]
var previousImage:UIView? = nil
let horizontalSpaceBetweenImages = 20.0
let viewToUse = self.view //change this view to the one in which you are adding images as subviews
for image in arrayOfImages {
viewToUse.addSubview(image)
if previousItem == nil {
// this is the first item
viewToUse.addConstraint(NSLayoutConstraint(item: viewToUse, attribute: .Leading, relatedBy: .Equal, toItem: image, attribute: .Leading, multiplier: 1.0, constant: 0.0))
}
else {
viewToUse.addConstraint(NSLayoutConstraint(item: image, attribute: .Leading, relatedBy: .Equal, toItem: previousImage, attribute: .Trailing, multiplier: 1.0, constant: horizontalSpaceBetweenImages))
viewToUse.addConstraint(NSLayoutConstraint(item: image, attribute: .Width, relatedBy: .Equal, toItem: previousImage, attribute: .Width, multiplier: 1.0, constant: 0.0))
}
previousImage = image
}
// now add trailing constraint for last image
viewToUse.addConstraint(NSLayoutConstraint(item: arrayOfImages.last, attribute: .Trailing, relatedBy: .Equal, toItem: viewToUse, attribute: .Trailing, multiplier: 1.0, constant: 0.0))
this code sorts out horizontal alignment. It should be trivial to then add vertical alignment constraints (they should be easier to implement)
A collectionView would be the best option for you if you are going to be switching between 3, 4, and 5 images in your view. If you are, however, trying to have a set number of images displaying (number of UIImages stays the same) You can try doing this method:
Everything here is going to be based on the horizontal distance available to you based on the device size. So to start, you need to figure out what height you'll want for the images. Do the heights need to be relative to the width? Or can the heights for each image stay the same?
If the heights need to stay relative to the widths, put an "Aspect Ratio" constraint on each image. If the heights can stay static, place a "height constraint" on each image.
Next you need to set constraints for the distances between the images.
At this point you are still going to have errors because the width for each image will be ambiguous. To fix this, select all the images simultaneously and then add the constraint "Equal Widths." As long as you've made sure that the images are pinned to the top, you should no longer have auto-layout issues at this point.
Let me know if I can be more clear on any of these steps.

Resources