Search code examples
swiftuikitrounded-cornerssnapkit

ImageView + scaleAspectFit in containerView, then in cell


i have a cell, that contains containerView with top and bottom cornerRadius = 8. Then i have to put UIImageView with contentMode = .scaleAspectFit and corner radius ONLY on the top (cornerRadius = 8) But the problem that code 'corner radius' is not working with contentMode = .scaleAspectFit

Here is properties

 let containerView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 8
        return view
    }()

let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.layer.cornerRadius = 8
        imageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        imageView.clipsToBounds = true
        return imageView
    }()

Here is SnapKit method

private func setupViews() {
        addSubview(containerView)
        containerView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.top.bottom.equalToSuperview()
        }
        
        containerView.addSubview(imageView)
        imageView.snp.makeConstraints { make in
            make.top.equalTo(containerView.snp.top)
            make.trailing.equalTo(containerView.snp.trailing)
            make.leading.equalTo(containerView.snp.leading)
        }
}

as a result i got wrong result , i need my image.top EqualToContainer.top and with cornerRadius on the top


Solution

  • You haven't given the image view a height constraint, and a UIImageView has no intrinsic size until its .image has been set.

    So, when you set the image, the image view will use the height of the image to set its own height. Then, because you're telling it to use .scaleAspectFit, you get the image centered in the new height.

    What you need to do is leave the image view at the default of .scaleToFill and then set its height constraint to use the same proportion as the image.

    A quick example, using these two images:

    enter image description here enter image description here

    We want it to look like this (I've exaggerated the corner radius to make it obvious):

    enter image description here enter image description here

    We'll use a UIView subclass -- but the same thing will apply when using this in a cell:

    class AspectView: UIView {
    
        public var image: UIImage? {
            didSet {
                // set the image view's image
                imageView.image = image
                // if height constraint is already set
                if let h = hConstraint {
                    // deactivate it
                    h.isActive = false
                }
                // set new height constraint to image aspect ratio
                imageView.snp.makeConstraints { make in
                    self.hConstraint = make.height.equalTo(imageView.snp.width).multipliedBy(image!.size.height / image!.size.width).constraint
                }
            }
        }
        
        private var hConstraint: Constraint!
    
        private let containerView: UIView = {
            let view = UIView()
            // using corner radius of 32 to make it very obvious
            view.layer.cornerRadius = 32
            // this will clip the image view subview
            view.clipsToBounds = true
            return view
        }()
        
        private let imageView: UIImageView = {
            let imageView = UIImageView()
            return imageView
        }()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupViews()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            setupViews()
        }
        
        public func addImage(_ img: UIImage) {
            imageView.image = img
            print(img.size)
            if let h = hConstraint {
                h.isActive = false
            }
            imageView.snp.makeConstraints { make in
                self.hConstraint = make.height.equalTo(imageView.snp.width).multipliedBy(img.size.height / img.size.width).constraint
            }
        }
        private func setupViews() {
            addSubview(containerView)
            containerView.snp.makeConstraints { make in
                make.leading.trailing.equalToSuperview()
                make.top.bottom.equalToSuperview()
            }
            
            containerView.addSubview(imageView)
            imageView.snp.makeConstraints { make in
                make.top.equalTo(containerView.snp.top)
                make.trailing.equalTo(containerView.snp.trailing)
                make.leading.equalTo(containerView.snp.leading)
            }
            
            // so we can see the containerView frame
            containerView.backgroundColor = .systemBlue
        }
        
    }
    

    and a sample controller class - tapping anywhere will toggle between the two images:

    class AspectTestVC: UIViewController {
        
        let testView = AspectView()
        
        let imgNames: [String] = [
            "p320x160",
            "p320x240",
        ]
        var imgIdx: Int = 0
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // bkg640x360
        
            view.addSubview(testView)
            
            testView.snp.makeConstraints { make in
                make.top.leading.trailing.equalToSuperview().inset(40.0)
                make.height.equalTo(400.0)
            }
    
            if let img = UIImage(named: imgNames[imgIdx % imgNames.count]) {
                testView.image = img
            }
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            imgIdx += 1
            if let img = UIImage(named: imgNames[imgIdx % imgNames.count]) {
                testView.image = img
            }
        }
    }