Home > Mobile >  How to use lineJoin and lineJoinStyle with a UIBezierPath?
How to use lineJoin and lineJoinStyle with a UIBezierPath?

Time:03-19

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()

CodePudding user response:

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.

CodePudding user response:

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),
            
        ])
        
    }
    
}
  • Related