5

I'm trying to add a custom shape to an imageView. Please check the below images.

This is the required one:

Required Image Shape

This is what I have done so far:

Current Image Shape

I'm new to Core Graphics and I have done this so far:

    private func customImageClipper(imageV: UIImageView){

    let path = UIBezierPath()

    let size = imageV.frame.size

    print(size)

    path.move(to: CGPoint(x: 0.0, y: size.height))

    path.addLine(to: CGPoint(x: 0.8, y: size.height/2))

    path.close()

    let shape = CAShapeLayer()

    shape.path = path.cgPath

    imageV.layer.sublayers = [shape]

}

I'm creating a function to achieve a shape like this, but whenever I pass the imageView into this function, I can not see any change at all. I know that I have to move from points to another point to achieve this shape, but I have never done this. Any help would be appreciated. This is how I'm calling this function:

imageV.layoutIfNeeded()

customImageClipper(imageV: imageV)

P.S.: I'm not using Storyboard, I have created this programmatically.

Rob13
  • 381
  • 8
  • 20

3 Answers3

6

There are many ways to create shapes using UIBezierPaths. This post here discusses the use of the draw function to create a shape.

Here is an example using your clip function within the cell.

func clip(imageView: UIView, withOffset offset: CGFloat) {
    let path = UIBezierPath()

    //Move to Top Left
    path.move(to: .init(x: imageView.bounds.size.width * offset, y: 0))

    //Draw line from Top Left to Top Right
    path.addLine(to: .init(x: imageView.bounds.size.width, y: 0))

    //Draw Line from Top Right to Bottom Right
    path.addLine(to: .init(x: imageView.bounds.size.width * (1 - offset), y: imageView.bounds.size.height))

    //Draw Line from Bottom Right to Bottom Left
    path.addLine(to: .init(x: 0, y: imageView.bounds.size.height))

    //Close Path
    path.close()

    //Create the Shape Mask for the ImageView
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.fillColor = UIColor.black.cgColor
    imageView.layer.mask = shapeLayer
}

In this function, the offset is the amount of angle you would like on the shape, ranging from 0 to 1. (0.4) seems to work for your requirements.

This shares a lot of similarities with Apseri's answer, except I chose the route of percentages, rather than exact size. Nothing wrong with either approach, I just found it easier to understand with percentages. :)

One last note to point out, I used this function in the layoutSubviews function.

override func layoutSubviews() {
    super.layoutSubviews()
    imageView.layoutIfNeeded()
    clip(imageView: self.imageView, withOffset: 0.4)
}

This output the following image:

Image of a cell showing custom shape

Hope this helps.

Paulo
  • 602
  • 1
  • 8
  • 20
  • Hi, thank you for your answer. But, why have you used `layoutSubviews()`? – Rob13 Jan 16 '20 at 03:06
  • Because I used auto layout constraints to set out my view. Until layoutSubviews, the imageView doesn’t know what size it’s frame is. Therefore the frame would be zero and the function wouldn’t work. :) – Paulo Jan 16 '20 at 08:25
  • Doesn't `layoutifNeeded()` gives the frame size? – Rob13 Jan 16 '20 at 09:54
  • Hmm, turns out, I was wrong. I was under the impression that layoutIfNeeded simply notified the UI to update. I can edit the answer if you like. I just moved the layoutIfNeeded and function call into the init of the (i assume) table cell. – Paulo Jan 16 '20 at 10:10
2

Here is example of some path clipping. Of course path can be also put via parameters, and this can be applied to any view, as shown.

Before:

enter image description here

After (grey background is below ScrollView background):

enter image description here

func customImageClipper(imageV: UIView){

    let path = UIBezierPath()
    let size = imageV.frame.size

    path.move(to: CGPoint(x: size.width/3.0, y: 0))
    path.addLine(to: CGPoint(x: size.width/3.0 + 50, y: 0))
    path.addLine(to: CGPoint(x: size.width/3.0, y: size.height))
    path.addLine(to: CGPoint(x: size.width/3.0 - 50, y: size.height))
    path.addLine(to: CGPoint(x: size.width/3.0, y: 0))
    path.close()

    let shape = CAShapeLayer()
    shape.path = path.cgPath
    shape.fillColor = UIColor.black.cgColor

    imageV.layer.mask = shape
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
0

1- Subclassing your UIImageView

2- implement your custom drawings inside setNeedsLayout using UIBezierPath

class MyCustomImageView: UIImageView {  



    override func setNeedsLayout() {
        let path = UIBezierPath()
        path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
        path.addLineToPoint(CGPoint(x: self.frame.size.width, y: self.frame.size.height/2))
        path.addLineToPoint(CGPoint(x: self.frame.size.width/2, y: 0))
        path.addArcWithCenter(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2), radius: self.frame.size.width/2, startAngle:-CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2), clockwise: false)

        path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
        path.closePath()
        UIColor.redColor().setFill()
        path.stroke()
        path.bezierPathByReversingPath()
        let shapeLayer = CAShapeLayer()
        shapeLayer.frame = self.bounds
        shapeLayer.path = path.CGPath
        shapeLayer.fillColor = UIColor.redColor().CGColor
        self.layer.mask = shapeLayer;
        self.layer.masksToBounds = true;
    }

}
Sanad Barjawi
  • 539
  • 4
  • 15