iOS使用CAShapeLayer绘制自定义瞄准形状的Swift实现方案
Hey there! Great question—replicating that custom shape in Swift using CAShapeLayer is totally doable, and we can even make it cleaner than the Android approach by constructing paths directly instead of using clear paint to cut out sections. Here's a step-by-step solution:
Approach
Instead of relying on clearing parts of a shape (which can cause blending issues), we'll build the exact paths we want using UIBezierPath, then render them with CAShapeLayer (GPU-accelerated for smooth performance). We'll split the shape into three logical parts:
- The top filled bar with rounded notches
- The top vertical line (root color)
- The bottom vertical line plus hollow circle (icon color)
Complete Code
Create a custom UIView subclass to encapsulate the shape:
import UIKit class CustomShapeView: UIView { // Customize these values to match your Android paint settings private let rootColor = UIColor.blue.cgColor private let iconColor = UIColor.red.cgColor private let strokeSize: CGFloat = 4.0 private var rootShapeLayer: CAShapeLayer! private var rootLineLayer: CAShapeLayer! private var iconShapeLayer: CAShapeLayer! override init(frame: CGRect) { super.init(frame: frame) setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayers() } private func setupLayers() { // Layer for the top filled shape with notches rootShapeLayer = CAShapeLayer() rootShapeLayer.fillColor = rootColor layer.addSublayer(rootShapeLayer) // Layer for the top vertical line (root color) rootLineLayer = CAShapeLayer() rootLineLayer.strokeColor = rootColor rootLineLayer.lineWidth = strokeSize rootLineLayer.lineCap = .round // Optional: matches rounded line ends layer.addSublayer(rootLineLayer) // Layer for the bottom line + hollow circle (icon color) iconShapeLayer = CAShapeLayer() iconShapeLayer.fillColor = iconColor iconShapeLayer.strokeColor = iconColor iconShapeLayer.lineWidth = strokeSize layer.addSublayer(iconShapeLayer) } override func layoutSubviews() { super.layoutSubviews() updatePaths() } private func updatePaths() { let bounds = self.bounds let width = bounds.width let height = bounds.height let halfWidth = width / 2 let halfHeight = height / 2 let thirdWidth = width / 3 let radiusClear = halfWidth - strokeSize / 2 // 1. Update top filled shape path let rootShapePath = UIBezierPath() rootShapePath.move(to: CGPoint(x: 0, y: 0)) rootShapePath.addLine(to: CGPoint(x: width, y: 0)) // Add right notch (replaces Android's clear circle) rootShapePath.addArc(withCenter: CGPoint(x: width, y: radiusClear), radius: radiusClear, startAngle: 3 * .pi / 2, endAngle: .pi, clockwise: false) rootShapePath.addLine(to: CGPoint(x: radiusClear, y: radiusClear)) // Add left notch (replaces Android's clear circle) rootShapePath.addArc(withCenter: CGPoint(x: 0, y: radiusClear), radius: radiusClear, startAngle: .pi, endAngle: 3 * .pi / 2, clockwise: false) rootShapePath.close() rootShapeLayer.path = rootShapePath.cgPath rootShapeLayer.frame = bounds // 2. Update top vertical line path let rootLinePath = UIBezierPath() rootLinePath.move(to: CGPoint(x: halfWidth, y: 0)) rootLinePath.addLine(to: CGPoint(x: halfWidth, y: halfHeight)) rootLineLayer.path = rootLinePath.cgPath rootLineLayer.frame = bounds // 3. Update icon path (bottom line + hollow circle) let iconPath = UIBezierPath() // Add bottom vertical line iconPath.move(to: CGPoint(x: halfWidth, y: halfHeight)) iconPath.addLine(to: CGPoint(x: halfWidth, y: height)) // Add big circle let bigCircle = UIBezierPath(arcCenter: CGPoint(x: halfWidth, y: halfHeight), radius: halfWidth, startAngle: 0, endAngle: 2 * .pi, clockwise: true) iconPath.append(bigCircle) // Subtract small circle to make it hollow (no clear paint needed!) let smallCircle = UIBezierPath(arcCenter: CGPoint(x: halfWidth, y: halfHeight), radius: thirdWidth, startAngle: 0, endAngle: 2 * .pi, clockwise: true) smallCircle.reversing() // Reverse path to subtract it iconPath.append(smallCircle) iconShapeLayer.path = iconPath.cgPath iconShapeLayer.frame = bounds } }
Key Details
- No Clear Paint Needed: Instead of drawing a rect and clearing parts with circles, we directly draw the top shape with notches using
addArc—this is more efficient and avoids transparency issues. - Path Subtraction: For the hollow circle, we reverse the small circle path and append it to the icon path. This tells Core Animation to subtract the small circle from the big one, creating the hollow effect.
- Responsive: The
layoutSubviewsmethod updates the paths whenever the view's size changes, so the shape will adapt to different screen sizes or layout changes. - Customizable: Adjust
rootColor,iconColor, andstrokeSizeto match your original Android paint settings. You can also tweaklineCapon the line layers if you want different line end styles.
To use this, just add CustomShapeView to your layout programmatically or via Interface Builder!
内容的提问来源于stack exchange,提问作者harsh_v

