UICollectionView not showing selected data after long scroll - ios

I have one UICollectionView with five custom cells that need to be render on specific conditions, cells are getting generated. Now problem I am going through is consider I have selected one cell at specific index, and now I scroll downwards and now again I long scroll. When UICollectionView stops scrolling, if the index is the same as which we selected, UICollectionView doesn't show that cell as selected. But now if I even try to move the cell a little bit, even a bit, UICollectionView shows that cell as selected cell.
Following is my code, that I have wrote in prefetchItem:
(cell as? PATemplateTypeOneCollectionCell)?.fillCellData(row: indexPath.row,section:indexPath.section, paCategoryQuestions: currentIndexQuestion, paQuestionCollection: currentIndexCollection)
cell!.alpha = 0.4
if self.multipleIndexPathsArray[indexPath.section][0] != []{
collectionView.selectItem(at: self.multipleIndexPathsArray[indexPath.section][0], animated: true, scrollPosition: .right)
}
else{
print("self.multipleIndexPathsArray[indexPath.section][0] is empty")
}
UICollectionViewCell:
override var isSelected: Bool {
didSet {
if isSelected {
self.alpha = 1.0
self.layer.borderWidth = 2.0
self.layer.borderColor = ColorConstants.colorFromHexString(hexString: paCategoryQuestions.selection_color).cgColor
}else {
self.alpha = 0.4
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.lightGray.cgColor
}
}
Kindly request you guys to help with this issue.

So after hours of thinking I came across one solution. So as my cellForItem was not rendering selected scroll properly after long scroll, what I tried is recognizing UIScrollView's delegate scrollViewDidEndDecelerating. And recognizing if currently visible indexPath cells were selected previously or not by using my saved values array. Following is the code that worked for me:
fileprivate func checkIfCellIsSelected(){
for eachCell in afterPaymentPACollectionView.visibleCells{
let indexPath = afterPaymentPACollectionView.indexPath(for: eachCell)
for eachIndexSelected in multipleIndexPathsArray{
if eachIndexSelected.contains(indexPath!){
afterPaymentPACollectionView.selectItem(at: indexPath, animated: true, scrollPosition: .right)
}
}
}
}
//MARK: UIScrollViewDelegate
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
checkIfCellIsSelected()
}

Related

Swift segmented control and UITableView

I have segmented control as a header of UITableView with cell names. Tableview has different cells with different sizes. When I tap on segment, I do a scroll to specific row in a tableview.
segment.valueSelected = { index in
self.contentTableView.scrollToRow(at: IndexPath(row: index, section: 1), at: .top, animated: true)
}
But how can I change selected index of segmented control when I scroll my tableview? I mean I know how to change index, but the problem is how can I calculate specific offset since all cells has different sizes?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
segment.setSelectIndex(index : ???)
}
Solved by
let visiblerows = contentTableView.indexPathsForVisibleRows
if let lastIndex = visiblerows?.last {
segment.setSelectIndex(index: lastIndex.row, animated: true)
}

How to draw a circle around UICollectionViewCell when clicking on it and remove others circle from previous selected cells?

I've been working with UICollectionView lately. There is a requirement that needs to be implemented like: "There are several imageviews in several collectionview cell. When user selects one of the image/cell, the app will draw a blue circle around that image/cell."
Currently, I'm able to do the draw on the cell. But the problem now is that I am able only to draw all cells but not one cell at the time (as screenshot below)
So my question is: how can I select one image/cell, the blue circle of previous selected cell should be removed?
Thanks so much for the answers in advance.
It sounds like you want this:
You didn't say how you're putting the blue circle in the cell. Here's how I think you should handle selection: use the collection view's built-in selection support as much as possible.
A UICollectionView already has support for selecting cells. By default, its allowsSelection property is true and its allowsMultipleSelection property is false, so it allows the user to select one item at a time by tapping the item. This sounds like almost exactly what you want.
The collection view makes the current selection available in its indexPathsForSelectedItems property, which is either nil or empty when no cell is selected, and contains exactly one index path when one item is selected.
When an item is selected, and there is a visible cell for the item, the cell shows that its item is selected by making its selectedBackgroundView visible. So make a UIView subclass that shows a blue circle:
class CircleView: UIView {
override class var layerClass: AnyClass { return CAShapeLayer.self }
override func layoutSubviews() {
super.layoutSubviews()
let layer = self.layer as! CAShapeLayer
layer.strokeColor = UIColor.blue.cgColor
layer.fillColor = nil
let width: CGFloat = 3
layer.lineWidth = width
layer.path = CGPath(ellipseIn: bounds.insetBy(dx: width / 2, dy: width / 2), transform: nil)
}
}
Then use an instance of CircleView as the cell's selectedBackgroundView. You can create the instance lazily the first time the cell becomes selected:
class MyCell: UICollectionViewCell {
override var isSelected: Bool {
willSet {
if newValue && selectedBackgroundView == nil {
selectedBackgroundView = CircleView()
}
}
}
var title: String = "???" {
didSet {
label.text = title
}
}
#IBOutlet private var label: UILabel!
}
With this code in place, the user can tap a cell to select its item, and the cell will show a blue circle when selected. Tapping another cell will deselect the previously-selected item, and the blue circle will “move” to the newly-selected item's cell.
You might want to let the user deselect the selected item by tapping it again. UICollectionView doesn't do that by default if allowsMultipleSelection is false. One way to enable tap-again-to-deselect is by implementing collectionView(_:shouldSelectItemAt:) in your UICollectionViewDelegate:
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
if (collectionView.indexPathsForSelectedItems ?? []).contains(indexPath) {
// Item is already selected, so deselect it.
collectionView.deselectItem(at: indexPath, animated: false)
return false
} else {
return true
}
}

Reordering cell interactively with resized imageView

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

Stop cell from animating

In my tableView I'm using custom cells with two custom labels. Eventually the text of these labels will change but at the same time, layoutIfNeeded() is called to animate some other things. This is causing the text in the labels to animate as well which I'm trying to prevent.
The code I'm using to change the text:
func tapped(recognizer: UIGestureRecognizer) {
let path = NSIndexPath(forRow: myData.count - 1, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(path) as! CustomCell
cell.infoLabel.text = "Changing text here"
UIView.animateWithDuration(0.5) { self.anotherView.layoutIfNeeded() }
}
To prevent the labels from animating I have tried adding this in CustomLabel and CustomCell, as well as in willDisplayCell:
override func layoutSubviews() {
UIView.performWithoutAnimation { super.layoutSubviews() }
}
Or using another way of preventing it to animate in CustomLabel and CustomCell:
override func layoutSubviews() {
CATransaction.begin()
CATransaction.setDisableActions(true)
super.layoutSubviews()
CATransaction.commit()
}
I've also tried a property observer setting the text and removing all animations at the same time:
var text: String {
didSet {
infoLabel.text = text
infoLabel.layer.removeAllAnimations()
}
}
Nothing seems to work unless I use:
CATransaction.begin()
CATransaction.setDisableActions(true)
cell.infoLabel.text = "Changing text here"
CATransaction.commit()
But then I'd have to use it everywhere I'm changing the text. Is there a place I've overlooked to do this more elegantly?

Custom UIViewController transition where UITableViewCell grows to full screen and back?

I haven't been able to find any examples of this online. How can I achieve the following effect? Instead of the standard slide-to-left effect when tapping a table row, I'd like the view controller transition animation to look like the following:
User taps a cell in the table
The cell starts growing to fill the screen, pushing other rows above and below it "offscreen".
As the cell grows, cells elements (text, images, etc.) cross-fade into the new view's contents until the new view completely fills the screen.
I'd like to also be able to interactively transition back into the table view by dragging up from the bottom edge, such that the reverse of the above is achieved. i.e. view starts shrinking back into a normal table view cell as the "offscreen" cells animate back into position.
I've thought about taking a snapshot of the table and splitting it up at the points above and below the cell, and animating these snapshots offscreen as part of a custom view controller transition. Is there a better way? Ideally I'd like to not take snapshots, as I may want to have animations, etc., still happening in the table view rows as they fade offscreen.
First thing first, i have seen your post today and his is written in swift programming language.
the follwing code is to expand the selected cell to the full screen, where the subviews will fade out and background image will expands.
First complete cellForRowAtIndexPath, then in didSelectRowAtIndexPath,
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
isSelected = true
selectedCellIndex = indexPath.row
tableView.beginUpdates()
var cellSelected: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellSelected.frame = tableCities.rectForRowAtIndexPath(indexPath)
println("cell frame: \(cellSelected) and center: \(cellSelected.center)")
let newCell = cellSelected.frame.origin.y - tableOffset
println("new cell origin: \(newCell)")
var tempFrame: CGRect = cellSelected.frame
variableHeight = cellSelected.frame.origin.y - tableOffset
tempFrame.size.height = self.view.frame.height
println("contentoffset: \(tableView.contentOffset)")
let offset = tableView.contentOffset.y
println("cell tag: \(cellSelected.contentView.tag)")
let viewCell: UIView? = cellSelected.contentView.viewWithTag(cellSelected.contentView.tag)
println("label: \(viewCell)")
viewCell!.alpha = 1
UIView.animateWithDuration(5.0, delay: 0.1, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
tableView.setContentOffset(CGPointMake(0, offset + self.variableHeight), animated: false)
tableView.contentInset = UIEdgeInsetsMake(0, 0, self.variableHeight, 0)
tableView.endUpdates()
cellSelected.frame = tempFrame
viewCell!.alpha = 0
println("contentoffset: \(tableView.contentOffset)")
}, completion: nil)
}
then update your heightForRowAtIndexPath, as
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if isSelected && (selectedCellIndex == indexPath.row) {
return self.view.frame.height
}else {
return 100
}
}
Ignore this if already solved. and this might be helpful to others.

Resources