I have a UIView which is reusable via a nib/xib-file. I want to load this and fill a UITableViewCell which is to be used in a self-resizing UITableView. All with auto-layout.
Most works good, but It seems as the loaded UIView uses the added constraints around it to shrink the UITableViewCell's contentView. This is good for the height, but I don't want this for the width.
Ignore the grey cell below, this is just a selected cell.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cellId = "RowView0002"
var cell = tableView.dequeueReusableCell(withIdentifier: cellId)
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
let subView = RowView(frame: cell!.frame)
cell!.contentView.attachViewWithConstraints(subView)
let _ = subView.viewLoadedFromNibAttached(name: cellId)
}
return cell!
}
override public func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 40.0
tableView.rowHeight = UITableViewAutomaticDimension
}
extension UIView
{
public func attachViewWithConstraints(_ view:UIView)
{
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.layoutAttachAll(to: self)
}
@discardableResult
public func viewLoadedFromNibAttached<T : UIView>(name:String) -> T? {
guard let view = Bundle.main.loadNibNamed(name, owner: self, options: nil)?[0] as? T else {
return nil
}
attachViewWithConstraints(view)
return view
}
public func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
childView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
childView.addConstraints(constraints)
}
In RowView0002.xib I have set the rootviews background to red, added a UILabel with 4 constraints to it's sides with some margin as you can see. I both tried to set the rootView to the class RowView as well as it's File's Owner. Both "works".
Any idea how to get the contentView to match the UITableView?
*Edit 1: The green is the background of the UILabel. The red is the background of the nib-file. After running the app the View heirarcy is: UITableViewCell > ContentView > RowView > NibFileView (red) > UILabel (green)
Inspecting the view hierarchy shows that all constraints is setup as expected. However the UITableViewContentView have constraints that match the total size seen (wrong):
self.width = 156.5 @ 1000
I solved it by also adding a width constraint matching the tableViews width. This is code from CustomTableViewCell:
UIViewExtension:
UITableViewCellExtension:
It looks like the problem is that the cell created with
UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: cellId)
has no information about the width of the table view, so it is sizing itself based on the width of the label. Apparently the table view / cell don't force the cell's content view to take its width.You probably want to rework some of this cell-handling code.
If you want to load your cell from a xib, you can skip everything with constraints. Just implement:
Very important: The first top level item in the .xib file must be a UITableViewCell. Nibs are UIViews by default, delete the view in IB and drag a UITableViewCell from the object library in the lower-right of IB. Then if necessary set its subclass to a UITableViewCell subclass that you created. (You might also need to set the reuseIdentifier in IB.)
Then in
tableView(_:cellForRowAt IndexPath:)
:You probably will want to put that "RowView0002" in a constant somewhere.
If the "RowView0002" and the
RowView
class both need to be views, you should probably create a subclass ofUITableViewCell
. Override justinit(style:resueIdentifier:) and after calling
super` add your subviews in the code above. Hope this helps!A full implementation of layoutAttachAll is below.
Some usage examples first:
Note: myView must be added as a subview before attaching to superview using these methods. Also, all participating views must be set with translatesAutoresizingMaskIntoConstraints = false.
The full implementation: