Search code examples
iosuiimageviewcalayeruibezierpathcashapelayer

Some visual bug with CALayer


I have a UITableView with the following cells layout:

• UIView

•• UIImageView

••• Semi-transparent overlay UIView as subview of UIImageView

There are also icon view imitating the "play" button and UILabel, but they seem to be not related to this bug.

And I have the following code:

override func layoutSubviews() {
        super.layoutSubviews()

        createPath()
    }

    private func createPath() {
        let padding = CGFloat(16)
        let bottomLineLength = CGFloat(64)
        let arcPadding = CGFloat(8)

        let width = self.contentView.frame.width
        let height = self.contentView.frame.height

        if pathLayer != nil {
            pathLayer?.removeFromSuperlayer()
        }

        pathLayer = CAShapeLayer()

        let path = UIBezierPath()

        path.move(to: CGPoint(x: padding + arcPadding, y: padding))
        path.addLine(to: CGPoint(x: width - padding - arcPadding, y: padding))

        // Top-Right arc
        path.addArc(withCenter: CGPoint(x: width - padding - arcPadding, y: padding + arcPadding),
                    radius: arcPadding,
                    startAngle: CGFloat(3 * M_PI / 2),
                    endAngle: CGFloat(M_PI * 2),
                    clockwise: true)

        path.move(to: CGPoint(x: width - padding, y: padding + arcPadding))
        path.addLine(to: CGPoint(x: width - padding, y: height - padding - arcPadding))

        // Bottom-Right arc
        path.addArc(withCenter: CGPoint(x: width - padding - arcPadding, y: height - padding - arcPadding),
                    radius: arcPadding,
                    startAngle: CGFloat(M_PI * 2),
                    endAngle: CGFloat(M_PI / 2),
                    clockwise: true)
        path.move(to: CGPoint(x: width - padding - arcPadding, y: height - padding))
        path.addLine(to: CGPoint(x: width - padding - bottomLineLength, y: height - padding))

        path.move(to: CGPoint(x: padding + bottomLineLength, y: height - padding))
        path.addLine(to: CGPoint(x: padding + arcPadding, y: height - padding))

        // Bottom-Left arc
        path.addArc(withCenter: CGPoint(x: padding + arcPadding, y: height - padding - arcPadding),
                    radius: arcPadding,
                    startAngle: CGFloat(M_PI / 2),
                    endAngle: CGFloat(M_PI),
                    clockwise: true)

        path.move(to: CGPoint(x: padding, y: height - padding - arcPadding))
        path.addLine(to: CGPoint(x: padding, y: padding + arcPadding))

        // Top-Left arc
        path.addArc(withCenter: CGPoint(x: padding + arcPadding, y: padding + arcPadding),
                    radius: arcPadding,
                    startAngle: CGFloat(M_PI),
                    endAngle: CGFloat(3 * M_PI / 2),
                    clockwise: true)

        pathLayer?.rasterizationScale = 2 * UIScreen.main.scale
        pathLayer?.shouldRasterize = true
        pathLayer?.strokeColor = UIColor(red: 0xFF/255, green: 0xFF/255, blue: 0xFF/255, alpha: 0.5).cgColor
        pathLayer?.lineWidth = 2.0
        pathLayer?.path = path.cgPath

        contentView.layer.addSublayer(pathLayer!)
    }

I'm calling it in layoutSubviews for re-creating the path on device rotation.

And if you look at the screenshot, there are some black and rotated a bit black "holes" near the path. All of them look the same, and they are surely not present on the image in UIImageView.

Screenshot

I think it's related to my CALayer. How to fix it?


Solution

  • Your problem is these black wedges:

    black wedges

    The underlying cause is that the default fillColor of a CAShapeLayer is opaque black. Set it to nil to avoid filling:

    pathLayer?.fillColor = nil
    

    That done, let me show you a shorter way to construct your path:

    let cgPath = CGMutablePath()
    let start = CGPoint(x: padding + bottomLineLength, y: height - padding)
    let blCorner = CGPoint(x: padding, y: height - padding)
    let tlCorner = CGPoint(x: padding, y: padding)
    let trCorner = CGPoint(x: width - padding, y: padding)
    let brCorner = CGPoint(x: width - padding, y: height - padding)
    let end = CGPoint(x: width - padding - bottomLineLength, y: height - padding)
    
    cgPath.move(to: start)
    cgPath.addArc(tangent1End: blCorner, tangent2End: tlCorner, radius: arcPadding)
    cgPath.addArc(tangent1End: tlCorner, tangent2End: trCorner, radius: arcPadding)
    cgPath.addArc(tangent1End: trCorner, tangent2End: brCorner, radius: arcPadding)
    cgPath.addArc(tangent1End: brCorner, tangent2End: end, radius: arcPadding)
    cgPath.addLine(to: end)
    
    shapeLayer.path = cgPath
    

    Result:

    no wedges