Created
April 8, 2020 19:22
-
-
Save SpectralDragon/e1c01388db09752eac790ae23f1d4587 to your computer and use it in GitHub Desktop.
Simple way to implement preview context menu for SwiftUI
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!
This one works good also adding buttons underneath the destination view:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.contextMenu(PreviewContextMenu(destination: Text("Destination").padding(),
editAction: {
print("Edit action")
},
deleteAction: {
print("Delete action")
}))
}
}
}
// MARK: - Custom Menu Context Implementation
struct PreviewContextMenu<Content: View> {
let destination: Content
let editAction: () -> Void
let deleteAction: () -> Void
let actionProvider: UIContextMenuActionProvider?
init(destination: Content, editAction: @escaping () -> Void, deleteAction: @escaping () -> Void, actionProvider: UIContextMenuActionProvider? = nil) {
self.destination = destination
self.editAction = editAction
self.deleteAction = deleteAction
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: { _ in
let editAction = UIAction(title: "Edit", image: UIImage(systemName: "pencil")) { _ in
self.menu.editAction()
}
let deleteAction = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
self.menu.deleteAction()
}
return UIMenu(title: "", children: [editAction, deleteAction])
})
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.addCompletion(self.didCommitView)
}
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)
}
}
}
// 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 {
VStack {
menu.destination
HStack {
Button("Edit") {
menu.editAction()
}
.padding()
Button("Delete") {
menu.deleteAction()
}
.padding()
}
}
} else {
content.overlay(PreviewContextView(menu: menu, didCommitView: { self.isActive = true }))
}
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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!