Get indexPath for at Point when scrolling tableView - ios

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.

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")
}

Force touch on UICollectionView inside of UITableViewCell

I have a viewController with a Tableview, multiple TableViewCells and in each TableViewCell, a UICollectionView with multiple UICollectionViewItems. Each collectionView item has a label and image view. I'm trying to get 3d touch to work so that the user and peek and pop by force touching on areas of the tableCell that don't contain the collection view, to preview and pop into one view controller and then be able to do the same thing with one of the images in the collectionView but preview and pop into a different view controller. I have the first scenario working fine, the tableCell remains sharp on the screen when starting to force touch and "peek". I'm stuck on getting this to work in the collection view, no matter what I do only an image view frame remains sharp on the first tableview row regardless of which row i'm actually pressing. Code below:
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
//get the tableviewCell
if let tableCellPath = tableView.indexPathForRow(at: location) {
print("tableCellPath=", tableCellPath)
if let tableCell = tableView.cellForRow(at: tableCellPath) as? VenueMainTableViewCell {
//user tapped on a beer in the collectionview
if let collectionView = tableCell.collectionView {
let collectionPoint = collectionView.convert(location, from: tableView)
if let cvPath = collectionView.indexPathForItem(at: collectionPoint) {
let collectionViewCell = collectionView.cellForItem(at: cvPath) as? VenueMainCollectionViewCell
let cvFrame = collectionViewCell?.itemLabelImageView.frame
let bc = storyboard?.instantiateViewController(withIdentifier: "itemDetail") as! ItemDetailViewController
let ven = UTcheckin.sharedInstance.Venues[collectionView.tag]
let selectedItem = ven.ItemsAtVenue[(collectionViewCell?.tag)!]
bc.item = selectedItem
previewingContext.sourceRect = cvFrame!
return bc
}
}
}
if let tablePath = tableView.indexPathForRow(at: location) {
//user tapping on a venue, this works
previewingContext.sourceRect = tableView.rectForRow(at: tablePath)
let vc = storyboard?.instantiateViewController(withIdentifier: "venueDetail") as! VenueDetailViewController
vc.venue = UTcheckin.sharedInstance.Venues[tablePath.row]
return vc
}
return nil
}
return nil
}
It seems like I need to get the rect of the collection view item image view but how can I access this since it is in the table cell? Thanks in advance for any pointers.
I think the solution for this is the same as for UITableView. You have to register each cell for previewing using registerForPreviewingWithDelegate method. You should register it in
cellForRow method.
This should be very helpful for you. Especially The Solution Paragraph:
How to Peek & Pop A Specific View Inside a UITableViewCell

indexPathForRow won't return the correct value

I'm trying to use 3D Touch and everything is fine except in the function below I can't get the right value indexPath with indexPathForRow(at: location) when I'm scrolling. This function can't say if user scrolls the table or not and always set the first row that user can see in app as first one.
My code:
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = workoutTable?.indexPathForRow(at: location) else {return nil }
guard let cell = workoutTable?.cellForRow(at: indexPath) else {return nil }
guard let workoutDetailPeek = storyboard?.instantiateViewController(withIdentifier: "WorkoutDetailPeek") as? WorkoutDetailPeek else {print("else31"); return nil }
workoutDetailPeek.workoutId = "\(Int(workout[(indexPath as NSIndexPath).row].workoutId)!-2)"
workoutDetailPeek.preferredContentSize = CGSize(width: 400.0, height: 267.0)
previewingContext.sourceRect = cell.frame
return workoutDetailPeek
}
I been through different 3D Touch implementation, but this function seems to works fine for everyone except me.
In iOS each view has 2 coordinate systems, it's own, and it's superview's. A view's coordinate system almost always has 0,0 in the upper left of the view. That point is likely to be someplace other than 0,0 in it's superview's coordinate system.
When you get a point, you need to know who's coordinate system it's expressed in. There are various methods in UIView that let you convert points between coordinate systems.
In order for your code to work you need to know which view's coordinate system the point is expressed in. Since you're searching for a cell in a table view, using the table view's coordinate system would be a reasonable choice.
I haven't developed for 3D touch devices yet so I'm not familiar with the function you're using. Where does the point come from that you're passing into it, and in what coordinate system is it expressed?
You need to convert into the tableView's coordinate system
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
// convert location to the tableView's coordinate system to get the right cell
let locationInTableViewCoordinate = view.convert(location, to: tableView)
guard let indexPath = tableView.indexPathForRow(at: locationInTableViewCoordinate), let cell = tableView.cellForRow(at: indexPath) else { return nil }
// for the blur you need to to the reverse, since the cell is expressed in the tableView's coordinate and the blur is in the view's
let frame = tableView.convert(cell.frame, to: view)
previewingContext.sourceRect = frame
// you should not guard the instantiation, this is a case where you want to fail early is something is wrong
let workoutDetailPeek = storyboard?.instantiateViewController(withIdentifier: "WorkoutDetailPeek") as! WorkoutDetailPeek
return workoutDetailPeek
}

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

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.

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!)

Resources