0

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 enter image description here

but i need like this enter image description here

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()
        }
    }
  
    
}
3
  • You are looking for collectionView(_:layout:sizeForItemAt:) developer.apple.com/documentation/uikit/… which allows you to set the size at an index path. Commented Jan 31, 2022 at 16:24
  • Size for item calls before tableview loaded. i need to set height after inner tableview data loaded Commented Jan 31, 2022 at 16:35
  • It's not after or before, it is while data is being loaded. You need to have some logic to calculate your content height in this method and set the height for that cell. Commented Jan 31, 2022 at 16:47

1 Answer 1

0

For complex UICollectionView´s and UITableView´s i use this library: https://github.com/Instagram/IGListKit

For task´s related to threads: https://github.com/duemunk/Async

For API & Network request: https://github.com/Moya/Moya

For Promise & Async task´s: https://github.com/mxcl/PromiseKit

For layout of view´s: https://github.com/layoutBox/PinLayout

For Date & Time manipulation: https://github.com/malcommac/SwiftDate

A tip that I am going to give you is that you avoid mixing UITableView within UICollectionView. What you want to do visually can be done otherwise, you want to use UiviewControllers embedded for example.

Sign up to request clarification or add additional context in comments.

2 Comments

Yes i did that also but that one is supper costly because i have embeded some Controllers in Tableviewcells and have API calls on that. Which is consuming too much memory and CPU.
Then that is not the right way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.