I want to implement same custom popup view when press long press gesture on a view as shown in the photo (from Tweeter App), so I can show a custom view and context menu at same time.
Asked
Active
Viewed 3,781 times
2 Answers
3
You need to make a custom ContextMenu
using UIContextMenu
from UIKit
.
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))
}
}
}
Usage:
.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
}
For navigation:
add isActive
: $navigate
to your NavigationLink
:
NavigationLink(destination: SomeView(), isActive: $navigate)
along with a new property:
@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
}

Timmy
- 4,098
- 2
- 14
- 34
-
The custom Context Menu is perfect but I don't want to open url instead I want to show another custom view (just a simple view). – Ammar Ahmad Jan 21 '22 at 17:20
-
2Yes I know you can replace it with whatever you want this is an example, check my updated answer! – Timmy Jan 21 '22 at 17:26
3
There is a new method in iOS 16 SDK (currently in beta) that allows for showing a preview directly from SwiftUI without the need of tapping into the UIKit.

Witek Bobrowski
- 3,749
- 1
- 20
- 34
-
This is an improvement, but as far as I can see, this unfortunately does not yet support a tap gesture or similar on the preview as would be possible with UIKit. – nylki Mar 28 '23 at 10:21