I have a UICollectionView that scrolls horizontally. Each item can be swiped upward to delete. This is done through a pan gesture that inserted on the UICollectionViewCell. The animation code is ad follows:
let translation = gesture.translation(in: contentView)
if gesture.state == .changed {
tabView.center.y += translation.y
gesture.setTranslation(CGPoint.zero, in: contentView)
} else if gesture.state == .ended {
if tabView.center.y < contentView.center.y {
UIView.animate(withDuration: 0.2, animations: {
self.tabView.frame.origin.y = -self.tabView.frame.size.height
}) { (_) in
self.delegate?.didSwipeCell(at: self.indexPath())
}
}
}
This is the collection view delegate conformance
func didSwipeCell(at indexPath: IndexPath?) {
guard indexPath != nil else {
return
}
self.tabs.remove(at: indexPath!.row)
self.deleteItems(at: [indexPath!])
}
This produces the following effect:
As you can see, I have 6 items in my collection view, however, when swiping away and deleting the first four items for example, the last 2 items disappear. I have not been able to figure out the reason of the disappearance of those items. Any help is appreciated. Thanks.
I solved this issue by overriding prepareForReuse() in the UICollectionViewCell and setting the frame origin back to (0,0)
Related
Using UIPanGesture on a UITableView (Swift 4.0) to drag the tableview position (i.e. origin). UIPanGestureRecognizer detecting touch events and consuming them. At some point I want to delegate the events to table so it will scroll its items. How can I do so?
What have I tried:
#objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
// if origin moved to map origin it should not scroll above but cell should scroll normally. But returning does not help scrolling cells
if self.tableView.frame.origin == self.mapContainer.frame.origin && translation.y < 0 {
return
}
var frame = self.tableView.frame
let nHeight = (frame.origin.y + translation.y)
if (nHeight < self.view.frame.size.height) {
frame.origin.y = nHeight
self.tableView.frame = frame
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
}
Gesture added to tableView
let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.tableViewDragged(gestureRecognizer:)))
self.tableView.addGestureRecognizer(gesture)
self.tableView.isUserInteractionEnabled = true
gesture.delegate = self
Problem Summary:
For the same pan gesture at any point of tableview (i.e. on any cell), I need to scroll items when tableviews origin got fixed in top left corner of the container. Otherwise I have to move the tableview until its got attached to top left point. Moreover If If nothing to scroll down I need to again move the table below for pan down gesture.
*Problem Usecases: *
initial condition - tableview origin is at middle of the screen,
i.e. (0, Height/2)
pan up gesture - table moved up vertically, but no item scrolls, until origin got stuck to (0,0)
pan up gesture - cell items moved up but not table itself
pan up gesture - cell items moved up until end of the cells
pan down gesture - cell items moved down until index 0, lets say came to 0 index
pan down gesture - tableview moved to down vertically until its origin reached to middle of the screen (0, Height/2)
Best way to do this using tableView's own panGestureRecognizer (without adding new gestureRecognizer), like:
tableView.panGestureRecognizer.addTarget(self, action: #selector(self.tableViewDragged(gestureRecognizer:))
Action
#objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {
guard tableView.contentOffset.y < 0 else { return }
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
// if origin moved to map origin it should not scroll above but cell should scroll normally. But returning does not help scrolling cells
if self.tableView.frame.origin == self.mapContainer.frame.origin && translation.y < 0 {
return
}
var frame = self.tableView.frame
let nHeight = (frame.origin.y + translation.y)
if (nHeight < self.view.frame.size.height) {
frame.origin.y = nHeight
self.tableView.frame = frame
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
}
If I am understanding it correctly so you want to cancel PanGesture when you want to Scroll.
Try this :
func isItemAvailabe(gesture: UISwipeGestureRecognizer) -> Bool {
if gesture.direction == .down {
// check if we have some values in down if yes return true else false
} else if gesture.direction == .up {
// check if we have some values in up if yes return true else false
}
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.panGesture && otherGestureRecognizer == self.scrollGesture && isItemAvailabe(gesture: otherGestureRecognizer) {
return true
}
return false
}
Let me know if this solve your problem or I am not getting it correctly.
Also remember to add this too :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
You can check more here
I don't know a better solution than subclassing your tableView and blocks its contentOffset in the layoutSubviews of it. Something like :
class MyTableView: UITableView {
var blocksOffsetAtZero = false
override func layoutSubviews() {
super.layoutSubviews()
guard blocksOffsetAtZero else { return }
contentOffset = .zero
}
}
I think this is the solution used in the Maps app. When the user drags the tableView on the home screen, it does not scroll but lifts until it reaches the top of the screen and then scrolls again.
To do so, you add a gesture to your tableview which recognizes simultaneously with the recognizer of the tableView and blocks the contentOffset of the tableView each time the recognizer moves the frame of the tableView.
UPDATE
https://github.com/gaetanzanella/mapLike
Did you implement the delegate correctly ?
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Try something like :
#objc func tableViewDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
var frame = self.tableView.frame
let nHeight = (frame.origin.y + translation.y)
if (nHeight < self.view.frame.size.height) {
frame.origin.y = nHeight
self.tableView.frame = frame
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
tableView.blocksOffsetAtZero = true
} else {
tableView.blocksOffsetAtZero = false
}
}
}
The main idea : when you are changing the frame, block the scroll.
You could also use the delegate technique presented by kamaldeep singh bhatia but you will not be able to move first tableView then scroll in a single gesture.
See a example here
I created a horizontal tableView, with cells that take up the whole view controller. Instead of scrolling with the default setting, I would like to scroll to the next cell using scrollView.scrollToItem(at: IndexPath method.
Meaning, each time the user scrolls right, it will automatically scroll to the next cell, and each time the user swipes left, it will automatically scroll to the previous cell. Whenever you scroll a direction, it should automatically scroll to the next or previous cell.
I tried intercepting it using scrollViewDidScroll delegate method, but I am running into a ton of problems, it is autoscrolling back and forth and glitching a ton. What am I doing wrong?
var previousOffset: CGFloat = 0.0
var allowHorizontalScroll: Bool = true
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (allowHorizontalScroll){
let offset = scrollView.contentOffset.x
let diff = previousOffset - offset
previousOffset = offset
var currentBoardIndex = scrollView.indexPathForItem(at: CGPoint(x:offset, y:10))?.item
if currentBoardIndex != nil {
if diff > 0 {
//print("scroll left")
if (currentBoardIndex != 0){
currentBoardIndex = currentBoardIndex! - 1
allowHorizontalScroll = false
scrollView.scrollToItem(at: IndexPath(row: (currentBoardIndex!), section: 0), at: .left, animated: true)
}
}else{
//print("scroll right")
if (currentBoardIndex != ((boardVCDataSource?.fetchedResultsController.fetchedObjects?.count)! - 1)){
currentBoardIndex = currentBoardIndex! + 1
allowHorizontalScroll = false
scrollView.scrollToItem(at: IndexPath(row: (currentBoardIndex!), section: 0), at: .left, animated: true)
}
}
}
}
}
Whenever you scroll a direction, it should automatically scroll to the next or previous cell.
Why not throw out all that code, and instead set your scroll view's isPagingEnabled to true? A paging scroll view does what you describe, automatically.
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
{
}
When I drag section and move of UICollectionView using longPress Gesture then how to auto scroll UICollectionView's scrollview.
Note: in UICollectionView section 0 and 1 not scroll down, onward section only scrolling. 0 and 1 section set as sticky which is not scrolling when I move section.
UnTested Code:
Try this code inside your viewDidload or viewWillAppear.
let indexPath = NSIndexPath(forRow: 10, inSection: 0) // 1
self.collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: true)
Solved it.
func handleLongGesture(gesture: UILongPressGestureRecognizer) { //dragging Functionality handled here.
let point = gesture.locationInView(self.collectionView)
switch(gesture.state) {
case UIGestureRecognizerState.Changed:
let gesturePoint = gesture.locationInView(self.collectionView)
guard let changeIndexPath = self.collectionView.indexPathForItemAtPoint(gesturePoint)
where changeIndexPath.section > 1 else {
break
}
}
}
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")
}
}