zaitsman
zaitsman

Reputation: 9509

UITableView - Have an image span multiple UITableViewCells with scrolling

I have got a rather standard tableview for ipad where i place two rows of data by adding subviews to UITableViewCell.contentView.

I want to add an image to the right side of this table, and have it span multiple rows, like so (see image on the right):

Screenshot

Now the problem is that when i scroll my table, the image gets cut off once this cell is no longer fully in the view, looks like this:

Screenshot2

The way i add this image is

// priority 999 helps save a lot of headache on troubleshooting auto layout warnings for code created views
extension NSLayoutConstraint {
    func activateWithPriority(_ val: Float) {
        self.priority = UILayoutPriority(val)
        self.isActive = true
    }

    func activateWithBasicPriority() {
        self.priority = UILayoutPriority(999)
        self.isActive = true
    }
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) ?? UITableViewCell()

        if !cell.contentView.subviews.isEmpty {
            for sv in cell.contentView.subviews {
                sv.removeFromSuperview()
            }
        }

        if indexPath.row == 1 {
            let image = UIImageView(image: UIImage(named: "product_placeholder"))
            image.translatesAutoresizingMaskIntoConstraints = false
            cell.contentView.addSubview(image)

            image.widthAnchor.constraint(equalToConstant: 200).activateWithBasicPriority()
            image.heightAnchor.constraint(equalToConstant: 200).activateWithBasicPriority()
            image.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor, constant: -10).activateWithBasicPriority()
            image.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 10).activateWithBasicPriority()
        }

// other stuff here

return cell

}

I could just stick that image outside of my tableview, to overlay it, but the problem is that this would make it non-scrollable, and i want it to scroll along with my table.

Also, i know that i can probably put all the other rows to the left of image into one row with like a massive cell, but I want to avoid that, if possible.

Upvotes: 1

Views: 252

Answers (2)

DonMag
DonMag

Reputation: 77568

You can do this by adding your imageView as a subview of the tableView. It will then scroll along with the cells.

Setting its constraints is a little tricky, because you need to make it horizontally relative to the trailing edge of the tableView, and vertically relative to the desired cell.

However, a tableView's trailing constraint doesn't quite work for its subviews, and you don't know the location of the cell until it's been rendered.

So, couple things:

  1. we'll embed the tableView in a "container" UIView, and use that view's trailing constraint for the trailing edge of the image view.

  2. we'll create a Top Constraint variable and set its .constant in viewDidAppear().

Here is an example. It's all code (no @IBOutlets) so just add a new, empty view controller and assign its class to TableWithSubViewViewController:

//  TableWithSubViewViewController.swift
//  Created by Don Mag on 5/10/19

class SimpleOneLabelCell: UITableViewCell {

    // very simple one-label tableView cell

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.numberOfLines = 0
        return v
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        contentView.addSubview(theLabel)

        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
            theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
            theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),

            // trailing constraint set to -150, to allow room for our 120x120 imageview (with padding)
            theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -150.0),
            ])

    }

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

}

class TableWithSubViewViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let theContainerView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let theTableView: UITableView = {
        let v = UITableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let overlayImageView: UIImageView = {
        let v = UIImageView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    // top constraint for the image view
    var overlayViewTopConstraint: NSLayoutConstraint = NSLayoutConstraint()

    let reuseID = "SimpleOneLabelCell"

    override func viewDidLoad() {
        super.viewDidLoad()

        theTableView.dataSource = self
        theTableView.delegate = self

        theTableView.register(SimpleOneLabelCell.self, forCellReuseIdentifier: reuseID)

        // set the imageView's image
        if let img = UIImage(named: "overlay") {
            overlayImageView.image = img
        }

        // add the views
        view.addSubview(theContainerView)
        theContainerView.addSubview(theTableView)
        theTableView.addSubview(overlayImageView)

        // constrain the top of the imageView above the top of the tableView
        //   we'll change the constant in viewDidAppear()
        overlayViewTopConstraint = overlayImageView.topAnchor.constraint(equalTo: theTableView.topAnchor, constant: -120.0)

        NSLayoutConstraint.activate([

            // constrain containerView to all 4 sides (safe-area)
            theContainerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            theContainerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            theContainerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            theContainerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),

            // constrain tableView to all 4 sides of constainerView
            theTableView.topAnchor.constraint(equalTo: theContainerView.topAnchor),
            theTableView.bottomAnchor.constraint(equalTo: theContainerView.bottomAnchor),
            theTableView.leadingAnchor.constraint(equalTo: theContainerView.leadingAnchor),
            theTableView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor),

            // constrain the imageView using the Top Constraint we already created, plus
            overlayViewTopConstraint,

            // 20-pts from the right (trailing) edge of the Container View, plus
            overlayImageView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor, constant: -20.0),

            // width and height at 120-pt constants
            overlayImageView.widthAnchor.constraint(equalToConstant: 120.0),
            overlayImageView.heightAnchor.constraint(equalToConstant: 120.0),
            ])

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // at this point we have the table and its initial cells laid out, so we can use it for
        //  vertical positioning of the image view
        if let c = theTableView.cellForRow(at: IndexPath(row: 1, section: 0)) as? SimpleOneLabelCell {

            // get the frame of the cell on row 1
            let f = c.frame

            // add half-cell-height to the origin.y
            var y = f.origin.y + (f.size.height * 0.5)

            // get the frame of the image view
            let r = overlayImageView.frame

            // subtract half-imageView-height
            y -= (r.height * 0.5)

            // update imageView's top constraint
            overlayViewTopConstraint.constant = y

        }

    }

    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! SimpleOneLabelCell

        cell.theLabel.text = "\(indexPath)\nLine 2\nLine 3"

        return cell
    }

}

The result:

enter image description here

enter image description here

(using this image I clipped out of your image):

enter image description here

Upvotes: 2

Ajay saini
Ajay saini

Reputation: 2470

You can do one thing. put imageView inside scrollView side by side the tableView. and set your scrollView's contentOffset equal to tableView's contentOffset and vice versa.

Upvotes: 1

Related Questions