skip to Main Content

I have a mask applied to a view using CAShapeLayer and UIBezierPath. I’d like to add a rounding effect to the line joins but it’s not working. How do I round the corners of this shape?

You can plug the following into an Xcode playground.

import PlaygroundSupport
import UIKit

private class ProfileImageView: UIView {
    private let imageView = UIImageView()
    var image: UIImage?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        imageView.clipsToBounds = true
        imageView.backgroundColor = UIColor.black
        imageView.contentMode = .scaleAspectFill
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)
        imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        imageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        imageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
    
    required init?(coder: NSCoder) {
        return nil
    }
    
    override func draw(_ rect: CGRect) {
        let h = rect.height
        let w = rect.width
        let path = UIBezierPath()
        let shapeLayer = CAShapeLayer()
        
        path.move(to: .zero)
        path.addLine(to: CGPoint(x: w-32, y: 0))
        path.addLine(to: CGPoint(x: w, y: 32))
        path.addLine(to: CGPoint(x: w, y: h))
        path.addLine(to: CGPoint(x: 32, y: h))
        path.addLine(to: CGPoint(x: 0, y: h-32))
        path.close()
        path.lineJoinStyle = .round
        shapeLayer.lineJoin = .round
        shapeLayer.path = path.cgPath
        layer.mask = shapeLayer
        imageView.image = image
    }
}

class VC: UIViewController {
    override func loadView() {
        view = UIView()
        view.backgroundColor = .gray
        
        let imgView = ProfileImageView()
        imgView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(imgView)
        imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        imgView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        imgView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -64).isActive = true
        imgView.heightAnchor.constraint(equalTo: view.widthAnchor, constant: -64).isActive = true
    }
}

PlaygroundPage.current.liveView = VC()

2

Answers


  1. lineJoinStyle is only for stroked paths. Since yours is a mask, you need a filled path instead so I think you’ll need to use path.addCurve to achieve rounded corners in your mask. Or depending on your shape and size you may be able to just apply lineWidth, strokeColor and lineJoinStyle to your CAShapeLayer and get the rounded effect you’re looking for.

    Login or Signup to reply.
  2. Still trying to guess at your goal, but maybe this is what you’re looking for?

    enter image description here

    private class ProfileImageView: UIImageView {
    
        public var cornerRadius: Double = 16 {
            didSet {
                setNeedsLayout()
            }
        }
        public var angleRadius: Double = 24 {
            didSet {
                setNeedsLayout()
            }
        }
        public var angleIndent: CGFloat = 32 {
            didSet {
                setNeedsLayout()
            }
        }
        
        private let shapeLayer = CAShapeLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            contentMode = .scaleAspectFill
            layer.mask = shapeLayer
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let rect = bounds
            
            let path = CGMutablePath()
            
            path.move(to: CGPoint(x: rect.minX, y: rect.midY))
            
            path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY),
                        tangent2End: CGPoint(x: rect.maxX, y: rect.minY),
                        radius: cornerRadius)
            
            path.addArc(tangent1End: CGPoint(x: rect.maxX - angleIndent, y: rect.minY),
                        tangent2End: CGPoint(x: rect.maxX, y: rect.minY + angleIndent),
                        radius: angleRadius)
            
            path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY + angleIndent),
                        tangent2End: CGPoint(x: rect.maxX, y: rect.maxY),
                        radius: angleRadius)
            
            path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY),
                        tangent2End: CGPoint(x: rect.minX, y: rect.maxY),
                        radius: cornerRadius)
            
            path.addArc(tangent1End: CGPoint(x: rect.minX + angleIndent, y: rect.maxY),
                        tangent2End: CGPoint(x: rect.minX, y: rect.maxY - angleIndent),
                        radius: angleRadius)
            
            path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY - angleIndent),
                        tangent2End: CGPoint(x: rect.minX, y: rect.minY),
                        radius: angleRadius)
            
            path.closeSubpath()
            
            shapeLayer.path = path
        }
        
    }
    
    class VC: UIViewController {
        
        override func viewDidLoad() {
            
            super.viewDidLoad()
            view.backgroundColor = .systemBlue
    
            guard let img = UIImage(named: "sampleImage") else {
                fatalError("Could not load sample image!!")
            }
    
            let stackView = UIStackView()
            stackView.axis = .vertical
            stackView.distribution = .fillEqually
            stackView.spacing = 20
            stackView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(stackView)
            
            let imgView1 = ProfileImageView(frame: .zero)
            imgView1.image = img
            
            let imgView2 = ProfileImageView(frame: .zero)
            imgView2.image = img
    
            // top view uses default properties,
            stackView.addArrangedSubview(imgView1)
    
            // slightly different properties for the bottom view
            imgView2.cornerRadius = 24
            imgView2.angleRadius = 32
            imgView2.angleIndent = 48
    
            stackView.addArrangedSubview(imgView2)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                stackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                stackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                stackView.widthAnchor.constraint(equalTo: g.widthAnchor, constant: -100.0),
                
                imgView1.heightAnchor.constraint(equalTo: imgView1.widthAnchor),
                
            ])
            
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search