Reputation: 9509
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):
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:
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
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:
we'll embed the tableView in a "container" UIView
, and use that view's trailing constraint for the trailing edge of the image view.
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:
(using this image I clipped out of your image):
Upvotes: 2
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