Wana_B3_Nerd
Wana_B3_Nerd

Reputation: 643

TableView Cells not formatting correctly unless image is cached

I am having an issue with my tableview, where the cells don't orient correctly before an image is cached, and only once I return back to a page and my image is cached do they orient correctly. Here is an example of what I am talking about, the first image is when I first go to the page, and I have a function which stores images in an imagecache, and the second picture is when I return to that page after navigating somewhere else, and the images are cached.

Incorrectly Formatted:

enter image description here

Correctly Formatted:

enter image description here

I know it is a very subtle difference with the way it looks but I'd love to get that figured out and understand why this is happening

My cells constant height is 85, and here is the relevant code:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Indexing part I left out dw about this



        let cell = discountTableView.dequeueReusableCell(withIdentifier: self.discountCellId, for: indexPath) as! DiscountCell
        cell.textLabel?.text = discount.retailerName
        cell.detailTextLabel?.text = discount.matchedFirstName + " " + discount.matchedLastName
        cell.profileImageView.image = UIImage.gif(name: "imageLoading")!
        grabImageWithCache(discount.retailerImageLink) { (profilePic) in
            cell.profileImageView.image = profilePic
        }
        //Adding second Image
        cell.matchImageView.image = UIImage.gif(name: "imageLoading")!
        grabImageWithCache(discount.matchedProfileImage) { (matchProfilepic) in
            cell.matchImageView.image = matchProfilepic
        }
        //declare no selection style for cell (avoid gray highlight)
        cell.selectionStyle = .none
        //format the cell's curvature
        cell.layer.cornerRadius = 38
        return cell
    }

**I am curious as to why the height of the cells seem to change when the image is cached, because the height is set to a constant number, so I have no idea why it is changing. When I print the height of the cells it says it is 85 both times as well...

UITableViewCell Class:

class DiscountCell: UITableViewCell {

    override func layoutSubviews() {
        super.layoutSubviews()
        //vertical spacing between cells
        let padding = UIEdgeInsets(top: 0, left: 0, bottom: 7, right: 0)
        bounds = bounds.inset(by: padding)
        
        textLabel?.frame = CGRect(x: 120, y: textLabel!.frame.origin.y-10, width: textLabel!.frame.width, height: textLabel!.frame.height)
        detailTextLabel?.frame = CGRect(x: 120, y: detailTextLabel!.frame.origin.y, width: detailTextLabel!.frame.width, height: detailTextLabel!.frame.height)
        //spacing between cells
        let margins = UIEdgeInsets(top: 0, left: 0, bottom: 85, right: 0)
        contentView.frame = contentView.frame.inset(by: margins)

    }
    
    //sets a placeholder imageview
    let profileImageView: UIImageView = {
        let imageView = UIImageView ()
        imageView.image = UIImage(named: "failed")
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.cornerRadius = 35
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.borderWidth = 2
        imageView.layer.borderColor = #colorLiteral(red: 0.9725490196, green: 0.9725490196, blue: 0.9725490196, alpha: 1)
        return imageView
    }()
    //PlaceHolder imageview for match
    let matchImageView:UIImageView = {
        let imageView = UIImageView ()
        imageView.image = UIImage(named: "failed")
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.cornerRadius = 35
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
        
        addSubview(matchImageView)
        addSubview(profileImageView)
        //Setting Profile Pic anchors
        profileImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 5).isActive = true
        profileImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        profileImageView.widthAnchor.constraint(equalToConstant: 70).isActive = true
        profileImageView.heightAnchor.constraint(equalToConstant: 70).isActive = true
        //Setting Match Anchors
        matchImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        matchImageView.widthAnchor.constraint(equalToConstant: 70).isActive = true
        matchImageView.heightAnchor.constraint(equalToConstant: 70).isActive = true
        matchImageView.leftAnchor.constraint(equalTo: profileImageView.leftAnchor,constant: 35).isActive = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

I believe I have narrowed in on the issue, I noticed that the spacing between the cells was inconsistent both times, so I think the issue is with these lines of code

        //vertical spacing between cells
        let padding = UIEdgeInsets(top: 0, left: 0, bottom: 7, right: 0)
        bounds = bounds.inset(by: padding)

I am not sure what to do differently, as I watched tutorials saying to change the contentviews insets but when I do, it has no effect, and I see things saying iOS 11 changed the way you do this, but haven't been able to find how to actually go about fixing this.

Upvotes: 2

Views: 277

Answers (2)

DonMag
DonMag

Reputation: 77431

Couple notes...

Not a good idea to modify a cell's bounds or its contentView. UIKit uses those for a lot of things (such as adjusting contents when implementing table editing).

Cell subviews should be added to the cell's contentView, not to the cell itself. Again, due to how table views rely on that hierarchy.

You can use UIView and UIImageView subclasses to handle the "rounding" for you. For example:

class RoundImageView: UIImageView {
    override func layoutSubviews() {
        layer.cornerRadius = bounds.size.height * 0.5
    }
}
class RoundEndView: UIView {
    override func layoutSubviews() {
        layer.cornerRadius = bounds.size.height * 0.5
    }
}

Here is a complete implementation:

class RoundImageView: UIImageView {
    override func layoutSubviews() {
        layer.cornerRadius = bounds.size.height * 0.5
    }
}
class RoundEndView: UIView {
    override func layoutSubviews() {
        layer.cornerRadius = bounds.size.height * 0.5
    }
}

class DiscountCell: UITableViewCell {
    
    // "Holder" view... contains all other UI elements
    // use RoundEndView so it handles the corner radius by itself
    let holderView: RoundEndView = {
        let v = RoundEndView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .white
        return v
    }()
    
    //sets a placeholder imageview
    // use RoundImageView so it handles the corner radius by itself
    let profileImageView: RoundImageView = {
        let imageView = RoundImageView()
        imageView.image = UIImage(named: "failed")
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.borderWidth = 2
        imageView.layer.borderColor = #colorLiteral(red: 0.9725490196, green: 0.9725490196, blue: 0.9725490196, alpha: 1)
        return imageView
    }()
    
    //PlaceHolder imageview for match
    // use RoundImageView so it handles the corner radius by itself
    let matchImageView: RoundImageView = {
        let imageView = RoundImageView()
        imageView.image = UIImage(named: "failed")
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.layer.masksToBounds = true
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        return imageView
    }()
    
    let mainLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = UIFont.systemFont(ofSize: 17.0, weight: .regular)
        return v
    }()
    
    let subLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = UIFont.systemFont(ofSize: 14.0, weight: .regular)
        return v
    }()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    func commonInit() -> Void {
        
        backgroundColor = .clear
        
        contentView.addSubview(holderView)
        
        holderView.addSubview(matchImageView)
        holderView.addSubview(profileImageView)
        holderView.addSubview(mainLabel)
        holderView.addSubview(subLabel)
        
        NSLayoutConstraint.activate([

            // holder view constraints Top 5 pts from contentView Top
            holderView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5.0),
            // Leading 12 pts from contentView Leading
            holderView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 12.0),
            // Trailing 5 pts from contentView Trailing
            holderView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5.0),
            // 5 pts from contentView Bottom (use lessThanOrEqualTo to avoid auto-layout warnings)
            holderView.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -5.0),

            //Setting Profile Pic anchors - Top and Leading 5 pts from Top and Leading of Holder view
            profileImageView.topAnchor.constraint(equalTo: holderView.topAnchor, constant: 5),
            profileImageView.leadingAnchor.constraint(equalTo: holderView.leadingAnchor, constant: 5),
            // Botom 5 pts from contentView Bottom - this will determine the height of the Holder view
            profileImageView.bottomAnchor.constraint(equalTo: holderView.bottomAnchor, constant: -5),
            // width 70 pts, height equal to width (keeps it "round")
            profileImageView.widthAnchor.constraint(equalToConstant: 70.0),
            profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
        
            //Setting Match Pic Anchors - 35 pts from Profile Leading, centerY to Profile
            matchImageView.leadingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: 35),
            matchImageView.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor, constant: 0.0),
            // same width and height as Profile
            matchImageView.widthAnchor.constraint(equalTo: profileImageView.widthAnchor),
            matchImageView.heightAnchor.constraint(equalTo: profileImageView.heightAnchor),
            
            //Setting Main Label Anchors - Top equal to Top of Match image
            mainLabel.topAnchor.constraint(equalTo: matchImageView.topAnchor, constant: 0.0),
            // Leading 12 pts from Match image Trailing
            mainLabel.leadingAnchor.constraint(equalTo: matchImageView.trailingAnchor, constant: 12.0),
            // prevent Trailing from going past holder view Trailing
            mainLabel.trailingAnchor.constraint(equalTo: holderView.trailingAnchor, constant: -16.0),
            
            //Setting Sub Label Anchors - Top 8 pts from Main label Bottom
            subLabel.topAnchor.constraint(equalTo: mainLabel.bottomAnchor, constant: 8.0),
            // Leading and Trailing equal to Main label Leading / Trailing
            subLabel.leadingAnchor.constraint(equalTo: mainLabel.leadingAnchor, constant: 0.0),
            subLabel.trailingAnchor.constraint(equalTo: mainLabel.trailingAnchor, constant: 0.0),
            
        ])
        
    }
}

// example Discount object struct
struct Discount {
    var retailerName: String = ""
    var matchedFirstName: String = ""
    var matchedLastName: String = ""
    var matchedProfileImage: String = ""
    var retailerImageLink: String = ""
}

class DiscountViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let discountTableView = UITableView()
    
    let discountCellId = "dcID"
    
    var myData: [Discount] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // a little sample data
        var d = Discount(retailerName: "Cup & Cone", matchedFirstName: "Kara", matchedLastName: "Tomlinson", matchedProfileImage: "pro1", retailerImageLink: "")
        myData.append(d)
        d = Discount(retailerName: "Cup & Cone", matchedFirstName: "Sophie", matchedLastName: "Turner", matchedProfileImage: "pro2", retailerImageLink: "")
        myData.append(d)
        d = Discount(retailerName: "Another Retailer", matchedFirstName: "WanaB3", matchedLastName: "Nerd", matchedProfileImage: "pro3", retailerImageLink: "")
        myData.append(d)

        
        discountTableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(discountTableView)
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            discountTableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            discountTableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            discountTableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            discountTableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
        ])
        
        view.backgroundColor = UIColor(red: 1.0, green: 0.9, blue: 0.7, alpha: 1.0)
        discountTableView.backgroundColor = UIColor(red: 0.66, green: 0.66, blue: 0.9, alpha: 1.0)
        discountTableView.separatorStyle = .none

        discountTableView.delegate = self
        discountTableView.dataSource = self
        
        discountTableView.register(DiscountCell.self, forCellReuseIdentifier: discountCellId)
        
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return myData.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //Indexing part I left out dw about this
        
        let discount = myData[indexPath.row]
        
        let cell = discountTableView.dequeueReusableCell(withIdentifier: self.discountCellId, for: indexPath) as! DiscountCell
        
        cell.mainLabel.text = discount.retailerName
        cell.subLabel.text = discount.matchedFirstName + " " + discount.matchedLastName

        // add first image
        if let img = UIImage(named: "imageLoading") {
            cell.profileImageView.image = img
        }
        // use your custom image funcs
        //cell.profileImageView.image = UIImage.gif(name: "imageLoading")!
        //grabImageWithCache(discount.retailerImageLink) { (profilePic) in
        //  cell.profileImageView.image = profilePic
        //}

        //Adding second Image
        if let img = UIImage(named: discount.matchedProfileImage) {
            cell.matchImageView.image = img
        }
        // use your custom image funcs
        //cell.matchImageView.image = UIImage.gif(name: "imageLoading")!
        //grabImageWithCache(discount.matchedProfileImage) { (matchProfilepic) in
        //  cell.matchImageView.image = matchProfilepic
        //}
        
        //declare no selection style for cell (avoid gray highlight)
        cell.selectionStyle = .none

        return cell
    }
    
}

Results:

enter image description here

enter image description here

Upvotes: 4

Tad
Tad

Reputation: 947

Your cells are a pill shape, which means you can achieve the same effect by doing cell.layer.cornerRadius = cell.frame.height / 2. Next you should use top and bottom constraints for your profileImageView so it forces the cell to have a certain padding instead of trying to create the padding yourself. The problem is that your cell height is changing a little bit when the images are cached, no idea what could be causing that.

Upvotes: 2

Related Questions