How to change subview of uitableviewcell programmatically? - ios

I'm working on app that allows user to share photo like instagram. User can post a photo with or without caption, so the caption could be empty and the photo still posted to their feed. My client want to remove the view when the caption is empty and I'm using uitableview cell with autolayout for the post. When I tried to connect the captionview height constraint to my viewcontroller it gives me an error it says that the constraint outlet can't be connected to repeated content. So, I tried to do it inside my code , but it doesn't change anything.
let captionString = object["caption"] as? String
let captionView = cell.contentView.viewWithTag(111)
if captionString == ""{
captionView?.frame = CGRectMake(0 , 0, captionView!.frame.width, 0)
}else{
(cell.contentView.viewWithTag(12) as! UILabel).text = captionString
}
I put above code inside tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell and I'm giving constraint in every view inside uitableviewcell so if I can change the captionView height other view will automatically resizing. How can I change the height of the captionView?

Related info here:
Outlets cannot be connected to repeating content iOS
You can't set the frame, when you're using autolayout that's invalid and won't work properly.
Either have 2 different styles of cell and choose which one to dequeue based on data availability, or add constraints so that you can collapse the label height to zero.

you can create outlet of height constraint and set the height 0
like as:
#IBOutlet var captionViewHeightConstraint: NSLayoutConstraint!
captionViewHeightConstraint.constant = 0

Related

Swift UITableViewCell fails to setup its constraint after cellForRowAtIndexPath

In my tableViewController i use a custom cell which contains an imageView, two labels and some other irrelevant elements. Also there's a constraint, of which the constant value shall be changed, if a certain condition is given. This works fine on the first sight but if i scroll down and get cells where the constraint constant is set to another value the some of the following cells kinda keep this constant from this previous cell and don't set it up at appearing.
This is the relevant part of my cellForRowAtIndexpath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = whichCellToShow ? "ThisCell" : "TheOtherCellCell"
//...
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
if let customCell = cell as? CustomCellProtocol,
indexPath.section < data.count,
indexPath.row < data[indexPath.section].count {
customCell.display(data: data[indexPath.section][indexPath.row])
}
return cell
}
These are the relevant parts of my custom cell class:
class CustomCell: UITableViewCell, CustomCellProtocol {
#IBOutlet weak var picture: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var weblink: UILabel!
#IBOutlet weak var titleIndentationConstraint: NSLayoutConstraint!
//...
func display(data: Data) {
//...
title.text = data.title
//...
weblink.text = data.hasWeblink ? URLUtilities.hostOfURL(urlstr: data.sourceUrl) : nil
weblink.isHidden = !data.hasWeblink
//This is the spot where things seem to go wrong
titleIndentationConstraint.constant = data.hasWeblink ? weblink.frame.height : 0
setNeedsLayout()
layoutIfNeeded()
}
}
If data.hasWeblink == true the constraint constant shall be equal to the height of the weblink label, if it's false it shall be 0.
My first thought was, that the view recycling of UITableView could collide with titleIndentationConstraint.constant = data.hasWeblink ? weblink.frame.height : 0. That's why i called setNeedsLayout()and layoutIfNeeded()straight afterwards, but this doesn't seem to help. All the other elements of the cell do their job correctly.
Also i'm setting tableView.rowHeightand tableView.estimatedRowHeightinside the viewDidLoad() of my tableViewController.
Does anybody have an idea what in particular is going wrong there or what i forgot to do? Thanks in forward!
Fixed it myself, had to change some constraint priorities of the title label:
My title label has a bottom constraint which is always greater or equal than 8 pts. Also i limited the number of lines to 4 and told the label to truncate the text's tail, if it's too long to be displayed. Funnily always, if the text was truncated, i got this silly behavior described above, so it has absolutely nothing to do with my titleIndetantionConstraint. Seems like if the text has to be truncated the label aligned itself to the bottom constraint and kinda dragged the content to center left (so watch what content mode says in the property editor, it should be left by default). While changing the label's content mode seems like a bad idea to me, i lowered the priority of the title label bottom constraint by 1 so it aligns itself to the top of the picture, like i planned. Hopefully this helps people how have similar issues B).

how to hide the image/label in StoryBoard to show up when we run the app?

I am making a table view app which retrieves the data from the Firebase. when making the user interface in the storyboard, I am using dummy image and label to visualize my app.
but when i run the app which consists of dynamic table view, those dummy images and label also shows up before immediately replaced by the actual data that i download from the Firebase storage.
can I set those images and labels to not show up when i run the app but still available in the storyboard?
thanks in advance :)
If they're just dummies, you can get rid of them when your view loads, before it appears onscreen:
override func viewDidLoad() -> Void{
super.viewDidLoad()
dummy.removeFromSuperview()
}
Whenever you want to hide/show an UIView:
myView.isHidden = true // hide
myView.isHidden = false // show
I assume what you need is to hide the views in viewWillAppear and then show them when necessary.
In your custom cell class, define something to hide the unwanted views:
func hideDummyViews() {
// do some stuff to hide what you don't want, e.g.
myEnclosingStackView.isHidden = true
}
In your table view controller, in the cellForRowAt indexPath func:
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell ..
if yourDataSource[indexPath.row].isCompletelyLoaded {
// do your fancy dynamic cell layout
} else {
// show the basic version (minus any dummy views)
cell.hideDummyViews()
}
return cell
You can choose your preferred method for hiding the items (isHidden for each view, removing, adjusting constraints). I prefer to embed any disappearing views in a stack view and then use isHidden = true on the enclosing stack. This keeps things organized in your storyboard/XIB file and neatly recalculates constraints for the hidden stacks.
It seems that you want to show some empty (or incomplete) cells until database content arrives and then you will reload each cell as you process new entries in the datasource. This answer will initially give you a set of cells appearing as per your storyboard/XIB, minus the hidden dummy elements. Then as items in your datasource are loaded fully, you can reload the cells.
By the way, it seems like a lot of work to carefully layout these dummy views for "visualization" and then never show them in the app. Why not have some user-friendly place holders or progress indicators showing and then animate in the real/dynamic views as the data arrives?
I assume you download the Image from FireBase and does't want the dummy Image to appear
(Why dont you declare an empty ImageView and an empty Label!). Try setting an Image array in the viewController. Or you can use a struct array if you want a Image and label text together.
var arrImage=[UIImage]()
var arrLblTxt=[String]()
In view did load append your Demo Image's if required.
arrImage.append(UIImage("Demo"))
arrLblTxt.append("Demo")
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tbl_AssignEmployees.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as YourCell
if arrImage[IndexPath.row] != UIImage("Demo")
{
cell.ImageView.Image = arrImage[indexPath.row]
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
else
{
cell.ImageView.Image = nil
cell.textLabel!.text = arrLblTxt[indexPath.row]
}
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
return cell
}
Then After you download your Image From FireBase,
Reset your Image array with the new Images downloaded from fireBase
Then reload your tableView. tableView.reloadData

UITextView in a UITableViewCell - Height resizes correctly but cell is cut off

The Problem
I have a UITextView inside of a custom UITableViewCell subclass that is producing some odd behavior.
Here is a gif of the problem I'm seeing (relevant items are colored):
The height resizes correctly, but the full contents of the cell are not shown.
But when I pause execution and print out frames of the following:
Cell
Cell's content view
Text View
the frames are all correct!
Further, when I inspect the view using the view hierarchy debugger, the frames are all correct there too. There is one difference when using the view debugger though, and that is that I'm able to view the contents of the text view.
Here is what I see on the simulator vs in the debugger:
There seems to be an extraneous cell separator at the point where the yellow stops. Other than that, I can't find any sort of indicator of why the cell is not expanding past its original height.
My Code
In the storyboard, the UITextView has top, bottom and trailing constraints to the cell's content view, and a vertical spacing constraint to the UIImageView. The UIImageView has a fixed width and has a leading constraint to the cell's content view. I believe my constraints are set up correctly. Oh yeah, and scrolling is disabled on the UITextView.
As for code, I have a custom protocol that informs the table view controller when the notes field has changed:
protocol AddContactCellDelegate {
func notesViewDidChange(textView: UITextView)
}
Here is the relevant code from my UITableViewCell subclass:
class AddContactCell: UITableViewCell, UITextViewDelegate {
var delegate: AddContactCellDelegate?
func textViewDidChange(textView: UITextView) {
delegate?.notesViewDidChange(textView)
}
}
and from my UITableViewController subclass:
class AddContactViewController: UITableViewController, AddContactCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 60.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = rows[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(row.cellIdentifier, forIndexPath: indexPath) as! AddContactCell
cell.configureForRow(row)
cell.delegate = self
return cell
}
func notesViewDidChange(textView: UITextView) {
tableView.beginUpdates()
tableView.endUpdates()
}
}
Discussion
I've tried adding setNeedsLayout(), in to either the cell, the cell's content view, the tableview, or the textview, in just about every place, to no avail.
I rebuilt the entire view in the storyboard from scratch, same thing.
I tried creating a bare-bones project that has basically only the code above, and the sizing works correctly.
I've ruled out tons of other variables as being the culprit (for example, the tableview is in a container view, but the behavior persists without that).
One final weird point is that sometimes if I scroll the notes cell off the screen, leave the page, and come back again, everything looks as it should. The cell is fully resized and everything is visible. However, the problem resumes as soon as the text view goes to the next line, this time with all the previous text visible but none of the additional text.
If anyone has any ideas on what I might try next, it would be extremely helpful. Thanks!
Thanks to Steve asking for an example project that exhibits the behavior, I was able to figure out what is causing this issue.
To get the rounded corner effect, I had been using a layer mask on my cells. The mask uses the bounds of the cell to calculate its path, so when the cell updated itself to reflect the height of the text view, the mask was still in place and covered up the rest of the cell.
I didn't catch it because the rounding implementation was abstracted away in an extension. Whoops!

UITableViewCell height is not fitted when hiding UIView inside the cell using AutoLayout

I have been struggling this issue for 3 days and still can not figure it out. I do hope anyone here can help me.
Currently, i have an UITableView with customized cell(subclass of UITableViewCell) on it. Within this customized cell, there are many UILabels and all of them are set with Auto Layout (pining to cell content view) properly. By doing so, the cell height could display proper height no matter the UILabel is with long or short text.
The problem is that when i try to set one of the UILabels (the bottom one) to be hidden, the content view is not adjusted height accordingly and so as cell.
What i have down is i add an Hight Constraint from Interface Builder to that bottom label with following.
Priority = 250 (Low)
Constant = 0
Multiplier = 1
This make the constrain with the dotted line. Then, in the Swift file, i put following codes.
override func viewDidLoad() {
super.viewDidLoad()
//Setup TableView
tableView.allowsMultipleSelectionDuringEditing = true
//For tableView cell resize with autolayout
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
}
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! RecordTableViewCell
cell.lbLine.hidden = !cell.lbLine.hidden
if cell.lbLine.hidden != true{
//show
cell.ConstrainHeightForLine.priority = 250
}else{
//not show
cell.ConstrainHeightForLine.priority = 999
}
//tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
return indexPath
}
The tricky thing is that when i call tableView.reloadRowAtIndexPaths(), the cell would display the correct height but with a bug that it has to be trigger by double click (selecting) on the same row rather than one click.
For this, i also try following code inside the willSelectRowAtIndexPath method, but none of them is worked.
cell.contentView.setNeedsDisplay()
cell.contentView.layoutIfNeeded()
cell.contentView.setNeedsUpdateConstraints()
Currently the result is as following (with wrong cell Height):
As showed in the Figure 2, UILabel 6 could be with long text and when i hide this view, the content view is still showing as large as it before hiding.
Please do point me out where i am wrong and i will be appreciated.
I finally change the code
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
to the following
tableView.reloadData()
Then, it work perfectly.
However, i don't really know the exactly reason on it. Hope someone can still comment it out.

UIScrollView position in reusable cell affecting UIScrollView position in other cell

I'm building an iOS app with Swift where a user can scroll through a feed of images up and down (like instagram) and can also scroll left or right on the cell to see more images. Refer to this album(not able to upload images yet) and see F1 for how the UI is laid out.
My problem is when I scroll over to image 1C, the 3rd cell is also scrolling over to 3C. So when I scroll down to the third cell, it's already at 3C. See F2.
Additionally, if I scroll on the 3rd cell to 3B, it also repositions the first cell to 1B. See F3.
I'd like some help in understanding what's going on...
I've created a custom class for the cell with a function to load the images.
(Where I also initialize the imageView frames, scrollView frame and content size, and other properties which I'm not including in the code below.)
class CustomCell: UITableViewCell {
var imageView1: UIImageView = UIImageView()
var imageView2: UIImageView = UIImageView()
var imageView3: UIImageView = UIImageView()
func loadImages(#image1: String, image2: String, image3: String){
imageView1.image = UIImage(named: image1)
imageView2.image = UIImage(named: image2)
imageView3.image = UIImage(named: image3)
}}
and in my View Controller - cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomCell = self.tableView.dequeueReusableCellWithIdentifier("CustomCell") as CustomCell
var (firstImage, secondImage, thirdImage) = images[indexPath.row]
cell.loadImages(image1: firstImage, image2: secondImage, image3: thirdImage)
return cell
}
FYI: This appears to only be happening when the view is the root view controller. It works fine when the view has been presented. ???
Thanks!
The UITableView dequeues reusable cells. That means that your horizontal scrollview in your cell will have the same content offset as the cell he dequeued. So if you scroll to let's say contentOffset.x = 100.0 in cell 1 and than scroll down in your tableview, it might happen that one of the following cells will have its scrollview subview with the same contentOffset.
Just set contentOffset.y = 0 in your cellForRowAtIndexPath.
Or better: Save the cell's selected imageIndex and set an appropriate contentOffset.
Reuseable means exactly that: to speed things up the cells gets reused. But as you are changing view properties these are present when the cell is populated with new values. Set the values back on population in cellF orRiwAtIndexPath or – cleaner IMHO – in cellWillDisplay.

Resources