how to calculate interval between collectionView sections - ios

How to calculate interval between UICollectionView Section or Header frame. I have custom PhotoViewFlowLayout it works pretty well when scrolling through the collection view, but problem in the headerView my headerView display wrong place.
override func prepareLayout() {
super.prepareLayout()
let columnWidth:CGFloat = contentWidth / numberOfColumns
if cache.isEmpty {
var column = 0
var row = 0
if let numberOfSections = collectionView?.numberOfSections() {
for index in 0 ..< numberOfSections {
let indexPath = NSIndexPath(forItem: 0, inSection: index)
stickyHeaderIndexPaths.append(indexPath)
for item in 0 ..< collectionView!.numberOfItemsInSection(0) {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
...
let frame = CGRect(x: cell_x, y: cell_y, width: cell_size, height: cell_size)
let insetFrame = CGRectInset(frame, cellPadding, cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
}
}
}
contentHeight = CGFloat(row) * CGFloat(columnWidth - cellPadding*2)
}
}
override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if CGRectIntersectsRect(attributes.frame, rect) {
layoutAttributes.append(attributes)
}
}
for indexPath in stickyHeaderIndexPaths {
let headerAttribute = layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)
print(headerAttribute)
layoutAttributes.append(headerAttribute!)
}
return layoutAttributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttribute = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: indexPath)
let contentOffset = collectionView?.contentOffset ?? CGPointZero
var nextHeaderOrigin = CGPointZero
var nextHeaderIndex : Int = 0
if (indexPath.section + 1 < collectionView?.numberOfSections()) {
nextHeaderIndex = indexPath.section + 1
let nextIndexPath = NSIndexPath(forItem: 0, inSection: nextHeaderIndex)
let nextHeaderFrame = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: nextIndexPath)!.frame
nextHeaderOrigin = nextHeaderFrame.origin
}
else {
return layoutAttribute;
}
var headerFrame = layoutAttribute!.frame
if (scrollDirection == UICollectionViewScrollDirection.Vertical) {
let nextStickyCellY = nextHeaderOrigin.y - headerFrame.size.height
let currentStickyCellY = max(contentOffset.y, headerFrame.origin.y)
headerFrame.origin.y = min(currentStickyCellY, nextStickyCellY)
}
else {
let nextStickyCellX = nextHeaderOrigin.x - headerFrame.size.width
let currentStickyCellX = max(contentOffset.x, headerFrame.origin.x)
headerFrame.origin.x = min(currentStickyCellX, nextStickyCellX)
}
layoutAttribute!.zIndex = stickyHeaderZIndex
layoutAttribute!.frame = headerFrame
return layoutAttribute
}
image:

Related

Change in Display for a custom collectionview with both horizontal and vertical scroll

Here I have output with code
CustomCollectionViewController
class CustomCollectionViewController: UICollectionViewController {
// MARK: UICollectionViewDataSource
let value = [["a","b","c","d","e","f"], ["a","b","c"], ["a","b","c","d"], ["a","b","c","d","e","f"], ["a","b","c","d","e","f"]]
override func numberOfSections(in collectionView: UICollectionView) -> Int {
//#warning Incomplete method implementation -- Return the number of sections
return value.count
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//#warning Incomplete method implementation -- Return the number of items in the section
return value[section].count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCollectionViewCell
// Configure the cell
let text = value[indexPath.section][indexPath.item]
// cell.label.text = "Sec " + indexPath.item.description + "/Item " + indexPath.section.description
cell.label.text = "\(text)"
return cell
}
}
CustomCollectionViewLayout
import UIKit
class CustomCollectionViewLayout: UICollectionViewLayout {
let CELL_HEIGHT = 30.0
let CELL_WIDTH = 100.0
let STATUS_BAR = UIApplication.shared.statusBarFrame.height
var cellAttrsDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
var dataSourceDidUpdate = true
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func prepare() {
dataSourceDidUpdate = false
// Cycle through each section of the data source.
if let sectionCount = collectionView?.numberOfSections, sectionCount > 0 {
for section in 0...sectionCount-1 {
// Cycle through each item in the section.
if let rowCount = collectionView?.numberOfItems(inSection: section), rowCount > 0 {
for item in 0...rowCount-1 {
// Build the UICollectionVieLayoutAttributes for the cell.
let cellIndex = IndexPath(item: item, section: section)
let xPos = Double(item) * CELL_WIDTH
let yPos = Double(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
let contentWidth = Double(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttributes in cellAttrsDictionary.values {
if rect.intersects(cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Problem
I want output as reverse order i.e.
what change should I make here.
I am making it in a bigger application, this is just a prototype of it.
I have tried your code. By changing following things in CustomCollectionViewLayout class.
override func prepare() {
dataSourceDidUpdate = false
// Cycle through each section of the data source.
if let sectionCount = collectionView?.numberOfSections, sectionCount > 0 {
for section in 0...sectionCount-1 {
//ADD THIS NEW LINES.
let xPos = (Double(section) * CELL_WIDTH) + (Double(section) * 5)
var yPos : Double = 0.0
if let rowCount = collectionView?.numberOfItems(inSection: section), rowCount > 0 {
for item in 0...rowCount-1 {
// Build the UICollectionVieLayoutAttributes for the cell.
let cellIndex = IndexPath(item: item, section: section)
//let xPos = Double(item) * CELL_WIDTH
//let yPos = Double(section) * CELL_HEIGHT
// Comment above lines and add the below lines.
yPos = (Double(item) * CELL_HEIGHT) + (Double(item) * 5)
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
let contentWidth = Double(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
Output

How to set each UICollectionView section in an individual column

I have a UICollectionView which contains a given amount of sections that each one of them consists of a given amount of rows.
Now, since I assigned the grid layout as vertical, the UICollectionView looks something like this:
However when the width of the screen enlarges such as on landscape mode or on an IPad, I would like the collectionView to have a horizontal grid, and each row in the grid I wand it to contain each one of the UICollectionView sections vertically.
something like this:
Is there a simple way to work around this?
I have it tried in UICollectionViewController.
Click CollectionView and change to CustomLayout [UICollectionViewLayout]
UICollectionViewController
let values = [["a","b","c","d","e","f"], ["a","b","c"], ["a","b","c","d"], ["a","b","c","d","e","f"], ["a","b","c","d","e","f"],["a","b","c","d","e","f"], ["a","b","c"], ["a","b","c","d"], ["a","b","c","d","e","f"], ["a","b","c","d","e","f"]]
override func viewDidLoad() {
super.viewDidLoad()
let XIB = UINib.init(nibName: "RulesExrView", bundle: Bundle.main)
rulesCollexnVw.register(XIB, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerreuse")
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return values.count
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return values[section].count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! RulesCollectionViewCell
let text = values[indexPath.section][indexPath.item]
cell.backgroundColor = UIColor.clear
cell.layer.borderColor = UIColor(red: 81/255, green: 57/255, blue: 141/255, alpha: 1.0).cgColor
cell.layer.borderWidth = 1.0
cell.txtLbl.text = text
cell.txtLbl.textAlignment = .center
cell.txtLbl.textColor = .white
cell.layer.cornerRadius = 5
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("\n\n Selected indPath ", indexPath)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
print("Kinddd ", kind)
switch kind {
case UICollectionElementKindSectionHeader:
if let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "headerreuse", for: indexPath) as? RulesReusableView {
// Configure Supplementary View
supplementaryView.backgroundColor = UIColor.clear
supplementaryView.headLbl.text = "Section \(indexPath.section)".uppercased()
supplementaryView.headLbl.textColor = UIColor.white
return supplementaryView
}
fatalError("Unable to Dequeue Reusable Supplementary View")
default:
assert(false, "Unexpected element kind")
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
rulesCollexnVw.reloadData()
} else {
print("Portrait")
rulesCollexnVw.reloadData()
}
}
UICollectionViewLayout [Not UICollectionViewFlowLayout]
class RulesLayout: UICollectionViewLayout {
let CELL_HEIGHT = 30.0
let CELL_WIDTH = 100.0
let horizontalSpacing = 5.0
let verticalSpaing = 5.0
let headerSpacing = 40.0
let STATUS_BAR = UIApplication.shared.statusBarFrame.height
var portrait_Ypos : Double = 0.0
var cellAttrsDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
var dataSourceDidUpdate = true
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func prepare() {
dataSourceDidUpdate = false
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight
{
if let sectionCount = collectionView?.numberOfSections, sectionCount > 0 {
for section in 0...sectionCount-1 {
let xPos = (Double(section) * CELL_WIDTH) + (Double(section) * horizontalSpacing)
var yPos : Double = 0.0
if let rowCount = collectionView?.numberOfItems(inSection: section), rowCount > 0 {
for item in 0...rowCount-1 {
let cellIndex = IndexPath(item: item, section: section)
if item == 0
{
portrait_Ypos = headerSpacing
}
else
{
portrait_Ypos = portrait_Ypos + CELL_HEIGHT + verticalSpaing
}
yPos = portrait_Ypos
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
let contentWidth = Double(collectionView!.numberOfSections) * CELL_WIDTH + (Double(collectionView!.numberOfSections - 1) * horizontalSpacing)
let contentHeight = Double(collectionView!.numberOfSections) * CELL_HEIGHT
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
print("self.contentSizeself.contentSize ", self.contentSize)
}
else
{
if let sectionCount = collectionView?.numberOfSections, sectionCount > 0 {
for section in 0...sectionCount-1 {
let xPos = (Double(UIScreen.main.bounds.width) - CELL_WIDTH) / 2.0
if let rowCount = collectionView?.numberOfItems(inSection: section), rowCount > 0 {
for item in 0...rowCount-1 {
let cellIndex = IndexPath(item: item, section: section)
if section != 0
{
if item == 0
{
portrait_Ypos = portrait_Ypos + CELL_HEIGHT + headerSpacing
}
else
{
portrait_Ypos = portrait_Ypos + CELL_HEIGHT + verticalSpaing
}
}
else
{
if item == 0
{
portrait_Ypos = headerSpacing
}
else
{
portrait_Ypos = portrait_Ypos + CELL_HEIGHT + verticalSpaing
}
}
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
cellAttributes.frame = CGRect(x: xPos, y: portrait_Ypos, width: CELL_WIDTH, height: CELL_HEIGHT)
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttrsDictionary[cellIndex] = cellAttributes
}
}
}
}
let contentWidth = UIScreen.main.bounds.width
let contentHeight = CGFloat(portrait_Ypos) + CGFloat(CELL_HEIGHT)
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
print("sPort.contentSize ", self.contentSize)
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttributes in cellAttrsDictionary.values {
if rect.intersects(cellAttributes.frame) {
attributesInRect.append(cellAttributes)
let celIndPth = cellAttributes.indexPath
if celIndPth.item == 0
{ // YOU HAVE TO ADD SUPPLEMENTARY HEADER TO THIS LAYOUT ATTRIBUTES
if let supplementaryAttributes = layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: cellAttributes.indexPath) {
attributesInRect.append(supplementaryAttributes)
}
}
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttrsDictionary[indexPath]!
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if elementKind == UICollectionElementKindSectionHeader {
let atts = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: indexPath)
if let itemAttributes = layoutAttributesForItem(at: indexPath) { // HERE WE HAVE TO SET FRAME FOR SUPPLEMENTARY VIEW
atts.frame = CGRect(x: itemAttributes.frame.origin.x,
y: itemAttributes.frame.origin.y - CGFloat(headerSpacing),width: itemAttributes.frame.width,height: CGFloat(headerSpacing))
return atts
}
}
return nil
}
}
XIB Subclass - [Collection Reusable View]
class RulesReusableView: UICollectionReusableView {
#IBOutlet weak var headLbl: UILabel!
}
UICollectionViewCell
class RulesCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var txtLbl: UILabel!
}
Portrait Output
Landscape Output

Create a UICollectionView with a reversed flow layout

I want to create a view that resembles a chat view, where the newest message appears at the bottom of the screen. I would like the UICollectionViewCells to be 'pinned' to the bottom of the screen - the opposite of what the default flow layout does. How can I do this?
I've got this code from another stackoverflow answer, but it doesn't work with variable height items (which it needs to):
import Foundation
import UIKit
class InvertedStackLayout: UICollectionViewLayout {
let cellHeight: CGFloat = 100 // Your cell height here...
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttrs = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numberOfSectionItems = numberOfItemsInSection(section) {
for item in 0 ..< numberOfSectionItems {
let indexPath = IndexPath(item: item, section: section)
let layoutAttr = layoutAttributesForItem(at: indexPath)
if let layoutAttr = layoutAttr, layoutAttr.frame.intersects(rect) {
layoutAttrs.append(layoutAttr)
}
}
}
}
}
return layoutAttrs
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let contentSize = self.collectionViewContentSize
layoutAttr.frame = CGRect(
x: 0, y: contentSize.height - CGFloat(indexPath.item + 1) * cellHeight,
width: contentSize.width, height: cellHeight)
return layoutAttr
}
func numberOfItemsInSection(_ section: Int) -> Int? {
if let collectionView = self.collectionView,
let numSectionItems = collectionView.dataSource?.collectionView(collectionView, numberOfItemsInSection: section)
{
return numSectionItems
}
return 0
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0
var bounds: CGRect = .zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
if let numItems = numberOfItemsInSection(section) {
height += CGFloat(numItems) * cellHeight
}
}
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height
{
return true
}
return false
}
}
Flip your collectionView with a CGAffineTransform so that it starts at the bottom.
collectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
The only problem now is that all your cells display upside down. You then apply the transform to each cell to flip it back upright.
class InvertedCollectionViewFlowLayout: UICollectionViewFlowLayout {
// inverting the transform in the layout, rather than directly on the cell,
// is the only way I've found to prevent cells from flipping during animated
// cell updates
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attrs = super.layoutAttributesForItem(at: indexPath)
attrs?.transform = CGAffineTransform(scaleX: 1, y: -1)
return attrs
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attrsList = super.layoutAttributesForElements(in: rect)
if let list = attrsList {
for i in 0..<list.count {
list[i].transform = CGAffineTransform(scaleX: 1, y: -1)
}
}
return attrsList
}
}
UICollectionView with a reversed flow layout and dynamic cell and header height.
import Foundation
import UIKit
class InvertedFlowLayout: UICollectionViewFlowLayout {
override func prepare() {
super.prepare()
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard super.layoutAttributesForElements(in: rect) != nil else { return nil }
var attributesArrayNew = [UICollectionViewLayoutAttributes]()
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
if let attributeCell = layoutAttributesForItem(at: indexPathCurrent) {
if attributeCell.frame.intersects(rect) {
attributesArrayNew.append(attributeCell)
}
}
}
}
for section in 0 ..< collectionView.numberOfSections {
let indexPathCurrent = IndexPath(item: 0, section: section)
if let attributeKind = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPathCurrent) {
attributesArrayNew.append(attributeKind)
}
}
}
return attributesArrayNew
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributeKind = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
if let collectionView = self.collectionView {
var fullHeight: CGFloat = 0.0
for section in 0 ..< indexPath.section + 1 {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
fullHeight += cellHeight(indexPathCurrent) + minimumLineSpacing
}
}
attributeKind.frame = CGRect(x: 0, y: collectionViewContentSize.height - fullHeight - CGFloat(indexPath.section + 1) * headerHeight(indexPath.section) - sectionInset.bottom + minimumLineSpacing/2, width: collectionViewContentSize.width, height: headerHeight(indexPath.section))
}
return attributeKind
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributeCell = UICollectionViewLayoutAttributes(forCellWith: indexPath)
if let collectionView = self.collectionView {
var fullHeight: CGFloat = 0.0
for section in 0 ..< indexPath.section + 1 {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
fullHeight += cellHeight(indexPathCurrent) + minimumLineSpacing
if section == indexPath.section && item == indexPath.item {
break
}
}
}
attributeCell.frame = CGRect(x: 0, y: collectionViewContentSize.height - fullHeight + minimumLineSpacing - CGFloat(indexPath.section) * headerHeight(indexPath.section) - sectionInset.bottom, width: collectionViewContentSize.width, height: cellHeight(indexPath) )
}
return attributeCell
}
override var collectionViewContentSize: CGSize {
get {
var height: CGFloat = 0.0
var bounds = CGRect.zero
if let collectionView = self.collectionView {
for section in 0 ..< collectionView.numberOfSections {
for item in 0 ..< collectionView.numberOfItems(inSection: section) {
let indexPathCurrent = IndexPath(item: item, section: section)
height += cellHeight(indexPathCurrent) + minimumLineSpacing
}
}
height += sectionInset.bottom + CGFloat(collectionView.numberOfSections) * headerHeight(0)
bounds = collectionView.bounds
}
return CGSize(width: bounds.width, height: max(height, bounds.height))
}
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if let oldBounds = self.collectionView?.bounds,
oldBounds.width != newBounds.width || oldBounds.height != newBounds.height {
return true
}
return false
}
func cellHeight(_ indexPath: IndexPath) -> CGFloat {
if let collectionView = self.collectionView, let delegateFlowLayout = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
let size = delegateFlowLayout.collectionView!(collectionView, layout: self, sizeForItemAt: indexPath)
return size.height
}
return 0
}
func headerHeight(_ section: Int) -> CGFloat {
if let collectionView = self.collectionView, let delegateFlowLayout = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
let size = delegateFlowLayout.collectionView!(collectionView, layout: self, referenceSizeForHeaderInSection: section)
return size.height
}
return 0
}
}
you could rotate(transform) collectionView 180 degrees so top will be at the bottom and rotate(transform) each cell 180 to make them look normal.

CustomCollectionViewLayout not working on Swift 3

I have implemented UICollectionView and it can scroll horizontal and vertical with sticky rows and columns using below example:
Example
Github
Apps works find on Swift2 but after upgrade Swift3 CustomCollectionViewLayout giving me error
class CustomCollectionViewLayout: UICollectionViewLayout {
let numberOfColumns = 8
var itemAttributes : NSMutableArray!
var itemsSize : NSMutableArray!
var contentSize : CGSize!
override func prepare() {
if self.collectionView?.numberOfSections == 0 {
return
}
if (self.itemAttributes != nil && self.itemAttributes.count > 0) {
for section in 0..<self.collectionView!.numberOfSections {
let numberOfItems : Int = self.collectionView!.numberOfItems(inSection: section)
for index in 0..<numberOfItems {
if section != 0 && index != 0 {
continue
}
let attributes : UICollectionViewLayoutAttributes = self.layoutAttributesForItem(at: IndexPath(item: index, section: section))
if section == 0 {
var frame = attributes.frame
frame.origin.y = self.collectionView!.contentOffset.y
attributes.frame = frame
}
if index == 0 {
var frame = attributes.frame
frame.origin.x = self.collectionView!.contentOffset.x
attributes.frame = frame
}
}
}
return
}
if (self.itemsSize == nil || self.itemsSize.count != numberOfColumns) {
self.calculateItemsSize()
}
var column = 0
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0
var contentWidth : CGFloat = 0
var contentHeight : CGFloat = 0
for section in 0..<self.collectionView!.numberOfSections {
let sectionAttributes = NSMutableArray()
for index in 0..<numberOfColumns {
let itemSize = (self.itemsSize[index] as AnyObject).cgSizeValue
let indexPath = IndexPath(item: index, section: section)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = CGRect(x: xOffset, y: yOffset, width: (itemSize?.width)!, height: (itemSize?.height)!).integral
if section == 0 && index == 0 {
attributes.zIndex = 1024;
} else if section == 0 || index == 0 {
attributes.zIndex = 1023
}
if section == 0 {
var frame = attributes.frame
frame.origin.y = self.collectionView!.contentOffset.y
attributes.frame = frame
}
if index == 0 {
var frame = attributes.frame
frame.origin.x = self.collectionView!.contentOffset.x
attributes.frame = frame
}
sectionAttributes.add(attributes)
xOffset += (itemSize?.width)!
column += 1
if column == numberOfColumns {
if xOffset > contentWidth {
contentWidth = xOffset
}
column = 0
xOffset = 0
yOffset += (itemSize?.height)!
}
}
if (self.itemAttributes == nil) {
self.itemAttributes = NSMutableArray(capacity: self.collectionView!.numberOfSections)
}
self.itemAttributes .add(sectionAttributes)
}
let attributes : UICollectionViewLayoutAttributes = (self.itemAttributes.lastObject as AnyObject).lastObject as! UICollectionViewLayoutAttributes
contentHeight = attributes.frame.origin.y + attributes.frame.size.height
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes! {
return self.itemAttributes[indexPath.section][indexPath.row] as! UICollectionViewLayoutAttributes
// Error in above return line and error is Type 'Any' has no subscript members
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
if self.itemAttributes != nil {
for section in self.itemAttributes {
let filteredArray = (section as AnyObject).filtered(
using: NSPredicate(block: { (evaluatedObject, bindings) -> Bool in
return rect.intersects(evaluatedObject.frame)
// Error in above return line and error is Value of type 'Any?' has no member 'frame'
})
) as! [UICollectionViewLayoutAttributes]
attributes.append(contentsOf: filteredArray)
}
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
func sizeForItemWithColumnIndex(_ columnIndex: Int) -> CGSize {
var text : String = ""
switch (columnIndex) {
case 0:
text = "Col 0"
case 1:
text = "Col 1"
case 2:
text = "Col 2"
case 3:
text = "Col 3"
case 4:
text = "Col 4"
case 5:
text = "Col 5"
case 6:
text = "Col 6"
default:
text = "Col 7"
}
let size : CGSize = (text as NSString).size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 17.0)])
let width : CGFloat = size.width + 25
return CGSize(width: width, height: 30)
}
func calculateItemsSize() {
self.itemsSize = NSMutableArray(capacity: numberOfColumns)
for index in 0..<numberOfColumns {
self.itemsSize.add(NSValue(cgSize: self.sizeForItemWithColumnIndex(index)))
}
}
}
ContentCollectionViewCell
class ContentCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var contentLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
DateCollectionViewCell
class DateCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
CollectionViewController
class CollectionViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
let dateCellIdentifier = "DateCellIdentifier"
let contentCellIdentifier = "ContentCellIdentifier"
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView .register(UINib(nibName: "DateCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: dateCellIdentifier)
self.collectionView .register(UINib(nibName: "ContentCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: contentCellIdentifier)
}
// MARK - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(indexPath.description)
if indexPath.section == 0 {
if indexPath.row == 0 {
let dateCell : DateCollectionViewCell = collectionView .dequeueReusableCell(withReuseIdentifier: dateCellIdentifier, for: indexPath) as! DateCollectionViewCell
dateCell.backgroundColor = UIColor.white
dateCell.dateLabel.font = UIFont.systemFont(ofSize: 13)
dateCell.dateLabel.textColor = UIColor.black
dateCell.dateLabel.text = "Donor ID"
return dateCell
} else {
let contentCell : ContentCollectionViewCell = collectionView .dequeueReusableCell(withReuseIdentifier: contentCellIdentifier, for: indexPath) as! ContentCollectionViewCell
contentCell.contentLabel.font = UIFont.systemFont(ofSize: 13)
contentCell.contentLabel.textColor = UIColor.black
contentCell.contentLabel.text = "Section"
if indexPath.section % 2 != 0 {
contentCell.backgroundColor = UIColor(white: 242/255.0, alpha: 1.0)
} else {
contentCell.backgroundColor = UIColor.white
}
return contentCell
}
} else {
if indexPath.row == 0 {
let dateCell : DateCollectionViewCell = collectionView .dequeueReusableCell(withReuseIdentifier: dateCellIdentifier, for: indexPath) as! DateCollectionViewCell
dateCell.dateLabel.font = UIFont.systemFont(ofSize: 13)
dateCell.dateLabel.textColor = UIColor.black
dateCell.dateLabel.text = String(indexPath.section)
if indexPath.section % 2 != 0 {
dateCell.backgroundColor = UIColor(white: 242/255.0, alpha: 1.0)
} else {
dateCell.backgroundColor = UIColor.white
}
return dateCell
} else {
let contentCell : ContentCollectionViewCell = collectionView .dequeueReusableCell(withReuseIdentifier: contentCellIdentifier, for: indexPath) as! ContentCollectionViewCell
contentCell.contentLabel.font = UIFont.systemFont(ofSize: 13)
contentCell.contentLabel.textColor = UIColor.black
contentCell.contentLabel.text = "Section:\(indexPath.section.description) / Row: \(indexPath.row.description)"
if indexPath.section % 2 != 0 {
contentCell.backgroundColor = UIColor(white: 242/255.0, alpha: 1.0)
} else {
contentCell.backgroundColor = UIColor.white
}
return contentCell
}
}
}
}
Thanks
Hello replace your CustomCollectionViewLayout with this
CustomCollectionViewLayout.swift
class CustomCollectionViewLayout: UICollectionViewLayout {
let numberOfColumns = 8
var itemAttributes : NSMutableArray!
var itemsSize : NSMutableArray!
var contentSize : CGSize!
override func prepare() {
if self.collectionView?.numberOfSections == 0 {
return
}
if (self.itemAttributes != nil && self.itemAttributes.count > 0) {
for section in 0..<self.collectionView!.numberOfSections {
let numberOfItems : Int = self.collectionView!.numberOfItems(inSection: section)
for index in 0..<numberOfItems {
if section != 0 && index != 0 {
continue
}
let attributes : UICollectionViewLayoutAttributes = self.layoutAttributesForItem(at: IndexPath(item: index, section: section))
if section == 0 {
var frame = attributes.frame
frame.origin.y = self.collectionView!.contentOffset.y
attributes.frame = frame
}
if index == 0 {
var frame = attributes.frame
frame.origin.x = self.collectionView!.contentOffset.x
attributes.frame = frame
}
}
}
return
}
if (self.itemsSize == nil || self.itemsSize.count != numberOfColumns) {
self.calculateItemsSize()
}
var column = 0
var xOffset : CGFloat = 0
var yOffset : CGFloat = 0
var contentWidth : CGFloat = 0
var contentHeight : CGFloat = 0
for section in 0..<self.collectionView!.numberOfSections {
let sectionAttributes = NSMutableArray()
for index in 0..<numberOfColumns {
let itemSize = (self.itemsSize[index] as AnyObject).cgSizeValue
let indexPath = IndexPath(item: index, section: section)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = CGRect(x: xOffset, y: yOffset, width: (itemSize?.width)!, height: (itemSize?.height)!).integral
if section == 0 && index == 0 {
attributes.zIndex = 1024;
} else if section == 0 || index == 0 {
attributes.zIndex = 1023
}
if section == 0 {
var frame = attributes.frame
frame.origin.y = self.collectionView!.contentOffset.y
attributes.frame = frame
}
if index == 0 {
var frame = attributes.frame
frame.origin.x = self.collectionView!.contentOffset.x
attributes.frame = frame
}
sectionAttributes.add(attributes)
xOffset += (itemSize?.width)!
column += 1
if column == numberOfColumns {
if xOffset > contentWidth {
contentWidth = xOffset
}
column = 0
xOffset = 0
yOffset += (itemSize?.height)!
}
}
if (self.itemAttributes == nil) {
self.itemAttributes = NSMutableArray(capacity: self.collectionView!.numberOfSections)
}
self.itemAttributes .add(sectionAttributes)
}
let attributes : UICollectionViewLayoutAttributes = (self.itemAttributes.lastObject as AnyObject).lastObject as! UICollectionViewLayoutAttributes
contentHeight = attributes.frame.origin.y + attributes.frame.size.height
self.contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes! {
//return self.itemAttributes[indexPath.section][indexPath.row] as! UICollectionViewLayoutAttributes
let arr = self.itemAttributes[indexPath.section] as! NSMutableArray
return arr[indexPath.row] as! UICollectionViewLayoutAttributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
if self.itemAttributes != nil {
for section in self.itemAttributes {
let filteredArray = (section as! NSMutableArray).filtered(
using: NSPredicate(block: { (evaluatedObject , bindings) -> Bool in
let a = evaluatedObject as! UICollectionViewLayoutAttributes
return rect.intersects(a.frame)
})
) as! [UICollectionViewLayoutAttributes]
attributes.append(contentsOf: filteredArray)
}
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
func sizeForItemWithColumnIndex(_ columnIndex: Int) -> CGSize {
var text : String = ""
switch (columnIndex) {
case 0:
text = "Col 0"
case 1:
text = "Col 1"
case 2:
text = "Col 2"
case 3:
text = "Col 3"
case 4:
text = "Col 4"
case 5:
text = "Col 5"
case 6:
text = "Col 6"
default:
text = "Col 7"
}
let size : CGSize = (text as NSString).size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 17.0)])
let width : CGFloat = size.width + 25
return CGSize(width: width, height: 30)
}
func calculateItemsSize() {
self.itemsSize = NSMutableArray(capacity: numberOfColumns)
for index in 0..<numberOfColumns {
self.itemsSize.add(NSValue(cgSize: self.sizeForItemWithColumnIndex(index)))
}
}
}
I'm using https://github.com/bartjacobs/StickyHeadersCollectionView
For Swift 4 with xCode 9.2 my problem was that StickyHeadersCollectionViewFlowLayout stopped working when Swift 3 #objc inference was set to Default or Off (setting it to On the problem was gone) but since Swift 3 #objc inference is something that should be resolved I took a deeper look and found out that I only needed to use #objc on UICollectionViewDelegate methods (for me, to be more precise was just the last one):
// MARK: - Collection View Delegate Flow Layout Methods
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: self.gridCellHeight)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets.zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
#objc func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
if useStickyHeader {
return CGSize(width: collectionView.bounds.width, height: self.gridCellHeight)
} else {
return CGSize.zero
}
}

gestureRecognizerShouldBegin: Unrecognized selector sent to instance

I am implementing a Collection View with a Calendar From based on Sapporo from nghialv ( https://github.com/nghialv/Sapporo ) for a complete different purpose. I want to define pulses which are going to be sent in six different channels in a 10 second frame. Therefore I have a PulseCollectionView of Seconds vs. Channels.
I want to implement cell dragging after long press using this tutorial: http://karmadust.com/drag-and-rearrange-uicollectionviews-through-layouts/.
My problem is that I am getting an error when I do long press on a cell:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMapTable gestureRecognizerShouldBegin:]: unrecognized selector sent to instance 0x7b6bea30'
I have tried to find the faulty line through the debugger, but it seems I cannot find it. It breaks before even getting to gestureRecognizerShouldBegin
This is my Layout File:
import UIKit
import Sapporo
let SecondsMaximum : CGFloat = 10
let ChannelsMaximum : CGFloat = 6
let HorizontalSpacing : CGFloat = 10
let VerticalSpacing : CGFloat = 4
//let WidthPerSecond : CGFloat = 100
let SecondHeaderHeight : CGFloat = 40
let ChannelHeaderWidth : CGFloat = 100
class PulseLayout: SALayout, UIGestureRecognizerDelegate {
override func collectionViewContentSize() -> CGSize {
let contentHeight = collectionView!.bounds.size.height
let contentWidth = collectionView!.bounds.size.width
return CGSizeMake(contentWidth, contentHeight)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setupGestureRecognizer()
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
// Cells
let visibleIndexPaths = indexPathsOfItemsInRect(rect)
layoutAttributes += visibleIndexPaths.map {
self.layoutAttributesForItemAtIndexPath($0)
}
// Supplementary views
let secondHeaderViewIndexPaths = indexPathsOfSecondHeaderViewsInRect(rect)
layoutAttributes += secondHeaderViewIndexPaths.map {
self.layoutAttributesForSupplementaryViewOfKind(PulseHeaderType.Second.rawValue, atIndexPath: $0)
}
let channelHeaderViewIndexPaths = indexPathsOfChannelHeaderViewsInRect(rect)
layoutAttributes += channelHeaderViewIndexPaths.map {
self.layoutAttributesForSupplementaryViewOfKind(PulseHeaderType.Channel.rawValue, atIndexPath: $0)
}
// Decoration Views
let verticalGridViewIndexPaths = indexPathsOfVerticalGridLineViewsInRect(rect)
layoutAttributes += verticalGridViewIndexPaths.map {
self.layoutAttributesForDecorationViewOfKind(GridLineType.Vertical.rawValue, atIndexPath: $0)
}
let horizontalGridViewIndexPaths = indexPathsOfHorizontalGridLineViewsInRect(rect)
layoutAttributes += horizontalGridViewIndexPaths.map {
self.layoutAttributesForDecorationViewOfKind(GridLineType.Horizontal.rawValue, atIndexPath: $0)
}
return layoutAttributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
if let event = (getCellModel(indexPath) as? PulseEventCellModel)?.event {
attributes.frame = frameForEvent(event)
}
return attributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, withIndexPath: indexPath)
let totalHeight = collectionViewContentSize().height
let totalWidth = collectionViewContentSize().width
if elementKind == PulseHeaderType.Second.rawValue {
let availableWidth = totalWidth - ChannelHeaderWidth
let widthPerSecond = availableWidth / SecondsMaximum
attributes.frame = CGRectMake(ChannelHeaderWidth + widthPerSecond * CGFloat(indexPath.item), 0, widthPerSecond, SecondHeaderHeight)
attributes.zIndex = -10
} else if elementKind == PulseHeaderType.Channel.rawValue {
let availableHeight = totalHeight - SecondHeaderHeight
let heightPerChannel = availableHeight / ChannelsMaximum
attributes.frame = CGRectMake(0, SecondHeaderHeight + (heightPerChannel * CGFloat(indexPath.item)), ChannelHeaderWidth, heightPerChannel)
attributes.zIndex = -10
}
return attributes
}
override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, withIndexPath: indexPath)
let totalHeight = collectionViewContentSize().height
let totalWidth = collectionViewContentSize().width
if elementKind == GridLineType.Vertical.rawValue {
let availableWidth = totalWidth - ChannelHeaderWidth
let widthPerSecond = availableWidth / SecondsMaximum
attributes.frame = CGRectMake(ChannelHeaderWidth + widthPerSecond * CGFloat(indexPath.item), 0.0, 1, totalHeight)
attributes.zIndex = -11
} else if elementKind == GridLineType.Horizontal.rawValue {
let availableHeight = totalHeight - SecondHeaderHeight
let heightPerChannel = availableHeight / ChannelsMaximum
attributes.frame = CGRectMake(0.0, SecondHeaderHeight + (heightPerChannel * CGFloat(indexPath.item)), totalWidth, 1)
attributes.zIndex = -11
}
return attributes
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
struct Bundle {
var offset : CGPoint = CGPointZero
var sourceCell : UICollectionViewCell
var representationImageView : UIView
var currentIndexPath : NSIndexPath
var canvas : UIView
}
var bundle : Bundle?
var canvas : UIView? {
didSet {
if canvas != nil {
// self.calculateBorders()
}
}
}
func setupGestureRecognizer() {
if let collectionView = self.collectionView {
let longPressGestureRecogniser = UILongPressGestureRecognizer(target: self, action: "handleGesture:")
longPressGestureRecogniser.minimumPressDuration = 0.2
longPressGestureRecogniser.delegate = self
collectionView.addGestureRecognizer(longPressGestureRecogniser)
if self.canvas == nil {
self.canvas = self.collectionView!.superview
}
}
}
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let ca = self.collectionView!.superview {
if let cv = self.collectionView {
let pointPressedInCanvas = gestureRecognizer.locationInView(ca)
for cell in cv.visibleCells() as! [PulseEventCell] {
let cellInCanvasFrame = ca.convertRect(cell.frame, fromView: cv)
if CGRectContainsPoint(cellInCanvasFrame, pointPressedInCanvas ) {
let representationImage = cell.snapshotViewAfterScreenUpdates(true)
representationImage.frame = cellInCanvasFrame
let offset = CGPointMake(pointPressedInCanvas.x - cellInCanvasFrame.origin.x, pointPressedInCanvas.y - cellInCanvasFrame.origin.y)
let indexPath : NSIndexPath = cv.indexPathForCell(cell as UICollectionViewCell)!
self.bundle = Bundle(offset: offset, sourceCell: cell, representationImageView:representationImage, currentIndexPath: indexPath, canvas: ca)
break
}
}
}
}
return (self.bundle != nil)
}
func handleGesture(gesture: UILongPressGestureRecognizer) -> Void {
if let bundle = self.bundle {
let dragPointOnCanvas = gesture.locationInView(self.collectionView!.superview)
if gesture.state == UIGestureRecognizerState.Began {
bundle.sourceCell.hidden = true
self.canvas?.addSubview(bundle.representationImageView)
UIView.animateWithDuration(0.5, animations: { () -> Void in
bundle.representationImageView.alpha = 0.8
});
}
if gesture.state == UIGestureRecognizerState.Changed {
// Update the representation image
var imageViewFrame = bundle.representationImageView.frame
var point = CGPointZero
point.x = dragPointOnCanvas.x - bundle.offset.x
point.y = dragPointOnCanvas.y - bundle.offset.y
imageViewFrame.origin = point
bundle.representationImageView.frame = imageViewFrame
let dragPointOnCollectionView = gesture.locationInView(self.collectionView)
if let indexPath : NSIndexPath = self.collectionView?.indexPathForItemAtPoint(dragPointOnCollectionView) {
// self.checkForDraggingAtTheEdgeAndAnimatePaging(gesture)
if indexPath.isEqual(bundle.currentIndexPath) == false {
// If we have a collection view controller that implements the delegate we call the method first
/*if let delegate = self.collectionView!.delegate as UICollectionViewDelegate? {
delegate.moveDataItem(bundle.currentIndexPath, toIndexPath: indexPath)
}*/
self.collectionView!.moveItemAtIndexPath(bundle.currentIndexPath, toIndexPath: indexPath)
self.bundle!.currentIndexPath = indexPath
}
}
}
if gesture.state == UIGestureRecognizerState.Ended {
bundle.sourceCell.hidden = false
bundle.representationImageView.removeFromSuperview()
if let delegate = self.collectionView!.delegate as UICollectionViewDelegate? { // if we have a proper data source then we can reload and have the data displayed correctly
self.collectionView!.reloadData()
}
self.bundle = nil
}
}
}
}
extension PulseLayout {
func indexPathsOfEventsBetweenMinSecondIndex(minChannelIndex: Int, maxChannelIndex: Int, minStartSecond: Int, maxStartSecond: Int) -> [NSIndexPath] {
var indexPaths = [NSIndexPath]()
if let cellmodels = getCellModels(0) as? [PulseEventCellModel] {
for i in 0..<cellmodels.count {
let event = cellmodels[i].event
if event.channel >= minChannelIndex && event.channel <= maxChannelIndex && event.startSecond >= minStartSecond && event.startSecond <= maxStartSecond {
let indexPath = NSIndexPath(forItem: i, inSection: 0)
indexPaths.append(indexPath)
}
}
}
return indexPaths
}
func indexPathsOfItemsInRect(rect: CGRect) -> [NSIndexPath] {
let minVisibleChannel = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxVisibleChannel = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
let minVisibleSecond = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxVisibleSecond = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return indexPathsOfEventsBetweenMinSecondIndex(minVisibleChannel, maxChannelIndex: maxVisibleChannel, minStartSecond: minVisibleSecond, maxStartSecond: maxVisibleSecond)
}
func channelIndexFromYCoordinate(yPosition: CGFloat) -> Int {
let contentHeight = collectionViewContentSize().height - SecondHeaderHeight
let HeightPerChannel = contentHeight / ChannelsMaximum
let channelIndex = max(0, Int((yPosition - SecondHeaderHeight) / HeightPerChannel))
return channelIndex
}
func secondIndexFromXCoordinate(xPosition: CGFloat) -> Int {
let contentWidth = collectionViewContentSize().width - ChannelHeaderWidth
let WidthPerSecond = contentWidth / SecondsMaximum
let secondIndex = max(0, Int((xPosition - ChannelHeaderWidth) / WidthPerSecond))
return secondIndex
}
func indexPathsOfSecondHeaderViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMinY(rect) > SecondHeaderHeight {
return []
}
let minSecondIndex = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxSecondIndex = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return (minSecondIndex...maxSecondIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfChannelHeaderViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMinX(rect) > ChannelHeaderWidth {
return []
}
let minChannelIndex = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxChannelIndex = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
return (minChannelIndex...maxChannelIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfVerticalGridLineViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMaxX(rect) < SecondHeaderHeight {
return []
}
let minSecondIndex = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxSecondIndex = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return (minSecondIndex...maxSecondIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfHorizontalGridLineViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMaxY(rect) < ChannelHeaderWidth {
return []
}
let minChannelIndex = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxChannelIndex = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
return (minChannelIndex...maxChannelIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func frameForEvent(event: PulseEvent) -> CGRect {
let totalHeight = collectionViewContentSize().height - SecondHeaderHeight
let HeightPerChannel = totalHeight / ChannelsMaximum
let contentWidth = collectionViewContentSize().width - ChannelHeaderWidth
let WidthPerSecond = contentWidth / SecondsMaximum
var frame = CGRectZero
frame.origin.x = ChannelHeaderWidth + WidthPerSecond * CGFloat(event.startSecond)
frame.origin.y = SecondHeaderHeight + HeightPerChannel * CGFloat(event.channel)
frame.size.height = HeightPerChannel
frame.size.width = CGFloat(event.durationInSeconds) * WidthPerSecond
frame = CGRectInset(frame, 2.0, HeightPerChannel/8.0)
return frame
}
}
And this is my ViewController:
import UIKit
import Sapporo
enum PulseHeaderType: String {
case Second = "SecondHeaderView"
case Channel = "ChannelHeaderView"
}
enum GridLineType: String {
case Vertical = "VerticalGridLineView"
case Horizontal = "HorizontalGridLineView"
}
class PulseViewController: UIViewController {
#IBOutlet var collectionView: UICollectionView!
lazy var sapporo: Sapporo = { [unowned self] in
return Sapporo(collectionView: self.collectionView)
}()
override func viewDidLoad() {
super.viewDidLoad()
sapporo.delegate = self
sapporo.registerNibForClass(PulseEventCell)
sapporo.registerNibForSupplementaryClass(PulseHeaderView.self, kind: PulseHeaderType.Second.rawValue)
sapporo.registerNibForSupplementaryClass(PulseHeaderView.self, kind: PulseHeaderType.Channel.rawValue)
let layout = PulseLayout()
layout.registerClass(GridLineView.self, forDecorationViewOfKind: GridLineType.Vertical.rawValue)
layout.registerClass(GridLineView.self, forDecorationViewOfKind: GridLineType.Horizontal.rawValue)
sapporo.setLayout(layout)
let randomEvent = { () -> PulseEvent in
let randomID = arc4random_uniform(10000)
let title = "Event \(randomID)"
let randomChannel = Int(arc4random_uniform(6))
let randomStartSecond = Int(arc4random_uniform(8))
let randomDuration = Int(arc4random_uniform(2) + 1)
return PulseEvent(title: title, channel: randomChannel, startSecond: randomStartSecond, durationInSeconds: randomDuration)
}
let cellmodels = (0...20).map { _ -> PulseEventCellModel in
let event = randomEvent()
return PulseEventCellModel(event: event) { _ in
println("Selected event: \(event.title)")
}
}
sapporo[0].append(cellmodels)
sapporo.bump()
}
}
extension PulseViewController: SapporoDelegate {
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
if kind == PulseHeaderType.Second.rawValue {
let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: PulseHeaderView.reuseIdentifier, forIndexPath: indexPath) as! PulseHeaderView
view.titleLabel.text = "\(indexPath.item)"
return view
}
let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: PulseHeaderView.reuseIdentifier, forIndexPath: indexPath) as! PulseHeaderView
view.titleLabel.text = "Channel \(indexPath.item + 1)"
return view
}
}
Can you please help me spot what I am missing?
Regards,
C
I solved this problem a while ago.:
struct Bundle
func setupGestureRecognizer()
func gestureRecognizerShouldBegin
func handleGesture(gesture: UILongPressGestureRecognizer) -> Void
should all go in the ViewController instead of the Layout File.

Resources