Detecting UITouches while UIViewController is being shown as 3DTouch preview
Asked Answered
L

1

7

Is it possible to detect touches and get the location of a touch from a UIViewController which is being currently used as previewingContext view controller for 3D Touch? (I want to change the image of within the preview controller when the touch moves from left to right)

I've tried both touchesBegan and touchesMoved none of them are fired.

class ThreeDTouchPreviewController: UIViewController {

func getLocationFromTouch(touches: Set<UITouch>) -> CGPoint?{
        guard let touch  = touches.first else { return nil }
        return touch.location(in: self.view)
    }
    //Not fired
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

        let location = getLocationFromTouch(touches: touches)
        print("LOCATION", location)
    }
    //Not fired
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let location = getLocationFromTouch(touches: touches)
        print("LOCATION", location)
    }
}

Even tried adding a UIPanGesture.

Attempting to replicate FaceBook's 3D Touch feature where a user can move finger from left to right to change the current image being displayed. Video for context: https://streamable.com/ilnln

Leaved answered 21/6, 2017 at 3:37 Comment(0)
F
6

If you want to achive result same as in FaceBook's 3D Touch feature you need to create your own 3D Touch gesture class

    import UIKit.UIGestureRecognizerSubclass

class ForceTouchGestureRecognizer: UIGestureRecognizer {

    var forceValue: CGFloat = 0
    var isForceTouch: Bool = false

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        handleForceWithTouches(touches: touches)
        state = .began
        self.isForceTouch = false
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesMoved(touches, with: event)
        handleForceWithTouches(touches: touches)
        if self.forceValue > 6.0 {
            state = .changed
            self.isForceTouch = true
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesEnded(touches, with: event)
        state = .ended
        handleForceWithTouches(touches: touches)
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesCancelled(touches, with: event)
        state = .cancelled
        handleForceWithTouches(touches: touches)
    }

    func handleForceWithTouches(touches: Set<UITouch>) {
        if touches.count != 1 {
            state = .failed
            return
        }

        guard let touch = touches.first else {
            state = .failed
            return
        }
        forceValue = touch.force
    }
}

and now you can add this gesture in your ViewController in viewDidLoad method

override func viewDidLoad() {
    super.viewDidLoad()
    let gesture = ForceTouchGestureRecognizer(target: self, action: #selector(imagePressed(sender:)))
    self.view.addGestureRecognizer(gesture)
}

Now you can manage your controller UI in Storyboard. Add cover view above UICollectionView and UIImageView in a center and connect it with IBOutlets in code.

Now you can add handler methods for gesture

func imagePressed(sender: ForceTouchGestureRecognizer) {

let location = sender.location(in: self.view)

guard let indexPath = collectionView?.indexPathForItem(
    at: location) else { return }

let image = self.images[indexPath.row]

switch sender.state {
case .changed:
    if sender.isForceTouch {
        self.coverView?.isHidden = false
        self.selectedImageView?.isHidden = false
        self.selectedImageView?.image = image
    }

case .ended:
    print("force: \(sender.forceValue)")
    if sender.isForceTouch {
        self.coverView?.isHidden = true
        self.selectedImageView?.isHidden = true
        self.selectedImageView?.image = nil
    } else {
        //TODO: handle selecting items of UICollectionView here,
        //you can refer to this SO question for more info: https://mcmap.net/q/1623872/-collectionview-didn-39-t-call-didselectitematindexpath-when-superview-has-gesture
        print("Did select row at indexPath: \(indexPath)")
        self.collectionView?.selectItem(at: indexPath, animated: true, scrollPosition: .centeredVertically)
    }

default: break
}

}

From this point you need to customize your view to make it look in same way as Facebook do.

Also I created small example project on GitHub https://github.com/ChernyshenkoTaras/3DTouchExample to demonstrate it

Frederickafredericks answered 30/6, 2017 at 12:7 Comment(2)
Just tested this out, great idea but doesn't work well with UICollectionView, it disables scrolling and tap actionsLeaved
You are right. I've answer and fixed code on GitHub. Now scrolling is fixed, but you need to handle collectionView items selecting in imagePressed: functionFrederickafredericks

© 2022 - 2024 — McMap. All rights reserved.