Context Menu with Preview in SwiftUI

Cuneyt Zafer
SwiftUI Made Easy
Published in
4 min readMar 28, 2021

--

Photo by Croissant on Unsplash

SwiftUI already provides many native components and .contextMenu is one of them. However, unfortunately, it doesn’t have as much customization as we have in UIKit. ContextMenu only supports showing menu items with a 3D preview of the applied view but does not support showing a custom 3D preview or having a destination.

For this reason, I have created a context menu with preview component that can mimic UIKit features. As this is only a workaround for now, I took advantage of UIViewRepresentable. I hope and believe that a native preview/destination support will be available in SwiftUI 3.0 this fall.

This is what we are trying to achieve today.

Part 1: Setting Up the View Extension

I aimed to solve three problems:

  1. Displaying a custom preview with no destination
  2. Displaying a custom preview with the same destination
  3. Displaying a custom preview with a different destination

Hence, I created three functions in a View extension:

Using the above View extension, we can now achieve the tasks above.

In total, we have five variables we can use to customize our experience:

  1. preview: This is the 3D preview that we want to display.
  2. destination: This is the destination view that we want to navigate if the user taps on the preview.
  3. preferredContentSize: This is a size we can set if we want to that will force the view to have that size. If we do not set a size here, the view will take the all available space which is usually what we want.
  4. presentAsSheet: This specifies whether we want the destination view be presented as a sheet or in a navigation stack.
  5. actions: These are the actions/buttons that will show up under the 3D preview.

Part 2: Setting Up the Modifier

As you can see in the code above, we initialize PreviewContextViewModifier on each modifier. PreviewContextViewModifier will be the core of this component.

In this modifier we have three initializers as well. Most important part of this view is in the body.

Depending on the presentAsSheet variable, we either create a NavigationLink or add .sheet modifier to the view. For sheet modifier, I used a custom if block that you can also use in your code.

I set the overlay opacity as 0.05 as there will be a short amount of time that the preview appears after the preview is dismissed that this opacity modifier will fix.

On top of the content view, we add an overlay of PreviewContextView that I will explain next.

Part 3: Setting Up the View

PreviewContextView will be a wrapper of the UIKit component. It will handle the UIContextMenuInteractionDelegate using its Coordinator.

Here, we basically provide the preview and activate the destination when the preview is tapped.

Part 4: Setting Up the Actions

How do we handle the actions for our preview context menu then? Normally, in UIKit we use UIActions to add actions. In my wrapper component though, I wanted to create a custom action struct that will make creating buttons/actions easier.

Using this action struct, we can add actions using title, image, system image, and attributes. Good thing about this action is that we can now pass .destructive attribute that would make the button red (this is unfortunately is not possible in SwiftUI) yet.

I have also created a ButtonBuilder using the @_functionBuilder modifier. Don’t worry, it is not a private API. It just has not gone through Swift Evolution yet and not recommended to use in a production code, however, as I created this component as a workaround I decided to use it. This way we can pass the actions without using an array. You can always remove the function builder and pass an array instead.

Finally: Preview Context Menu in Action

I will demo some different cases we can achieve:

1. Displaying a custom preview with no destination

2. Displaying a custom preview with destination (same view for both)

3. Displaying a custom preview with a custom destination

4. Displaying a custom preview with custom size

5. Displaying the destination as a sheet

I hope you enjoyed this tutorial. Please let me know If I have missed anything or if you want me to add/explain any specific point.

I look forward to hearing your feedback.

--

--