Philipp
Philipp

Reputation: 347

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.

Upvotes: 0

Views: 61

Answers (2)

Philipp
Philipp

Reputation: 347

I was following @HangarRash advice from the comments and managed to fix the issue. The thing I did is setting corner radius (and making image rounded) in layoutSubViews while also adding .scaleAspectFill to UIImageView. The final code is:

import UIKit

class ProfileHeaderView: UIView {
    
    private lazy var profileImage: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "hipsterCat"))
        imageView.contentMode = .scaleAspectFit
        
        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.minX + 16,
            y: bounds.minY + 16,
            width: 128,
            height: 128
        )
        
        //Making image round
        profileImage.layer.cornerRadius = profileImage.frame.size.width / 2
        profileImage.clipsToBounds = true
        
        profileImage.layer.borderColor = UIColor.white.cgColor
        profileImage.layer.borderWidth = 3
    }
}

Upvotes: 0

Matic Oblak
Matic Oblak

Reputation: 16794

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
    }
}

Upvotes: 1

Related Questions