iOS - Today Extension 'show more' & 'show less' state incorrect. Collapsed says 'show less' - requiring multipule presses to fix

Viewed 1305

I have an extension I've taken down the bare bones, which is putting itself in incorrect states, where it'll say "Show Less" when it's collapsed.

This is happening in two circumstances

  1. I expand the extension with "Show More" and then leave the screen. I open up another app, and then return to the extension. The expanded extension visibly collapses in front of me, but still says "Show Less"
  2. I push a new build to test changes. It will be expanded from before, and when the new build pushes it becomes collapsed and says "show less"

I also tried having another extension active (weather) with it expanded, and it always remains expanded once expanded, while my extension is collapsing and showing the wrong state.

This happens with and without the weather widget present.

When I put break points in the code, in step #1 ViewDidLoad is being called again.

Here's the code, I deleted everything bit by bit until this was all that was left and still causing the problem.

class TodayController: UICollectionViewController, NCWidgetProviding {

    let reuseIdentifier = "TimeGridCell"

    override func viewDidLoad() {
        super.viewDidLoad()
        self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    override func collectionView(_ collectionView: UICollectionView,
                                 numberOfItemsInSection section: Int) -> Int {
        return 3
    }

    override func collectionView(_ collectionView: UICollectionView,
                                 cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
                for: indexPath) as! ArtistTimeGridTableViewCell
        cell.nameLabel.text = "blah blah"
        return cell
    }

    func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
        if activeDisplayMode == .expanded {
            self.preferredContentSize = (self.collectionView?.contentSize)!
        } else if activeDisplayMode == .compact {
            self.preferredContentSize = maxSize

        }
    }
}

Note that if I remove self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded then I don't get the option to expand or collapse at all, so it's required.

Here's images of the problem. First image is correct, second one is incorrect.

Fake expanded Fake expanded

5 Answers

Just figured out the proper answer for this.

Since you are allowing the extension to have expanded size in viewDidLoad, the widgetActiveDisplayModeDidChange is called directly from viewDidLoad.

The problem lies in accessing collection/table view properties (size/element number) in your widgetActiveDisplayModeDidChange method, since the collection/table view is fully initialized only after viewWillAppear.

So, in my case, I just duplicated the numberOfRowsInSection calculation inside widgetActiveDisplayModeDidChange, and everything started to work as intended.

I got exactly the same problem, but I was using layout constraint to set height for expanded state. For anyone who runs into this, the fix was simply to add layoutIfNeeded to the end of widgetActiveDisplayModeDidChange.

Example usage:

func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
    switch activeDisplayMode {
    case .compact:
        heightContstraint.constant = 0
    case .expanded:
        heightContstraint.constant = 44 * CGFloat((items.count))
    }
    view.layoutIfNeeded()
}

In Swift 3 and 4

override func viewDidLoad() {
            super.viewDidLoad()
                self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
        }

        func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
            if activeDisplayMode == .expanded {
                self.preferredContentSize = (self.myTableView?.contentSize)!
          //Instead of table content you can user user own size
            } else if activeDisplayMode == .compact {
                self.preferredContentSize = maxSize

            }
    }

The real issue is that during widgetActiveDisplayModeDidChange, the collectionView doesn't know the contentSize yet, so it will give you a smaller value. If you print it out in the function, and compare it with viewWillAppear, you will see the difference.

The 0.2 seconds delay seems quite hacky, so I prefer it this way:

override func viewWillAppear(_ animated: Bool) {
    if (extensionContext?.widgetActiveDisplayMode == .expanded) {
        self.preferredContentSize = self.collectionView.contentSize
    }
}

It applies the same to tableView also, which is the widget that I am using. The code above will handle the case if when you swipe over to the home screen and swipe back. But it won't be triggered if you just click on show more/less, so you still need to maintain that same code in widgetActiveDisplayModeDidChange.

Related