Kodeco Forums

Video Tutorial: Custom Collection View Layouts Part 8: Ultravisual – Featured Cell

Learn the how to create a layout like Ultravisual so that as the user scrolls, the next standard size cell morphs to become the large featured cell.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/3987-custom-collection-view-layout/lessons/9

Would you please give more explanation of the code? like why should we calculate maxY or yOffset this way in the video?
Otherwise, from 2:40, you are reading the code which I can download and checkout out myself without much explanation.
This is a general suggestion. As I found the topic is pretty complicated so more details we require.

Hello,

I’m using Xcode 8 and swift 3 to try reproduce the behaviour of the collection view layout explained in this video.

I get this error: no UICollectionViewLayoutAttributes instance

Therefore, I have implemented the following method in the UICollectionViewLayout class to fix it:

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
	
    return self.cache[indexPath.row]

}

Let’s say on the initial screen after running the app got 5 cells visible including the first featured cell.

When scrolling down, I get a strange behaviour. The initial featured cell and the next cell got the behaviour expected. However, I have the cell 6 appearing over the other cells.

How can I fix it?

Here is the code I have replicated from the lesson:

//
//  UltravisualLayout.swift
//  RWDevCon
//
//  Created by Mic Pringle on 27/02/2015.
//  Copyright (c) 2015 Ray Wenderlich. All rights reserved.
//

import UIKit

/* The heights are declared as constants outside of the class so they can be easily referenced     elsewhere */
struct UltravisualLayoutConstants {
  struct Cell {
/* The height of the non-featured cell */
static let standardHeight: CGFloat = 100
/* The height of the first visible cell */
static let featuredHeight: CGFloat = 280
  }
}

class UltravisualLayout: UICollectionViewLayout {

  // MARK: Properties and Variables

  /* The amount the user needs to scroll before the featured cell changes */
  let dragOffset: CGFloat = 180.0

  var cache = [UICollectionViewLayoutAttributes]()

  /* Returns the item index of the currently featured cell */
  var featuredItemIndex: Int {
    get {
      /* Use max to make sure the featureItemIndex is never < 0 */
      return max(0, Int(collectionView!.contentOffset.y / dragOffset))
    }
  }

  /* Returns a value between 0 and 1 that represents how close the next cell is to becoming the     featured cell */
  var nextItemPercentageOffset: CGFloat {
    get {
      return (collectionView!.contentOffset.y / dragOffset) - CGFloat(featuredItemIndex)
    }
  }

  /* Returns the width of the collection view */
  var width: CGFloat {
    get {
      return collectionView!.bounds.width
    }
  }

  /* Returns the height of the collection view */
  var height: CGFloat {
    get {
      return collectionView!.bounds.height
    }
  }

  /* Returns the number of items in the collection view */
  var numberOfItems: Int {
    get {
      return collectionView!.numberOfItems(inSection: 0)
        }
  }
  
  // MARK: UICollectionViewLayout

  /* Return the size of all the content in the collection view */
  override var collectionViewContentSize : CGSize {

    let contentHeight = (CGFloat(numberOfItems) * dragOffset) + (height - dragOffset)

    return CGSize(width: width, height: contentHeight)
  }

  override func prepare() {

cache.removeAll(keepingCapacity: false)

let standardHeight = UltravisualLayoutConstants.Cell.standardHeight

let featuredHeight = UltravisualLayoutConstants.Cell.featuredHeight

var frame = CGRect.zero

var y:CGFloat = 0

for item in 0 ... (numberOfItems - 1) {

	let indexPath = NSIndexPath(item: item, section: 0)
	
	let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
	
	attributes.zIndex = item
	
	var height = standardHeight
	
	if indexPath.item == featuredItemIndex {
		
		let yOffset = standardHeight * nextItemPercentageOffset
		
		y = self.collectionView!.contentOffset.y - yOffset
		
		height = featuredHeight
		
	} else if (indexPath as NSIndexPath).item == (featuredItemIndex + 1) && (indexPath as NSIndexPath).item != numberOfItems {
		
		let maxY = y + standardHeight
		
		height = standardHeight + max((featuredHeight - standardHeight) * nextItemPercentageOffset, 0)
		
		y = maxY - height
	}
	
	frame = CGRect(x: 0, y: y, width: width, height: height)
	
	attributes.frame = frame
	
	cache.append(attributes)
	
	y = frame.maxY
	
	print("y value is: \(y)")
	
}


  }

  /* Return all attributes in the cache whose frame intersects with the rect passed to the method */

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

var layoutAttributes = [UICollectionViewLayoutAttributes]()

for attributes in cache {
	
  if attributes.frame.intersects(rect) {
	
    layoutAttributes.append(attributes)
  }
}

return layoutAttributes
  }

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
	
	return self.cache[indexPath.row]
}

/* Return true so that the layout is continuously invalidated as the user scrolls */
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
	
return true
  }

}

This layout does not work with iOS 10 prefetching. If you are running this tutorial code on an iOS 10 device or simulator, put the following code in InspirationsViewController.viewDidLoad():

if #available(iOS 10.0, *) {
    collectionView!.prefetchingEnabled = false
}

I’m not sure why this layout doesn’t work with iOS 10 prefetching, but the code above fixes it for me.

Thanks zurzurzur. Works perfectly.

I’ve passed hours torturing the code to make it work. At least, it helps me to understand how all this works!

Thanks that sorted me too, i think cells that are being prefetched are given a value which is on the screen, when if fact they shouldn’t be visible. does anyone know how to cater for these

Good video tutorial. I was wondering if it would be possible to provide a version of the finished project without a story board file. So basically everything created programatically. Being able to compare both a storyboard version and non storyboard version would help me understand everything even better.

@ys_xs I agree, this course overall has felt very dense with little explanation. Thank you Michael Briscoe for making this course. Next time, could you please explain what is happening and why? :smile:

This is the worst video ever, where did MosaicViewLayout come from? He only shows how to set the delegate methods. In general this video course for collectionview feels like the shortest and the one in which I’ve spent more time in, trying to read code that the instructor didn’t explain.