iosswiftimageuicollectionviewhorizontallist

How to image setup in UICollectionView in UICollectionViewCell Swift


I am trying to set up images in the horizontal alignment in collectionView, but it's not working properly. Please check the attached screenshot.

View Controller Code:-

import UIKit

class ViewController: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {

@IBOutlet weak var texting1: UICollectionView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.texting1.register(UINib(nibName:"CollectionViewCell1", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell1")
    self.texting1.delegate = self
    self.texting1.dataSource = self
    texting1.backgroundColor = .red
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 1
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell1", for: indexPath) as! CollectionViewCell1
    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: self.view.frame.width, height: 120)
  }
}

Output:- Image

CollectionViewCell1 Code:-

import UIKit

class CollectionViewCell1: UICollectionViewCell,UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {

@IBOutlet weak var userImageColectionView: UICollectionView!

override func awakeFromNib() {
    super.awakeFromNib()
    self.userImageColectionView.register(UINib(nibName:"CollectionViewCell2", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell2")
    userImageColectionView.delegate = self
    userImageColectionView.dataSource = self
    userImageColectionView.backgroundColor = .black
}
    
var items = ["1", "2", "3", "4"]

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return self.items.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell2", for: indexPath as IndexPath) as! CollectionViewCell2
    return cell
}

// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    print("You selected cell #\(indexPath.item)!")
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: 70, height: 60);
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return -10
  }
}

Xib ScreenShot:- Image

CollectionViewCell2 Code:-

class CollectionViewCell2: UICollectionViewCell {

@IBOutlet weak var imguserView: UIImageView!
override func awakeFromNib() {
    super.awakeFromNib()
    imguserView.image = UIImage(systemName: "person.crop.circle")
   }
}

Xib ScreenShot:- Image

My Goal:- Image

Can someone please explain to me how to show images under in collection view in the horizontal alignment and setup dynamic width according to cell images count, I've tried to implement by above but no results yet.

Any help would be greatly appreciated.

Thanks in advance.


Solution

  • One way to do this without using a collection view is to create a "horizontal chain" of constraints for the "cell views."

    For example, if we have 3 image views - red, green, and blue - with these constraints:

        NSLayoutConstraint.activate([
            red.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
            green.leadingAnchor.constraint(equalTo: red.trailingAnchor, constant: 10.0),
            blue.leadingAnchor.constraint(equalTo: green.trailingAnchor, constant: 10.0),
        ])
    

    it will look like this:

    enter image description here

    If we instead use negative constants:

        NSLayoutConstraint.activate([
            red.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0),
            green.leadingAnchor.constraint(equalTo: red.trailingAnchor, constant: -20.0),
            blue.leadingAnchor.constraint(equalTo: green.trailingAnchor, constant: -20.0),
        ])
        
    

    it can look like this:

    enter image description here

    So we can create a custom UIView subclass that handles all of that for us -- such as this:

    class OverlapView: UIView {
        
        public var overlap: CGFloat = -30 { didSet { setNeedsLayout() } }
        
        public var cellViews: [UIView] = [] {
            didSet {
                // clear out any existing subviews
                subviews.forEach { v in
                    v.removeFromSuperview()
                }
                cellViews.forEach { v in
                    // in case it wasn't set by the caller
                    v.translatesAutoresizingMaskIntoConstraints = false
                    addSubview(v)
                    // center it vertically
                    v.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
                    // we want the overlap to go from left-to-right
                    sendSubviewToBack(v)
                }
                setNeedsLayout()
            }
        }
        
        private var horizontalConstraints: [NSLayoutConstraint] = []
        
        override func layoutSubviews() {
            super.layoutSubviews()
    
            guard cellViews.count > 0 else { return }
    
            // de-activate any existing horizontal constraints
            NSLayoutConstraint.deactivate(horizontalConstraints)
            
            var prevView: UIView!
            cellViews.forEach { v in
                // if it's the first one
                if prevView == nil {
                    horizontalConstraints.append(v.leadingAnchor.constraint(equalTo: leadingAnchor))
                } else {
                    horizontalConstraints.append(v.leadingAnchor.constraint(equalTo: prevView.trailingAnchor, constant: overlap))
                }
                prevView = v
            }
            // constrain last view's trailing
            horizontalConstraints.append(prevView.trailingAnchor.constraint(equalTo: trailingAnchor))
    
            // activate the udpated constraints
            NSLayoutConstraint.activate(horizontalConstraints)
        }
    }
    

    Here's a sample controller demonstrating that:

    class OverlapTestVC: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let testView = OverlapView()
            testView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(testView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
                testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
                testView.heightAnchor.constraint(equalToConstant: 70.0),
                
                // no trailing or width constraint
                //  width will be determined by the number of "cells"
            ])
    
            // let's add 6 "cells" -- one for each color
            let colors: [UIColor] = [
                .systemRed, .systemGreen, .systemBlue,
                .systemYellow, .systemCyan, .magenta,
            ]
            var cellViews: [UIView] = []
            colors.forEach { c in
                if let img = UIImage(systemName: "person.crop.circle") {
                    let imgView = UIImageView(image: img)
                    imgView.tintColor = c
                    imgView.backgroundColor = .white
                    imgView.translatesAutoresizingMaskIntoConstraints = false
                    // let's use a 60x60 image view
                    imgView.widthAnchor.constraint(equalToConstant: 60.0).isActive = true
                    imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor).isActive = true
                    // we want "round" views
                    imgView.layer.cornerRadius = 30.0
                    imgView.layer.masksToBounds = true
                    cellViews.append(imgView)
                }
            }
    
            testView.cellViews = cellViews
            
            // we can change the overlap value here if we don't want to use
            //  our custom view's default
            // for example:
            //testView.overlap = -40.0
            
            // so we can see the framing
            testView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
    
        }
        
    }
    

    Output looks like this:

    enter image description here

    (We gave the custom view itself a light gray background so we can see its frame.)