Skip to content

Instantly share code, notes, and snippets.

@zhwayne
Created September 21, 2023 09:44
Show Gist options
  • Save zhwayne/afb14b83723e6d31c5df877f725ed7ba to your computer and use it in GitHub Desktop.
Save zhwayne/afb14b83723e6d31c5df877f725ed7ba to your computer and use it in GitHub Desktop.
EditMenu for SwiftUI
//
// EditMenu.swift
// CalcApp
//
// Created by iya on 2023/6/9.
// Copyright © 2023 wayne. All rights reserved.
//
import SwiftUI
import UIKit
extension View {
func editMenu(
@ArrayBuilder<UIAction> _ actions: @escaping () -> [UIAction],
onPresent: (() -> Void)? = nil,
onDismiss: (() -> Void)? = nil
) -> some View {
self.overlay {
EditMenu(
content: self,
actions: actions,
onPresent: onPresent,
onDismiss: onDismiss)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
fileprivate struct EditMenu<Content: View>: UIViewRepresentable {
let content: Content
let actions: () -> [UIAction]
let onPresent: (() -> Void)?
let onDismiss: (() -> Void)?
typealias UIViewType = UIView
func makeUIView(context: Context) -> UIView {
if let interaction = context.coordinator.interaction {
context.coordinator.uiView.addInteraction(interaction)
}
let longPress =
UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleLongPress(_:)))
context.coordinator.uiView.addGestureRecognizer(longPress)
return context.coordinator.uiView
}
func updateUIView(_ uiView: UIView, context: Context) {
}
func makeCoordinator() -> Coordinator {
let coordinator = Coordinator(self)
coordinator.interaction = UIEditMenuInteraction(delegate: coordinator)
return coordinator
}
class Coordinator: NSObject, UIEditMenuInteractionDelegate {
let editMenu: EditMenu
let uiView = UIView()
var interaction: UIEditMenuInteraction?
private var isPresent = false
deinit {
print("\(#function)")
}
init(_ editMenu: EditMenu) {
self.editMenu = editMenu
}
@objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard isPresent == false, gestureRecognizer.state == .began else { return }
let point = CGPoint(x: uiView.bounds.midX, y: 0)
let configuration = UIEditMenuConfiguration(
identifier: "EditMenuInteractionIdentifier",
sourcePoint: point
)
interaction?.presentEditMenu(with: configuration)
UIImpactFeedbackGenerator(style: .light).impactOccurred()
}
func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
// var actions = suggestedActions
let customMenu = UIMenu(options: .displayInline, children: editMenu.actions())
// actions.append(customMenu)
// return UIMenu(children: actions) // For Custom and Suggested Menu
return UIMenu(children: customMenu.children) // For Custom Menu Only
}
func editMenuInteraction(_ interaction: UIEditMenuInteraction, willPresentMenuFor configuration: UIEditMenuConfiguration, animator: UIEditMenuInteractionAnimating) {
editMenu.onPresent?()
isPresent = true
}
func editMenuInteraction(_ interaction: UIEditMenuInteraction, willDismissMenuFor configuration: UIEditMenuConfiguration, animator: UIEditMenuInteractionAnimating) {
editMenu.onDismiss?()
isPresent = false
}
}
}
@eonist
Copy link

eonist commented Mar 12, 2024

here is a preview:

import SwiftUI

struct EditMenu_Previews: PreviewProvider {
   static var previews: some View {
      Text("Hello, World!")
         .editMenu({
            return [
               UIAction(title: "Action 1", image: UIImage(systemName: "1.circle"), handler: { _ in
                  print("Action 1 selected")
               }),
               UIAction(title: "Action 2", image: UIImage(systemName: "2.circle"), handler: { _ in
                  print("Action 2 selected")
               })
            ]
         }, onPresent: {
            print("Menu presented")
         }, onDismiss: {
            print("Menu dismissed")
         })
         .padding()
         .previewLayout(.sizeThatFits)
   }
}

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