I have a tableView inside a collectionviewCell and i want to set collectionview cell height as tableview contentSize after tableview loaded data.
if i set itemSize with CollectionViewFlowlayout it's been changing height of every collectionCell and hiding tableView content.
Can anyone refer any library or customlayout So i can set my collectionCell height once tableview content change it's height.
I have call callback in collectioncell that is updating me once inner tableview content changed.
I have done like this

My Class where i have placed collectionView
class FormBuilderRowTVCViewModel {
let row : FormLayoutModel?
var onContentHeightChange: FormGridLayoutChange = { _,_ in }
var coordinator:DynamicFormCordinator?
var dModel:FormBuilderFields?
let formType:DynamicFormType
init(formType:DynamicFormType, _ row:FormLayoutModel,_ coordinator:DynamicFormCordinator?, _ dModel:FormBuilderFields?){
self.formType = formType
self.row = row
self.coordinator = coordinator
self.dModel = dModel
}
}
class FormBuilderRowTVC: CoreTableViewCell {
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var collectionViewHgt: NSLayoutConstraint!
fileprivate var tasks : [FormLayoutChildrenModel] = []
var viewModel : FormBuilderRowTVCViewModel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.registerGalleryCell()
}
private func registerGalleryCell(){
let cellName = "FormBuilderGrid"
self.collectionView.register(UINib.init(nibName: cellName, bundle: nil), forCellWithReuseIdentifier: cellName)
}
fileprivate func handleClicks() {
viewModel.onContentHeightChange = { [weak self] height,item in
guard let self = self else { return }
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize.height = height as! CGFloat
self.collectionView.collectionViewLayout = layout
self.reloadInputViews()
}
}
}
override func reloadInputViews() {
super.reloadInputViews()
// self.collectionView.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.setTableViewHeightRespectToContent()
self.setTableViewHeightRespectToContent()
self.setTableViewHeightRespectToContent()
}
}
func setTableViewHeightRespectToContent(){
let height:CGFloat = self.collectionView.contentSize.height
if let tbl : UITableView = self.superview as? UITableView{
if(height != self.collectionViewHgt.constant){
tbl.beginUpdates()
self.collectionViewHgt.constant = height
tbl.endUpdates()
}
}
self.layoutIfNeeded()
self.setNeedsLayout()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(_ viewModel:FormBuilderRowTVCViewModel){
self.viewModel = viewModel
if let columns = viewModel.row?.children{
self.tasks = columns
self.setCollectionFlowLayout()
self.handleClicks()
self.collectionView.reloadData()
}
self.reloadInputViews()
}
func setCollectionFlowLayout(){
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
var width = UIScreen.main.bounds.width
if(self.tasks.count > 1){
width = UIScreen.main.bounds.width/2//CGFloat(self.tasks.count)
}
// layout.estimatedItemSize.height = 100
// layout.estimatedItemSize = CGSize(width: width, height: 100)
layout.itemSize.width = width
self.collectionView.collectionViewLayout = layout
}
func updateChildComponent(){
DispatchQueue.main.async {
self.collectionView.indexPathsForVisibleItems.forEach {
if let cell = self.collectionView.cellForItem(at: $0) as? FormBuilderGrid {
cell.updateChildComponent()
}
}
}
}
}
extension FormBuilderRowTVC : UICollectionViewDelegate,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tasks.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FormBuilderGrid.identifier, for: indexPath) as! FormBuilderGrid
cell.tag = indexPath.item
let rowModel = self.tasks[indexPath.item]
cell.configure(FormBuilderGridViewModel(formType: viewModel.formType, rowModel, viewModel.onContentHeightChange,viewModel.coordinator,viewModel.dModel))
return cell
}
}
CollectionViewCell Class where i have tableview in it.
class FormBuilderGridViewModel: FormComponentBaseViewModel {
var model:FormLayoutChildrenModel!
var onContentHeightChange: FormGridLayoutChange!
init(formType : DynamicFormType, _ model:FormLayoutChildrenModel, _ onContentHeightChange: @escaping FormGridLayoutChange,_ coordinator:DynamicFormCordinator?, _ dModel:FormBuilderFields?){
super.init(formType:formType,coordinator, dModel)
self.model = model
if(self.onContentHeightChange == nil){
self.onContentHeightChange = onContentHeightChange
}
}
func loadFormBuilderList() {
tasks = []
DispatchQueue.main.async {
if(self.formType == .inputForm){
self.tasks.append(contentsOf: self.loadInputFormBuilderCells())
}else if(self.formType == .displayForm){
self.loadDisplayFormBuilderCells()
}
self.onUpdate()
}
}
}
class FormBuilderGrid: FormComponentBase {
//MARK:- Properties
var viewModel : FormBuilderGridViewModel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func viewDidLoad(){
self.tableView.estimatedRowHeight = UITableView.automaticDimension
self.registerTVCell()
self.handleClicks()
self.tableView.delegate = self
self.tableView.dataSource = self
tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func reloadInputViews() {
super.reloadInputViews()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.setTableViewHeightRespectToContent()
self.setTableViewHeightRespectToContent()
self.setTableViewHeightRespectToContent()
}
}
func setTableViewHeightRespectToContent(){
let height:CGFloat = self.tableView.contentSize.height
if(height != self.tableViewHgt.constant){
self.tableViewHgt.constant = height
self.viewModel.onContentHeightChange(height,self.tag)
}
}
fileprivate func handleClicks() {
viewModel.onUpdate = { [weak self] in
guard let self = self else { return }
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
viewModel.toggleLoading = { doShow in
DispatchQueue.main.async {
doShow ? LoadingView.show() : LoadingView.hide()
}
}
}
func configure(_ viewModel:FormBuilderGridViewModel){
self.viewModel = viewModel
self.formType = viewModel.formType
self.viewDidLoad()
for model in viewModel.model.children ?? []{
if let fields = model.fieldsById {
self.viewModel.formList.append(fields)
}
}
self.viewModel.loadFormBuilderList()
}
func updateChildComponent(){
self.viewModel.onUpdate()
}
deinit {
self.tableView.removeObserver(self, forKeyPath: "contentSize")
}
}
//MARK:- TableView DataSource Methods
extension FormBuilderGrid:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.numberOfRows()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if viewModel.tasks.count == 0 {
return UITableViewCell()
}
switch viewModel.cell(for: indexPath) {
case .FormTextFieldTVCell(let viewModel):
return getTextFieldWith(indexPath, viewModel)
case .FormTextAreaTVCell(let viewModel):
return getTextViewWith(indexPath, viewModel)
case .FormRadioTVCell(let viewModel):
return getRadioViewWith(indexPath, viewModel)
case .FormCheckBoxTVCell(let viewModel):
return getCheckBoxWith(indexPath, viewModel)
case .FormDatePickerTVCell(let viewModel):
return self.getDatePickerWith(indexPath, viewModel)
case .InputFormSelectOptionTVC(let viewModel):
return getSelectionCellWith(indexPath,viewModel)
case .InputFormFilePickerTVC(let viewModel):
return getAttachmentCellWith(indexPath, viewModel)
case .FormInputGeoTagTVCell(let viewModel):
return getGeoTagCellWith(indexPath, viewModel)
case .FormEntityTVCell(let viewModel):
return getEntityCellWith(indexPath, viewModel)
case .InputFormScannerTVC(let viewModel):
return getScannerCellWith(indexPath, viewModel)
case .OutputFormTextFieldTVC(let viewModel):
return self.getTextCell(with: indexPath, viewModel)
case .OutFormTextAreaTVCell(let viewModel):
return self.getTextAreaCell(with: indexPath, viewModel)
case .OutputFormRadioTVCell(let viewModel):
return self.getFormRadioCell(with: indexPath, viewModel)
case .OutputFormCheckBoxTVCell(let viewModel):
return self.getFormCheckBoxCell(with: indexPath, viewModel)
case .OutputFormDateTVCell(let viewModel):
return self.getFormDateCell(with: indexPath, viewModel)
case .OutputFormSelectOptionTVC(let viewModel):
return self.getSelectFormCell(with: indexPath, viewModel)
case .FilesDisplayComponentTVC(let viewModel):
return self.getFilesDisplayFormCell(with: indexPath, viewModel)
case .FormDisplayGeoTagTVCell(let viewModel):
return self.getGeoTagDisplayCell(with: indexPath, viewModel)
case .FormDisplayEntityTVCell(let viewModel):
return self.getDisplayEntityCell(with: indexPath, viewModel)
case .FormScannerDisplayTVC(let viewModel):
return self.getScannerDisplayCell(with: indexPath, viewModel)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
viewModel.didSelect(indexPath: indexPath)
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (object as? UITableView) == tableView && (keyPath == "contentSize") {
// perform your updates here
self.reloadInputViews()
}
}
}

collectionView(_:layout:sizeForItemAt:)developer.apple.com/documentation/uikit/… which allows you to set the size at an index path.