App is freezing in 'peek and pop' implementation in iPhone 6s - ios

I have implemented peek and pop in my app and it works perfectly. But on continuously trying it for 7-8 times, the app freezes on peek view. The only option I have is to kill the app and re-run. Please let me know the reason for the freeze. I have used the following code for peek and pop in my project:
var isPresentedBy3Dtouch: Bool = false
var passedDetails:DetailModel!
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = tableView?.indexPathForRowAtPoint(location)
else { return nil }
guard let cell = tableView?.cellForRowAtIndexPath(indexPath)
else { return nil }
guard let detailViewController = self.storyboard?.instantiateViewControllerWithIdentifier("Navigation") as? UINavigationController
else { return nil }
(detailViewController.topViewController as! DetailViewController).passedDetails = self.customerLists[indexPath.row]
(detailViewController.topViewController as! DetailViewController).isPresentedBy3Dtouch = true
detailVC.preferredContentSize = CGSize(width: 0.0, height: 480.0)
previewingContext.sourceRect = cell.frame
return detailVC
}
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit :UIViewController) {
showViewController(viewControllerToCommit, sender: self)
}

This is an issue I have brought up to the engineers over at Apple months ago with no answer from them so far. If you debug the view hierarchy, you'll notice that a UITransitionView layer is the top-most view and it is not being removed. That's what's causing the app to freeze. Actually, the functionality of the app isn't frozen - it still works as expected, but the UI is "stuck." Here is my original post here on Stack Overflow: Force Touch animation freezes if gently touched

I found a reason for this bug.
If your view controller need to support preview for force touch, you need register this vc with delegate by calling - (id <UIViewControllerPreviewing>)registerForPreviewingWithDelegate:(id<UIViewControllerPreviewingDelegate>)delegate sourceView:(UIView *)sourceView NS_AVAILABLE_IOS(9_0); method to do this.
I just suddenly call this function twice (once in super class 's viewDidLoad(), once in sub vc's), and when I remove once in my sub vc, this bug fixed! Amazing...
It's still an Apple bug since it makes no sence for that happens. However, wish this answer can help developers who has the same issue with me.

I found another possible reason for this bug.
In the viewControllerForLocation I instantiated a view controller to show...
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
let vc = VCImageFullScreen.loadView()
return vc
}
...but this ViewController had a wrong super call in its viewDidAppear:
class VCImageFullScreen : UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//BUG --> should be super.view**Did**Appear(animated)
...
}
}
After fixing this issue everything worked as expected.

Related

Peek & pop does not trigger only on the last cell

I have a ProfileVC that contain a list.
I can click on any of the row cell will show peek and pop feature.
ProfileVC.swift
I added the extention
extension ProfileViewController : UIViewControllerPreviewingDelegate {
func detailViewController(for indexPath: IndexPath) -> ProfileDetailViewController {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "ProfileDetailViewController") as? ProfileDetailViewController else {
fatalError("Couldn't load detail view controller")
}
let cell = profileTableView.cellForRow(at: indexPath) as! ProfileTableViewCell
// Pass over a reference to the next VC
vc.title = cell.profileName?.text
vc.cpe = loginAccount.cpe
vc.profile = loginAccount.cpeProfiles[indexPath.row - 1]
consoleLog(indexPath.row - 1)
//print("3D Touch Detected !!!",vc)
return vc
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if let indexPath = profileTableView.indexPathForRow(at: location) {
// Enable blurring of other UI elements, and a zoom in animation while peeking.
previewingContext.sourceRect = profileTableView.rectForRow(at: indexPath)
return detailViewController(for: indexPath)
}
return nil
}
//ViewControllerToCommit
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
// Push the configured view controller onto the navigation stack.
navigationController?.pushViewController(viewControllerToCommit, animated: true)
}
}
Then, in the same file ProfileVC.swift in viewDidLoad() I registered it
if (self.traitCollection.forceTouchCapability == .available){
print("-------->", "Force Touch is Available")
registerForPreviewing(with: self, sourceView: view)
}
else{
print("-------->", "Force Touch is NOT Available")
}
Result
I have no idea why I can not click on the 4th cell.
The last cell of the row does not trigger Peek & Pop.
How would one go about and debug this further?
You are registering your view controller's root view as the source view for the peek context. As a result, the CGPoint that is passed to previewingContext(_ viewControllerForLocation:)` is in the coordinate space of that view.
When you try and retrieve the corresponding row from your table view the point will actually be offset from the corresponding point in the table view's frame based on the relative position of the table view in the root view.
This offset means that a corresponding row can’t be retrieved for the last row in the table; indexPathForRow(at:) returns nil and your function returns without doing anything.
You might also find that if you force touch towards the bottom of a cell you actually get a peek for the next row.
You could translate the CGPoint into the table view’s frame, but it is simpler to just specify your tableview as the source view when you register for previewing:
if (self.traitCollection.forceTouchCapability == .available){
print("-------->", "Force Touch is Available")
registerForPreviewing(with: self, sourceView: self.profileTableView)
}
else{
print("-------->", "Force Touch is NOT Available")
}

Get indexPath for at Point when scrolling tableView

I want to get the cell on which I pressed when I preview (UIViewControllerPreviewing), the problem is that the location that returns the method is on the view and not on the actual position of the cell when scrolling in the tableview. I'm trying this:
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "TaskDetail", bundle: nil)
guard let detailViewController = mainStoryboard.instantiateViewController(withIdentifier: "DetailTaskViewController") as? DetailTaskViewController else { return nil }
guard let indexPath = self.listTasksUITableView.indexPathForRow(at: location) else { return nil }
print(indexPath.row)
}
This would return an indexpath of eg 2, when I actually scroll to row 18
As #Sulthan notes in the comments, you need to convert the coordinate to the 'local' coordinate system of listTasksUITableView with the UIView instance method convert(_:from:).
Put this line*
let convertedLocation = listTasksUITableView.convert(location, from: self)
before
guard let indexPath = ...
and use convertedLocation in that line instead of location.
I needed a similar trick for a Xamarin project where I was manually calculating coordinates (similar to location.Y - tableView.Y) and that failed when the view was scrolled. This way of converting does apply scrolling into account.
*: my Swift is too rusty to tell me whether to put guard, ! etc. there. I always rely on the compiler to tell me that.

Programmatically turns the page, indicators does not change

I try the following approach found here
extension UIPageViewController {
func goToNextPage(){
guard let currentViewController = self.viewControllers?.first else { return }
guard let nextViewController = dataSource?.pageViewController( self, viewControllerAfter: currentViewController ) else { return }
setViewControllers([nextViewController], direction: .forward, animated: true, completion: nil)
}
}
It works, but there is one issue:
When the page is turned programatically, the indicator does not move. It seems that they move only when user turns. page with swipe
that's how indicators should look like after programmatic turn is performed:
instead they remain unchanged
Which leads to issue that hierarchy shown by indicators is rather [2,0,1] instead of [0,1,2]
This is how I implement indicators:
func presentationCount(for PageViewController:UIPageViewController) -> Int {
return 3
}
func presentationIndex(for PageViewController:UIPageViewController) -> Int {
return 0
}
How to make dots indicators move when the page is turned programatically?
Unfortunately you can't update UIPageControl embedded in UIPageViewController. However, you can have your own UIPageControl in UIPageViewController in order to get full control. Then you can update UIPageControl property programmatically upon updating your Page. Have a look at this article.
There is a workaround get the subviews of UIPageViewController, set the value to currentPage.
for subView in self.view.subviews{
if let pageControl = subView as? UIPageControl,
pageControl.numberOfPages > currentIndex{
pageControl.currentPage = currentIndex
}
}
You can, all you need to do is maintain the page index in a variable, let's call it currentPageIndex and use the following method:
// Part of UIPageViewControllerDelegate
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return currentPageIndex;
}
// In your Button action, set this variable
#IBAction func nextPage(_ sender: Any) {
var index = (pageViewController?.viewControllers?.last as! SinglePageViewController).pageIndex
currentPageIndex = index
}
That's it!! Your page indicator should work now.
I was stuck in a same situation as yours.

layoutAttributesForItemAtIndexPath doesn't return the actual position when performing peek and pop

I am trying to implement Peek and Pop to preview the image on photo library with the following code:
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
if let indexPath = self.collectionView!.indexPathForItemAtPoint(location), cellAttributes = self.collectionView!.layoutAttributesForItemAtIndexPath(indexPath) {
previewingContext.sourceRect = cellAttributes.frame
}
let selectedPhotoAsset = self.allPhotos[self.selectedPhotoIndexPath![0].item] as! PHAsset
guard let displayImageDetailVC = storyboard?.instantiateViewControllerWithIdentifier("DisplayImageDetailVC") as? DisplayImageViewController else {
print("Error instantiate DisplayImageDetail View Controller")
return nil
}
displayImageDetailVC.selectedPhotoAsset = selectedPhotoAsset
return displayImageDetailVC
}
Even though it works, but the problem is after the collection view scrolled, the preview area sometimes is not at the exact cell's position when trying to peek.
Is there any way that I can get the actual position of the cell?
I have found the solution for this issue, turns out it is related to registerForPreviewingWithDelegate.
Instead of
registerForPreviewingWithDelegate(self, sourceView: view)
It should be
registerForPreviewingWithDelegate(self, sourceView: self.collectionView!)

How to register for previewing single cell under 3D Touch?

Currently I have the following code in viewDidLoad:
if #available(iOS 9.0, *) {
if traitCollection.forceTouchCapability == .Available {
registerForPreviewingWithDelegate(self, sourceView: tableView)
}
}
The point is that in this case blurred is everything out of UITableView instead of everything out of that one specific UITableViewCell.
How can I expose only one cell?
The state of this image in during forcing 3d touch.
This should be in viewDidLoad():
if traitCollection.forceTouchCapability == .available {
registerForPreviewing(with: self, sourceView: tableView)
}
Then in previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) add this:
guard let selectedIndexPath = tableView.indexPathForRow(at: location) else { return nil }
previewingContext.sourceRect = tableView.rectForRow(at: selectedIndexPath)
You did right, you do need to register for previewing in the viewDidLoad
.
Although, you also need to implement the previewingContext method, to provide a source rect. Basically, you'll give the cell as the source rect, and everything outside of it will become blurry.
Please checkout this Peek and Pop tutorial for more details.
You can also checkout the sample of code

Resources