Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conflict with iOS 13 UIContextMenu functionality #63

Open
thejeff77 opened this issue Sep 15, 2019 · 10 comments
Open

Conflict with iOS 13 UIContextMenu functionality #63

thejeff77 opened this issue Sep 15, 2019 · 10 comments

Comments

@thejeff77
Copy link

Hey there,

I opened a PR for an example of this issue. Check it out - if you add UIContextMenu interactions to your cells via the new iOS 13 APIs, the reordering doesn't work very well. This is a gesture recognizer conflict. I'm not sure how to fix it. Suggestions are encouraged :)

@thejeff77 thejeff77 changed the title Doesn't work well with iOS 13 UIContextMenu functionality Conflict with iOS 13 UIContextMenu functionality Sep 15, 2019
@adamshin
Copy link
Owner

Hmm.. I don't know if there's a simple solution here. Both the reorder action and the context menu are triggered by a long press gesture, so even if you adjusted the press duration, one of them would always trigger before the other. What kind of behavior would you expect in this case?

@thejeff77
Copy link
Author

Great question. And to back up a little further - what kind of function would apple expect on elements that previously used 3D touch for details (popover preview thing), and now deprecated it for long press for elements that previously had a long press for something else?

I think the touch to reorder and showing details on TableViews and CollectionViews is pretty common.

I think I'll open a Code Help request with Apple to see what kind of best practices they recommend for situations like this.

I was able to get this working decently with a really low press duration on the reorder library - and also adding a haptic for when the cell pops for reordering (so the user knows they can drag it within the short window) - however it is obvious that the context menu animation starts pretty quick and the two animations / gestures are still getting in each other's way.

@thejeff77
Copy link
Author

thejeff77 commented Sep 20, 2019

The result is...

Apple fixed it for their own drag and drop apis - the drag and drop delegate. I'm not sure why someone hasn't made a library that makes this simpler use case of drag and drop in a table view with Apple's APIs, just abstracting out the huge amount of code you need for it.

So now the pull request I opened basically works the same, without some of the animation clipping... and theirs is a bit easier to drag than my example. I have sample code I could send from Apple if you want it. My bet is you could spin up a library with that example in a day - but that this is also probably not your main interest anymore, lol.

I've updated the example in the PR so it works as well as it can, and cleaned it up a lot.

I've figured out how to identify the gesture recognizer that the context menu adds to the object - It might be possible to link SwiftReorder's gesture recognizer to that one with this gesture method:

open func require(toFail otherGestureRecognizer: UIGestureRecognizer)

Perhaps if a threshold is reached for the Pan on the reorderGestureRecognizer it could cancel the contextMenu one?

Anyway.. unless you have ideas or more feedback - I'll wait and see what you think and keep the PR open for you to merge, reject, or use as a starting point to play around with options.

@adamshin
Copy link
Owner

Yeah – for most people implementing drag-and-drop today, the built in APIs are probably a better place to start. I think this library still provides useful functionality, especially if you need to customize the interaction or support older versions of iOS. The advantage of using native APIs of course is that they always stay up to date... :)

If there's a simple fix for this I'd be glad to merge it in. However, I'm wondering if this is an issue many people will run into. From a ux perspective it seems odd to have two interactions (drag and drop, context menu) triggered by the same long-press gesture. I doubt it's something Apple would recommend, considering the general experience around context menus.

You mentioned you're able to get the gesture recognizer for the context menu – is it accessible through public APIs, or do you have to go find it in the view hierarchy? Maybe you could close the context menu like you mentioned if the user drags beyond a threshold.

@thejeff77
Copy link
Author

So I was able to fix this so it works really well. In my opinion it works as well as the sample Apple gave me, but perhaps its only 95% or something. The UI clipping is due to the UIContextMenu adding a solid background to the cell while the drag is in progress, which makes it look super weird. I added the same timings to the example I put together, but somehow I'm not able to reproduce the good experience. Its like the override of the timing only works on the first drag-drop.

I'd also say that this library is better than Apple's APIs - waaaaaaay easier to get what you want on a tableview, but it also provides drag and drop for a much larger array of possibilities which I didn't need.

Anyway here is the code I used to customize the preview - basically a shton of code to make the uicontextmenu's cell background clear as it pops out.

Have you tried the sample after I added in the custom timings? I'm curious what you think if you have. If not I might mess around with the sample again and see if I can get it to suck less - cause my bet is that someone else might find it useful.

    @available(iOS 13.0, *)
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
                                previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {

        let parameters = UIPreviewParameters()
        parameters.backgroundColor = UIColor.clear

        //let visibleRect = detailDescriptionLabel.bounds.insetBy(dx: -10, dy: -10)
        guard let cell = self.spacerCell(forInteraction: interaction) else {
            assertionFailure("coult not retrieve the spacer cell for context menu interaction.")
            return nil
        }
        
        let visibleRect = cell.bounds.insetBy(dx: -10, dy: -10)
        let visiblePath = UIBezierPath(roundedRect: visibleRect, cornerRadius: 10.0)
        parameters.visiblePath = visiblePath
        return UITargetedPreview(view: cell, parameters: parameters)
    }
    
    @available(iOS 13.0, *)
    func spacerCell(forInteraction interaction: UIContextMenuInteraction) -> UITableViewCell? {
        guard let indexPath = self.getIndexPath(forInteraction: interaction) else {
            return nil
        }
        
        if let spacer = self.cellForRow(at: indexPath) {
            return spacer
        }
        
        return nil
    }
    
    @available(iOS 13.0, *)
    func index(forInteraction interaction: UIContextMenuInteraction) -> Int? {
        guard let cellIndexPath = self.getIndexPath(forInteraction: interaction) else {
            return nil
        }
        return cellIndexPath.row
    }
    
    @available(iOS 13.0, *)
    func getIndexPath(forInteraction interaction: UIContextMenuInteraction) -> IndexPath? {
        let actualLocation = interaction.location(in: self)
        
        guard let indexPath = self.indexPathForRow(at: actualLocation) else {
            NSLog("ERROR: Couldn't find cell index path for selected point at location for context menu")
            return nil
        }
        
        return indexPath
    }

@adamshin
Copy link
Owner

I haven't tried the sample yet – I'll check it out tomorrow. Thanks for your time and effort on this.

@adamshin
Copy link
Owner

adamshin commented Oct 1, 2019

Hey, sorry it's taken a while for me to get back to you on this. Got sidetracked with some other things last week.

I tried out the example and I'm having a hard time getting the cells to reorder. The context menu almost always triggers before I'm able to drag. It seems intermittent — maybe there's just a small timing window. Is that the same thing you're experiencing, or is it working more consistently for you?

@thejeff77
Copy link
Author

My app functions much better - and as mentioned I'm not able to reproduce the same greate experience in the sample, its almost as if the first drag works ok then it stops working well. I'll try adding the transparent background on the pop and double checking the timings and send you an update.

@thejeff77
Copy link
Author

Well, as I started using the apple drag/drop, it works so much better. You can drag the context menu into a reordering cell and apple's done a ton of work to make the two work seamlessly. I recommend using this, although implementing apple drag drop can be quite difficult without an example, as there is quite a bit you need to do.

@adamshin
Copy link
Owner

adamshin commented Jan 9, 2020

Makes sense, glad it worked out for you. Sounds like that's the best course of action if you need context menu functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants