I just started adding basic 3D Touch functionality to my app, and the first attempt at adding it has gone well, seems fairly straightforward.
I was wondering however whether there was a way to detect that a peek had finished, and not gone into the pop.
The UIViewControllerPreviewingDelegate methods are good for telling you that a peek or pop is requested but I don't see a way to be told that the peek has ended and NOT gone into a pop.
Does the Peeked ViewController have a way of knowing it's peeked at the moment and going away as I guess this would be sufficient. Basically I have a segue that normally creates some things as it goes into the view, which if I peek into it would need to be undone if the user chooses to just end the peek without popping in. At the moment I can't seem to see a good way of detecting this case to be able to perform the required clean up.
Cheers
When you register for previewing using registerForPreviewingWithDelegate()
, this returns a context that conforms to the UIViewControllerPreviewing
protocol. That protocol contains a reference to the gesture recognizer that is used in peeking/popping, called previewingGestureRecognizerForFailureRelationship
. It is intended to use when other gesture recognizers might be recognized simultaneously, but you can also add your own object as a target to observe changes.
Now, when you are peeking, the state of this gesture recognizer will be .Changed
. When you release without popping, the state will change to .Ended
. When you do pop, the state will change to .Cancelled
(I actually expected this to be the other way, but at least we can tell the difference). Importantly, this state changes before the viewDidDisappear
of your peeked view controller is called, so you can adjust your flag in time.
I had the same issue in my app where I needed to know when a view controller started and stopped being peeked at and came up with the following.
In order to monitor the lifecycle of a peek, you can track the lifecycle of the view controller being peeked at, starting with the view controller being created in previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController
, and ending with its viewDidDisappear()
.
I created a callback handler in the view controller being peeked at, PeekingViewController
,
var viewDidDisappearHandler: (()->())? = nil
and placed it in PeekingViewController
's viewDidDisappear
as such:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
viewDidDisappearHandler?()
}
Back in OriginalViewcontroller
, where we're peeking at PeekingViewController
from, keep a weak reference to the instance of the view controller being peeked at like so:
weak var peekingViewController: PeekingViewController?
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
self.peekingViewController = PeekingViewController()
return peekingViewController
}
Then, you can observe changes to the weak reference to the view controller being peeked at by filling in didSet in the peekingViewController
instance like so:
weak private var peekingViewController: PeekingViewController? {
didSet {
peekingViewController?.viewDidDisappearHandler = { [weak self] in
self?.peekingViewController = nil
}
if peekingViewController == nil { // Peek ended
handlePeekEnded()
} else { // Peek began
handlePeekBegan()
}
}
}
Note:
This logic will be triggered if a peak is performed and cancelled, but also if a peak is performed, the segue is completed, and then the newly presented PeekingViewController
is popped.
If you need logic regarding the peek being cancelled to only be triggered with an incomplete peek, and not a full peak and then a dismissal, you can achieve this by:
including a new boolean in OriginalViewController
to track if a view controller was completely pushed (OriginalViewController
's viewDidDisappear
will fire on a complete push, but not on a peek), and checking that boolean in peekingViewController
's didSet
, to determine if any action should be taken, and setting peekingViewController
to nil in OriginalViewController
's viewWillAppear
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With