Search code examples
iosiphoneswiftuiimageview

Corner radius image Swift


I'm trying to make this corner radius image...it's not exactly the same shape of the image..any easy answer instead of trying random numbers of width and height ? thanks alot

enter image description here

 let rectShape = CAShapeLayer()
 rectShape.bounds = self.mainImg.frame
 rectShape.position = self.mainImg.center
 rectShape.path = UIBezierPath(roundedRect: self.mainImg.bounds, byRoundingCorners: [.bottomLeft , .bottomRight ], cornerRadii: CGSize(width: 50, height: 4)).cgPath

Solution

  • You can use QuadCurve to get the design you want.

    Here is a Swift @IBDesignable class that lets you specify the image and the "height" of the rounding in Storyboard / Interface Builder:

    @IBDesignable
    
    class RoundedBottomImageView: UIView {
    
        var imageView: UIImageView!
        
        @IBInspectable var image: UIImage? {
            didSet { self.imageView.image = image }
        }
        
        @IBInspectable var roundingValue: CGFloat = 0.0 {
            didSet {
                self.setNeedsLayout()
            }
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            doMyInit()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            doMyInit()
        }
        
        func doMyInit() {
            
            imageView = UIImageView()
            imageView.backgroundColor = UIColor.red
            imageView.contentMode = UIViewContentMode.scaleAspectFill
            addSubview(imageView)
            
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            imageView.frame = self.bounds
    
            let rect = self.bounds
            let y:CGFloat = rect.size.height - roundingValue
            let curveTo:CGFloat = rect.size.height + roundingValue
            
            let myBezier = UIBezierPath()
            myBezier.move(to: CGPoint(x: 0, y: y))
            myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: curveTo))
            myBezier.addLine(to: CGPoint(x: rect.width, y: 0))
            myBezier.addLine(to: CGPoint(x: 0, y: 0))
            myBezier.close()
            
            let maskForPath = CAShapeLayer()
            maskForPath.path = myBezier.cgPath
            layer.mask = maskForPath
        
        }
    
    }
    

    Result with 300 x 200 image view, rounding set to 40:

    enter image description here


    Edit - (3.5 years later)...

    To answer @MiteshDobareeya comment, we can switch the rounded edge from Bottom to Top by transforming the bezier path:

    let c = CGAffineTransform(scaleX: 1, y: -1).concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
            myBezier.apply(c)
    

    It's been quite a while since this answer was originally posted, so a few changes:

    • subclass UIImageView directly - no need to make it a UIView with an embedded UIImageView
    • add a Bool roundTop var
      • if set to False (the default), we round the Bottom
      • if set to True, we round the Top
    • re-order and "name" our path points for clarity

    So, the basic principle:

    enter image description here

    We create a UIBezierPath and:

    • move to pt1
    • add a line to pt2
    • add a line to pt3
    • add a quad-curve to pt4 with controlPoint
    • close the path
    • use that path for a CAShapeLayer mask

    the result:

    enter image description here

    If we want to round the Top, after closing the path we can apply apply a scale transform using -1 as the y value to vertically mirror it. Because that transform mirror it at "y-zero" we also apply a translate transform to move it back down into place.

    That gives us:

    enter image description here

    Here's the updated class:

    @IBDesignable
    class RoundedTopBottomImageView: UIImageView {
        
        @IBInspectable var roundingValue: CGFloat = 0.0 {
            didSet {
                self.setNeedsLayout()
            }
        }
        
        @IBInspectable var roundTop: Bool = false {
            didSet {
                self.setNeedsLayout()
            }
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let r = bounds
            
            let myBezier = UIBezierPath()
            
            let pt1: CGPoint = CGPoint(x: r.minX, y: r.minY)
            let pt2: CGPoint = CGPoint(x: r.maxX, y: r.minY)
            let pt3: CGPoint = CGPoint(x: r.maxX, y: r.maxY - roundingValue)
            let pt4: CGPoint = CGPoint(x: r.minX, y: r.maxY - roundingValue)
            
            let controlPoint: CGPoint = CGPoint(x: r.midX, y: r.maxY + roundingValue)
            
            myBezier.move(to: pt1)
            myBezier.addLine(to: pt2)
            myBezier.addLine(to: pt3)
            myBezier.addQuadCurve(to: pt4, controlPoint: controlPoint)
            myBezier.close()
            
            if roundTop {
                // if we want to round the Top instead of the bottom,
                //  flip the path vertically
                let c = CGAffineTransform(scaleX: 1, y: -1) //.concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
                myBezier.apply(c)
            }
            
            let maskForPath = CAShapeLayer()
            maskForPath.path = myBezier.cgPath
            layer.mask = maskForPath
        }
        
    }