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/
Related
This is more of an approach question. Suppose I have a collection view where each cell is occupying the entire screen, which have images in them and some other data(eg: title, info etc). What I want is for the user to tap on the cell and the image to go to fullscreen mode. I am able to achieve this by initializing a separate view and scaling the image to fit the screen with this code.
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = UIScreen.main.bounds
newImageView.backgroundColor = .black
newImageView.contentMode = .scaleAspectFit
and then dismissing it by removing the view like so:
self.navigationController?.isNavigationBarHidden = false
navigationController?.isToolbarHidden = false
sender.view?.removeFromSuperview()
}
Q1. Is there a way I can manipulate all the collectionview cells to transform into the fullscreen view on tap so i can use the default swiping action of the collection view to scroll through the images horizontally ?
Q2. If not, I use this library INSPhotoGallery! to add the effect on tap, which gives me the desired effect but due to heavy loading of the images from the PHAsset library my app crashes.
This is how i initialized my phassets to pass into this library:
lazy var photos: [INSPhotoViewable] = {
var allPhotos: [INSPhoto] = Array()
fetchResult.enumerateObjects({ (asset, index, stop) in
let image = self.requestImageForPHAsset(asset: asset)
allPhotos.append(INSPhoto(image: image, thumbnailImage: nil))
})
return allPhotos
}()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
let cell = collectionView.cellForItem(at: indexPath) as! DetailedCollectionViewCell
let currentPhoto = photos[indexPath.row]
let galleryPreview = INSPhotosViewController(photos: photos, initialPhoto: currentPhoto, referenceView: cell)
galleryPreview.overlayView.photosViewController?.singleTapGestureRecognizer.isEnabled = false
galleryPreview.referenceViewForPhotoWhenDismissingHandler = { [weak self] photo in
if let index = self?.photos.index(where: {$0 === photo}) {
let indexPath = IndexPath(item: index, section: 0)
return collectionView.cellForItem(at: indexPath) as? DetailedCollectionViewCell
}
return nil
}
present(galleryPreview, animated: true, completion: nil)
}
Question being how can I prevent my app from crashing. I know this has something to do with caching the images and loading asynchronously. Do you know of a library that integrates well with PHAssets if nothing else? Thanks!
You also can use page view controller to achieve the same functionality. It's valuable if you have less then 5 cells.
You can display image in a full screen modally. Just create separate view controller and implement animators(transition delegates) for this modal screen. It will be more difficult but also more proper way. The AppStore working in the same way
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
I am trying to create a custom TableViewController with a custom UITableViewCell class. I need to be able to drag and drop a photo inside the cell to a panel at the bottom of the view. I have tried a few ways to do this, but I am running into a couple of problems and haven't found a complete solution online:
First of all, after dragging and dropping I would like to return the UIImage view to the cell it came from.
Secondly, I am not able to cast a 'view' to my custom UITableViewClass outside the 'cellForRowAtIndexPath' method (so I will have access to the outlets I have set up.)
Here is my relevant code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let thisCell:MealPlanTableViewCell = tableView.dequeueReusableCellWithIdentifier("mealPlanCell", forIndexPath: indexPath) as! MealPlanTableViewCell
let meal = mealPlanElementArray[indexPath.row]
thisCell.postTextLabel.text = meal.2
thisCell.foodImageView.image = meal.4
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
thisCell.foodImageView.addGestureRecognizer(gesture)
thisCell.foodImageView.userInteractionEnabled = true
return thisCell
}
func wasDragged(gesture: UIPanGestureRecognizer) {
if calendarIsUp == calendarIsUp {
let meal = gesture.view! as! UIImageView
let sview = gesture.view!.superview!.superview!
// CAN'T DO THE FOLLOWING -- CAN'T CAST TO CUSTOM CELL
let mptvc:MealPlanTableViewCell = gesture.view!.superview!.superview! as! MealPlanTableViewCell
if beginningDrag == true
{
beginningDrag = false
// Move
meal.superview?.superview?.superview?.superview?.superview?.superview?.superview?.addSubview(meal)
beforeDragX = (meal.center.x)
beforeDragY = (meal.bounds.maxY)
}
let translation = gesture.translationInView(self.view)
meal.center = CGPoint(x: beforeDragX + translation.x, y: beforeDragY + translation.y)
if gesture.state == UIGestureRecognizerState.Ended {
beginningDrag = true
// NEED TO ADD BACK TO custom UITableViewCell class.
sview.addSubview(meal)
meal.center = CGPoint(x: beforeDragX, y: beforeDragY)
}
}
Thanks for the help...
after dragging and dropping I would like to return the UIImage view to
the cell it came from.
Use the transform property to manipulate the position of the
foodImageView.
When the UIGestureRecognizer's state == UIGestureRecognizerState.Ended, instantiate a new UIImageView and assign the foodImageView image to the new UIImageView's image property.
Add the new UIImageView to the desired view.
Set the foodImageView's transform to CGAffineTransformIdentity and animate it.
I'll leave the coding up to you as a learning exercise. For reference, here's a good explanation on CGAffineTransform: Demystifying CGAffineTransform.
I am not able to cast a 'view' to my custom UITableViewClass outside
the 'cellForRowAtIndexPath' method (so I will have access to the
outlets I have set up.)
Instead of adding the UIPanGestureRecognizer to the foodImageView add it to the custom UITableViewCell. Then, in your wasDragged function, access the UIGestureRecognizer.view, which is the custom UITableViewCell, and then access foodImageView property. This will eliminate the need to access the superview property at all. Accessing the superview property on UI elements in the UIKit framework is never a good idea because Apple can decide to change the structure of the view hierarchy whenever they wish. Ultimately, you are essentially guessing at which view you're going to be using and causing your future self headaches.
Ex.
func wasDragged(gesture: UIPanGestureRecognizer)
{
if calendarIsUp == calendarIsUp
{
let cell = gesture.view! as! MealPlanTableViewCell
if beginningDrag
{
beginningDrag = false
beforeDragX = cell.foodImageView.center.x
beforeDragY = cell.foodImageView.bounds.maxY
// Move
/*
I have no idea what view this is so I don't know what to change it to.
*/
cell.foodImageView.superview?.superview?.superview?.superview?.superview?.superview?.superview?.addSubview(cell.foodImageView)
}
let translation = gesture.translationInView(self.view)
cell.foodImageView.center = CGPoint(x: beforeDragX + translation.x, y: beforeDragY + translation.y)
if gesture.state == UIGestureRecognizerState.Ended
{
beginningDrag = true
// NEED TO ADD BACK TO custom UITableViewCell class.
cell.contentView.addSubview(cell.foodImageView)
cell.foodImageView.center = CGPoint(x: beforeDragX, y: beforeDragY)
}
}
}
Experiencing a weird interaction flaw with my UI for my tableview cell. I implemented a long press gesture:
func handleLongPress(sender:UILongPressGestureRecognizer!) {
var myCharacters: SelectedCharacter?
let localLongPress = sender as UILongPressGestureRecognizer
let locationInView = localLongPress.locationInView(cardsListed)
let indexPath = charactersListed.indexPathForRowAtPoint(locationInView)
let listed = frc.objectAtIndexPath(indexPath!) as! Characters
let cell: firstCharacterDetails = charactersListed.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath!) as! firstCharacterDetails
if listed == 0 {
} else {
if (sender.state == UIGestureRecognizerState.Ended) {
print("Long press Ended")
} else if (sender.state == UIGestureRecognizerState.Began) {
let bounds: CGRect = UIScreen.mainScreen().bounds
let screenHeight: NSNumber = bounds.size.height
if screenHeight == 480 {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let characterDetailsController: CharacterDetailsViewController = storyboard.instantiateViewControllerWithIdentifier("characterDetails") as! CharacterDetailsViewController
characterDetailsController.modalPresentationStyle = UIModalPresentationStyle.Popover
let popoverplayersCharacterController = characterDetailsController.popoverPresentationController
popoverCharacterNoteController?.permittedArrowDirections = .Any
popoverCharacterNoteController?.delegate = self
popoverCharacterNoteController?.sourceView = cell.cardDescription
characterDetailsController.characterDetails = listed
self.presentViewController(characterDetailsController, animated: true, completion: nil)
}
this is an example sorry if the coding isn't complete as I just took out this area of code for reference. Now my issue is one that is quite unique and is only likely to happen on accidental gestures by the user but for the safety of the user I would like to remove this problem.
The issue I am facing is that when the user longpress gestures a selected cell if they accidentally drag to another cell with their finger while still holding the cell will actually duplicate itself or drag itself below the cell that the user long press gestured dragged to. I am unsure how to handle preventing this from happening but if anyone has any insight it would be appreciated!
Discovered what I was doing wrong. After looking at the code a second time through I realized calling the dequeueResuableCell was the incorrect function to use. I switch the line of code with:
let cell: firstCharacterDetails = charactersListed.cellForRowAtIndexPath(indexPath!) as! firstCharacterDetails
and problem solved. I wasn't think about much when I copy and pasted some of my code that I reused in a few different controllers.
I have a UICollectionView with alot of UICollectionViewCells inside it.
Searched alot, but couldn't find anything near this issue.
My goal is to drag one of the Pink squares (UICollectionViewCell) to the Gray view (UIView).
You can think of it like dragging a folder on your desktop to your trash can except that I dont want the Pink square to be deleted. Or dragging a icon from your dock (it snaps back).
When the pink area hits the gray area it should just go back to its original position and the same when dropping out anywhere else using some kind of easy statement.
Can anyone help me out or redirect me to some references or samples?
Much appreciated, if you would like more info. Let me know.
If i managed to understand you correctly, i did something very similar once before, i found its much better to use a fake image of the cell, instead of the cell itself.
So for example, cell number 2 is starting to be dragged, you immediately call "SetHidden=YES", and draw at the exact same place a fake one with a simple draw method:
UIGraphicsBeginImageContext(self.cell.bounds.size);
[self.cell.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
This one you can move as you like, and when it should go back, you do it the same way.
Hope this helps you..
this worked for me in swift 3:
class DragAndDropGestureManager: NSObject {
var originalPosition: CGPoint?
var draggedView: UIView?
var viewSize: CGSize?
var originalZIndex: CGFloat = 0
#objc func handleDrag(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
if let view = recognizer.view {
draggedView = view
originalPosition = view.frame.origin
viewSize = view.frame.size
originalZIndex = view.layer.zPosition
view.layer.zPosition = 1
}
case .changed:
if let view = recognizer.view {
let currentPosition = recognizer.location(in: view.superview!)
let newPosition = CGPoint(x: currentPosition.x - (viewSize!.width / 2.0), y: currentPosition.y - (viewSize!.height / 2.0))
view.frame.origin = newPosition
} else {
self.finishDrag()
}
case .ended:
finishDrag()
default:
print("drag default \(recognizer.state.rawValue)")
}
}
func finishDrag() {
self.draggedView?.frame.origin = originalPosition!
self.draggedView?.layer.zPosition = originalZIndex
originalZIndex = 0
originalPosition = nil
draggedView = nil
}
}
i added a gesture recognizer in the function public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
like this:
if self.dragManager != nil && cell.gestureRecognizers == nil {
cell.addGestureRecognizer(UIPanGestureRecognizer(target: dragManager!, action: #selector(DragAndDropGestureManager.handleDrag)))
}
the if, is for not adding gesutre recognition more than once because the cell are being reused
don't forget to add collectioView.clipToBounds = false otherwise the dragged cell will be clipped when trying to move out of the collection view's bounds