Force touch on UICollectionView inside of UITableViewCell - ios

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

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.

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

Automatic scroll to the next cell in a PFQueryTableViewController in Swift

I have a PFTableViewController with PFTableViewCells in Swift.
In each TableViewCell (with a height of 80% the screen size), there is a button. I would like that when a user select the button, there is an automatic scroll to the next TableViewCell.
Any idea how I can make it?
Thanks a lot
Just determine the indexPath and scroll to the next indexPath. This example assumes a flat table without sections.
func buttonTapped(sender: UIButton) {
let point = sender.convertPoint(CGPointZero, toView:tableView)
let indexPath = tableView.indexPathForRowAtPoint(point)!
// check for last item in table
if indexPath.row < tableView.numberOfRowsInSection(indexPath.section) {
let newIndexPath = NSIndexPath(forRow:indexPath.row + 1 inSection:indexPath.section)
tableView.scrollToRowAtIndexPath(
newIndexPath, atScrollPosition:.Top, animated: true)
}
}

UILongPressureRecognizer with Image view

Good morning everyone,
I am a newbie Swift developer and I am facing the following problem implementing an exercise I am dealing with.
I have a collection view with collection cells displaying images I have imported in XCODE; when I tap with the finger on the screen I would like to replace the image currently being display with another one that i have also imported, and animate this replacement.
I am implementing the UITapLongPressureRecognizer method but i am getting confused on which state to implement for the recognizer, just to replace the first image view with the one I want to be shown when I tap the screen to scroll up-down.
As you can see from the code below the two recognizer I think should be more appropriate to be implemented are the "Begin" and "Ended".
My problem is that when the state .Begin starts I don't know how to animate the replacement of one image view with another and so when the state is .Ended I don't know how to replace the second image with the first one and animate the replacement (I want it to be like for example a Modal segue with "Cross Dissolve" transition).
Thank you in advance for your kindness and patience.
class MainViewController: UICollectionViewController {
var fashionArray = [Fashion]()
private var selectedIndexPath: NSIndexPath?
override func viewDidLoad() {
super.viewDidLoad()
selectedIndexPath = NSIndexPath()
//Register the cell
let collectionViewCellNIB = UINib(nibName: "CollectionViewCell", bundle: nil)
self.collectionView!.registerNib(collectionViewCellNIB, forCellWithReuseIdentifier: reuseIdentifier)
//Configure the size of the image according to the device size
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let bounds = UIScreen.mainScreen().bounds
let width = bounds.size.width
let height = bounds.size.height
layout.itemSize = CGSize(width: width, height: height)
let longPressRecogn = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
collectionView!.addGestureRecognizer(longPressRecogn)
}
func handleLongPress(recognizer: UILongPressGestureRecognizer){
var cell: CollectionViewCell!
let location = recognizer.locationInView(collectionView)
let indexPath = collectionView!.indexPathForItemAtPoint(location)
if let indexPath = indexPath {
cell = collectionView!.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
}
switch recognizer.state {
case .Began:
cell.screenTapped = false
case .Ended:
cell.screenTapped = false
default:
println("")
}
}
First of all, I suggest you to use UITapGestureRecognizer instead of long press. Because, as far as I understand, you only tap once instead of pressing for a time.
let tapRecognizer = UITapGestureRecognizer(target: self, action:Selector("tapped:"))
collectionView.addGestureRecognizer(tapRecognizer)
And when the user tapped, you can use UIView animations to change the image. You can check the Example II from the following link to get insight about animations.
http://www.appcoda.com/view-animation-in-swift/

Resources