1

I have a table and a button in a UIScrollView, and I want the following design:

  • Button is at least 40 pts below the last row of table
  • Button is always 83 pts above the end of the view

With the below constraints I have managed to have the button be always 40 pts below last row of table, and 83 pts above end of view only if table is long enough. It looks to me that the priority for bottomConstraint isn't properly overriding the constraint of topConstraint. I have set the scroll view to encompass the entire screen.

/* - Sign Out button is 40 pts tall - */
let heightConstraint = NSLayoutConstraint(item: signOutBtn, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 41)

/* - Sign Out button is ALWAYS 83 pts above bottom of screen, when visible - */
let bottomConstraint = NSLayoutConstraint(item: signOutBtn, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1, constant: -83)
bottomConstraint.priority = UILayoutPriority.required

/* - Sign Out button is AT LEAST 40 pts below last row of table - */
let topConstraint = NSLayoutConstraint(item: signOutBtn, attribute: .top, relatedBy: .greaterThanOrEqual, toItem: tableView, attribute: .bottom, multiplier: 1, constant: 40)
topConstraint.priority = UILayoutPriority.defaultLow

/* - Sign Out button stretches across the screen - */
let leadingConstraint = NSLayoutConstraint(item: signOutBtn, attribute: .leading, relatedBy: .equal, toItem: scrollView, attribute: .leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: signOutBtn, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1, constant: 0)

scrollView.addConstraints([heightConstraint, bottomConstraint, leadingConstraint, trailingConstraint, topConstraint])

Screenshots:
(bad - this is what I have accomplished now)
enter image description here

(good)
enter image description here enter image description here

The sign out button doesn't appear if the table is too long, user needs to scroll down to it.

enter image description here

jcharch
  • 150
  • 16
  • Is this the same as your previous question? https://stackoverflow.com/questions/56972736/how-should-i-place-a-button-under-uitableview-and-have-it-move-down-as-the-table – DonMag Jul 11 '19 at 14:16
  • @DonMag I made a new question as it was puzzling me why the constraint priorities I added don't seem to have any effect, whereas the earlier question was asking what approach I should take to achieve this effect. – jcharch Jul 11 '19 at 14:22
  • 1
    It's still confusing... Based on your images, you want a button 40-pts below the last row of your table. So what is the 83-pts above the bottom of the view? Or, do you want the button to sit at 83-pts above the bottom of the view while the table is short (so the gap between bottom row and button may be 300-pts), until the tableView grows to "push it off the screen"? – DonMag Jul 11 '19 at 14:37
  • @DonMag I get your confusion. I've updated the question and I hope it clears things up... the first screenshot is what I see using the constraints that I already have. The remaining are what I want to see. So I want a button that's **always** 83 pts above bottom of view and the button gets pushed down as the tableView grows, maintaining 40 pts spacing between it and the last row of table. – jcharch Jul 11 '19 at 15:13
  • I posted a link in a comment on your previous question that does **exactly** what you are trying to do: *"Here is one approach: https://stackoverflow.com/a/56134043/6257435 ... in that example, just replace the "Footer View" (which is a normal UIView) with your button."* – DonMag Jul 11 '19 at 15:14

2 Answers2

0

I've created the following view controller that should solve your problem - I have to admit I don't know why you used scrollView in your layout, nevertheless I hope I managed to recreate your setup.

To simulate different table heights I used tableView.heightAnchor.constraint(equalToConstant: 300).

Anchors were used for creating constraints.

import UIKit

class ViewController: UIViewController {
    let scrollView = UIScrollView()
    let tableView = UITableView()
    let signOutBtn = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)
        scrollView.addSubview(tableView)
        scrollView.addSubview(signOutBtn)

        tableView.dataSource = self

        signOutBtn.setTitle("BUTTON", for: .normal)
        signOutBtn.setTitleColor(.blue, for: .normal)

        //ScrollView constraints
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
        ])

        //TableView constraints
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(lessThanOrEqualTo: signOutBtn.topAnchor, constant: -40),
            tableView.heightAnchor.constraint(equalToConstant: 300),
            tableView.trailingAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.trailingAnchor),
            tableView.leadingAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.leadingAnchor)
        ])

        //SignOutButton constraints
        signOutBtn.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            signOutBtn.heightAnchor.constraint(equalToConstant: 41),
            signOutBtn.bottomAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.bottomAnchor, constant: -83),
            signOutBtn.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])

    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: "")
        cell.textLabel?.text = "\(indexPath.row)"
        return cell
    }
}

I'm also attaching the screens with result:

Image with table with elements fitting view

Image with table with elements not fitting view

Łukasz Sypniewski
  • 602
  • 1
  • 10
  • 19
  • Thanks. I feel you may have misunderstood the question, and that's my fault as I realize I didn't put enough detail in the question description. I attached a new image that should clarify why I need a scroll view. The solution you have here would permanently affix the button in one place. – jcharch Jul 11 '19 at 14:20
0

To answer your specific question about Constraint Priorities...

You are confused about how constraints are used with scroll views.

In this image, the Label is constrained to the Top of the scroll view and the Button is constrained 40-pts from the Label with Priority 250 and 83-pts from the bottom of the scroll view with Priority 1000.

That bottom constrained determines the .contentSize height - or the "scrollable area" - of the content of the scroll view:

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86