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

如何为UIView添加上下端圆角的左侧绿色边框(适配动态高度Cell)

为UIView添加左侧自定义边框(Layer实现方案)

问题描述

参考图片

需要为UIView添加如图所示的左侧绿色边框,尝试过用图片作为子视图的方案,但无法适配Cell的动态高度,且自身实现的图片拉伸代码未生效(代码如下),希望获取通过Layer实现的适配方案。

补充代码(失效的图片方案)

private func addBorderImage() {
    print("commit init")
    guard let img = UIImage(named: "BorderLeft") else {
        fatalError("Could not load image named \"stretch1\"!!!")
    }
    // create a resizable version
    let stretchImg = img.resizableImage(withCapInsets: .init(top: 12.0, left: 0.0, bottom: 12.0, right: 0.0))
    let imgView = UIImageView()
    imgView.image = stretchImg.withRenderingMode(.alwaysTemplate)
    imgView.tintColor = UIColor(named: "AccentColor")
    
    imgView.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(imgView)
    
    let g = contentView.layoutMarginsGuide
    NSLayoutConstraint.activate([
        imgView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8.0),
        imgView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: -10.0),
        imgView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -8.0),
        imgView.widthAnchor.constraint(equalToConstant: 25.0),
    ])
    
}

Layer实现方案(完美适配动态高度)

方案1:CAShapeLayer绘制纯色自定义边框

直接通过绘制路径实现,完全适配视图动态高度,无需依赖图片,可灵活调整样式参数。

封装扩展代码

extension UIView {
    func addLeftCustomBorder(color: UIColor, 
                            width: CGFloat = 4, 
                            topInset: CGFloat = 8, 
                            bottomInset: CGFloat = 8, 
                            cornerRadius: CGFloat = 6) {
        // 移除已存在的边框层,避免重复添加
        layer.sublayers?.filter { $0.name == "LeftCustomBorder" }.forEach { $0.removeFromSuperlayer() }
        
        let borderLayer = CAShapeLayer()
        borderLayer.name = "LeftCustomBorder"
        borderLayer.fillColor = color.cgColor
        
        // 计算边框有效高度
        let borderHeight = bounds.height - topInset - bottomInset
        // 绘制带圆角的路径
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: topInset))
        path.addLine(to: CGPoint(x: width, y: topInset))
        // 右上角圆角
        path.addArc(withCenter: CGPoint(x: width, y: topInset + cornerRadius), 
                    radius: cornerRadius, 
                    startAngle: .pi * 3/2, 
                    endAngle: 0, 
                    clockwise: true)
        path.addLine(to: CGPoint(x: width, y: topInset + borderHeight - cornerRadius))
        // 右下角圆角
        path.addArc(withCenter: CGPoint(x: width, y: topInset + borderHeight), 
                    radius: cornerRadius, 
                    startAngle: 0, 
                    endAngle: .pi/2, 
                    clockwise: true)
        path.addLine(to: CGPoint(x: 0, y: topInset + borderHeight))
        path.close()
        
        borderLayer.path = path.cgPath
        layer.addSublayer(borderLayer)
        
        // 监听视图尺寸变化,动态更新边框
        addObserver(self, forKeyPath: "bounds", options: [.new], context: nil)
    }
    
    override open func observeValue(forKeyPath keyPath: String?, 
                                   of object: Any?, 
                                   change: [NSKeyValueChangeKey : Any]?, 
                                   context: UnsafeMutableRawPointer?) {
        if keyPath == "bounds" {
            // 重新绘制边框
            if let color = layer.sublayers?.first(where: { $0.name == "LeftCustomBorder" })?.fillColor {
                addLeftCustomBorder(color: UIColor(cgColor: color))
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
    
    deinit {
        // 移除观察者,避免内存泄漏
        removeObserver(self, forKeyPath: "bounds")
    }
}

使用方式

在Cell的layoutSubviews或配置方法中调用:

override func layoutSubviews() {
    super.layoutSubviews()
    contentView.addLeftCustomBorder(color: UIColor(named: "AccentColor") ?? .systemGreen)
}

方案2:CAGradientLayer实现渐变左侧边框

如果需要渐变效果(如图中可能的上下浅、中间深的绿色),可结合渐变层与遮罩实现:

封装扩展代码

extension UIView {
    func addLeftGradientBorder(colors: [UIColor], 
                              width: CGFloat = 4, 
                              topInset: CGFloat = 8, 
                              bottomInset: CGFloat = 8, 
                              cornerRadius: CGFloat = 6) {
        // 移除旧的渐变边框相关层
        layer.sublayers?.filter { 
            $0.name == "LeftGradientBorder" || $0.name == "LeftGradientBorderMask" 
        }.forEach { $0.removeFromSuperlayer() }
        
        // 创建渐变层
        let gradientLayer = CAGradientLayer()
        gradientLayer.name = "LeftGradientBorder"
        gradientLayer.colors = colors.map { $0.cgColor }
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
        gradientLayer.frame = CGRect(x: 0, y: topInset, width: width, height: bounds.height - topInset - bottomInset)
        
        // 创建遮罩层实现圆角
        let maskLayer = CAShapeLayer()
        maskLayer.name = "LeftGradientBorderMask"
        let maskPath = UIBezierPath(roundedRect: gradientLayer.bounds, 
                                   byRoundingCorners: [.topRight, .bottomRight], 
                                   cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        maskLayer.path = maskPath.cgPath
        gradientLayer.mask = maskLayer
        
        layer.addSublayer(gradientLayer)
        
        // 监听尺寸变化
        addObserver(self, forKeyPath: "bounds", options: [.new], context: nil)
    }
    
    override open func observeValue(forKeyPath keyPath: String?, 
                                   of object: Any?, 
                                   change: [NSKeyValueChangeKey : Any]?, 
                                   context: UnsafeMutableRawPointer?) {
        if keyPath == "bounds" {
            if let gradientLayer = layer.sublayers?.first(where: { $0.name == "LeftGradientBorder" }) as? CAGradientLayer {
                let colors = gradientLayer.colors?.map { UIColor(cgColor: $0 as! CGColor) } ?? []
                addLeftGradientBorder(colors: colors)
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

使用方式

contentView.addLeftGradientBorder(colors: [.systemGreen.withAlphaComponent(0.6), .systemGreen, .systemGreen.withAlphaComponent(0.6)])

关于你之前图片方案失效的原因

  1. resizableImage(withCapInsets:)的参数设置可能不符合图片结构:如果图片上下是圆角区域,需要确保top和bottom的capInsets值刚好覆盖圆角高度,否则拉伸后会变形。
  2. withRenderingMode(.alwaysTemplate)会忽略图片原有颜色,仅保留alpha通道,如果你的原图不是透明背景的模板图,会导致颜色显示异常。

内容的提问来源于stack exchange,提问作者Asha Gohel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:00:56