How to fade collection view cells as user scrolls? - swift

What I want to do: https://imgur.com/a/U9p2Iar
What I attempted to do: https://imgur.com/a/kHl1PIg
As the user scrolls, I want some UI elements on the cell to fade away and appear again depending on scroll position.
What I tried is:
func fadeCell(for indexPath: IndexPath) {
let screenHalf = collectionView.bounds.height / 2 + collectionView.bounds.height * CGFloat(indexPath.row)
let topPosition = collectionView.contentOffset.y
guard let cell = collectionView.cellForItem(at: indexPath) as? SomeCellClass,
topPosition >= 0 else { return }
let needOpacityToZeroChange = topPosition >= screenHalf
let opacity = needOpacityToZeroChange ? 0 : 1 - ((topPosition.truncatingRemainder(dividingBy: screenHalf)) / screenHalf)
cell.fade(with: opacity)
}
SomeCellClass is a UICollectionViewCell used for given collectionView.
cell.fade() is a method of SomeCellClass which sets alpha of certain UIViews to given opacity.
fadeCell(for indexPath: IndexPath) would be called in UICollectionViewDelegate's didScroll() method for visible cells on the screen.

Related

Collectionview inside tableview causing issue with 2 columns

I have vertical collection-view inside tableview cell. collection view contain feature of load more too. for self sizing of collection view, i make tableview cell automaticDimension.
Also i have give height constant to collection-view. first time its loaded correctly but once i go to last cell and its load-more after reloading it create lot of space after collection view. can any one let me know what i am doing wrong here. or is there any other way around to make collection-view inside tableview self sizing so it increase tableview cell height too
**
TableviewCell Class
**
justForYouCollectionView.dataSource = self
justForYouCollectionView.delegate = self
justForYouCollectionView.isScrollEnabled = false
self.collectionHeight.constant = self.justForYouCollectionView.collectionViewLayout.collectionViewContentSize.height
justForYouCollectionView.reloadData()
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.layoutIfNeeded()
let contentSize = self.justForYouCollectionView.collectionViewLayout.collectionViewContentSize
return CGSize(width: contentSize.width, height: contentSize.height + 20)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == self.justForYouArray.count - 1 && self.isLoadMore {
updateNextSet()
}
}
**
CollectionViewCell Class
**
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
// Specify you want _full width_
let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
// Calculate the size (height) using Auto Layout
let autoLayoutSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.defaultLow)
let autoLayoutFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: autoLayoutSize)
// Assign the new size to the layout attributes
autoLayoutAttributes.frame = autoLayoutFrame
return autoLayoutAttributes
}
extra space can be seen in image
I have worked on that earlier all I did is, set tableview height constraint set in storyboard and drop its outlet in viewController then after populate data get array count and divide by 2 and after dividing I multiply it by CollectionViewCell height and set that height to the UITableView height constraint like this.
let count = (array.count / 2) * cellHeight
tableviewHeightConstraint.constant = CGFloat(count)
This will solve your problem.
try
let a = (yourArray.count /2 ) * heightCell
tblViewHeightConstraint.constant = CGFloat(a)

Collection view cell vertical top alignment

I have a problem in collection view(Grid Format) cell height. The grid format contains two columns overall, so basically 2 cells in each row. My collection view cell height increase according to the content inside it but the content is center aligned and not top aligned. I want to achieve the height of the cells in a row which has the greater height. How can I do that? Please advice.
https://drive.google.com/file/d/0B56cE4V6JI-RYmdQT2pIZ0hYQ2phM3Z2YmJNYU1SeXNnYTNN/view?usp=sharing
https://drive.google.com/file/d/0B56cE4V6JI-RQUdqVmV4b244cFI5SGd2TnJfbG1tckdQU21Z/view?usp=sharing
https://drive.google.com/file/d/0B56cE4V6JI-RYTYxVzJyVUp1clpJTkVqYjN6QXBPeERvVHZR/view?usp=sharing
I have added a link above which is the problem right now.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let cellsAcross: CGFloat = 2
let spaceBetweenCells: CGFloat = 10
let dim = (collectionView.bounds.width - (cellsAcross - 1) * spaceBetweenCells) / cellsAcross
print(dim) // calculating width
//calculating my content height
let toppingHeight = sizeOfString(string: PizzaMenuItems[indexPath.row].pizzaToppings, constrainedToWidth: Double(dim))
let pizzaNameHeight = sizeOfString(string: PizzaMenuItems[indexPath.row].menuName, constrainedToWidth: Double(dim))
let newHeight = toppingHeight.height + pizzaNameHeight.height + 57
// to increase my collection view height
let heightt = (view.frame.width)/2
let count = self.PizzaMenuItems.count
if self.PizzaMenuItems.count == 1 {
}
else{
if count % 2 == 0
{ //even Number
collectionViewC_Height.constant = heightt * CGFloat(self.PizzaMenuItems.count/2) + 57
} else
{ // odd Number
collectionViewC_Height.constant = heightt * CGFloat(self.PizzaMenuItems.count/2+1)}
}
return CGSize(width: dim, height: newHeight)}
Verbal work around:
In your collection cell xib don't specify UILabel on the container view.
place a UIView on the bottom layer of container view with (top:0,lead:0,trail:0,bottom:0) constraints.
Now place your UILabel with constraints (top:0,lead:0,trail:0,height:<=UIView)
This helps you to adjust your label height according to their content which help to anchor label alignment on top.

Keep active cell in collectionview in one place

I'm working on a tvOS application where I want the active cell of a collectionView always in the same position. In other words, I don't want the active cell to go through my collectionView but I want the collectionView to go through my active cell.
Example in the following image. The active label is always in the center of the screen while you can scroll the collectionView to get a different active label over there.
Basically a pickerview with a custom UI. But the pickerview of iOS is unfortunately not provided on tvOS...
I've already looked into UICollectionViewScrollPosition.verticallyCentered. That partially gets me there but it isn't enough. If I scroll really fast, the active item jumps further down the list and when I pause it scrolls up all the way to the center. I really want the active item to be in center of the screen at all times.
Any ideas on how to do this?
Update based on #HMHero answer
Okay, I tried to do what you told me, but can't get the scrolling to work properly. This is perhaps due to my calculation of the offset, or because (like you said) setContentOffset(, animated: ) doesn't work.
Right now I did the following and not sure where to go from here;
Disable scrolling and center last and first label
override func viewDidLoad() {
super.viewDidLoad()
//Disable scrolling
self.collectionView.isScrollEnabled = false
//Place the first and last label in the middle of the screen
self.countryCollectionView.contentInset.top = collectionView.frame.height * 0.5 - 45
self.countryCollectionView.contentInset.bottom = collectionView.frame.height * 0.5 - 45
}
Getting the position of a label to retrieve the distance from the center of the screen (offset)
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if let indexPath = context.nextFocusedIndexPath,
let cell = countryCollectionView.cellForItem(at: indexPath) {
//Get the center of the next focused cell, and convert that to position in the visible part of the (tv) screen itself
let cellCenter = countryCollectionView.convert(cell.center, to: countryCollectionView.superview)
//Get the center of the collectionView
let centerView = countryCollectionView.frame.midY
//Calculate how far the next focused cell y position is from the centerview. (Offset)
let offset = cellCenter.y - centerView
}
}
The offset returns incrementals of 100 when printing. The labels' height is 90, and there is a spacing of 10. So I thought that would be correct although it runs through all the way up to 2400 (last label).
Any ideas on where to go from here?
It's been more than a year since I worked on tvOS but as I remember it should be fairly simple with a simple trick. There might be a better way to do this with updated api but this is what I did.
1) Disable to scroll on collection view isScrollEnabled = false
2) In delegate, optional public func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator), get nextFocusedIndexPath and calculate the offset of the cell to be at the center of the collection view.
3) With the offset, animate the scroll manually in the delegate callback.
I found the old post where I got an idea.
How to center UICollectionViewCell that was focused?
I ended up doing somewhat different from the answer in the thread but it was a good hint.
Okay, along with some pointers of #HMHero I've achieved the desired effect. It involved animating the contentOffset with a custom animation. This is my code;
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if let previousIndexPath = context.previouslyFocusedIndexPath,
let cell = countryCollectionView.cellForItem(at: previousIndexPath) {
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
cell.contentView.alpha = 0.3
cell.contentView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
})
}
if let indexPath = context.nextFocusedIndexPath,
let cell = countryCollectionView.cellForItem(at: indexPath) {
let cellCenter = CGPoint(x: cell.bounds.origin.x + cell.bounds.size.width / 2, y: cell.bounds.origin.y + cell.bounds.size.height / 2)
let cellLocation = cell.convert(cellCenter, to: self.countryCollectionView)
let centerView = countryCollectionView.frame.midY
let contentYOffset = cellLocation.y - centerView
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
collectionView.contentOffset.y = contentYOffset
cell.contentView.alpha = 1.0
cell.contentView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.countryCollectionView.layoutIfNeeded()
})
}
}

How to create grid of squares with no spaces between them?

I want a grid of squares with no spaces between them. Currently, I use custom UICollectionViewLayout:
class BoardViewLayout: UICollectionViewLayout {
override func collectionViewContentSize() -> CGSize {
return self.collectionView!.frame.size
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let boardSize = Int(sqrt(Double(self.collectionView!.numberOfItemsInSection(0))))
let index = indexPath.row
let size = self.collectionView!.frame.width / CGFloat(boardSize)
let xIndex = CGFloat(index%boardSize)
let yIndex = CGFloat(index/boardSize)
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = CGRect(origin: CGPointMake(size*xIndex, size*yIndex), size: CGSizeMake(size, size))
return attributes
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesArray = [UICollectionViewLayoutAttributes]()
for i in 0..<self.collectionView!.numberOfItemsInSection(0) {
attributesArray.append(self.layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: i, inSection: 0))!)
}
return attributesArray
}
}
It looks like this:
screenshot of the result
I need to get rid of the spaces between third and fourth row and third and fourth column (see the screenshot).
Later, there will be square images instead of only colours (borders won't change) and there will be background that has to perfectly fit the images (so it looks like one image if all squares are filled and borders disappear)
I know it strongly depends on how system scales images etc., but there must be way to do this.
Thank you for help
You just need to set your minimum spacing to be 0:
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
return 0.0;
}

Get RowHeight of each row in UITableView Swift

is there a way to get the row height for each row in an UITableView in swift ? Please help. Thanks in advance.
Swift 4:
var height: CGFloat = 0
for cell in tableView.visibleCells {
height += cell.bounds.height
}
I think this is what you are looking for. This assumes that "Cell" is the identifier of the given row, and indexPath is the index of the row in question.
let row = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)as! UITableViewCell
let height = row.bounds.height
Cells only exist when they are visible, and you have access to them through the table view's visibleCells() method.
for obj in tableView.visibleCells() {
if let cell = obj as? UITableViewCell {
let height = CGRectGetHeight( cell.bounds )
}
}
The functional way:
let sum = tableView.visibleCells.map( { $0.bounds.height } ).reduce(0,+)
print("Height:\(sum)")
You need the cell for a certain IndexPath to calculate its bounds.
You can do it this way in any of the delegate functions of the UITableView :
let row = tableView.cellForRow(at: indexPath)
let cellHeight = (row?.bounds.height)!
let cellWidth = (row?.bounds.width)!