Autolayout: How to Compress a Complex Popover

If you like to use the source code (which you really ), take a look at the Bitbucket repository .

I have a popover dialog that shows a list of settings. These settings are listed inside several UITableViews. UITableViews cannot be scrolled, since there is already a view of the general settings. In addition, the popover dialog should occupy as much vertical screen as needed, but should be compressed horizontally.

Thus, I conceived the following structure:

UIView => MySettingsViewController - UIScrollView - UIView (Content View) - Container View1 - UITableView (embedded) => MyTableViewController - Container View2 - UITableView (embedded) 

The structure is assembled through Interface Builder, and Autolayout is used to determine the size.

I have both a scroll view and a content view (I started with just one) and a container view in the corresponding supervisors (or layout guides). I limited the size of the content view as follows:

 contentView.width == (topmost) UIView.width contentView.height == 200 // removed at build time 

In addition, I set the size of the table view to the size of its contents, because otherwise the popover looks empty:

 class MyTableViewController: UITableViewController { override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) // this is Cartography syntax - the intention should be clear layout(view, replace: ConstraintGroup()) { [unowned self] view in view.width == self.tableView.contentSize.width view.height == self.tableView.contentSize.height } view.setNeedsLayout() } } 

The popover settings are filled with content, but its size is not quite right:

enter image description here

To fix this, I tried the following approach, which does not work:

 class MySettingsViewController: UIViewController { override var preferredContentSize: CGSize { get { let compressedSize = view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) // this is always (0, 0) because the subviews are not resized, yet return compressedSize } set { super.preferredContentSize = newValue } } } 

In conclusion: compression does not work.

+4
source share
1 answer

So I just fixed the problem myself, as you can see by looking at the Bitbucket repository.

Now the layout is fixed in both MyTableViewController and MySettingsViewController . The first is as follows:

 class MyTableViewController: UITableViewController { var heightConstraint: NSLayoutConstraint? var tableViewEdgesConstraints: [NSLayoutConstraint]? override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) if let container = tableView.superview where tableViewEdgesConstraints == nil { layout(tableView, container, replace: ConstraintGroup()) { [unowned self] tableView, container in self.tableViewEdgesConstraints = tableView.edges == inset(container.edges, 0) } } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let heightConstraint = heightConstraint { if Int(heightConstraint.constant) != Int(tableView.contentSize.height) { heightConstraint.constant = self.tableView.contentSize.height } } else { layout(view, replace: ConstraintGroup()) { [unowned self] view in if (self.tableView.contentSize.height > 0) { self.heightConstraint = view.height == self.tableView.contentSize.height } } } } } 

Basically, I limit the height of the table to its content height and change the limit if the height of the content changes. This is done as soon as the table is laid out. In addition, the representation of nested tables is fixed by its edges along the edges of the container view. I think this is a must because I could not figure out how to limit two views of different scenes directly in Interface Builder.

In MySettingsViewController size of the scrollview is set to the size of the content view frame (which is available through the output) as soon as this size is known. Also, to compress the popover, the preferredContentSize of the settings controller adapts accordingly when the height changes (if you omit the condition, you can get yourself in an infinite layout loop. In addition, I did 3 things to make the navigation controller wrapped in MySettingsViewController possible:

  • The width of the popover is set to a fixed value (otherwise, it sometimes expands to the full width).
  • The represented PreferController preferredContentSize must be set the same.
  • I had to set scrollView inserts to 0 to avoid ugly vertical offsets - this solution is not optimal because it breaks the scroll view a bit. But it works.

Here is the code:

 class MySettingsViewController: UIViewController { @IBOutlet weak var contentView: UIView! @IBOutlet weak var scrollView: UIScrollView! override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() scrollView.contentSize = contentView.frame.size if (preferredContentSize.height != scrollView.contentSize.height) { let newSize = CGSize(width: 400, height: scrollView.contentSize.height) preferredContentSize = newSize presentingViewController?.presentedViewController?.preferredContentSize = newSize scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) } } } 

And this is the result:

enter image description here

0
source

All Articles