Skip to content

Instantly share code, notes, and snippets.

@SpectralDragon
Created April 8, 2020 19:22
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SpectralDragon/e1c01388db09752eac790ae23f1d4587 to your computer and use it in GitHub Desktop.
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 }))
}
}
}
}
@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