上下文菜单的 SwiftUI 自定义视图
SwiftUI Custom View For Context Menu
我想在视图上按下长按手势时实现相同的自定义弹出视图,如图(来自 Tweeter App)所示,因此我可以同时显示自定义视图和上下文菜单。
您需要使用 UIKit 中的 UIContextMenu 制作自定义上下文菜单。
struct ContextMenuHelper<Content: View, Preview: View>: UIViewRepresentable {
var content: Content
var preview: Preview
var menu: UIMenu
var navigate: () -> Void
init(content: Content, preview: Preview, menu: UIMenu, navigate: @escaping () -> Void) {
self.content = content
self.preview = preview
self.menu = menu
self.navigate = navigate
}
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let hostView = UIHostingController(rootView: content)
hostView.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor)
]
view.addSubview(hostView.view)
view.addConstraints(constraints)
let interaction = UIContextMenuInteraction(delegate: context.coordinator)
view.addInteraction(interaction)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UIContextMenuInteractionDelegate {
var parent: ContextMenuHelper
init(_ parent: ContextMenuHelper) {
self.parent = parent
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil) {
let previewController = UIHostingController(rootView: self.parent.preview)
return previewController
} actionProvider: { items in
return self.parent.menu
}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
parent.navigate()
}
}
}
extension View {
func contextMenu<Preview: View>(navigate: @escaping () -> Void = {}, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) -> some View {
return CustomContextMenu(navigate: navigate, content: {self}, preview: preview, menu: menu)
}
}
struct CustomContextMenu<Content: View, Preview: View>: View {
var content: Content
var preview: Preview
var menu: UIMenu
var navigate: () -> Void
init(navigate: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) {
self.content = content()
self.preview = preview()
self.menu = menu()
self.navigate = navigate
}
var body: some View {
ZStack {
content
.overlay(ContextMenuHelper(content: content, preview: preview, menu: menu, navigate: navigate))
}
}
}
用法:
.contextMenu(navigate: {
UIApplication.shared.open(url) //User tapped the preview
}) {
LinkView(link: url.absoluteString) //Preview
.environment(\.managedObjectContext, viewContext)
.accentColor(Color(hex: "59AF97"))
.environmentObject(variables)
}menu: {
let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
withAnimation() {
UIApplication.shared.open(url)
}
}
let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
return menu
}
用于导航:
添加 isActive: $navigate 到您的 NavigationLink:
NavigationLink(destination: SomeView(), isActive: $navigate)
连同新的 属性:
@State var navigate = false
.contextMenu(navigate: {
navigate.toggle() //User tapped the preview
}) {
LinkView(link: url.absoluteString) //Preview
.environment(\.managedObjectContext, viewContext)
.accentColor(Color(hex: "59AF97"))
.environmentObject(variables)
}menu: {
let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
withAnimation() {
UIApplication.shared.open(url)
}
}
let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
return menu
}
我想在视图上按下长按手势时实现相同的自定义弹出视图,如图(来自 Tweeter App)所示,因此我可以同时显示自定义视图和上下文菜单。
您需要使用 UIKit 中的 UIContextMenu 制作自定义上下文菜单。
struct ContextMenuHelper<Content: View, Preview: View>: UIViewRepresentable {
var content: Content
var preview: Preview
var menu: UIMenu
var navigate: () -> Void
init(content: Content, preview: Preview, menu: UIMenu, navigate: @escaping () -> Void) {
self.content = content
self.preview = preview
self.menu = menu
self.navigate = navigate
}
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let hostView = UIHostingController(rootView: content)
hostView.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostView.view.topAnchor.constraint(equalTo: view.topAnchor),
hostView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostView.view.widthAnchor.constraint(equalTo: view.widthAnchor),
hostView.view.heightAnchor.constraint(equalTo: view.heightAnchor)
]
view.addSubview(hostView.view)
view.addConstraints(constraints)
let interaction = UIContextMenuInteraction(delegate: context.coordinator)
view.addInteraction(interaction)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UIContextMenuInteractionDelegate {
var parent: ContextMenuHelper
init(_ parent: ContextMenuHelper) {
self.parent = parent
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil) {
let previewController = UIHostingController(rootView: self.parent.preview)
return previewController
} actionProvider: { items in
return self.parent.menu
}
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
parent.navigate()
}
}
}
extension View {
func contextMenu<Preview: View>(navigate: @escaping () -> Void = {}, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) -> some View {
return CustomContextMenu(navigate: navigate, content: {self}, preview: preview, menu: menu)
}
}
struct CustomContextMenu<Content: View, Preview: View>: View {
var content: Content
var preview: Preview
var menu: UIMenu
var navigate: () -> Void
init(navigate: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content, @ViewBuilder preview: @escaping () -> Preview, menu: @escaping () -> UIMenu) {
self.content = content()
self.preview = preview()
self.menu = menu()
self.navigate = navigate
}
var body: some View {
ZStack {
content
.overlay(ContextMenuHelper(content: content, preview: preview, menu: menu, navigate: navigate))
}
}
}
用法:
.contextMenu(navigate: {
UIApplication.shared.open(url) //User tapped the preview
}) {
LinkView(link: url.absoluteString) //Preview
.environment(\.managedObjectContext, viewContext)
.accentColor(Color(hex: "59AF97"))
.environmentObject(variables)
}menu: {
let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
withAnimation() {
UIApplication.shared.open(url)
}
}
let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
return menu
}
用于导航:
添加 isActive: $navigate 到您的 NavigationLink:
NavigationLink(destination: SomeView(), isActive: $navigate)
连同新的 属性:
@State var navigate = false
.contextMenu(navigate: {
navigate.toggle() //User tapped the preview
}) {
LinkView(link: url.absoluteString) //Preview
.environment(\.managedObjectContext, viewContext)
.accentColor(Color(hex: "59AF97"))
.environmentObject(variables)
}menu: {
let openUrl = UIAction(title: "Open", image: UIImage(systemName: "sidebar.left")) { _ in
withAnimation() {
UIApplication.shared.open(url)
}
}
let menu = UIMenu(title: url.absoluteString, image: nil, identifier: nil, options: .displayInline, children: [openUrl]) //Menu
return menu
}