Constraints reactivate when contextMenu appears

197 views Asked by At

I am using UIContextMenuConfiguration for actions on a collection view cell. Everything works exactly as expected, however if my cell has (de)activated constraints from nib, it refreshes upon long press.

To demonstrate the problem, I have created a new project, with a collectionView in the storyboard. A custom cell in the collection view has a label with two constraints, a constraint pinning it to the bottom of the cell (initially active), and the other aligns it in the center (initially disabled).

Here's my code,

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        cell.configure()
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        return UIContextMenuConfiguration.init(identifier: nil, previewProvider: nil) { (array) -> UIMenu? in
            return UIMenu(title: "Hello", image: nil, identifier: nil, options: .destructive, children: [UIAction(title: "Share", image: UIImage(systemName: "tray.and.arrow.up"), identifier: nil) { _ in
                print("HelloWorld!")
              }])
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 265, height: 128)
    }
}

class CollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
    @IBOutlet weak var centerConstraint: NSLayoutConstraint!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func configure() {
        bottomConstraint.isActive = false
        centerConstraint.isActive = true
    }
}

Initial state Context menu presentation After context menu dismissal

1

There are 1 answers

6
DonMag On

The issue appears to be directly related to:

  • two constraints created in Storyboard which would conflict with each other
  • setting one constraint to Not Installed
  • de-activating the installed constraint and activating the not-installed constraint

When initializing UIContextMenuConfiguration with previewProvider: nil - which tells UIKit to auto-generate a preview view - the "installed" states of the constraints are reset.

One way to get around it:

Instead of marking the Center constraint "not installed" give it a Priority of 998, and give the Bottom constraint a Priority of 999 (that will prevent IB from complaining).

You can then keep your configure() func as:

func configure() {
    bottomConstraint.isActive = false
    centerConstraint.isActive = true
}

and the label's centerY constraint will remain active in both the cell and in the context menu's preview.

However, if you WANT the previewView to look different than the cell, your best bet (and probably a better approach to begin with) is to use a custom previewProvider.