Swift Custom Context Menu Previewprovider Can Not Click Any View Inside(Using Tapgesture)

swift custom context menu previewprovider can not click any view inside(using tapgesture)

i changed to present new VC with hero animation, so bored, but it is my best choice for now

Remove BackgroundView from UITargetedPreview in Swift

There is a tricky method to hide the shadow and to do that you should find a view with _UIPlatterSoftShadowView class name in the view hierarchy and then hide it.

func viewByClassName(view: UIView, className: String) -> UIView? {
let name = NSStringFromClass(type(of: view))
if name == className {
return view
}
else {
for subview in view.subviews {
if let view = viewByClassName(view: subview, className: className) {
return view
}
}
}
return nil
}

override func tableView(_ tableView: UITableView, willDisplayContextMenu configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
DispatchQueue.main.async {
if let window = UIApplication.shared.delegate?.window! {
if let view = self.viewByClassName(view: window, className: "_UIPlatterSoftShadowView") {
view.isHidden = true
}
}
}
}

NOTE: It's not documented internal class and can be changed anytime further but it works now on both ios 13/14.

Present VC modally above other VC - both touchable

present function will make the second view controller cover the whole page and you will no longer have access from screen to the view controller below the top one.

To accomplish your goal, you need to put a UITableView on a view, then keep showing or hiding the view. If you really want another view controller, you can create a containing view and have a embed segue to another view controller, like this

Sample Image

How to disable long-press animation of context menu for CollectionView iOS Swift?

Pass return nil insted of return UIContextMenuConfiguration(identifier: nil, previewProvider: nil)

func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {

switch collectionView {
case favoriteCollectionView:
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) {...}
case allCollectionView:
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) {...}
default:
return nil //<-- HERE
}
}

How to create Whats App preview animation?

That is UIContextMenuConfiguration which supports iOS13 onwards.

You can see a preview and menu when you long press on tableViewCell. You can display any viewController or any customView as menu's preview component.

I have displayed a viewController as preview.

// MainViewController is first ViewController and DetailsViewController is second ViewController
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

var castNameArr = ["Professor", "Berlin", "Nairobi", "Tokiyo", "Rio", "Denver", "Mosco", "Requel", "Helsinki", "Arturo"]
var selectedCast = ""
var detailVc = DetailsViewController()

override func viewDidLoad() {
super.viewDidLoad()
title = "Money Heist Casts"
detailVc = self.storyboard?.instantiateViewController(withIdentifier: "DetailsViewController") as! DetailsViewController
}
}

Using contextMenuConfigurationForRowAt method of tableView we can create menu and preview.

  func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
selectedCast = castNameArr[indexPath.row]
let identifier = "\(indexPath.row)" as NSString

return UIContextMenuConfiguration(
identifier: identifier,
previewProvider: makePreview) { _ in

let optionOne = UIAction(
title: "Option 1",
image: UIImage(systemName: "arrowshape.turn.up.right")){ _ in
//This will get called when you tap on Option 1
}

let optionTwo = UIAction(
title: "Option 2",
image: UIImage(systemName: "book.fill")){ _ in
//This will get called when you tap on Option 2
}

return UIMenu(title: "", image: nil, children: [optionOne, optionTwo])
}
}

// This will return a viewController which we want to display as preview
func makePreview() -> UIViewController {
detailVc.castNameStr = selectedCast
return detailVc
}

Using willPerformPreviewActionForMenuWith method will get called when the interaction is about to "commit" in response to the user tapping the preview.

func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
DispatchQueue.main.async {
print(configuration.identifier as? String)
self.navigationController?.pushViewController(self.detailVc, animated: true)
}
}

This is DetailsViewController which is shown as preview

class DetailsViewController: UIViewController {

@IBOutlet weak var titleLbl: UILabel!
@IBOutlet weak var castImgView: UIImageView!
var castNameStr = ""
override func viewDidLoad() {
super.viewDidLoad()

}

override func viewWillAppear(_ animated: Bool) {
titleLbl.text = "This is \(castNameStr)"
castImgView.image = UIImage(named: castNameStr)
}

Download demo from Here

Sample Image

For more details you can check here

https://www.raywenderlich.com/6328155-context-menus-tutorial-for-ios-getting-started

https://medium.com/better-programming/creating-a-context-menu-and-sf-symbol-in-swift-e45459f5f704

SwiftUI detect when contextMenu is open

A possible approach is to use simultaneous gesture for this purpose, like

Text("Demo Menu")
.contextMenu(menuItems: {
Button("Button") {}
})
.simultaneousGesture(LongPressGesture(minimumDuration: 0.5).onEnded { _ in
print("Opened")
})

Tested with Xcode 13.2 / iOS 15.2

iOS 14 Context Menu from UIView (Not from UIButton or UIBarButtonItem)

After some experimentation I was able to remove the dimming blur, like this. You will need a utility method:

extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type,
recursing:Bool = true) -> [T] {
var result = self.subviews.compactMap {$0 as? T}
guard recursing else { return result }
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}

Now we use a context menu interaction delegate method to find the UIVisualEffectView that is responsible for the blurring and eliminate it:

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
DispatchQueue.main.async {
let v = self.view.window!.subviews(ofType:UIVisualEffectView.self)
if let v = v.first {
v.alpha = 0
}
}
}

Typical result:

Sample Image

Unfortunately there is now zero shadow at all behind the menu, but it's better than the big blur.

And of course it’s still a long press gesture. I doubt anything can be done about that! If this were a normal UILongPressGestureRecognizer you could probably locate it and shorten its minimumPressDuration, but it isn't; you have to subject yourself to the UIContextMenuInteraction rules of the road.


However, having said all that, I can think of a much better way to do this, if possible: make this UIView be a UIControl! Now it behaves like a UIControl. So for example:

class MyControl : UIControl {
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: { _ in
let act = UIAction(title: "Red") { action in }
let act2 = UIAction(title: "Green") { action in }
let act3 = UIAction(title: "Blue") { action in }
let men = UIMenu(children: [act, act2, act3])
return men
})
return config
}
}

And:

let v = MyControl()
v.isContextMenuInteractionEnabled = true
v.showsMenuAsPrimaryAction = true
v.frame = CGRect(x: 100, y: 100, width: 200, height: 100)
v.backgroundColor = .red
self.view.addSubview(v)

And the result is that a simple tap summons the menu, which looks like this:

Sample Image

So if you can get away with that approach, I think it's much nicer.



Related Topics



Leave a reply



Submit