4

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:

enter image description here

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:

Something like this:

Is there a simple way to work around this?

Hudi Ilfeld
  • 1,905
  • 2
  • 16
  • 25
  • yes you can take two type of collection view cell and first is the your header cell another is content cell and load according to your requirement i think this is the easiest way to do this. – Ravi Panchal Feb 07 '18 at 13:07
  • @ravi.p I already have a different cell as the header [UICollectionReusableView], and for the content I have a [UIcollectionViewCell]. I don't get how that solve my problem – Hudi Ilfeld Feb 07 '18 at 13:11
  • you can take both UIcollectionViewCell one for header row and another for Content and in collectionView cellForItemAtIndexPath add one condition if indexPath == 0 then load first cell otherwise load content celll – Ravi Panchal Feb 07 '18 at 13:14
  • 1
    You likely need to write a custom layout class to do this with a single `UICollectionView`. An alternate approach would be to use 3 collection views in a scroll view, and then use constraints to position them based on available width. – DonMag Feb 07 '18 at 13:35
  • as @DonMag suggest , instead of scrollview Use 3 Collectionview in UIStackview. just change Axis either vertical or horizontal – Prashant Tukadiya Feb 07 '18 at 13:37
  • @PrashantTukadiya. on a normal iPhone portrait mode I want to keep the normal vertical collectionView like image 1. and when I rotate I need it to change. so how could I do this dynamically – Hudi Ilfeld Feb 07 '18 at 13:56
  • Good question. I have it completed. If u r satisfy, kindly accept and up vote. – McDonal_11 Feb 09 '18 at 09:03

1 Answers1

2

I have it tried in UICollectionViewController.

Click CollectionView and change to CustomLayout [UICollectionViewLayout]

enter image description here

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]

enter image description here

class RulesReusableView: UICollectionReusableView {

    @IBOutlet weak var headLbl: UILabel!
}

UICollectionViewCell

class RulesCollectionViewCell: UICollectionViewCell {
     @IBOutlet weak var txtLbl: UILabel!
}

Portrait Output

enter image description here

Landscape Output

enter image description here

McDonal_11
  • 3,935
  • 6
  • 24
  • 55
  • hey, listen it's seems like it's almost working for me but I'm receiving the following error: `2018-02-11 10:48:27.780706+0200 Mercava[13755:5279573] *** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.33.6/UICollectionViewData.m:435 2018-02-11 10:48:27.838705+0200 Mercava[13755:5279573] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist`: – Hudi Ilfeld Feb 11 '18 at 08:53
  • this collectionView that I'm trying to apply your solution is basically inside a collectionViewCell of a horizontal collectionView that each one it's cells takes the full length of the screen and inside of each one of the cells, I have a collectionView that displays the data as shown above. now, when I launch the app it seems like everything is displaying like your solution. but when I scroll to the next cell i.e the next collectionView I get the error I showed you above. any ideas? – Hudi Ilfeld Feb 11 '18 at 09:01
  • 1
    Before u try to implement in ur proj., try this sample as separate. Then u can easily find where u r struggling..?? – McDonal_11 Feb 11 '18 at 10:15
  • your solution is perfect when there is only one collectionView. but in my case, I have several collectionViews inside a super collectionView that scrolls horizontally and each time I scroll a new collectionView is suppose to display and from what I saw in your solution I need to call collectionView.reloadData each time the collectionView with the custom layout get's displayed or when the phone rotates. but I haven't managed to do so. since the collection views that get displayed are in a UicollectionViewCell class, and therefore cannot call viewWillTransition etc.. – Hudi Ilfeld Feb 11 '18 at 12:52
  • Can u give that storyborad’s hierarchy ?? How u r customising ur viewController ?? – McDonal_11 Feb 11 '18 at 15:41
  • hierarchy is as following: UIViewController -> CollectionView1 -> UICollectionViewCell -> CollectionView2. – Hudi Ilfeld Feb 11 '18 at 16:06
  • each collectionView1's cell taks the full size of the view and has a collectionView2 to display it's content. now, when I launch the app, the first cell displays it's collectionView like your solution, and it works bur when I scroll hoizontally to the next cell, I get the error I showed you before `Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist` – Hudi Ilfeld Feb 11 '18 at 16:12
  • I couldn’t understood ur issue clearly... any video or screenshot., can u post ??? – McDonal_11 Feb 11 '18 at 16:49
  • Turns out I had to remove the cache from the RulesLayout in the `prepare` function since every time I scroll to another collectionView the cache remains so I added: `cellAttrsDictionary.removeAll()` – Hudi Ilfeld Feb 12 '18 at 14:09
  • Though I'm still stuck on 2 small issues: 1) the collectionView is not centered horizontally. 2) I'm not able to scroll down vertically – Hudi Ilfeld Feb 12 '18 at 14:31
  • Can u send ur proj? I wil fix and send back u ? – McDonal_11 Feb 12 '18 at 14:36
  • how do I send a project on stackOverFlow? – Hudi Ilfeld Feb 12 '18 at 14:40
  • Give ur mail id ?? – McDonal_11 Feb 12 '18 at 14:43