iOS Context Menus and SF Symbols

Context Menus was one of the significant developments in this year’s WWDC 2019 for iOS 13. We even discussed it briefly in a previous piece on iOS 13 Checklist. It’s time for a full-scale implementation of Context Menus using SF Symbols. Let’s get started.

Plan Of Action

  • We’ll be covering the implementation of Context Menus using Collection Views.
  • We will be using SFSymbols which provides plenty of system image names to choose from.

Context Menus vs Peek And Pop

Context Menus introduced with iOS 13 are not the same as 3D touch Peek and Pop gesture.
Following are the key differences:

  • Peek And Pop work on devices that support 3D touches whereas Context Menus is for all devices.
  • Context Menu is displayed on long-press or force touch(if device supports) whereas Peek and Pop Menu is shown on force touch only.
  • Peek And Pop menu requires you to swipe up to view the menu whereas Context Menus are visible right there where you long press.

How Does ContextMenu Work?

To setup ContextMenu on a View, we need to add an interaction: UIContextMenuIteraction.
Along with that, we need to conform to UIContextMenuInteractionDelegate

Code snippet:

class MyViewController: UIViewController, 
                        UIContextMenuInteractionDelegate {
  override func viewDidLoad() {
    super.viewDidLoad()

    let interaction = UIContextMenuInteraction(delegate: self)
    view.addInteraction(interaction)
  }
}

Let’s take a glance at the delegate methods in UIContextMenuInteractionDelegate protocol:

//Provides a Context Menu for the give menu when the interaction starts
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, 
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration?

//Optional function. Gets triggered when the focused view is clicked.
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, 
willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating)

Initialising A Context Menu

To initialize a Context Menu, we need to set three arguments ( all optional):

  • identifier – You can set a string id here.
  • previewProvider – A preview of the next View Controller
  • actionProvider – We create our menu buttons and actions here.

SFSymbols

SFSymbols is a newly introduced Mac application that is essentially an encyclopedia of system icon names.
We can pass those names in UIImage(systemName:) initializer introduced with iOS 13.

Customising System Icons

We can customize the size/style of the system icons by passing a configuration argument as shown below:

let config = UIImage.SymbolConfiguration(textStyle: .largeTitle)
UIImage(systemName: "paperplane", withConfiguration : config)

ContextMenu And CollectionView

In the following sections, we’ll be implementing ContextMenu on a CollectionView.

iOS 13 introduces a few new UICollectionViewDelegate methods for context menus.
In order to display a ContextMenu on CollectionView cell long-press the following function is used:

func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        
        let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil){ action in
            let viewMenu = UIAction(title: "View", image: UIImage(systemName: "eye.fill"), identifier: UIAction.Identifier(rawValue: "view")) {_ in
                print("button clicked..")
            }

            let rotate = UIAction(title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise"), identifier: nil, state: .on, handler: {action in
                print("rotate clicked.")
            })

            let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), identifier: nil, discoverabilityTitle: nil, attributes: .destructive, state: .on, handler: {action in
                
                print("delete clicked.")
            })
            let editMenu = UIMenu(title: "Edit...", children: [rotate, delete])
            
            
            return UIMenu(title: "Options", image: nil, identifier: nil, children: [viewMenu, editMenu])
        }
        
        return configuration
    }

We’ve created a UIMenu which consists of a sub-menu as well. A quick look at the ContextMenu and CollectionView in action is given below:

ios-context-menu-collectionview-demo

ContextMenu With PreviewProvider

In this section, we’ll be displaying the next view controller in a preview along with the ContextMenu on cell press.

In order to display the menu, we need to set the target view controller in the PreviewProvider as shown below:


func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

let configuration = UIContextMenuConfiguration(identifier: "\(indexPath.row)" as NSCopying, previewProvider: {
            return SecondViewController(index: indexPath.row)
        }){ action in

//add your uimenu as before
}
}

To open the view controller on the preview click, we need to implement the following method which is newly added in UICollectionViewDelegate.

func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
        
        let id = configuration.identifier as! String
        
        animator.addCompletion {
            self.show(SecondViewController(index: Int(id)), sender: self)
        }
    }

The identifier set earlier is retrieved using configuration.identifier.

In our SecondViewController, we’ve set different CIFilter for each of the images on the ImageView.

The output of the application in action is given below:

ios-context-menu-preview-provider-collection-view-demo

That sums up this feature of iOS 13. ContextMenus look best along with system icons. The full source is available in this Github Repository.

Leave a Reply

Your email address will not be published. Required fields are marked *