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
{
}
Related
I'm pretty new so apologies if my title doesn't phrase things correctly. I've been hacking away and haven't been able to find an answer to this problem.
I have a horizontally scrolling collection. I'm trying to visually show poll results programatically adding CGRect to the relevant item in the collection based on voting data held in a Firestore array.
This is working, but the problem is when you scroll away (so the item in the collection is off screen) and then back, the code to draw the CGRects gets triggered again and more graphics get added to the view. Is there a way to delete these CGRects when the user scrolls an item collection off screen so when the user scrolls the item back into view, code is triggered again it doesn't create duplicates?
Here are a couple of screenshots showing first and second load
Here is my code (cell b is where the CGrect gets triggered)
//COLLECTION VIEW CODE
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.sentCollectionView {
let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: "sCell", for: indexPath) as! SentCollectionViewCell
cellA.sentQuestionLabel.text = sentMessages[indexPath.row].shoutText
// Set up cell
return cellA
}
else {
let cellB = receivedCollectionView.dequeueReusableCell(withReuseIdentifier: "pCell", for: indexPath) as! ReceivedCollectionViewCell
receivedMessages[indexPath.row].pollTotal = receivedMessages[indexPath.row].pollResults.reduce(0, +)
print("Sum of Array is : ", receivedMessages[indexPath.row].pollTotal!)
cellB.receivedShoutLabel.text = receivedMessages[indexPath.row].shoutText
print(receivedMessages[indexPath.row].pollResults.count)
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}
return cellB
}
}
//THIS DRAWS THE CGRECT
func sq(pollSum: Int, pollResult: Int) -> UIView {
// divide the width by total responses
let screenDivisions = Int(view.frame.size.width) / pollSum
// the rectangle top left point x axis position.
let xPos = 0
// the rectangle width.
let rectWidth = pollResult * screenDivisions
// the rectangle height.
let rectHeight = 10
// Create a CGRect object which is used to render a rectangle.
let rectFrame: CGRect = CGRect(x:CGFloat(xPos), y:CGFloat(yPos), width:CGFloat(rectWidth), height:CGFloat(rectHeight))
// Create a UIView object which use above CGRect object.
let greenView = UIView(frame: rectFrame)
// Set UIView background color.
greenView.backgroundColor = UIColor.green
//increment y position
yPos = yPos + 25
return greenView
}
Cells of collection are dequeued dequeueReusableCell , you need to override prepareForReuse
Or set a tag
greenView.tag = 333
And inside cellForItemAt do this
cellB.resultsView.subviews.forEach {
if $0.tag == 333 {
$0.removeFromSuperview()
}
}
if receivedMessages[indexPath.row].pollResults != [] {
for i in 0...receivedMessages[indexPath.row].pollResults.count - 1 {
cellB.resultsView.addSubview(sq(pollSum: receivedMessages[indexPath.row].pollTotal!, pollResult: receivedMessages[indexPath.row].pollResults[i]))
}
}
I have a problem while scrolling my collectionView
after choosing a cell, the last cell is not available for choosing
func changeViewSize(from: CGFloat, to: CGFloat, indexPath: IndexPath) {
UIView.transition(with: myCollection, duration: 0.5, options: .beginFromCurrentState, animations: {() -> Void in
let cell = self.myCollection.cellForItem(at: indexPath)!
let cellCenter = CGPoint(x: cell.center.x, y: cell.center.y + 50)
if cell.bounds.height == from {
let sizee = CGRect(x: cell.center.x, y: cell.center.y, width: cell.frame.width, height: to)
self.myCollection.cellForItem(at: indexPath)?.frame = sizee
self.myCollection.cellForItem(at: indexPath)?.center = cellCenter
for x in self.indexPathss {
if x.row > indexPath.row {
print("this is cells")
print(self.myCollection.visibleCells)
let cell = self.myCollection.cellForItem(at: x)!
cell.center.y += 100
}
}
}
}, completion: {(_ finished: Bool) -> Void in
print("finished animating of cell!!!")
})
}
It looks like you're changing cell sizes and positions manually. The UICollectionView won't be aware of these changes and therefore its size is not changing. The last cells simply move out of the visible area of the collection view.
I haven't done something like this before, but have a look at performBatchUpdates(_:completion:). I'd try to reload the cell that you want to make bigger using that method. Your layout will have to supply the correct attributes, i.e. size.
From the screenshots it looks like maybe you could use a UITableView instead? If that's a possibility it might simplify things. It also has a performBatchUpdates(_:completion:) method, and the UITableViewDelegate would then have to provide the correct heights for the rows.
Because of spacing of cell we used collectionView and I think changing height of tableviewcell is easier than collectionviewcell if you have an idea for spacing of tablviewcell wi will be glad to have your idea.
I am trying to achieve an effect that when detail button is pressed, details will slide in to ALL cells of tableView. So far I have achieved this trough iterating through the visible cells of the table view. Each cell has a detail label that is off screen that slides in with animation. This is how it looks:
Before Detail Button Pressed
After detail button pressed
Of cours since I am iterating through the visible cells, the cells at the bottom dont have the details when scrolling down after having pressed the detail button. I am aware of the fact that the cells that are not shown on screen is not in memory, but maybe there is an another approach to this?
EDIT
Here is the code that executes when user presses "Details" button
let animationDuration = 0.4
for cell in myTableView.visibleCells {
let cell = cell as! MainCell
if !detailView {
sender.tintColor = UIColor.gray
detailView = true
UIView.animate(withDuration: animationDuration, animations: ({
cell.noteLbl.center.x = cell.noteLbl.center.x + 100
}))
UIView.animate(withDuration: animationDuration, animations: ({
cell.accessoryLbl.center.x = cell.accessoryLbl.center.x + 100
}))
UIView.animate(withDuration: animationDuration, animations: ({
cell.timeLbl.center.x = cell.timeLbl.center.x + 100
}))
} else {
detailView = false
sender.tintColor = UIColor.white
UIView.animate(withDuration: animationDuration, animations: ({
cell.noteLbl.center.x = cell.noteLbl.center.x - 100
}))
UIView.animate(withDuration: animationDuration, animations: ({
cell.accessoryLbl.center.x = cell.accessoryLbl.center.x - 100
}))
UIView.animate(withDuration: animationDuration, animations: ({
cell.timeLbl.center.x = cell.timeLbl.center.x - 100
}))
}
}
Code for the cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainCell") as! MainCell
let shiftForRow = shifts[indexPath.section][indexPath.row]
cell.noteLbl.text = shiftForRow.note
cell.dateLbl.text = shiftForRow.date
cell.accessoryLbl.text = calcTotalHours(STField: shiftForRow.startingTime!, ETField: shiftForRow.endingTime!, lunchTime: shiftForRow.lunchTime!)
cell.timeLbl.text = shiftForRow.startingTime! + " - " + shiftForRow.endingTime!
return cell
}
As you scroll a table view (and when initially displayed), the cellForRowAt method is called for each cell that needs to be displayed.
At the moment your cellForRowAt only shows your cells in their "normal" layout as if the detail button has never been pressed. You need to update your cellForRowAt method so it configures the cell based on whether details should be shown or not.
You probably need a flag to track whether details are currently being shown or not. Toggle that flag based on the detail button being pressed.
Then, based on that state of that flag, your cellForRowAt method needs to setup the cell appropriately.
I want to be able to reorder cells in my collectionview but have the cell that is moving's image be scaled down to a smaller size for the duration of the reorder. I am able to get the cell to resize while moving but there is an issue when the cell crosses over another cell and attempts to reorder. At the moment the cell reaches another cell, it temporarily resizes back to normal size, causing a flickering when moving it around.
I have followed this blog description for using Apple's methods to interactively reorder cells in a UICollectionView. The methods I am using are listed under the "Reordering Items Interactively" section of this page.
I have a UICollectionView subclass, a UICollectionViewCell subclass, and extensions for UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, and UICollectionViewDelegate.
The way I am resizing the cell is by adding a boolean to my cell's subclass that is set to false by default but true when beginning and updating cell movement, and back to false when ending or canceling. In my sizeForItem method for the UICollectionViewDelegateFlowLayout extension, I am checking the boolean and returning a size accordingly.
It has been difficult to debug where the methods are getting the size to display when the cell's image returns to normal size for a split second, causing the flickering behavior, as each method is called many times to update the movement smoothly.
I am not married to resizing the cell in this way, if it is the cause of the issue, I'm just not sure how to resize it and prevent it from flickering when crossing over other cells in the collectionview.
Thank you in advance for any suggestions.
UPDATE
Some code samples.
I update the size of the bounds of imageView in beginInteractiveMovementForItem to shrink it initially. By the time the collectionview calls the update method, the size gets reset, but the cell gets its size from sizeForItem, below is my logic for that.
As a side note, I have also tried resizing the bounds of the cell itself in beginInteractiveMovement in addition to the imageView's bounds, but it had no effect so I removed that.
Here's how I'm handling resizing the imageView:
In the collection view's parent:
#objc func handleLongGesture(_ sender: UILongPressGestureRecognizer) {
switch(sender.state) {
case .began:
let location = sender.location(in: self.collectionView)
guard let selectedIndexPath = self.collectionView.indexPathForItem(at: location) else {
return
}
guard let dragCell = collectionView.cellForItem(at: selectedIndexPath) as? ActivityPhotoGalleryCell else { return }
UIView.animate(withDuration: 0.17, animations: {
dragCell.center = location
})
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(sender.location(in: sender.view))
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
In the collection view subclass:
override func beginInteractiveMovementForItem(at indexPath: IndexPath) -> Bool {
guard let dragCell = cellForItem(at: indexPath) as? CustomCellObject else { return false }
draggingCell = dragCell
dragCell.isDragging = true
return super.beginInteractiveMovementForItem(at: indexPath)
}
And here is my size for item logic:
var sizeForItem = CGSize(width: widthPerItem, height: widthPerItem)
let cell = collectionView.cellForItem(at: indexPath) as? CustomCellObject
if (cell?.isDragging ?? false) {
sizeForItem.height *= 0.7
sizeForItem.width *= 0.7
}
return sizeForItem
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.