How to implement interactive cell swiping to trigger transitioning animation like WhatsApp
Asked Answered
C

4

9

I'm looking for how to implement like WhatsApp cell swiping, I already have implemented the cell swiping animation using UIPanGestureRecognizer, the only left is performing the interactive animation -adding the new UIViewController to the window and showing it based on the gesture recognizer velocity and X-axis value-.

Some additional note to be accurate on what I want to achieve:

  • I have a UITableViewController, which has custom UITableViewCells in it. I want to be able to drag a cell from left to right to start the interactive animations. (Note: I already have implemented the cell swiping).

  • The new UIViewController will be pushed from left right.

  • While swiping the cell, the UITableViewController's view will be moving to the right, at that point, I want to show the pushing UIViewController beside it.

Here's a GIF for more details on what I need (The GIF is swiping the cell from right to left, I need the opposite):

enter image description here

Ciprian answered 14/6, 2017 at 20:55 Comment(4)
Not a good idea to delete your question and then repost the same thing... My comment was to "slide a view over the otter one" to which you said no, you want to use a segue. Are you now looking for some other suggestions?Costard
No I'm not sticking with the segue. I just need any idea. I have been trying actually to do it for more than 2 days, so ideas are welcomed.Ciprian
You probably want to look into custom View Controller Transitions. On place to start is Apple's docs: developer.apple.com/documentation/uikit/animation_and_haptics/… - in particular for what you're trying to do look at UIViewControllerInteractiveTransitioning. You also might want to play around with one of the many examples available by searching for iOS slide menu or iOS slide drawerCostard
Did you get your question answered?Rockie
R
6

I suggest using SWRevealViewController. It is very easy to set up using their guide and it looks absolutely great. I have found that it even works better when you pre-load the UIViewController that you use to be what is shown underneath.

It adds a great user experience for the functionality you are looking for.

enter image description here

It can also be user interactive if you wish to opt-in to that functionality. I have not used the interactive feature but it is very easy to get up and running with just a few lines of code:

let storyboard = UIStoryboard(name: "Main", bundle: .main)
let mainVC = storyboard.instantiateInitialViewController()
let menuStoryboard = UIStoryboard(name: "Menu", bundle: sdkBundle)
let menuNav = menuStoryboard.instantiateInitialViewController() as! UINavigationController
let mainRevealVC = SWRevealViewController(rearViewController: menuNav, frontViewController: mainVC)
mainRevealVC?.modalTransitionStyle = .crossDissolve
present(mainRevealVC!, animated: true, completion: nil)

And to get the reveal UIViewController to get shown, you just call

// Every UIViewController will have a `self.revealViewController()` when you `import SWRevealViewController`
self.revealViewController().revealToggle(animated: true)
Rockie answered 21/6, 2017 at 19:39 Comment(0)
C
1

I agree with @DonMag, a iOS slide menu might be your best bet. Here is an example of a simple one: SimpleSideMenu

Copenhagen answered 21/6, 2017 at 19:14 Comment(0)
H
1

Does it necessarily have to be a new controller behind the table view? Let me try to explain my approach on the WhatsApp example. Let's assume that the app has ChatController that has the table view with the chat and a ChatDetailController that is revealed with the swipe.

When you select a conversation, instead of presenting a ChatController present instead a ChatParent, that automatically creates and adds two children. The ChatController and ChatDetailController. Next define a protocol called SwipeableCellDelegate with a function cellDidSwipe(toPosition position: CGPoint) and make the ChatParent conform to it. When the cell is swiped, the parent can make the decision whether should the chat be moved away and if so, then how much. It can then simply move the ChatController view directly through its .view property, revealing the second child, the ChatDetailController behind it.

There are two downsides to this compared to the gif you posted.

  1. The navigation bar doesn't fade from chat to chat detail. I would, however, argue that it is better to update the navigation bar when the animation completes, at least I personally am not a fan of this fade through where you can see both sets of navigation items at times. I would think that if chat is on screen then chat items should be present and only when detail view fully appears should the items be updated.
  2. Second thing is the animated keyboard dismissal. I have no idea how to change keyboard frame to make it disappear proportionally to how far the user scrolls, but perhaps it could be dismissed automatically as soon as a swipe is detected? This is standard practice among many apps so it should be a decent solution.

Best of luck!

Hyperventilation answered 23/6, 2017 at 8:6 Comment(0)
C
0

There is a very simple yet Perfect for your situation Library called SWNavigationController which implements just like UINavigationController's interactivePopGestureRecognizer also interactivePushGestureRecognizer. In your case you don't want the push to be triggered from UIScreenEdgePangesturerecognizer so you're better off customizing the implementation rather than installing the pod which is what I did. Here you can find the full simple project that does just what you asked.

I've made few modifications to SWNavigationController to support replacing UIScreenEdgePangesturerecognizer with a UIPanGestureRecognizer

import UIKit

// First in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    self.window = UIWindow(frame: UIScreen.main.bounds)

    let firstVc = ViewController()

    let initialViewController: SWNavigationController = SWNavigationController(rootViewController: firstVc)

    self.window?.rootViewController = initialViewController
    self.window?.makeKeyAndVisible()

    return true
}

// Your chat viewController
class ViewController: UIViewController {

    var backgroundColors: [IndexPath : UIColor] = [ : ]

    var swNavigationController: SWNavigationController {
        return navigationController as! SWNavigationController
    }

    /// The collectionView if you're not using UICollectionViewController
    lazy var collectionView: UICollectionView = {
        let cv: UICollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.layout)
        cv.backgroundColor = UIColor.white

        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        cv.dataSource = self

        return cv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "chat vc"
        view.addSubview(collectionView)

        let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:)))
        panGestureRecognizer.delegate = self
        collectionView.addGestureRecognizer(panGestureRecognizer)

        // Replace navigation controller's interactivePushGestureRecognizer with our own pan recognizer.
        // SWNavigationController uses uiscreenedgerecognizer by default which we don't need in our case.
        swNavigationController.interactivePushGestureRecognizer = panGestureRecognizer
    }

    func handlePan(_ recognizer: UIPanGestureRecognizer) {

        guard fabs(recognizer.translation(in: collectionView).x) > fabs(recognizer.translation(in: collectionView).y) else {
            return
        }

        // create the new view controller upon .began
        if recognizer.state == .began {

            // disable scrolling(optional)
            collectionView.isScrollEnabled = false

            // pan location
            let location: CGPoint = recognizer.location(in: collectionView)

            // get indexPath of cell where pan is taking place
            if let panCellIndexPath: IndexPath = collectionView.indexPathForItem(at: location) {

                // clear previously pushed viewControllers
                swNavigationController.pushableViewControllers.removeAllObjects()

                // create detail view controller for pan indexPath
                let dvc = DetailViewController(indexPath: panCellIndexPath, backgroundColor: backgroundColors[panCellIndexPath]!)

                swNavigationController.pushableViewControllers.add(dvc)
            }
        } else if recognizer.state != .changed {
            collectionView.isScrollEnabled = true
        }

        // let navigation controller handle presenting
        // (you can consume the initial pan translation on x axis to drag the cell to the left until a defined threshold and call handleRightSwipe: only after that)
        swNavigationController.handleRightSwipe(recognizer)
    }

}

// Cell detail view controller
class DetailViewController: UIViewController {

    var indexPath: IndexPath

    var backgroundColor: UIColor

    init(indexPath: IndexPath, backgroundColor: UIColor) {
        self.indexPath = indexPath
        self.backgroundColor = backgroundColor

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.title = "detail vc at: \(indexPath.row)"

        view.backgroundColor = backgroundColor
    }

}
Centonze answered 23/6, 2017 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.