You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

  1. The top filled bar with rounded notches
  2. The top vertical line (root color)
  3. 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 layoutSubviews method 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, and strokeSize to match your original Android paint settings. You can also tweak lineCap on 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:12:33