Search code examples
iosswiftuitableviewnslayoutconstraintpure-layout

Swift trailing constraint not working as expected in tableview


I have UITableView extension defined with a function to place a default view on top of a tableView. For some reason the following lines of code do not work as expected.

1) Code which did not provide the right solution

func addNoDataView() -> UIView {
        let containerView = UIView(forAutoLayout: ())
        self.addSubview(containerView)
        containerView.backgroundColor = UIColor.black
        containerView.autoPinEdge(.leading, to: .leading, of: self, withOffset: 8)
        containerView.autoPinEdge(.trailing, to: .trailing, of: self, withOffset: 8)
        containerView.autoPinEdge(.top, to: .top, of: self, withOffset: 40)
        containerView.autoSetDimension(.height, toSize: 150)
        return containerView
}

In this case expected output would be a black view with 40 pixels from the top the tableView with leading and trailing margins. But when I ran I could not even spot the view.

2) Code which provided the right solution

func addNoDataView() -> UIView {
            let containerView = UIView(forAutoLayout: ())
            containerView.backgroundColor = UIColor.black
            self.addSubview(containerView)
            containerView.autoPinEdge(.leading, to: .leading, of: self, withOffset: 8)
            containerView.autoPinEdge(.trailing, to: .trailing, of: self, withOffset: self.frame.width - 8)
            containerView.autoPinEdge(.top, to: .top, of: self, withOffset: 40)
            containerView.autoSetDimension(.height, toSize: 150)
            return containerView
    }

In this case, I got the expected solution. i.e a black view 40 from the top and with 8 as margins from trailing and leading edges from tableView with 150 as height.

I have one more function which is almost on the same lines as above but with very less difference (I think).

func addNoDataLabelToTableView() -> UILabel {
        let label = UILabel(forAutoLayout: ())
        self.addSubview(label)
        label.textColor = UIColor.darkGray
        label.textAlignment = .center
        label.text = "No Data Available"
        label.autoPinEdge(.trailing, to: .trailing, of: self, withOffset: 8)
        label.autoPinEdge(.leading, to: .leading, of: self, withOffset: 8)
        label.autoSetDimension(.height, toSize: 20)
        label.autoCenterInSuperview()
        return label
    }

This function adds a label to the center of the tableView and works perfectly as expected.

tableView is pinned to its superview edges on all 4 sides. No issues with this.

However, I can't get my head around as to why did Case 1 not work. Can someone please explain the difference between these two lines?

1) containerView.autoPinEdge(.trailing, to: .trailing, of: self, withOffset: 8) which did not work

2) containerView.autoPinEdge(.trailing, to: .trailing, of: self, withOffset: self.frame.width - 8) which did work surprisingly.


Solution

  • You are adding .containerView to a UITableView ... which inherits from UIScrollView.

    Scroll views have a slightly different relationship with constraints, as they also determine the .contentSize for the scroll view.

    If you really want to add a subview to a table view (better to put it on top, rather than in, but anyway...), you must give that subview a Width and Height (unless it's something like a UILabel, which has an intrinsic size).

    So, you'll want to change your addNoDataView() func to:

    func addNoDataView() -> UIView {
        let containerView = UIView(forAutoLayout: ())
    
        self.addSubview(containerView)
        containerView.backgroundColor = UIColor.black
    
        // inset view 8-pts from left
        containerView.autoPinEdge(.left, to: .left, of: self, withOffset: 8)
    
        // inset view 40-pts from top
        containerView.autoPinEdge(.top, to: .top, of: self, withOffset: 40)
    
        // set width to width of self (a scrollview) -16 (inset 8 from left and 8 from right)
        containerView.autoMatch(.width, to: .width, of: self, withOffset: -16.0)
    
        // set explicit height to 150
        containerView.autoSetDimension(.height, toSize: 150)
    
        return containerView
    }