Skip to content

Instantly share code, notes, and snippets.

@ericlewis
Created September 19, 2019 21:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericlewis/3f4bc386f510ac8fabad4b66199ebc21 to your computer and use it in GitHub Desktop.
Save ericlewis/3f4bc386f510ac8fabad4b66199ebc21 to your computer and use it in GitHub Desktop.
this is some real icky code, not for production use. experiment only.
//
// ContentView.swift
// ContextMenyu
//
// Created by Eric Lewis on 9/19/19.
// Copyright © 2019 Eric Lewis, Inc. All rights reserved.
//
import SwiftUI
struct MenuItem: View {
typealias Body = Never
typealias Attributes = UIMenuElement.Attributes
var text: String
var systemName: String?
var attributes: Attributes?
var action: (() -> Void)?
fileprivate var actions: [UIAction]? = nil
init(_ text: String, systemName: String? = nil, attributes: Attributes? = nil, action: (() -> Void)? = nil) {
self.text = text
self.systemName = systemName
self.attributes = attributes
self.action = action
}
init<MenuItems>(_ text: String, systemName: String? = nil, attributes: Attributes? = nil, @ViewBuilder content: () -> MenuItems) where MenuItems: View {
self.text = text
self.systemName = systemName
self.attributes = attributes
let tupleMirror = Mirror(reflecting: content())
tupleMirror.children.map({ $0.value }).first.map {
let tupleMirror = Mirror(reflecting: $0)
let actions: [UIAction] = tupleMirror.children.compactMap({ $0.value as? MenuItem }).map { actionItem in
guard let attributes = actionItem.attributes else {
return UIAction(title: actionItem.text, image: UIImage(systemName: actionItem.systemName ?? "")) { _ in
actionItem.action?()
}
}
return UIAction(title: actionItem.text, image: UIImage(systemName: actionItem.systemName ?? ""), attributes: attributes) { _ in
actionItem.action?()
}
}
self.actions = actions
}
}
var body: Never {
fatalError("Something very, very bad happened")
}
}
struct ContextMenyu<Content, MenuItems>: UIViewRepresentable where Content: View, MenuItems: View {
var content: Content
var title: String? = nil
var menuItems: () -> MenuItems
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIView {
UIHostingController<Content>(rootView: content).view
}
func updateUIView(_ uiView: UIView, context: Context) {
uiView.backgroundColor = .clear
uiView.addInteraction(UIContextMenuInteraction(delegate: context.coordinator))
}
class Coordinator: NSObject, UIContextMenuInteractionDelegate {
var parent: ContextMenyu<Content, MenuItems>
init(_ parent: ContextMenyu<Content, MenuItems>) {
self.parent = parent
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
var menuHolder: UIMenu? = nil
let tupleMirror = Mirror(reflecting: self.parent.menuItems())
tupleMirror.children.map({ $0.value }).first.map {
let tupleMirror = Mirror(reflecting: $0)
let actions: [UIMenuElement] = tupleMirror.children.compactMap({ $0.value as? MenuItem }).map { actionItem in
if (actionItem.actions?.count ?? 0) > 0 {
return UIMenu(title: actionItem.text, image: UIImage(systemName: actionItem.systemName ?? ""), children: actionItem.actions ?? [])
} else {
guard let attributes = actionItem.attributes else {
return UIAction(title: actionItem.text, image: UIImage(systemName: actionItem.systemName ?? "")) { _ in
actionItem.action?()
}
}
return UIAction(title: actionItem.text, image: UIImage(systemName: actionItem.systemName ?? ""), attributes: attributes) { _ in
actionItem.action?()
}
}
}
menuHolder = UIMenu(title: self.parent.title ?? "", children: actions)
}
guard let menu = menuHolder else {
return nil
}
return menu
}
}
}
}
extension View {
func contextMenyu<MenuItems>(_ title: String? = nil, @ViewBuilder menuItems: @escaping () -> MenuItems) -> some View where MenuItems: View {
self.opacity(0)
.overlay(ContextMenyu(content: self, title: title, menuItems: menuItems))
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Menu")
.contextMenu {
Text("test")
Text("test")
Text("Testing")
}
Text("")
Text("Menyu")
.contextMenyu {
MenuItem("test") {
MenuItem("test")
MenuItem("test")
}
MenuItem("test")
MenuItem("Testing", systemName: "gear", attributes: .destructive)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment