Created
September 19, 2019 21:28
-
-
Save ericlewis/3f4bc386f510ac8fabad4b66199ebc21 to your computer and use it in GitHub Desktop.
this is some real icky code, not for production use. experiment only.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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