Bartłomiej Semańczyk Bartłomiej Semańczyk - 7 months ago 18
Swift Question

How to create sections along with headers on the left of cells with UICollectionView?

I am not sure, if I composed my question fairly well. If you consider to improve it, do it:) but shortly this is what I mean:

enter image description here


  • Every orange box is the header of new section box (width, and height is static).

  • This will be linked to fetched results controller with sections

  • The yellow cells always take the rest of screen in the width, and have automatic dimension for height (depending on length of text).



Please tell me:


  1. Is it possible to do this at all with
    UICollectionView
    ?

  2. How to arrange automatic height dimensions for yellow cells?

  3. How to float yellow ones like it is on the image?


Answer

I think you can use UITableViewController to do something similar.

enter image description here

The idea is:

  • Customize the cell as two parts, left and right.
  • The left of the first item of a section is used for section header, note that Clip Subviews of CellView and ContentView should be unchecked.
  • The height of the last item of a section need to be adjusted, so that there is no overlap.

Here is the sample code:

class ViewController: UITableViewController {

    struct Item {
        var height: CGFloat = 0
    }

    var itemDic: [Int: [Item]]!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        itemDic = [0 : [Item(height: 10), Item(height: 30), Item(height: 50)],
                 1 : [Item(height: 20), Item(height: 10)],
                 2 : [Item(height: 40), Item(height: 30), Item(height: 20)]]
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return itemDic.count
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemDic[section]!.count
    }

    let sectionHeaderHeight: CGFloat = 100

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

        let section = indexPath.section
        let row = indexPath.row
        let items = itemDic[section]!

        var height = items[row].height
        if (row == items.count - 1) {

            var total: CGFloat = 0
            for i in items {
                total += i.height + 10
            }

            if(sectionHeaderHeight + 10 > total){
                height += (sectionHeaderHeight + 10 - total)
            }
        }

        return height + 10
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let section = indexPath.section
        let row = indexPath.row
        let items = itemDic[section]!

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

        let orangeView = cell.viewWithTag(100)!
        let yellowView = cell.viewWithTag(101)!

        if(row == 0){
            let orangeFrame = orangeView.frame
            orangeView.frame = CGRect(origin: orangeFrame.origin, size: CGSize(width: orangeFrame.width, height: sectionHeaderHeight))
        }

        let yellowFrame = yellowView.frame
        let height = items[row].height
        yellowView.frame = CGRect(origin: yellowFrame.origin, size: CGSize(width: yellowFrame.width, height: height))

        return cell
    }

    override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        let orangeView = cell.viewWithTag(100)!
        let yellowView = cell.viewWithTag(101)!

        let orangeFrame = orangeView.frame

        print(orangeFrame)

        let yellowFrame = yellowView.frame

        print(yellowFrame)
    }
}

Update: Actually there is a bug in above solution, when scrolling to top until the first section disappears, and then you will see it get re-rendered and the layout will be broken.

There is a better way to do this.

Add an inner view (orange) into the headerView, that will be rendered over the tableCells. Remember to set the height of header > 0. In this case, the cell only need to contain the right part (yellow). Still, you need to adjust the height of last item.

Sample code:

class ViewController: UITableViewController {

    struct Item {
        var height: CGFloat = 0
    }

    var itemDic: [Int: [Item]]!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        itemDic = [0 : [Item(height: 10), Item(height: 30), Item(height: 50), Item(height: 20)],
                 1 : [Item(height: 20), Item(height: 10)],
                 2 : [Item(height: 40), Item(height: 30), Item(height: 20), Item(height: 30)]]
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return itemDic.count
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemDic[section]!.count
    }

    let sectionHeaderHeight: CGFloat = 100
    let innerViewOffset: CGFloat = 10
    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

        // Must > 0
        return innerViewOffset
    }

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = UIView(frame: CGRect())

        view.backgroundColor = UIColor.blueColor()

        let innerView = UIView(frame: CGRect(x: 0, y: innerViewOffset, width: 100, height: 100))
        innerView.backgroundColor = colorForSection(section)
        view.addSubview(innerView)

        return view
    }

    func colorForSection(section: Int) -> UIColor {
        let colors = [UIColor.greenColor(), UIColor.redColor(), UIColor.purpleColor()]

        return colors[section]
    }

    override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

        let section = indexPath.section
        let row = indexPath.row
        let items = itemDic[section]!

        var height = items[row].height
        if (row == items.count - 1) {

            var total: CGFloat = 0
            for i in items {
                total += i.height + 10
            }

            if(sectionHeaderHeight + 10 > total){
                height += (sectionHeaderHeight + 10 - total)
            }
        }

        return height + 10
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

        return cell
    }
}