kAiN
kAiN

Reputation: 2793

Change UITableViewCell Height based UICollectionView content size height

I'm working on a calendar created with a UICollectionView.

The UICollectionView is located inside a UITableViewCell The UITableViewCell in the heightForRowAt method return UITableView.automaticDimension

The UICollectionView used to create the calendar updates its height based on how many days are shown, for example the month of November has its first day which falls on Sunday so to place the day under the label "Sunday" the collectionView must necessarily add a whole line.

Normally each month has 5 rows (week) but when the first day of the month happens on Sunday the collectionview returns 6

Now as I said I was able to update the height of the UICollectionView based on how many rows it returns but I cannot dynamically change the height of the TableViewCell that contains the collectionView

How can I resize the tableViewCell based on the height of the CollectionView within it?


EDIT

my cell

enter image description here


I have set UITableView.automaticDimension for the height of the cell that contains the CalendarView

enter image description here

In the class CalendarView: UIView I created a variable CGFloat calendarH to set the default height of the collectionView

enter image description here

CollectionView implementation

enter image description here

I have added an observer which tracks the height of the collctionView when it changes

enter image description here

I am currently able to change the height of the collectionView but its superview (bookingCalendarView) and the tableView cell continue to remain with a fixed height and do not adapt to the collectionView

Upvotes: 0

Views: 362

Answers (1)

DonMag
DonMag

Reputation: 77691

You need to create an @IBOutlet for your collection view's height constraint.

When you set a row's calendar / month data, determine whether you need 5 or 6 rows.

If it's 6, set the .constant on the height constraint to 750

If it's 5, set the .constant on the height constraint to 675


Edit

First, I'm going to suggest you forget about using a "self-sizing" collection view. UICollectionView is designed to lay out cells based on the size of the collection view, providing automatic scrolling when there are too many cells.

Trying to "self-size" it may work in one instance, but fail in another. The reason it fails in this case is because your table view lays out the cell and calculates its height before the collection view is populated, and thus before it can "self-size."

Instead, since you know your cell Height is 75, you can calculate how many rows your calendar will need and either set the .constant on a height constraint for your collection view, or (since you're already using heightForRowAt) calculate the row height there.

Look at this code:

let dateComponents = DateComponents(year: year, month: month)
        
// startDate will be the first date of the month (Jan 1, Feb 1, Mar 1, etc...)
guard let startDate = calendar.date(from: dateComponents) else {
    fatalError("Something is wrong with the date!")
}
// get the range of days in the month
guard let range = calendar.range(of: .day, in: .month, for: startDate) else {
    fatalError("Something is wrong with the date!")
}
        
// get number of days in the month
let numberOfDaysInMonth = range.count
        
// get the day of the week for the first date in the month
//  this returns 1-based numbering
//  Nov 1, 2020 was a Sunday, so this would return 1
let startDayOfWeek = Calendar.current.component(.weekday, from: startDate)
        
// add the "leading days to the start date"
//  so, if startDayOfWeek == 3 (Tuesday)
//  we need to add 2 "empty day cells" for Sunday and Monday
let totalCellsNeeded = numberOfDaysInMonth + (startDayOfWeek - 1)
        
// calculate number of rows needed -- this will be 4, 5 or 6
//  the only time we get 4 is if Feb 1st in a non-leapYear falls on a Sunday
let numRows = Int(ceil(Double(totalCellsNeeded) / Double(7)))
        
// we now know the Height needed for the collection view
//  you said your calendar cell height is 75, so...
//  cvHeight = numRows * 75

We can put that in a loop and print() the information to the debug console like this:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let calendar = Calendar.current

    // 2026 is the next year where Feb starts on a Sunday
    //  so let's use that year to see that we get 4 rows for Feb
    let year = 2026
    
    for month in 1...12 {
        
        let dateComponents = DateComponents(year: year, month: month)
        
        // startDate will be the first date of the month (Jan 1, Feb 1, Mar 1, etc...)
        guard let startDate = calendar.date(from: dateComponents) else {
            fatalError("Something is wrong with the date!")
        }
        // get the range of days in the month
        guard let range = calendar.range(of: .day, in: .month, for: startDate) else {
            fatalError("Something is wrong with the date!")
        }
        
        // get number of days in the month
        let numberOfDaysInMonth = range.count
        
        // get the day of the week for the first date in the month
        //  this returns 1-based numbering
        //  Nov 1, 2020 was a Sunday, so this would return 1
        let startDayOfWeek = Calendar.current.component(.weekday, from: startDate)
        
        // add the "leading days to the start date"
        //  so, if startDayOfWeek == 3 (Tuesday)
        //  we need to add 2 "empty day cells" for Sunday and Monday
        let totalCellsNeeded = numberOfDaysInMonth + (startDayOfWeek - 1)
        
        // calculate number of rows needed -- this will be 4, 5 or 6
        //  the only time we get 4 is if Feb 1st in a non-leapYear falls on a Sunday
        let numRows = Int(ceil(Double(totalCellsNeeded) / Double(7)))
        
        // we now know the Height needed for the collection view
        //  you said your calendar cell height is 75, so...
        //  cvHeight = numRows * 75
        
        // debug output
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE"
        let dayName = dateFormatter.string(from: startDate)
        dateFormatter.dateFormat = "LLLL y"
        let dateString = dateFormatter.string(from: startDate)
        
        let dayPadded = dayName.padding(toLength: 10, withPad: " ", startingAt: 0)
        let datePadded = dateString.padding(toLength: 16, withPad: " ", startingAt: 0)

        print("\(datePadded) has \(numberOfDaysInMonth) days, starting on \(dayPadded) requiring \(numRows) rows")
        
    }
    
}

Here's the output:

January 2026     has 31 days, starting on Thursday   requiring 5 rows
February 2026    has 28 days, starting on Sunday     requiring 4 rows
March 2026       has 31 days, starting on Sunday     requiring 5 rows
April 2026       has 30 days, starting on Wednesday  requiring 5 rows
May 2026         has 31 days, starting on Friday     requiring 6 rows
June 2026        has 30 days, starting on Monday     requiring 5 rows
July 2026        has 31 days, starting on Wednesday  requiring 5 rows
August 2026      has 31 days, starting on Saturday   requiring 6 rows
September 2026   has 30 days, starting on Tuesday    requiring 5 rows
October 2026     has 31 days, starting on Thursday   requiring 5 rows
November 2026    has 30 days, starting on Sunday     requiring 5 rows
December 2026    has 31 days, starting on Tuesday    requiring 5 rows

So... either in:

  • cellForRowAt ... calculate the needed height and set the CV height in the cell, or
  • heightForRowAt ... calculate and return the needed height for the row

Side note: I'd suggest using auto-layout for all of your cells, instead of returning various row heights.

Upvotes: 1

Related Questions