Search code examples
swiftuicollectionviewgriduikitswift5

Need to make a grid this on UIKit using a UICollectionView


I want to make a grid in the photo. How can I achieve this? Can you tell me where to look? Googled everything but found nothing, except through CoreGraphics, but that's not an option I'm considering. enter image description here


Solution

  • Here's a really quick example using a CAShapeLayer...

    UIImageView subclass

    class GridImageView: UIImageView {
        
        public var numRows: Int = 1
        public var numColumns: Int = 1
        
        private let gridLayer = CAShapeLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        override init(image: UIImage?) {
            super.init(image: image)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            layer.addSublayer(gridLayer)
            gridLayer.strokeColor = UIColor.white.cgColor
            gridLayer.fillColor = UIColor.clear.cgColor
            gridLayer.lineWidth = 1
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            if numRows == 1 && numColumns == 1 {
                // no need to draw any lines
                gridLayer.path = nil
                return
            }
            
            let bez = UIBezierPath()
            
            if numRows > 1 {
                let yIncrement: CGFloat = bounds.height / CGFloat(numRows)
                var y: CGFloat = yIncrement
                for _ in 0..<numRows-1 {
                    bez.move(to: CGPoint(x: bounds.minX, y: y))
                    bez.addLine(to: CGPoint(x: bounds.maxX, y: y))
                    y += yIncrement
                }
            }
            
            if numColumns > 1 {
                let xIncrement: CGFloat = bounds.width / CGFloat(numColumns)
                var x: CGFloat = xIncrement
                for _ in 0..<numColumns-1 {
                    bez.move(to: CGPoint(x: x, y: bounds.minY))
                    bez.addLine(to: CGPoint(x: x, y: bounds.maxY))
                    x += xIncrement
                }
            }
            
            gridLayer.path = bez.cgPath
        }
    }
    

    Example Controller

    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemYellow
            
            guard let img = UIImage(named: "beach") else { return }
            
            let imgView = GridImageView(image: img)
            imgView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(imgView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                imgView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                imgView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: img.size.height / img.size.width)
            ])
            
            imgView.numRows = 4
            imgView.numColumns = 3
            
        }
        
    }
    

    Output (using a random beach jpg):

    enter image description here


    Edit - after further comments on OP...

    To get the "effect" from the video you posted, we can simply add a "grid overlay view" on top of the scroll view.

    So, we'll use an almost identical subclassed UIView:

    class GridOverlayView: UIView {
        
        public var lineColor: UIColor = .white { didSet { setNeedsLayout() } }
        public var numRows: Int = 1 { didSet { setNeedsLayout() } }
        public var numColumns: Int = 1 { didSet { setNeedsLayout() } }
        
        private let gridLayer = CAShapeLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            layer.addSublayer(gridLayer)
            gridLayer.fillColor = UIColor.clear.cgColor
            gridLayer.lineWidth = 1
        }
        override func layoutSubviews() {
            super.layoutSubviews()
            
            if numRows == 1 && numColumns == 1 {
                // no need to draw any lines
                gridLayer.path = nil
                return
            }
            
            let bez = UIBezierPath()
            
            if numRows > 1 {
                let yIncrement: CGFloat = bounds.height / CGFloat(numRows)
                var y: CGFloat = yIncrement
                for _ in 0..<numRows-1 {
                    bez.move(to: CGPoint(x: bounds.minX, y: y))
                    bez.addLine(to: CGPoint(x: bounds.maxX, y: y))
                    y += yIncrement
                }
            }
            
            if numColumns > 1 {
                let xIncrement: CGFloat = bounds.width / CGFloat(numColumns)
                var x: CGFloat = xIncrement
                for _ in 0..<numColumns-1 {
                    bez.move(to: CGPoint(x: x, y: bounds.minY))
                    bez.addLine(to: CGPoint(x: x, y: bounds.maxY))
                    x += xIncrement
                }
            }
            
            gridLayer.strokeColor = lineColor.cgColor
            gridLayer.path = bez.cgPath
        }
    }
    

    with this "beach" image:

    enter image description here

    and this controller class with a scroll view:

    class GridScrollTestVC: UIViewController, UIScrollViewDelegate {
        
        let imgView = UIImageView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemYellow
            
            guard let img = UIImage(named: "beach") else { return }
    
            imgView.image = img
            
            let scrollView = UIScrollView()
            scrollView.backgroundColor = .red
            scrollView.delegate = self
            
            imgView.translatesAutoresizingMaskIntoConstraints = false
            scrollView.translatesAutoresizingMaskIntoConstraints = false
            
            view.addSubview(scrollView)
            scrollView.addSubview(imgView)
            
            let g = view.safeAreaLayoutGuide
            let cg = scrollView.contentLayoutGuide
            let fg = scrollView.frameLayoutGuide
            
            NSLayoutConstraint.activate([
    
                scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
    
                imgView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 0.0),
                imgView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 0.0),
                imgView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: 0.0),
                imgView.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: 0.0),
    
                imgView.widthAnchor.constraint(equalTo: fg.widthAnchor),
                imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: img.size.height / img.size.width)
                
            ])
            
            scrollView.minimumZoomScale = 1.0
            scrollView.maximumZoomScale = 5.0
            
            // now let's add the grid "overlay"
            let gridView = GridOverlayView()
            gridView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(gridView)
            gridView.numRows = 4
            gridView.numColumns = 3
            gridView.lineColor = .black
            
            NSLayoutConstraint.activate([
                gridView.topAnchor.constraint(equalTo: scrollView.topAnchor),
                gridView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
                gridView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
                gridView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            ])
            
            // don't let the grid overlay view interfere with the scrolling/zooming
            gridView.isUserInteractionEnabled = false
            
        }
        
        func viewForZooming(in scrollView: UIScrollView) -> UIView? {
            return imgView
        }
        
    }
    

    and we get this output:

    enter image description here

    and after zooming in a bit:

    enter image description here