How do I adjust my popover to the size of the content in my tableview in swift?

33,637

Solution 1

Checkout the preferredContentSize property of UIViewController:

let height = yourDataArray.count * Int(popOverViewController.tableView.rowHeight)
popOverViewController.preferredContentSize = CGSize(width: 300, height: height)

Solution 2

In your UITableViewController's viewDidLoad() you can add an observer:

self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

Then add this method:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    self.preferredContentSize = tableView.contentSize
}

Lastly, in viewDidDisappear(), make sure you remove the observer:

tableView.removeObserver(self, forKeyPath: "contentSize")

This way the popover will automatically adjust size to fit the content, whenever it is loaded, or changed.

Solution 3

Override the preferredContentSize property in your extension of the uitableviewcontroller as following:

override var preferredContentSize: CGSize {
    get {
        let height = calculate the height here....
        return CGSize(width: super.preferredContentSize.width, height: height)
    }
    set { super.preferredContentSize = newValue }
}

For calculating the height check out tableView.rectForSection(<#section: Int#>)

Solution 4

Simple dynamic answer for Swift 4.x and Swift 5.x involving no size-computation (modern version of Bo Frese answer):

private var contentSizeObserver : NSKeyValueObservation?

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    contentSizeObserver = tableView.observe(\.contentSize) { [weak self] tableView, _ in
        self?.preferredContentSize = CGSize(width: 320, height: tableView.contentSize.height) // Here I fixed the width but you can do whatever you want
    }
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    contentSizeObserver?.invalidate()
    contentSizeObserver = nil
}

Solution 5

For swift 4 if you want to observe the content size I found this to be the optimal solution. Reporting it here since I did not find a complete example online:

class MyTableViewController: UITableViewController {

    private var kvoContext = 0

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        addObserver(self, forKeyPath: #keyPath(tableView.contentSize), options: .new, context: &kvoContext)

    }

    override func viewDidDisappear(_ animated: Bool) {
        removeObserver(self, forKeyPath: #keyPath(tableView.contentSize))
        super.viewDidDisappear(animated)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &kvoContext, keyPath == #keyPath(tableView.contentSize),
            let contentSize = change?[NSKeyValueChangeKey.newKey] as? CGSize  {
            self.popoverPresentationController?.presentedViewController.preferredContentSize = contentSize
        }
    }
}
Share:
33,637
Henny Lee
Author by

Henny Lee

Updated on January 12, 2020

Comments

  • Henny Lee
    Henny Lee over 4 years

    I'm using popoverPresentationController to show my popover. The UITableViewController used to show as popover is created programmatically and will usually contain 1 to 5 rows. How do I set up this popover to adjust the size to the content of the tableview?

    Code for my popover:

    if recognizer.state == .Began {
        let translation = recognizer.locationInView(view)
    
        // Create popoverViewController
        var popoverViewController = UITableViewController()
        popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
        popoverViewController.tableView.backgroundColor = UIColor.popupColor()
    
        // Settings for the popover
        let popover = popoverViewController.popoverPresentationController!
        popover.delegate = self
        popover.sourceView = self.view
        popover.sourceRect = CGRect(x: translation.x, y: translation.y, width: 0, height: 0)
        popover.backgroundColor = UIColor.popupColor()
    
        presentViewController(popoverViewController, animated: true, completion: nil)
    }
    
  • Henny Lee
    Henny Lee about 9 years
    thanks, quite simpel. I tried preferredContentSize before but didn't think of using the count of my array :o. When I use this solution, it only works after I've accessed popoverViewController.tableView.rowHeight first or else the count and height will just return 0. How can I do this without accessing the height first?
  • zisoft
    zisoft about 9 years
    I usually set tableview.rowHeight = 44 in viewDidLoad.
  • Jordan Wood
    Jordan Wood over 7 years
    This is getting called before rows get added to my table, so rectForSection has a height of zero.
  • Aditya
    Aditya almost 7 years
    Thanks for the answer Adrian and Bo. I want to disable popover's animation when it is resizing. How can I dot that?
  • Ashley Mills
    Ashley Mills over 6 years
    @AdityaShinde Please ask this as another question
  • Trev14
    Trev14 about 5 years
    I feel uncomfortable overriding the observeValue method. Adding the super method crashes...shouldn't there be a better way to observe changes?
  • Christian Di Lorenzo
    Christian Di Lorenzo about 5 years
    @Trev14 observeValue is actually a super common way of observing changes for a key path. You shouldn't be calling super at all. It's extremely rare to call super in that method. BTW, this answer is much better than the others since it doesn't require a manual calculation.
  • dec-vt100
    dec-vt100 about 5 years
    ah, much better than any other solutions. Too bad RXSwift doesn't have an observable for that. And I'm too lazy to write my own right now.
  • Zaphod
    Zaphod over 4 years
    You should add it in you viewWillAppear if you remove it in the viewDidDisappear. Moreover, you also should use a not nil context.
  • Greg Brown
    Greg Brown about 4 years
    This assumes a fixed row height, which is not ideal.
  • Saranjith
    Saranjith almost 4 years
    you saved my life!!
  • Antoine Rucquoy
    Antoine Rucquoy over 3 years
    ⚠️ ⚠️ ⚠️ UI Handling into KVO completion block should be run on Main Queue. I edited the sample above.
  • Zaphod
    Zaphod over 3 years
    Hello @AntoineRucquoy, in fact, you don't need to run it on the main queue, because it's already running onto it. In fact the KVO handler is run on the queue performing the modification. As the observed property, here \.contentSize, is updated only on the main thread (by calling updateData and friends) you are sure your handler will be executed on the main thread.
  • Antoine Rucquoy
    Antoine Rucquoy over 3 years
    Hello @Zaphod, I had crashes while displaying my ViewController (where I'm using KVO on the contentSize's UITableView) into a forced popover on a mobile device (not an iPad, strangely). Maybe this is purely depending on how is my project built. However, I still assume that this is always safer to perform UI changes on the main thread.
  • Zaphod
    Zaphod over 3 years
    @AntoineRucquoy I agree, it's always safer. But it seems the problem you are facing is just a symptom and you just hide it when you fix it by forcing the call on the main thread. You should check why your content size is not updated from the main thread in the first place.
  • swiftyboi
    swiftyboi over 2 years
    Any way to do this and maintain size class? Setting this property on iPad is causing me to see my iPhone layout.