Search code examples
iosuiviewuikitcgrect

Can't position UIImage through CGRect


I'm trying to build a UIView of Social Media User's Profile and got stuck right in the beginning - when adding a User Image (UIImage) to my UIView. Here's the code I'm trying to use:

import UIKit

class ProfileHeaderView: UIView {
    
    private lazy var profileImage: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "hipsterCat"))
        
        // Making Image Round
        imageView.layer.cornerRadius = imageView.frame.size.width / 2
        imageView.clipsToBounds = true
        
        imageView.layer.borderColor = UIColor.white.cgColor
        imageView.layer.borderWidth = 3
                
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }
    
    private func setupView() {
        backgroundColor = .lightGray
        addSubview(profileImage)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        profileImage.frame = CGRect(
            x: bounds.midX - 64,
            y: bounds.midY - 64,
            width: 128,
            height: 128
        )
    }
}

My task is to use CGRect to position UIImage on the superView, yet once I run the project it just doesn't appear on the screen (only light gray background is there).

UIImageView is all right, the image appears when commenting layoutSubviews() override but it's not positioned.


Solution

  • There are multiple things that might have gotten wrong. The strangest result you are seeing is that you don't see the image at all once it is resized.

    My best guess is that you are also setting the frame for the ProfileHeaderView instance. Note that there are 2 APIs for lay outing the views; one is using frame and the other one is auto layout. From auto layout a method layoutSubviews will be called. But using frame then only frame will be called. So you may be missing another override.

    Try to add the following:

    override var frame: CGRect {
        didSet {
            updateImagePosition()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        updateImagePosition()
    }
    
    private func updateImagePosition() {
        profileImage.contentMode = .scaleAspectFit
        profileImage.frame = CGRect(
            x: bounds.midX - 64,
            y: bounds.midY - 64,
            width: 128,
            height: 128
        )
        profileImage.layer.cornerRadius = 64
    }
    

    Next to adding an override for frame I have also added to option to determine content mode of your view. You might want to move that into your setup method but it is not too bad if it stays here. And the other one is setting corner radius which for sure needs to be set in here. Both mentioned by @HangarRash.

    In the end it would be nice to have something like the following to be fair:

    @IBDesignable
    class ProfileHeaderView: UIView {
        
        @IBInspectable
        private var imageName: String = "hipsterCat" {
            didSet {
                profileImage.image = UIImage(named: imageName)
            }
        }
        @IBInspectable
        private var imageRadius: CGFloat = 64 {
            didSet {
                updateImagePosition()
            }
        }
        
        private lazy var profileImage: UIImageView = {
            let imageView = UIImageView(image: UIImage(named: imageName))
            imageView.contentMode = .scaleAspectFit
            imageView.clipsToBounds = true
            imageView.layer.borderColor = UIColor.white.cgColor
            imageView.layer.borderWidth = 3
            return imageView
        }()
        
        override var frame: CGRect {
            didSet {
                updateImagePosition()
            }
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupView()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupView()
        }
        
        private func setupView() {
            backgroundColor = .lightGray
            addSubview(profileImage)
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            updateImagePosition()
        }
        
        private func updateImagePosition() {
            profileImage.frame = CGRect(
                x: bounds.midX - imageRadius,
                y: bounds.midY - imageRadius,
                width: imageRadius*2.0,
                height: imageRadius*2.0
            )
            profileImage.layer.cornerRadius = imageRadius
        }
    }