I want to have the facilities to show a bigger cell when selected in UICollectionView.
I am doing this using the below code, its doing its task, what I want, that all cell that are in left and right to the selected cell will be slided to left and right so that selected cell will have a bigger size.
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if let prev = context.previouslyFocusedView as? ShowListCollectionViewCell
{
coordinator.addCoordinatedAnimations({
prev?.transform = CGAffineTransformMakeScale(1, 1)
}, completion: nil)
}
if let next = context.nextFocusedView as? ShowListCollectionViewCell
{
coordinator.addCoordinatedAnimations({
next.transform = CGAffineTransformMakeScale(1.1, 1.1)
}, completion: nil)
So if put the scale to 1.1 from 1.0 then its okay. If I want a bigger size, like 1.5 then its covering the its right cell. which I dont want, I want cells of right and left hand to be slided to give the selected cell space.
Related
I am able to reorder my UICollectionViewCells on iOS 9 by dragging it using a gesture recognizer and simplementing newly iOS 9 support for reordering.
public func beginInteractiveMovementForItemAtIndexPath(indexPath: NSIndexPath) -> Bool // returns NO if reordering was prevented from beginning - otherwise YES
public func updateInteractiveMovementTargetPosition(targetPosition: CGPoint)
public func endInteractiveMovement()
public func cancelInteractiveMovement()
I noticed that when I start dragging a cell, its center changes to be the touch location, I don't like that.
I would like to be able to drag my cell by it's corner if I want.
Do you know how to do this?
Thanks a lot.
(Written in Swift 3.1)
For the targetPosition parameter of the updateInteractiveMovementTargetPosition function, instead of using the gesture recognizer's location directly like this...
var location = recognizer.location(in: collectionView)
collectionView.updateInteractiveMovementTargetPosition(location)
... I created a function that takes the center of the cell to be dragged (The location that the collectionView updateInteractiveMovementTargetPosition would use, and then takes the location of the gesture recognizer's touch in the cell, and subtracts that from the center of the cell.
func offsetOfTouchFrom(recognizer: UIGestureRecognizer, inCell cell: UICollectionViewCell) -> CGPoint {
let locationOfTouchInCell = recognizer.location(in: cell)
let cellCenterX = cell.frame.width / 2
let cellCenterY = cell.frame.height / 2
let cellCenter = CGPoint(x: cellCenterX, y: cellCenterY)
var offSetPoint = CGPoint.zero
offSetPoint.y = cellCenter.y - locationOfTouchInCell.y
offSetPoint.x = cellCenter.x - locationOfTouchInCell.x
return offSetPoint
}
I have a simple var offsetForCollectionViewCellBeingMoved: CGPoint = .zero in my view controller that will store that offset so function above doesn't need to be called every time the gesture recognizer's location changes.
So the target of my gesture recognizer would look like this:
func collectionViewLongPressGestureRecognizerWasTriggered(recognizer: UILongPressGestureRecognizer) {
guard let indexPath = collectionView.indexPathForItem(at: recognizer.location(in: self.collectionView)),
let cell = collectionView.cellForItem(at: indexPath), indexPath.item != 0 else { return }
switch recognizer.state {
case .began:
collectionView.beginInteractiveMovementForItem(at: indexPath)
// This is the class variable I mentioned above
offsetForCollectionViewCellBeingMoved = offsetOfTouchFrom(recognizer: recognizer, inCell: cell)
// This is the vanilla location of the touch that alone would make the cell's center snap to your touch location
var location = recognizer.location(in: collectionView)
/* These two lines add the offset calculated a couple lines up to
the normal location to make it so you can drag from any part of the
cell and have it stay where your finger is. */
location.x += offsetForCollectionViewCellBeingMoved.x
location.y += offsetForCollectionViewCellBeingMoved.y
collectionView.updateInteractiveMovementTargetPosition(location)
case .changed:
var location = recognizer.location(in: collectionView)
location.x += offsetForCollectionViewCellBeingMoved.x
location.y += offsetForCollectionViewCellBeingMoved.y
collectionView.updateInteractiveMovementTargetPosition(location)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
If your collection view is only scrolling in once direction then the easiest way to achieve this is to simple lock the axis which isnt scrolling to something hardcoded, this means your cell will only move in the axis that you can scroll. Here is the code, see the changed case...
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch gesture.state {
case .began:
guard let selectedIndexPath = self.collectionView
.indexPathForItem(at: gesture
.location(in: self.collectionView)) else { break }
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
var gesturePosition = gesture.location(in: gesture.view!)
gesturePosition.x = (self.collectionView.frame.width / 2) - 20
collectionView.updateInteractiveMovementTargetPosition(gesturePosition)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
On iOS 10.0, UICollectionView pre-fetches cells by default. This leads to cells that are prepared for being shown on screen, but are hidden. This question describes it really well.
The following code will successfully deselect an index path when its cell is either visible or does not exist at all. If the cell exists and is hidden, the index path will be deselected, but the cell becomes stuck in the selected state until it is reused.
collectionView!.deselectItem(at: indexPath, animated: false)
This problem does not exits on iOS 9 or when pre-fetching is disabled with isPrefetchingEnabled = false on iOS 10.0.
Is this a bug in UICollectionView or am I misunderstanding how deselectItem is supposed to work?
Here is the full code of a UICollectionViewController subclass that demonstrates this behaviour with the following steps:
Tap on a cell, so that it becomes selected (red)
Scroll the cell slightly off-screen
Tap the "Deselect Cell" button
Scroll the cell back on screen
Observe how it still looks selected
Tap on another cell
Observe how both cells look selected
Scroll the first cell far off-screen and back again
Observe how the first cell finally does not look selected
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
let button = UIButton(frame: CGRect(x: 10, y: 30, width: 360, height: 44))
button.backgroundColor = #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)
button.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal)
button.setTitleColor(#colorLiteral(red: 0.05882352963, green: 0.180392161, blue: 0.2470588237, alpha: 1), for: .highlighted)
button.setTitle("Deselect Cell", for: .normal)
button.addTarget(self, action: #selector(CollectionViewController.buttonPress), for: .touchUpInside)
view.addSubview(button)
}
func buttonPress() {
for indexPath in collectionView!.indexPathsForSelectedItems ?? [IndexPath]() {
let cell = collectionView!.cellForItem(at: indexPath)
NSLog("Deselecting indexPath: %#, cell: %#", indexPath.description, cell?.frame.debugDescription ?? "not visible")
collectionView!.deselectItem(at: indexPath, animated: false)
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 300
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
cell.selectedBackgroundView = UIView(frame: cell.bounds)
cell.selectedBackgroundView!.backgroundColor = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)
return cell
}
}
As far as I can tell this is a bug in UICollectionView, and I've opened a Radar myself. You might want to do so as well to apply more pressure.
Even before prefetching, collection view didn't bother deselecting cells that weren't visible. Even cellForItemAtIndexPath: states that it returns nil for cells that are not visible.
But before prefetching, as soon as a cell left the content view it was added to the reuse pool, and when you scrolled back you got a reused cell that had its selected state reset.
Now, that cell remains loaded and wont reset until it's reused, by scrolling further away, beyond the prefetching area.
To fix this you can either set prefetchingEnabled to NO and lose its benefits, or update selection state whenever a cell appears -
- (void)collectionView:(UICollectionView *)collectionView
willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (!cell.isSelected) {
return;
}
if ([[collectionView indexPathsForSelectedItems] containsObject:indexPath]) {
return;
}
cell.selected = NO;
}
This doesn't seem to diminish performance.
I have a simple UICollectionView written in Swift. My sectionInsets used to be 0, 35, 0, 35 which gave the entire collectionView margins left end right. My minimumLineSpacing is set to default 10, which means all of my cells have a margin of 10 between each other. All good.
Then I divided the UICollectionView into two sections. The first section serves to hold a cell that acts as a prompt to create a new cell (which will end up in section 2). The second section holds all other cells (the actual content).
My problem is that ever since I added the second section, the spacing between the one static cell in section 1 and the first cell in section 2 isn't 10 anymore but 35+10. I'm now trying to get this particular spacing back to 10.
My thought was to add an if condition to identify whether it's section 1 or 2 and set the sectionInsets accordingly. I'm stuck however as to where and how exactly I should do this. I don't seem to be able to call sectionInsets or FlowLayout from my View Controller at e.g. cellForItemAtIndexPath. Should I do it in my CustomCollectionViewFlowLayout subclass? Here is the code that I have in there.
- (void)awakeFromNib
{
self.itemSize = CGSizeMake(305.0, 407.0);
self.minimumInteritemSpacing = 20.0;
self.minimumLineSpacing = 10.0;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(0, 35, 0, 35);
}
And this is the code for my sections in my view controller:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let firstCell = collectionView.dequeueReusableCellWithReuseIdentifier("createCell", forIndexPath: indexPath) as! CreateCollectionViewCell
firstCell.imageView.image = UIImage(named: "puppy3")
return firstCell
} else {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("mainCell", forIndexPath: indexPath) as! MainCollectionViewCell
cell.imageView?.image = self.imageArray[indexPath.row]
return cell
}
}
UPDATE:
I figured it out. It works for me by adding two variables for sectionInsets of type UIEdgeInsets in my view controller and then adding this function:
//Set custom insets per section
func collectionView(collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
if section == 0 {
return sectionInsets1
} else {
return sectionInsets2
}
}
I'm new to tvOS. I have created UICollectionView using a XIB file in Objective-C. How can I focus UICollectionViewCell?
I have a label and an image view in the cell, and I want to focus both the label and the image view.
UICollectionViewCell are not focus appearance by default, but you can achive this by adding one yourself.
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
if (self.focused)
{
// Apply focused appearence,
// e.g scale both of them using transform or apply background color
}
else
{
// Apply normal appearance
}
}
OR
If you just want to focus ImageView like scaling it up when collection view cell get focus you can do like this in awakeFromNib method
self.imageView.adjustsImageWhenAncestorFocused = YES;
self.clipToBounds = NO;
Add this in custom class of collectionview:
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if (self.focused) {
collectionView.image.adjustsImageWhenAncestorFocused = true
} else {
collectionView.image.adjustsImageWhenAncestorFocused = false
}
}
Add below methods to your collectionview cell.It will show title of focussed cell only giving complete look and feel of focus.
override func awakeFromNib() {
super.awakeFromNib()
// These properties are also exposed in Interface Builder.
imageView.adjustsImageWhenAncestorFocused = true
imageView.clipsToBounds = false
title.alpha = 0.0
}
// MARK: UICollectionReusableView
override func prepareForReuse() {
super.prepareForReuse()
// Reset the label's alpha value so it's initially hidden.
title.alpha = 0.0
}
// MARK: UIFocusEnvironment
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
/*
Update the label's alpha value using the `UIFocusAnimationCoordinator`.
This will ensure all animations run alongside each other when the focus
changes.
*/
coordinator.addCoordinatedAnimations({
if self.focused {
self.title.alpha = 1.0
}
else {
self.title.alpha = 0.0
}
}, completion: nil)
}
The natural direction for a UICollectionView to scroll when set horizontally is from left to right. Is there any way to reverse this? The simpler the better.
I'm not sure exactly what you mean -- if you set the scrolling to horizontal, it scrolls equally well, left and right. If you want it to start it from the right side, you can use this method:
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:self.theData.count - 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
This assumes that you have 1 section, and the array populating the collection view is called theData.
Swift4 solution
in cellForItemAt collectionView function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = categoryBook.dequeueReusableCell(withReuseIdentifier: "HomeCategoryCell", for: indexPath) as! HomeCategoryCell
collectionView.transform = CGAffineTransform(scaleX:-1,y: 1);
cell.transform = CGAffineTransform(scaleX:-1,y: 1);
}
but this solution in some cases did not work properly if it dose not you can use ColletctionView scrollToItem method and you can implement it after you reload the data .
self.YourCollectionView.reloadData()
self.YourCollectionView.scrollToItem(at: NSIndexPath(item: self.YourObjectListData.count - 1, section: 0) as IndexPath, at: .right, animated: false)
Same thing for swift:
collectionView?.scrollToItemAtIndexPath(NSIndexPath(forItem: theData.count - 1, inSection: 0), atScrollPosition: .Right, animated: false)
Use This Extention
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true //RETURN true if collection view needs to enable RTL
}
}
I have found using xCode 12.4 with an app that targets iOS 12 that this there seems to be no need to load the items in a different order or do any transforms. The only issue has to do with the initial scroll position. So all I need to do to get things working in both RTL and LTR is the following:
collectionView.reloadData {
if self.collectionView.effectiveUserInterfaceLayoutDirection == .rightToLeft {
self.collectionView?.scrollToItem(at: IndexPath(row:0, section:0), at: .right, animated: false)
}
}