1

I have a dynamic cells with a label between two images and two buttons underneath the bottom image in each cell. The size of the label depends on the number of lines in the label and that can be anything. I get the text for the label in cellForRowAt indexPath. In my viewDidLoad() I have already set up for a dynamic cell through using this:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 265

The Problem: When the label is more than 1 line, the size of the height of the cell does not change. It only chances to the correct size in two instances: 1) When I refresh the table. 2) when I scroll down where the broken cell is not in the view then when I scroll back up the broken cell is at the correct position.

What I've Tried: After hours of trying to figure this out there were many sources that said 2 of the same thing: 1) Make sure all of you items have constraints for all of its sides (I did that, same problem happened). 2) the only other one said to use the UITableViewAutomaticDimension and estimatedRowHeight (I have).

How do I fix this or what did I miss?

EDIT: I call the label's text in the cellForRowAt indexPath after getting which type of cell it is (I have 3 and they all do different things). Here's the code:

    func loadNews() {        
            //start finding followers
            let followQuery = PFQuery(className: "Follow")
            followQuery.whereKey("follower", equalTo: PFUser.current()?.objectId! ?? String())
            followQuery.findObjectsInBackground { (objects, error) in
                if error == nil {

                    self.followArray.removeAll(keepingCapacity: false)

                    //find users we are following
                    for object in objects!{
                        self.followArray.append(object.object(forKey: "following") as! String)
                    }
                    self.followArray.append(PFUser.current()?.objectId! ?? String()) //so we can see our own post


                    //getting related news post
                    let newsQuery = PFQuery(className: "News")
                    newsQuery.whereKey("user", containedIn: self.followArray) //find this info from who we're following
                    newsQuery.limit = 30
                    newsQuery.addDescendingOrder("createdAt")
                    newsQuery.findObjectsInBackground(block: { (objects, error) in
                        if error == nil {     
                            //clean up
                            self.newsTypeArray.removeAll(keepingCapacity: false)
                            self.animalArray.removeAll(keepingCapacity: false)
                            self.newsDateArray.removeAll(keepingCapacity: false)

                            for object in objects! {                            
                                self.newsTypeArray.append(object.value(forKey: "type") as! String) //get what type (animal / human / elements)

                                self.animalArray.append(object.value(forKey: "id") as! String) //get the object ID that corresponds to different class with its info
                                self.newsDateArray.append(object.createdAt) //get when posted
                            }
                            self.tableView.reloadData()
                        } else {
                            print(error?.localizedDescription ?? String())
                        }
                    })
                } else {
                    print(error?.localizedDescription ?? String())
                }
            }   
        }



        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let type = newsTypeArray[indexPath.row]

    if type == "element" {
      //fills cell just like animal one
    } else if type == "human" {
      //fills cell just like animal one

    } else { //its an animal cell

                            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! AnimalCell
                            let query = query(className: "Animals")
                            query.whereKey("objectId", equalTo: animalArray[indexPath.row])
                            query.limit = 1
                            query.findObjectsInBackground(block: { (objects, error) in
                                if error == nil {                            
                                    for object in objects! {

                                        let caption = (object.object(forKey: "caption") as! String)
                                        cell.captionLabel.text = caption

                                    }                               
                                } else {
                                    print(error?.localizedDescription ?? String())
                                }
                            })
           return cell
        }
    }
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
fphelp
  • 1,544
  • 1
  • 15
  • 34
  • Did you change the label numberOfLines as 0? – Imad Ali Aug 20 '17 at 04:34
  • The labels number of lines is at 0 @Imad Ali – fphelp Aug 20 '17 at 04:35
  • show your custom cell – Imad Ali Aug 20 '17 at 04:36
  • Take ref from [here](https://stackoverflow.com/questions/42416733/self-sizing-tableview-cells-based-on-two-subviews-height/42417325#42417325), if you still have doubt you can ask. – dahiya_boy Aug 20 '17 at 05:13
  • @fphelp can you add your cellForRowAt method? Specifically if you're doing any sort of dispatch or network calls to get the label text? – Dave Wood Aug 20 '17 at 05:31
  • added it the `cellForRowAt` code – fphelp Aug 20 '17 at 05:51
  • @fphelp **Don't do your object fetching in cellForRowAt**. This will be called for each cell, and every time you scroll and a cell comes to the view, please include the code where you create the animalArray so I can write a proper answer for you. – Swifty Aug 20 '17 at 06:39
  • How I am doing this is a little complicated. I have 3 different cells that do different things from each other. We are only discussing the cell I am having trouble with. I get which type of cell and their corresponding objectIds' of other items in a different class then I check if this type equals one of the cells such as "animals" (the cell with the label problem) then it will show me that cell then I use the objectId to go into the animal class to get the data. **Will input the code for the other cells and the loading of their types so you can see what I am doing** – fphelp Aug 20 '17 at 17:47
  • added the code @Sam_M – fphelp Aug 20 '17 at 17:57
  • I cleared up my code and added it to a new question: https://stackoverflow.com/questions/45785798/loading-3-different-information-to-3-different-types-of-cells – fphelp Aug 20 '17 at 18:59

2 Answers2

1

1. Check that you have used proper constraints in the cell.

2. Implement these UITableViewDelegate methods:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
    return UITableViewAutomaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat
{
    return 265
}

Remove these 2 lines:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 265

Screenshots:

1. View Hierarchy: Image1 -> Label -> Image2 -> Button1 -> Button2

enter image description here

2. Output

enter image description here

Still if it don't work:

Provide preferredMaxLayoutWidth of UILabel in your custom UITableViewCell, i.e.

override func awakeFromNib()
{
    super.awakeFromNib()
    self.label.preferredMaxLayoutWidth = UIScreen.main.bounds.width //Provide here the calculated width of your label
}

UITableViewDataSource Methods:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    return 2
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableCell
    if indexPath.row == 0
    {
        cell.label.text = "When the label is more than 1 line, the size of the height of the cell does not change."
    }
    else
    {
        cell.label.text = "When the label is more than 1 line, the size of the height of the cell does not change. It only chances to the correct size in two instances: 1) When I refresh the table. 2) when I scroll down where the broken cell is not in the view then when I scroll back up the broken cell is at the correct position."
    }
    return cell
}
PGDev
  • 23,751
  • 6
  • 34
  • 88
  • Sadly got the same results – fphelp Aug 20 '17 at 04:57
  • Does the fact that I get the text for the cell's label inside the `cellForRowAt indexPath` have anything to do with the problem? – fphelp Aug 20 '17 at 05:17
  • No, its perfectly fine. I will add the code for UITableViewDataSource too so that you can have a better idea. – PGDev Aug 20 '17 at 05:19
  • Which iOS version are you using? – PGDev Aug 20 '17 at 05:20
  • I am running iOS 10.3 on the Xcode simulator and iOS 10.3 on an iPhone 6s. And am receiving the same results for both devices – fphelp Aug 20 '17 at 05:24
  • I have added the project here: https://github.com/pgpt10/Tester Check it. This might resolve your issue – PGDev Aug 20 '17 at 05:25
  • With the `preferredMaxLayoutWidth` how to do I calculate my label's width? As of now I have the `Leading Space to: Superview Equals: 15` and `Trailing Space to: Superview Equals: 5`. So it adjust for any device – fphelp Aug 20 '17 at 05:27
  • Try this: self.label.preferredMaxLayoutWidth = self.bounds.width - 20 inside your custom UITableViewCell. – PGDev Aug 20 '17 at 05:29
  • I added that to my code and it still doesn't work for some reason – fphelp Aug 20 '17 at 05:38
  • Show your custom cell and its constraints. If you don't show your code how will anyone be able to help you? – PGDev Aug 20 '17 at 05:39
  • Added the `cellForRowAt` custom cell to the original question – fphelp Aug 20 '17 at 05:56
  • I cleared up my code and added it to a new question: https://stackoverflow.com/questions/45785798/loading-3-different-information-to-3-different-types-of-cells – fphelp Aug 20 '17 at 18:59
0

This sounds like you're setting the content of the label after the initial table data has loaded (possibly using a background method to fetch the value from the network?).

If that's the case, you can reload just that one cell in the table using the UITableView.reloadRows(at:with:) method. See: https://developer.apple.com/documentation/uikit/uitableview/1614935-reloadrows

e.g.: tableView.reloadRows(at: [indexPathToCell], with: .automatic)

Dave Wood
  • 13,143
  • 2
  • 59
  • 67
  • I implemented the `tableView.reloadRows` in `cellForRowAt` after the label's text was gathered and it started to reload all of my rows for some reason. I am using parse and I just query for the label's text and set `label.text = \\what I get from server` nothing special – fphelp Aug 20 '17 at 05:38
  • That's your issue then. Parse is doing a network call and you're setting the value of the label at some point after the `cellForRowAt` method has returned. So you need to trigger the reload after that. Add `DispatchQueue.main.async { tableView.reloadRows(at: [indexPathToCell], with: .none) }` after the `label.text = ...` line. – Dave Wood Aug 20 '17 at 05:42
  • @fphelp Note: depending on how often that label text changes, you'd probably be better to load the value via parse at some point before the view appears, and cache it. Shouldn't need to use Parse every time the cell appears on screen, unless the data can change frequently. – Dave Wood Aug 20 '17 at 05:50
  • I tried the `DispatchQueue.main.async { tableView.reloadRows(at: [indexPathToCell], with: .none) }` and it reloaded the cells over and over again. I used `let index = IndexPath(row: indexPath.row, section: 0)` to replace your "indexPathToCell" – fphelp Aug 20 '17 at 05:53
  • I will like to load the text label again, but it's quite complicated because I have 2 other cells that can go in the tableview that do different things than the one I am having troubles with. So I load what type and their corresponding objectId's that are from different classes into arrays then in each cell I load their content. **How can I work around this if there is a possible solution?** – fphelp Aug 20 '17 at 05:56
  • Right that makes sense. So you'd want to change it to something like: `let caption = (object.object(forKey: "caption") as! String) if caption != cell.captionLabel.text { DispatchQueue.main.async { cell.captionLabel.text = caption tableView.reloadRows(at: [indexPathToCell], with: .none) } }` – Dave Wood Aug 20 '17 at 05:58
  • Hmm, code in comments doesn't format correctly, but you see the idea. Only reload the cell if the label text changes. As I say above though, you'd be much better off if you move that whole query out of the `cellForRowAt` method. You want to keep your data fetching out of your view controllers, but at a minimum, at least move it to the `viewDidLoad` method or something. – Dave Wood Aug 20 '17 at 06:01
  • In order for me to move it out of the `cellForRow` I need a clear way to fill up the cells because I have 3 different cells all doing three different things. I get which type of cell and their corresponding objectIds' of other items in a different class then I check if this type equals one of the cells such as "animals" (the cell with the label problem) then it will show me that cell then I use the objectId to go into the animal class to get the data. **How can I do this outside of the `cellForRowAt`?** – fphelp Aug 20 '17 at 06:10
  • The answer is probably bigger than can fit in a comment. But the short version is that you need to create a local representation of your data (normally in some sort of DataController class). That would fetch your info from Parse before it's needed in your view, and then you'll know what data to put in the table without needing background methods while the table is reloading. – Dave Wood Aug 20 '17 at 06:19
  • I am a novice coder and will really like to figure this problem out. Should I open up a new question about switching this out of the cellForRowAt? – fphelp Aug 20 '17 at 06:32
  • That might be a good idea. What you want to look into is called MVC (Model-View-Controller) architecture. It lets you separate the data, views, and logic in your app. Apple's docs help a bit: https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html But I'd also read: https://www.smashingmagazine.com/2016/05/better-architecture-for-ios-apps-model-view-controller-pattern/ and https://yalantis.com/blog/lightweight-ios-view-controllers-separate-data-sources-guided-mvc/ – Dave Wood Aug 20 '17 at 06:41
  • Thank you! I cleared up my code and added it to a new question: https://stackoverflow.com/questions/45785798/loading-3-different-information-to-3-different-types-of-cells – fphelp Aug 20 '17 at 18:59
  • @fphelp Looks good. Don't forget to mark this one as solved (and up vote helpful answers). – Dave Wood Aug 20 '17 at 19:02
  • no one has answered my question and I am in desperate need to get this fixed. Can you please help me? – fphelp Aug 21 '17 at 03:01
  • @fphelp Sorry, I'm not going to be at a computer for a few days. Did you read those articles? – Dave Wood Aug 21 '17 at 06:03
  • I did but I am still terribly confused – fphelp Aug 21 '17 at 06:47