0
votes

I'm facing a very strange problem right now where the same code produces different results on a UIView and a UIImageView.

I have a custom UITableViewCell. To add a shadow and also got some separation between the cells. I've used a container view that contains the shadow and the rounded corners. Everything works as expected.

One layer below I use a UIStackView so I can easily remove the image of the cell if the post has no image at all. This stack view contains one image and another stack view. The corner radius on top of the outer container view doesn't work because the image has no rounded corners. So I've tried to implement this for the image and also for a view above that image that is used as an overlay.

The overlay is outside of the stack view. Now, I've written some code to also add the corner radius to these both views and it works perfectly on the overlay but not on the UIImageView. Even when I pull out the image out of the stack views.

Here is what it looks like:

enter image description here

I've used a slightly bigger corner radius to visualize it and added a border to the image.

What I expect to happen: The image has exactly the same width and corner radius on the correct sides like the overlay (grey).

Here is the code for the custom UIImageView class:

import UIKit

@IBDesignable
class RoundedImageView: UIImageView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 { didSet { setUpView() } }
    @IBInspectable var isOnlyTop: Bool = false { didSet { setUpView() } }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func draw(_ rect: CGRect) {
        setUpView()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setUpView()
    }

    func setUpView() {
        self.clipsToBounds = true
        self.layer.borderColor = UIColor.green.cgColor
        self.layer.borderWidth = 2.0

        if isOnlyTop {
            setTopCornerRadius()
        } else {
            self.layer.cornerRadius = cornerRadius
        }
    }

    func setTopCornerRadius() {
        let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners:[.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = path.cgPath
        self.layer.mask = maskLayer
        self.layer.masksToBounds = true
    }
}

I've copied the code from the rounded view. However, for completion I will add it below:

import UIKit

@IBDesignable
class RoundedView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 { didSet { setUpView() } }
    @IBInspectable var isOnlyTop: Bool = false { didSet { setUpView() } }

    override func awakeFromNib() {
        super.awakeFromNib()
        setUpView()
    }

    override func draw(_ rect: CGRect) {
        setUpView()
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setUpView()
    }

    func setUpView() {
        self.clipsToBounds = true
        self.layer.borderColor = UIColor.red.cgColor
        //self.layer.borderWidth = 2.0

        if isOnlyTop {
            setTopCornerRadius()
        } else {
            self.layer.cornerRadius = cornerRadius
        }
    }

    private func setTopCornerRadius() {
        let path = UIBezierPath(roundedRect: self.bounds, byRoundingCorners:[.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = path.cgPath
        self.layer.mask = maskLayer
    }
} 

Here is the whole view tree of the cell:

enter image description here

I know, that I am violating the DRY principle here, but I just have written the same code for UIView and UIImageView so I can verify why it doesn't work.

Hint: Notice that I have two times the same UIImage in this cell (Post Image and Image out of stack view) as I thought it is maybe caused by Constraints or the stack view. So the image outside of the stack view has exactly the same constraints as the Overlay View.

So long story short: Do you know why my UIImageView has the wrong size and also doesn't apply the corner radius that works perfectly on the Overlay View (UIView). Thanks in advance!

EDIT: The expected result should look like this. Unless the top corners of the image should also be rounded (you see that little difference at the top corners of the image? That's caused by the overlay. It has the correct corner radius but the image doesn't).

EDIT 2: I was asked to add the code of the view controller. However, I don't change anything here.

import UIKit

protocol PostListDisplayLogic: class {
}

class PostListViewController: UIViewController, PostListDisplayLogic, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var interactor: PostListBusinessLogic?
    var router: (NSObjectProtocol & PostListRoutingLogic & PostListDataPassing)?

    // MARK: Object lifecycle

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    // MARK: Setup

    private func setup() {
       let viewController = self
       let interactor = PostListInteractor()
       let presenter = PostListPresenter()
       let router = PostListRouter()
       viewController.interactor = interactor
       viewController.router = router
       interactor.presenter = presenter
       presenter.viewController = viewController
       router.viewController = viewController
       router.dataStore = interactor
   }

   // MARK: Routing

   override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let scene = segue.identifier {
            let selector = NSSelectorFromString("routeTo\(scene)WithSegue:")
            if let router = router, router.responds(to: selector) {
                router.perform(selector, with: segue)
            }
        }
   }

   // MARK: View lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.delegate = self
        self.tableView.dataSource = self
        tableView.register(UINib(nibName: "PostCell", bundle: nil), forCellReuseIdentifier: "PostCell")
    }

    // MARK: TableView DataSource & Delegate
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell") as? PostCell {
            return cell
            }

        return UITableViewCell()
    }
}

enter image description here

3
IN the images you posted above, which ImageView are we seeing one inside stackView or out side one ?? You have used a vertical stackView and added a UIImage so obviously imageView width should be equal to that of stackView but in the image you posted imageView has smaller width. Can u uninstall outer imageView and check your O/PSandeep Bhandari
In the image above you can see both image views (probably the outer image view is above the other one). It looks exactly the same when I uninstall the outer image view.Brudus
Lets forget outer imageView, as it was anyway added for testing. So from now on am talking only about imageView inside stack :) Looks like there are some constraints added to imageView (the arrow next to imageView called postImage in stackView looks suspicious) Can you please confirm what constraints you have on imageViewSandeep Bhandari
Finally if you dont mind can you add background color to postImage and check if imageView is actually expanding or not and also let us know the content mode you set to imageView, is it scaleToFill, AspectFit or AspectFillSandeep Bhandari
It seems like it's not expanding. I've added a red color and I still only see the grey color of the overlay. I use scale to fill right now as the content mode. It's just a height constraint and bottom space to the stack view below (the inner stack view with the controls, user name, etc.).Brudus

3 Answers

0
votes

You should draw it manually into layer. There is no pre-defined function to achieve this.

0
votes

I was able to solve it. I detected that the frame of the image was always having the wrong size.

However, I changed the code to the following:

RoundedImageView:

import UIKit

@IBDesignable
class RoundedImageView: UIImageView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 { didSet { setUpView() } }

    func setUpView() {
        self.clipsToBounds = true

        setTopCornerRadius(rect: self.bounds)
    }

    func setTopCornerRadius(rect: CGRect) {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners:[.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = rect
        maskLayer.path = path.cgPath
        self.layer.mask = maskLayer
        self.layer.masksToBounds = true
    }
}

And in the PostViewController I make use of 'willDisplay cell':

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if let postCell = cell as? PostCell, let postImage = postCell.postImage {
           postCell.postImage.setTopCornerRadius(rect: CGRect(x: postImage.frame.origin.x, y: postImage.frame.origin.y, width: (cell.frame.width - 16.0), height: postImage.frame.size.height))
        }
}

The latter method gets called when the cell already has the correct size. Notice I used -16.0 in respect to the margin of the cell. However, I still understand why it's a difference between an UIView and an UIImageView.

-1
votes

Try call super in 'draw' function:

 override func draw(_ rect: CGRect) {
      super.draw(rect)
      setUpView()
 }