Vinícius Albino
Vinícius Albino

Reputation: 547

Create dynamic collectionView inside tableview cell

I'm trying to create a horizontal collection view inside a tableviewcell, with a dynamic size uiimageview inside, which will be the mandatory view.

I created a UIView :

public class CardImage: UIView {
    
    private var imageView: UIImageView?
    private var titleLabel: UILabel?
    private var descriptionLabel: UILabel?
    private var subtitleLabel: UILabel?
    
    private var icon: UIImage?
    private var imageURL: String?
    
    private var screenPercentage: CGFloat = 1.0
    
    override public func layoutSubviews() {
        super.layoutSubviews()
        layer.cornerRadius = 4
    }
    
    public override func awakeFromNib() {
        super.awakeFromNib()
        setup()
        layoutSubviews()
    }
    
    public init() {
        super.init(frame: .zero)
        setup()
        layoutSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
        layoutSubviews()
    }
    
    public func fill(dto: SomeDTO) {
        setupImage(icon: dto.image, imageUrl: dto.imageURL)
        descriptionLabel?.text = dto.description
        titleLabel?.text = dto.title
        subtitleLabel?.text = dto.subtitle
        screenPercentage = dto.screenPercentage
    }
    
        private func setup() {
            isUserInteractionEnabled = true
            
            translatesAutoresizingMaskIntoConstraints = false
            
            backgroundColor = .red
            
            titleLabel = UILabel()
            titleLabel?.textColor = .white
            titleLabel?.numberOfLines = 1
            addSubview(titleLabel!)
            
            descriptionLabel = UILabel()
            descriptionLabel?.textColor = .white
            descriptionLabel?.numberOfLines = 2
            addSubview(descriptionLabel!)
            
            subtitleLabel = UILabel()
            subtitleLabel?.textColor = .white
            subtitleLabel?.numberOfLines = 1
            addSubview(subtitleLabel!)
            
            imageView = UIImageView(frame: .zero)
            imageView?.backgroundColor = .red
            addSubview(imageView!)
            
            setupConstraints()
        }
        
        private func setupImage(icon: UIImage?, imageUrl: String?) {
            if let url = imageURL {
                return
            }
            
            guard let image = icon else {
                return
            }
            
            imageView?.image = image
            imageView?.contentMode = .scaleAspectFit
            
            setNeedsDisplay()
            setNeedsLayout()
        }
        
        
        private func setupConstraints() {
            imageView?.translatesAutoresizingMaskIntoConstraints = false
            imageView?.topAnchor.constraint(equalTo: topAnchor).isActive = true
            imageView?.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
            imageView?.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
            
            let screenSize = UIScreen.main.bounds
            let computedWidth = screenSize.width * screenPercentage
            imageView?.widthAnchor.constraint(equalToConstant: computedWidth).isActive = true
            //the image should be 16:9
            imageView?.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 9.0/16.0).isActive = true
            
            titleLabel?.translatesAutoresizingMaskIntoConstraints = false
            titleLabel?.topAnchor.constraint(equalTo: imageView!.bottomAnchor, constant: 16).isActive = true
            titleLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
            titleLabel?.heightAnchor.constraint(equalToConstant: 18).isActive = true
            
            subtitleLabel?.translatesAutoresizingMaskIntoConstraints = false
            subtitleLabel?.topAnchor.constraint(equalTo: titleLabel!.topAnchor).isActive = true
            subtitleLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
            
            titleLabel?.widthAnchor.constraint(equalTo: subtitleLabel!.widthAnchor).isActive = true
            titleLabel?.trailingAnchor.constraint(equalTo: subtitleLabel!.leadingAnchor, constant: 6).isActive = true
            
            descriptionLabel?.translatesAutoresizingMaskIntoConstraints = false
            descriptionLabel?.topAnchor.constraint(equalTo: titleLabel!.bottomAnchor, constant: 16).isActive = true
            descriptionLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
            descriptionLabel?.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
            descriptionLabel?.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
        }
}

Which will be inside a CollectionViewCell:

public class CardImageCollectionViewCell: UICollectionViewCell {
    private let view: CardImage = CardImage()
    
    override public func awakeFromNib() {
        super.awakeFromNib()
        
        translatesAutoresizingMaskIntoConstraints = false
//this only pin the view to the four anchors of the uicollectionview
        view.pinToBounds(of: self.contentView)
        backgroundColor = .clear
        
        AccessibilityManager.setup(self, log: true)
    }
    
    public func fill(dto: SomeDTO) {
        view.fill(dto: dto)
    }
}

And then the CollectionViewCell, inside a tableviewcell, which has a collectionview:

public class CardImageCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    
        @IBOutlet private weak var collectionView: UICollectionView!
        private var dto: [SomeDTO] = []
        
        public override func awakeFromNib() {
            super.awakeFromNib()
            setup()
        }
        
        private func setup() {
            backgroundColor = .blue
            
            collectionView.backgroundColor = .clear
            collectionView.delegate = self
            collectionView.dataSource = self
            
            if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
                flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
            }
            
            registerCells()
        }
        
        func fill(dto: [SomaImageCardDTO]) {
            self.dto = dto
            DispatchQueue.main.async {
                self.collectionView.reloadData()
                self.collectionView.layoutIfNeeded()
            }
        }
        
        private func registerCells() {
            collectionView.register(UINib(nibName: String(describing: CardImageCollectionViewCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: CardImageCollectionViewCell.self))
        }
        
        public func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
        
        public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return dto.count
        }
        //this should not be setted since the sizing is automatic 
    //    public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    //        return CGSize(width: 304, height: 238)
    //    }
    //
        public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: CardImageCollectionViewCell.self), for: indexPath) as! CardImageCollectionViewCell
            
            cell.fill(dto: dto[indexPath.row])
            return cell
        }
    }

The two problems I'm facing is, the image cannot assume any size, since the collection view doesn't have any size until it's filled with some info.

And then, even if I set the image size, I cannot pass the info to the UITableViewcell size.

Upvotes: 4

Views: 332

Answers (1)

Endanke
Endanke

Reputation: 887

If I understand your problem correctly, you have more than one rows in an UITableView which have horizontal UICollectionViews. Those collection views have cells which have dynamic size based on the images inside them.

So the width of the UITableView is fixed, but the height for the row depends on the height of the UICollectionView, correct?

I would recommend using self-sizing cells in the UITableView. See the reference here: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html

After that, you need to find a way to calculate the height of the UICollectionView correctly, so the UITableView cell can determine the correct height. There are several ways to do it, for example by overriding the intrinsicContentSize property of the UICollectionView and returning the largest height of the images.

Upvotes: 3

Related Questions