Im trying to create an effect similar to that in TastemadeCity for the iPad. Landscape only. A collectionView made up of thin columns (7.5 across the screen) for a dynamic number of images. The idea being that as the collectionView is scrolled horizontally, the image itself will barely seem to move, but the cell will move across it and show a little more of the image.
Ive got it working almost perfectly. However when the cell is created it is centring the image in the cell and not the overall screen (offset by the number of the cell, or collectionView content offset). So when the collection is scrolled, eery image starts too far over to the right and it eventually runs out of image before the edge of the display. I have tried playing with Aspect fit and aspect fill if I change it, it just shrinks the image to the width of the cell. It has to keep the height/width of the screen.
To make things simpler I have tried to auto crop all the images in the array to the same shape as the device screen(I think that code is ok?)
So basically, how do I set the ImageView to the size of the screen, but with only a sliver of it visible through the cell, but with an offset of the collectionView content?
import UIKit
let parallaxCellIdentifier = "parallaxCell"
class HomeViewController: UICollectionViewController {
var images = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
for i in 1...14 {
let img = cropToBounds(UIImage(named:"\(i)@2x")!, width: Double(UIScreen.mainScreen().bounds.size.width), height: Double(UIScreen.mainScreen().bounds.size.height))
images.append(img)
}
}
func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {
let contextImage: UIImage = UIImage(CGImage: image.CGImage!)
let contextSize: CGSize = UIScreen.mainScreen().bounds.size
var posX: CGFloat = 0.0
var posY: CGFloat = 0.0
var cgwidth: Double = width
var cgheight: Double = height
if Double(image.size.width/image.size.height) > 1 {
posX = ((contextSize.height - contextSize.width) / 2)
posY = 0
cgwidth = Double(contextSize.width)
cgheight = Double(contextSize.height)
} else if Double(image.size.width/image.size.height) <= 1 {
posX = 0
posY = ((contextSize.width - contextSize.height) / 2)
cgwidth = Double(contextSize.width)
cgheight = Double(contextSize.height)
}
let rect: CGRect = CGRectMake(posX, posY, CGFloat(cgwidth), CGFloat(cgheight))
// Create bitmap image from context using the rect
let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(CGImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let parallaxCell = collectionView.dequeueReusableCellWithReuseIdentifier(parallaxCellIdentifier, forIndexPath: indexPath) as! ParallaxCollectionViewCell
parallaxCell.image = images[indexPath.row]
parallaxCell.imageView.contentMode = .ScaleAspectFill
let xOffset = (collectionView.contentOffset.x )
parallaxCell.offset(CGPointMake(xOffset, 0.0)) // this doesnt make any difference at this point, i can remove it. I have tried moving the constraints which works here, but breaks them when scrolling
return parallaxCell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let widthOfPoster = collectionView.frame.size.width / 7.5
let heightOfPoster = collectionView.frame.size.height
return CGSizeMake(widthOfPoster, heightOfPoster)
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
if let visibleCells = collectionView!.visibleCells() as? [ParallaxCollectionViewCell] {
for parallaxCell in visibleCells {
let xOffset = (collectionView!.contentOffset.x )
parallaxCell.offset(CGPointMake(xOffset, 0.0)) // this doesnt seem to do anything here, but works in the sr
}
}
}
}
This is the function within the cell, it just changes the offset during the scroll
func offset(offset: CGPoint) {
imageView.frame = CGRectOffset(self.imageView.bounds, offset.x, offset.y)
}
So I worked it out. I cropped all images to the shape of the screen. Then created the imageView inside a subview of the cell. This gave me control over the offset.
let parallaxCellIdentifier = "parallaxCell"
class HomeViewController: UICollectionViewController {
var images = [UIImage]()
let OffsetSpeed: CGFloat = 1025
let cellsPerScreen: CGFloat = 7.5
let ImageWidth: CGFloat = UIScreen.mainScreen().bounds.size.width
let imageHeight: CGFloat = UIScreen.mainScreen().bounds.size.height
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
for i in 1...22 {
let img = cropToBounds(UIImage(named:"\(i)@2x")!, width: Double(ImageWidth), height: Double(imageHeight))
images.append(img)
}
}
func cropToBounds(image: UIImage, width: Double, height: Double) -> UIImage {
let contextImage: UIImage = UIImage(CGImage: image.CGImage!)
let posX: CGFloat = 0.0
let posY: CGFloat = 0.0
var cgwidth: Double = width
var cgheight: Double = height
let imageRatio = contextImage.size.width / contextImage.size.height
let contextRatio = CGFloat(width) / CGFloat(height)
if imageRatio >= contextRatio {
cgwidth = Double(contextImage.size.height) * Double(contextRatio)
cgheight = Double(contextImage.size.height)
} else {
cgwidth = Double(contextImage.size.width)
cgheight = (Double(contextImage.size.width) / Double(contextRatio))
}
let rect: CGRect = CGRectMake(posX, posY, CGFloat(cgwidth), CGFloat(cgheight))
// Create bitmap image from context using the rect
let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)!
// Create a new image based on the imageRef and rotate back to the original orientation
let image: UIImage = UIImage(CGImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
return image
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let parallaxCell = collectionView.dequeueReusableCellWithReuseIdentifier(parallaxCellIdentifier, forIndexPath: indexPath) as! ParallaxCollectionViewCell
let xOffset = -((UIScreen.mainScreen().bounds.size.width / cellsPerScreen) * CGFloat(indexPath.row))
parallaxCell.offsetX = xOffset
parallaxCell.viewDidLayoutSubviews()
parallaxCell.rangeView.image = images[indexPath.row]
parallaxCell.path = indexPath
return parallaxCell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 0
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let widthOfPoster = collectionView.frame.size.width / cellsPerScreen
let heightOfPoster = collectionView.frame.size.height
return CGSizeMake(widthOfPoster, heightOfPoster)
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
if let visibleCells = collectionView!.visibleCells() as? [ParallaxCollectionViewCell] {
for parallaxCell in visibleCells {
let xOffset = (((collectionView!.contentOffset.x ) / ImageWidth) * OffsetSpeed)
parallaxCell.offset(CGPointMake(xOffset, 0.0))
}
}
}
}
and the Cell code. (the RangeImage, was just a blank UIImageView class)
class ParallaxCollectionViewCell: UICollectionViewCell {
let rangeView = RangeImage(frame: CGRectZero)
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
var offsetX:CGFloat = 0.0
var path = NSIndexPath()
override func awakeFromNib() {
rangeView.backgroundColor = UIColor.redColor()
contentView.addSubview(rangeView)
}
func viewDidLayoutSubviews() {
rangeView.frame = CGRect(x: offsetX, y: 0.0,
width: width, height: height)
}
func offset(offset: CGPoint) {
contentView.frame = CGRectOffset(contentView.bounds, offset.x, offset.y)
}
}