1

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()
lurning too koad
  • 2,698
  • 1
  • 17
  • 47
  • You might find your answer here: https://stackoverflow.com/questions/31214620/how-to-add-rounded-corner-to-a-uibezierpath-custom-rectangle – Shawn Frank Mar 18 '22 at 03:47
  • *"How do I round the corners of this shape?"* -- we need to see the result you are trying to get. Right now, your shape has 6 corners.... Which ones do you want "round"? How do you *want* it to look? – DonMag Mar 18 '22 at 12:39
  • @DonMag I want all of them rounded. – lurning too koad Mar 18 '22 at 17:00
  • That doesn't answer the question... Rounded by how much? In this image - https://i.stack.imgur.com/jxWkY.png - your current path looks like the top one... do you want it to look like the bottom one? Or something else? – DonMag Mar 18 '22 at 17:30
  • @DonMag well, if they can be rounded then hopefully I can adjust by how much but if not then I will still take it. I don't want to lose any joints. Rounding corners (maybe this is just a graphic-design term and I should have been more clear) just means rounding the point where two segments of the same line join to change direction. In your example, the shape has lost 2 of its joints and now has 4 corners, no longer 6. – lurning too koad Mar 18 '22 at 17:36
  • This is why I said ***"we need to see the result you are trying to get"*** -- grab your favorite graphics program and draw how you ***want*** it to look - then update your question with the image(s). Trying to guess is just a waste of time. – DonMag Mar 18 '22 at 18:22

2 Answers2

0

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.

Lalo
  • 383
  • 2
  • 8
0

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),
            
        ])
        
    }
    
}
DonMag
  • 69,424
  • 5
  • 50
  • 86