Skip to content

Instantly share code, notes, and snippets.

@SpectralDragon
Created April 8, 2020 19:22
  • Star 27 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save SpectralDragon/e1c01388db09752eac790ae23f1d4587 to your computer and use it in GitHub Desktop.
Simple way to implement preview context menu for SwiftUI
//
// ContentView.swift
// PreviewSwiftUI
//
// Created by v.prusakov on 4/8/20.
// Copyright © 2020 v.prusakov. All rights reserved.
//
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.contextMenu(PreviewContextMenu(destination: Text("Destination"), actionProvider: { items in
return UIMenu(title: "My Menu", children: items)
}))
}
}
}
// MARK: - Custom Menu Context Implementation
struct PreviewContextMenu<Content: View> {
let destination: Content
let actionProvider: UIContextMenuActionProvider?
init(destination: Content, actionProvider: UIContextMenuActionProvider? = nil) {
self.destination = destination
self.actionProvider = actionProvider
}
}
// UIView wrapper with UIContextMenuInteraction
struct PreviewContextView<Content: View>: UIViewRepresentable {
let menu: PreviewContextMenu<Content>
let didCommitView: () -> Void
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let menuInteraction = UIContextMenuInteraction(delegate: context.coordinator)
view.addInteraction(menuInteraction)
return view
}
func updateUIView(_ uiView: UIView, context: Context) { }
func makeCoordinator() -> Coordinator {
return Coordinator(menu: self.menu, didCommitView: self.didCommitView)
}
class Coordinator: NSObject, UIContextMenuInteractionDelegate {
let menu: PreviewContextMenu<Content>
let didCommitView: () -> Void
init(menu: PreviewContextMenu<Content>, didCommitView: @escaping () -> Void) {
self.menu = menu
self.didCommitView = didCommitView
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
UIHostingController(rootView: self.menu.destination)
}, actionProvider: self.menu.actionProvider)
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.addCompletion(self.didCommitView)
}
}
}
// Add context menu modifier
extension View {
func contextMenu<Content: View>(_ menu: PreviewContextMenu<Content>) -> some View {
self.modifier(PreviewContextViewModifier(menu: menu))
}
}
struct PreviewContextViewModifier<V: View>: ViewModifier {
let menu: PreviewContextMenu<V>
@Environment(\.presentationMode) var mode
@State var isActive: Bool = false
func body(content: Content) -> some View {
Group {
if isActive {
menu.destination
} else {
content.overlay(PreviewContextView(menu: menu, didCommitView: { self.isActive = true }))
}
}
}
}
@esmondmissen
Copy link

HI there, this looks cool. Did you have an example with the destination view and some menu options?

@SpectralDragon
Copy link
Author

Hi! Sorry, I didn’t save the example project

@vulgur
Copy link

vulgur commented May 26, 2021

This is so great. But how can I change the frame size of the preview?

@SpectralDragon
Copy link
Author

SpectralDragon commented Jun 4, 2021

This is so great. But how can I change the frame size of the preview?

@vulgur I'm not sure, but maybe u can use preferredContentSize for it? Currently, it's works only for subclasses of UIHostingViewController (https://openradar.appspot.com/FB7650894)

@jalvini
Copy link

jalvini commented Aug 14, 2021

I am having a problem implementing this. While the preview works perfectly. During the animation effect the text that is long pressed has a white overlay. This same thing happens when I close the view. I wouldn't mind this but for the fact that I am using it inside of a list and it pretty much covers the entire item inside of the list for like a second when I close the preview. Any ideas on how I may be able to fix this?

Screen Shot 2021-08-13 at 8 04 29 PM

@acal11
Copy link

acal11 commented Dec 21, 2021

@jalvini did you ever manage to resolve this?

@robinst
Copy link

robinst commented Apr 4, 2022

@jalvini @acal11 It looks like the white overlay can be fixed by adding these to Coordinator:

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    let parameters = UIPreviewParameters()
    parameters.backgroundColor = .clear
    return UITargetedPreview(view: interaction.view!, parameters: parameters)
}

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
    let parameters = UIPreviewParameters()
    parameters.backgroundColor = .clear
    return UITargetedPreview(view: interaction.view!, parameters: parameters)
}

@karenxpn
Copy link

what if we have a button in the preview. how action should be triggered? cause now buttons are not working

@SpectralDragon
Copy link
Author

what if we have a button in the preview. how action should be triggered? cause now buttons are not working

I did it only for previewing :) You could not interact with it at the moment, but if you know how to improve this solution, you're welcome!

@trisapple
Copy link

trisapple commented May 15, 2022

The animation when long pressing does not appear but it is a good attempt and does not screw up my layout like other methods! Would appreciate an update to the code where the animation can appear just like Apple's implementation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment