3

I have a simple UI (very similar to Uber) where the user can scroll a table view on top of the main content.

The UI and the corresponding UITableView animation bug can be displayed as:

enter image description here

Here is what is happening:

  • User taps on the table view and the table view is expanded. This is done via adding the layout constraint that makes sure that tableView.top = topMenu.bottom. The constraint that gets removed is tableView.height = 30. Everything looks good.
  • User then scrolls down a certain amount (55+ pixels) and the constraints are reverted back to their original states. This happens inside an animation block so that the flow looks smooth.
  • The bug occurs here. As far as I understand, the tableView's visible area is calculated of how it will look like after the animation ends. However, this calculation happens before the animation. Therefore, during the animation only 1-2 cells are displayed on the table view; causing the bug.
  • I can have a workaround here by temporarily setting the tableView's height to a large value and only setting it back to a small value after the animation ends. However, that doesn't work because the safe area on iPhoneX gets covered by the tableView.

Relevant code is here:

    private func animateTheChange() {
        UIView.animate(withDuration: 0.8, animations: {
            self.view.setNeedsUpdateConstraints()
            self.view.layoutIfNeeded()
        })
    }

    override func updateViewConstraints() {
        self.touchConstraints()
        super.updateViewConstraints()
    }

    private func touchConstraints() {
        if self.collapsed {
            self.view.addConstraint(self.collapsedConstraint)
            self.view.removeConstraint(self.expandedConstraint)
            if UserHardware.IS_IPHONE_X {
                self.bottomConstraint.constant = 0
            }
        }
        else { //  Expand
            self.view.addConstraint(self.expandedConstraint)
            self.view.removeConstraint(self.collapsedConstraint)
            if UserHardware.IS_IPHONE_X {
                self.bottomConstraint.constant = 34
            }
        }
    }

Relevant Stackoverflow questions (that help but don't solve the issue):

Guven
  • 2,280
  • 2
  • 20
  • 34

1 Answers1

1

One option...

  • Embed your tableView inside a "containing" UIView
  • Constrain the tableView to Top and Bottom of the containing view
  • Constrain the containing view Bottom to the Safe Area Bottom
  • Constrain the containing view Top to the Bottom of topMenu with a Priority of 250 (default low), and connect it to @IBOutlet var tableContainerTop: NSLayoutConstraint!
  • Constrain the Height of the containing view to 30 with a Priority of 750 (default high), and connect it to @IBOutlet var tableContainerHeight: NSLayoutConstraint!

When you want to "expand" or "collapse" your tableView, change the priorities of the containing view's constraints.

    UIView.animate(withDuration: 0.8, animations: {

        if self.isExpanded {
            self.tableContainerHeight.priority = .defaultHigh
            self.tableContainerTop.priority = .defaultLow
        } else {
            self.tableContainerHeight.priority = .defaultLow
            self.tableContainerTop.priority = .defaultHigh
        }
        self.view.layoutIfNeeded()

    })
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks DonMag, I will give this a try tomorrow. Before I do that, why do you think changing priorities as opposed to adding/removing constraints make a difference? – Guven Jun 04 '18 at 20:02
  • Hmm... I don't know what all goes on "under-the-hood" but my assumption would be that *changing* constraint properties would be considerably "lighter" than adding/removiing. – DonMag Jun 05 '18 at 13:55