I have a ViewController with a basic collection view below. I am now able to click a cell in the collection view and add a subview of the image selected.
I am stuck on allowing the user to push down on the cell and drag the new subview onto the mainview without the user having to lift their finger and reselect thus activating the pan gesture.
How do I programmatically initiate the pan gesture for the newly created subview, so it seems like the user is dragging a copy off the collectionView?
func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
stickerImage = imageArray[indexPath.item]!
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
// update UI on the main thread
var newImageView = UIImageView(image: self.stickerImage)
newImageView.userInteractionEnabled = true
newImageView.multipleTouchEnabled = true
newImageView.exclusiveTouch = true
newImageView.contentMode = .ScaleAspectFit
let panGesture = UIPanGestureRecognizer(target: self, action:Selector("handlePan:"))
panGesture.delegate = self
newImageView.addGestureRecognizer(panGesture)
newImageView.frame = self.view.bounds
newImageView.frame.size.width = 150
newImageView.frame.size.height = 150
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(self.reuseIdentifier, forIndexPath: indexPath) as! CollectionViewCell
newImageView.center.y = self.view.frame.height - collectionView.frame.height / 2
newImageView.center.x = cell.center.x
self.view.addSubview(newImageView)
self.view.bringSubviewToFront(newImageView)
}
}
}
func handlePan(recognizer:UIPanGestureRecognizer) {
self.view.bringSubviewToFront(recognizer.view!)
let translation = recognizer.translationInView(recognizer.view)
if let view = recognizer.view {
view.transform = CGAffineTransformTranslate(view.transform, translation.x, translation.y)
}
recognizer.setTranslation(CGPointZero, inView: recognizer.view)
}
Have you tried overriding touch methods as touchbegin and touchend. Override these methods and you will not have to care about the pan gesture just need to check the view on which touch is recognised.
Related
I have a UITableView that detects a swipe from left to right. I want to show a delete button when the user does the swipe by changing the constant of the width constraint on it. But no matter what I try to do the button won't animate or change width at all. The only way I can get the button to display the constraint change is by reloading the row.
This is what I have right now. Every answer I see contains this method of animating constraints.
func TableViewSwipeRight(gesture: UIGestureRecognizer)
{
let point = gesture.location(in: favoriteStationsTableview)
let indexPath = favoriteStationsTableview.indexPathForRow(at: point)
if let indexPath = indexPath {
if let favoriteCell = favoriteStationsTableview.dequeueReusableCell(withIdentifier: FavoriteCell.GetReuseId(), for: indexPath) as? FavoriteCell {
self.view.layoutIfNeeded()
favoriteCell.deleteBtnConstraint.constant = 50
UIView.animate(withDuration: 0.5, animations: {
self.view.layoutIfNeeded()
})
}
}
}
You need to get the cell at the given indexPath and not dequeue it (as it won't be part of the tableview at this point.)
if let indexPath = indexPath {
if let favoriteCell = favoriteStationsTableview.cellForRowAtIndexPath(indexPath) as? FavoriteCell
{
}
I am working on implementing a parallax effect for my UICollectionView. The effect thankfully is working. However, when I scroll the collection vertically for the very first time, there is a choppy effect that occurs. This ONLY happens on the first scroll through of the list. After that, the parallax effect is smooth while scrolling.
Below is my relevant code:
MyViewController.swift:
extension FeedViewController: UIScrollViewDelegate{
func scrollViewDidScroll(scrollView: UIScrollView) {
// Scroll navigation bar animation
let scrolledDown = scrollView.contentOffset.y > -self.topLayoutGuide.length
let newAlpha:CGFloat = scrolledDown ? 0.65 : 0.00
UIView.animateWithDuration(0.25) {
self.navBarBackground.alpha = newAlpha
}
// Parallax effect
for cell in collectionView.visibleCells() {
updateParallaxCell(cell)
}
}
func updateParallaxCell(cell: UICollectionViewCell) {
if let myCell = cell as? MyCustomCell {
myCell.updateParallaxEffect(collectionView, mainView: view)
}
}
}
MyCustomCell.swift
override func awakeFromNib() {
super.awakeFromNib()
backgroundImageOriginalOrigin = backgroundImage.frame.origin
}
func updateParallaxEffect(collectionView: UICollectionView, mainView view: UIView) {
let convertedRect = collectionView.convertRect(self.frame, toView: view) //frame of the cell within the coordinate system of the main view
let distanceFromCenter = CGRectGetHeight(self.frame) / 2 - CGRectGetMinY(convertedRect) //y coordinate distance of the cell from the center of the main view
let difference : CGFloat = 50.0 //maximum relative offset of the image within the cell
let move = -difference / 4 + (distanceFromCenter / self.frame.size.height) * difference // calculated relative offset of the image
let y = self.backgroundImageOriginalOrigin.y + move // newly calculated Y coordinate of the image
self.backgroundImage.frame.origin = CGPointMake(self.backgroundImageOriginalOrigin.x, y) // adjusting the image view frame
}
override func layoutSubviews() {
updateParallaxEffect(collectionView, mainView: mainView)
super.layoutSubviews()
}
Can anyone see what it is I'm doing wrong?
Try adding this to your viewDidLoad():
UICollectionView().setContentOffset(CGPointMake(0, 1), animated: true)
UICollectionView().setContentOffset(CGPointMake(0, 0), animated: false)
I have a UICollectionView with 2 sections. I want to select the cell when the user taps on it.
My code runs correctly every time a user taps on the cell, the cell become smaller and a checkmark appears in it ( it's the imageView I add as subview of the cell). The problem is that if I tap a cell on the first section, it selects another cell in the second section. This is weird as I use the indexPath.
This is my code:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// handle tap events
let cell = collectionView.cellForItemAtIndexPath(indexPath)
let centerCell = cell?.center
if cell!.frame.size.width == cellWidth {
cell?.frame.size.width = (cell?.frame.size.width)!/1.12
cell?.frame.size.height = (cell?.frame.size.height)!/1.12
cell?.center = centerCell!
let imageView = UIImageView()
imageView.image = MaterialIcon.check?.imageWithColor(MaterialColor.white)
imageView.backgroundColor = MaterialColor.blue.accent2
imageView.frame = CGRectMake(1, 1, 20, 20)
imageView.layer.cornerRadius = imageView.frame.height/2
imageView.clipsToBounds = true
if indexPath.section == 0 {
imageView.tag = indexPath.row+4000
} else {
imageView.tag = indexPath.row+5000
}
print("IMAGEVIEW TAG: ",imageView.tag)
cell?.addSubview(imageView)
}
}
Be sure to have the multiple selection property on collectionView set to true in your viewDidLoad() or in storyboard
collectionView?.allowsMultipleSelection = true
I am attempting to use UIPanGestureRecognizer to translate and dismiss a UITableViewController. I want the gesture to trigger translation of the TableView only when it has reached the bottom of its scroll view and dismiss the TableView when it has been translated 1/3 the height of the screen. I've tried to add the GestureRecognizer when the TableView has reached the bottom of its scroll view, but the application ends up adding the gesture recognizer and disabling the freedom to scroll back up in the TableView.
I can supply code upon request, but I should be able to follow general solutions you may have.
Thanks in advance.
Few extra things to note:
I've added the gesture recognizer to the table view
I want to create a similar effect to a particular view in Facebook's iPhone application. In their app, whenever you select a photo from a photo album, it presents a TableView that allows you to translate and dismiss it whenever you reach the any of the edges in its scrollview.
Here's the code I currently have:
override func scrollViewDidScroll(scrollView: UIScrollView) {
// MARK: - Scrollview dismiss from bottom
let scrollViewHeight = scrollView.frame.height
let scrollContentSizeHeight = scrollView.contentSize.height
let scrollOffset = scrollView.contentOffset.y
// Detects if scroll view is at bottom of table view
if scrollOffset + scrollViewHeight >= scrollContentSizeHeight {
println("Reached bottom of table view")
self.panGesture = UIPanGestureRecognizer(target: self, action: "slideViewFromBottom:")
self.imageTableView.addGestureRecognizer(panGesture)
}
}
func slideViewFromBottom(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(imageTableView).y
let velocity = recognizer.velocityInView(self.imageTableView)
var centerOfImageTableView = self.imageTableView.center.y
switch recognizer.state {
case .Began:
// Touches begin
println("Touches began")
case .Changed:
// Limits view translation to only pan up
if velocity.y < 0.0 {
centerOfImageTableView = self.screenbounds.height/2 + translation
}
case .Ended:
// Determines length of translation to be animated
let moveToTop = screenbounds.height + translation
// Animates view depending on view location at end of gesture
if translation <= -UIScreen.mainScreen().bounds.height/2 {
UIView.animateWithDuration(0.2) {
self.imageTableView.transform = CGAffineTransformMakeTranslation(0.0, -moveToTop)
return
}
delay(0.3) {
self.presentingViewController?.dismissViewControllerAnimated(false, completion: nil)
}
}
else {
UIView.animateWithDuration(0.3) {
self.imageTableView.transform = CGAffineTransformMakeTranslation(0.0, -translation)
return
}
recognizer.enabled = false
}
default:
println("Default executed")
}
}
I'm trying to implement functionality in my practice app very similar to that of Snapchat, where you drag a UITableViewCell to the right or left, and as you're dragging, an image behind the view is slowly appearing until it reaches a certain point, and after it does, it starts a segue or PageViewController type segue to another view controller that you can use to chat with your friend.
At first, I tried using the screen edge pan gesture recognizer, but that only works if you start swiping from the edge of the screen. I need to be able to swipe from anywhere within the UITableViewCell.
A demonstration of my needed functionality is in Snapchat, where you see it more clearly when you slowly swipe right on one of your friend's table cell's and it slowly shows the image of a messaging icon and eventually leads to another view where you can chat.
So would a pan gesture recognizer be enough for this? If so, what would be the best method to follow to get this done? I've seen tutorials on pan gesture recognizers but I don't see how it could eventually lead to another view controller after swiping a certain distance. I think I could get away with putting the messaging icon behind the displayed table cell content that could appear while swiping right, but how could I implement such smooth functionality?
Learning this would really increase my experience in smooth user experience. Any advice or methods would be greatly appreciated, thanks.
EDIT: Please leave answers in Objective C, please. I don't know Swift.
You can create a custom TableViewCell for this. Code below should do what you're asking. Just need to add the delegate to your ViewController
protocol TableViewCellDelegate {
func something()
}
class TableViewCell: UITableViewCell {
var startSegue = false
var label: UILabel // Or UIView, whatever you want to show
var delegate: TableViewCellDelegate? // Delegate to your ViewController to perform the segue
required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
// Utility method for creating the label / view
func createLabel() -> UILabel {
let label = UILabel(frame: CGRect.nullRect)
// Add label customization here
return label
}
// Create "check" & "cross" labels for context cues
label = createLabel() // Create the label
label.text = "\u{2713}" // Add check symbol as text
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(label)
// Add a pan recognizer
var recognizer = UIPanGestureRecognizer(target: self, action: "handleSwipe:")
recognizer.delegate = self
addGestureRecognizer(recognizer)
}
let cueMargin: CGFloat = 10.0, cueWidth: CGFloat = 50.0
override func layoutSubviews() {
// The label starts out of view
label.frame = CGRect(x: bounds.size.width + cueMargin, y: 0, width: cueWidth, height: bounds.size.height)
}
func handleSwipe(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self)
if recognizer.state == .Changed {
center = CGPointMake(center.x + translation.x, center.y)
startSegue = frame.origin.x < -frame.size.width / 2.0 // If swiped over 50%, return true
label.textColor = startSegue ? UIColor.redColor() : UIColor.whiteColor() // If swiped over 50%, become red
recognizer.setTranslation(CGPointZero, inView: self)
}
if recognizer.state == .Ended {
let originalFrame = CGRect(x: 0, y: frame.origin.y, width: bounds.size.width, height: bounds.size.height)
if delegate != nil {
startSegue ? delegate!.something() : UIView.animateWithDuration(0.2) { self.frame = originalFrame }
}
}
}
// Need this to handle the conflict between vertical swipes of tableviewgesture and pangesture
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let velocity = panGestureRecognizer.velocityInView(superview!)
if fabs(velocity.x) >= fabs(velocity.y) { return true }
return false
}
return false
}
}
I got this from this tutorial
EDIT:
In your ViewController you'll have to 'register' this TableViewCell by using:
yourTableName.registerClass(TableViewCell.self, forCellReuseIdentifier: "cellIdentifier")
And your TableViewDataSource for cellForRowAtIndexPath would look like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as TableViewCell
cell.delegate = self
// Add stuff for your other labels like:
// "cell.name = item.name" etc
return cell
}
If you want to pass data back to the ViewController, just use the delegate.