【问题标题】:CAShapeLayer stroke issueCAShapeLayer 中风问题
【发布时间】:2020-12-15 23:20:53
【问题描述】:

我使用CAShapeLayer 而不是CALayer 的唯一原因是动画属性。

view.layer 的红色边框是参考。如果将lineWidth 设置为shapeLayer,则该层超出red 框架。

但我希望它适合 red 框。 (适合NSView

代码:

CustomView.swift:

 class CustomView: NSView{
 
    let shapeLayer = CAShapeLayer()
    
    init(){
        super.init(frame: .zero)
        
        wantsLayer = true
 
        layer?.borderWidth = 1.0
        layer?.borderColor = NSColor.red.cgColor
        layer?.masksToBounds = false
 
        layer!.addSublayer(shapeLayer)
 
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
 
    override func draw(_ rect: NSRect) {
        super.draw(rect)
    
        let path = CGMutablePath()

        path.move(to: CGPoint.zero)
        path.addLine(to: CGPoint(x: rect.width/2, y:rect.height))
        path.addLine(to: CGPoint(x: rect.width, y: 0))
        path.closeSubpath()
        
        shapeLayer.path = path
        
        shapeLayer.lineWidth = 30
        shapeLayer.strokeColor = NSColor.lightGray.cgColor
        shapeLayer.fillColor = .white
 
     }
}

ViewController.swift

     class ViewController: NSViewController {
 
    private lazy var customView: CustomView = {
        
        let customView = CustomView()
        
        view.addSubview(customView)
        
        customView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
        
            customView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            customView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            customView.heightAnchor.constraint(equalToConstant: 144),
            customView.widthAnchor.constraint(equalToConstant: 144)
        ])
        
        return customView
 
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        customView.shapeLayer.fillColor = NSColor.systemGreen.cgColor
    }
   
}

截图:

更新:

根据对这个问题的回答和评论。我确实通过以下代码更新了override func draw(_ rect: NSRect)

override func draw(_ rect: NSRect) {
        super.draw(rect)
                
        let path = CGMutablePath()
        
        let lineWidth: CGFloat = 30
       
        path.move(to: .init(x: lineWidth/2, y: lineWidth/2))
        path.addLine(to: .init(x: rect.width/2, y: rect.height - lineWidth/2))
        path.addLine(to: .init(x: rect.width - lineWidth/2, y: lineWidth/2))
        
        path.closeSubpath()
        
        shapeLayer.path = path
        shapeLayer.lineWidth = lineWidth
     }

CustomView.init 是,

init(){
        super.init(frame: .zero)
        
        wantsLayer = true
 
        layer?.borderWidth = 1.0
        layer?.borderColor = NSColor.red.cgColor
        layer?.masksToBounds = false
 
        layer!.addSublayer(shapeLayer)
 
        shapeLayer.strokeColor = NSColor.lightGray.cgColor
        shapeLayer.fillColor = .white
 
    }

输出:

但我仍然无法正确绘图。

更新:

修改路径中的第一行...所以整个路径将是,

let path = CGMutablePath()
    
    let lineWidth: CGFloat = 30
   
    path.move(to: .init(x: lineWidth/2, y: lineWidth/2))
    path.addLine(to: .init(x: rect.width/2, y: rect.height - lineWidth))
    path.addLine(to: .init(x: rect.width - lineWidth/2, y: lineWidth/2))
    
    path.closeSubpath()

输出:

现在,我对修改第二行感到困惑。我不知道如何解决它。请提示/帮助我解决这个问题。提前谢谢...

【问题讨论】:

  • 改变路径,从边缘减去一半的描边宽度。
  • @ElTomato,谢谢.. 不知道该怎么做,我试过但不理解三角形的第 3 行总是越界。谢谢您的回答。但它仍然不起作用。请参阅问题中的更新。

标签: swift macos cocoa cashapelayer


【解决方案1】:

编辑/更新:

将线宽乘以 2 并为您的形状添加蒙版会更容易:

class Triangle: NSView {
    let shapeLayer = CAShapeLayer()
    var lineWidth: CGFloat
    var strokeColor: NSColor = .clear
    var fillColor: NSColor = .clear
    init(size: CGSize, lineWidth: CGFloat = 10, strokeColor: NSColor = .white,  fillColor: NSColor = .black) {
        self.lineWidth = lineWidth*2
        self.strokeColor = strokeColor
        self.fillColor = fillColor
        super.init(frame: .init(origin: .zero, size: size))
        wantsLayer = true
        let path = NSBezierPath()
        path.move(to: .zero)
        path.line(to: .init(x: size.width/2 , y: size.height))
        path.line(to: .init(x: size.width, y: .zero))
        path.close()
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        shapeLayer.mask = mask
        shapeLayer.path = path.cgPath
        shapeLayer.lineWidth = self.lineWidth
        shapeLayer.strokeColor = strokeColor.cgColor
        shapeLayer.fillColor = fillColor.cgColor
//      layer?.borderWidth = 1
//      layer?.borderColor = NSColor.red.cgColor
        layer?.addSublayer(shapeLayer)
    }
    convenience init(width: CGFloat, height: CGFloat, lineWidth: CGFloat = 10, strokeColor: NSColor = .white, fillColor: NSColor = .black) {
        self.init(size: .init(width: width, height: height), lineWidth: lineWidth, strokeColor: strokeColor, fillColor: fillColor)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension NSBezierPath {
    var cgPath: CGPath {
        let path = CGMutablePath()
        var points: [CGPoint] = .init(repeating: .zero, count: 3)
        for i in 0..<elementCount {
            switch element(at: i, associatedPoints: &points) {
            case .moveTo: path.move(to: points[0])
            case .lineTo: path.addLine(to: points[0])
            case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1])
            case .closePath: path.closeSubpath()
            @unknown default: fatalError()
            }
        }
        return path
    }

}

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let triangle = Triangle(width: 200, height: 200, lineWidth: 1)
        view.addSubview(triangle)
    }
}

【讨论】:

  • 感谢您的回答。但它仍然不起作用。请查看问题中的更新。
  • 一个简单的解决方法是将形状用作图层的蒙版
  • 谢谢,layer?.mask = shapeLayer。这是吗?
  • path.addLine(to: .init(x: rect.width/2, y: rect.height - lineWidth));path.addLine(to: .init(x: rect.width - lineWidth, y: lineWidth/2))
  • @ElTomato 谢谢,添加了您的解决方案(替换了两行)。但仍然没有预期的输出。这是截图:ibb.co/HgWjHk5
【解决方案2】:

“带遮罩的双线宽”实现的另一种变体:

class TriangleView: NSView {
    let lineWidth: CGFloat = 20

    private lazy var shapeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = NSColor.clear.cgColor
        shapeLayer.strokeColor = NSColor.red.cgColor
        shapeLayer.lineWidth = lineWidth * 2
        return shapeLayer
    }()

    private let maskLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = NSColor.white.cgColor
        shapeLayer.strokeColor = NSColor.clear.cgColor
        shapeLayer.lineWidth = 0
        return shapeLayer
    }()

    override init(frame frameRect: NSRect = .zero) {
        super.init(frame: frameRect)
        configure()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    func configure() {
        wantsLayer = true
        layer?.borderColor = NSColor.blue.cgColor
        layer?.borderWidth = 1
        layer?.addSublayer(shapeLayer)
    }

    override func layout() {
        super.layout()

        let path = CGMutablePath()
        path.move(to: NSPoint(x: bounds.midX, y: bounds.maxY))
        path.addLine(to: NSPoint(x: bounds.maxX, y: bounds.minY))
        path.addLine(to: NSPoint(x: bounds.minX, y: bounds.minY))
        path.closeSubpath()

        shapeLayer.path = path
        maskLayer.path = path
        shapeLayer.mask = maskLayer
    }
}

概念与接受的答案相同(re double lineWidth and mask)。

如您所见,在使用CAShapeLayer 时,我们没有实现draw,而是让形状层负责渲染。但我们确实想响应帧变化,所以我们在layout 中设置(并重置)路径。

无论如何,这会产生:

【讨论】:

  • 谢谢 Rob,我很清楚如何使用。是的,它对我很有用...非常感谢!
【解决方案3】:

我并不真正开发 iOS 应用程序。但请参阅以下内容。

// View controller //
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let rect = CGRect(origin: CGPoint.zero, size: CGSize(width: 200, height: 200))
        let triangleView = TriangleView(frame: rect, backColor: UIColor.green, strokeColor: UIColor.blue, lineWidth: 10)
        view.addSubview(triangleView)
    }
}

// Subclass of UIView //
import UIKit

class TriangleView: UIView {
    var backColor: UIColor
    var strokeColor: UIColor
    var lineWidth: CGFloat
    
    init(frame: CGRect, backColor: UIColor, strokeColor: UIColor, lineWidth: CGFloat) {
        self.backColor = backColor
        self.strokeColor = strokeColor
        self.lineWidth = lineWidth
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        backColor.set()
        //UIRectFill(rect)
        
        let shapeLayer = CAShapeLayer()
        let path = UIBezierPath()
        path.move(to: CGPoint(x: rect.width/2.0, y: lineWidth/2.0))
        path.addLine(to: CGPoint(x: lineWidth/2.0, y: rect.height - lineWidth/2.0))
        path.addLine(to: CGPoint(x: rect.width - lineWidth/2.0, y: rect.height - lineWidth/2.0))
        path.addLine(to: CGPoint(x: rect.width/2.0, y: lineWidth/2.0))
        path.close()
        shapeLayer.path = path.cgPath
        shapeLayer.lineWidth = lineWidth
        shapeLayer.fillColor = backColor.cgColor
        shapeLayer.strokeColor = strokeColor.cgColor
        layer.insertSublayer(shapeLayer, at: 0)
        layer.masksToBounds = false
    }
}

以下是Cocoa版本。

// View controller //
import Cocoa

class ViewController: NSViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let rect = CGRect(origin: CGPoint(x: 50, y: 50), size: CGSize(width: 200, height: 200))
        let myView = MyView(frame: rect, fillColor: NSColor.green, strokeColor: NSColor.red, lineWidth: 10.0)
        myView.wantsLayer = true
        view.addSubview(myView)
    }
}

// Subclass of NSView //
import Cocoa

class MyView: NSView {
    override var isFlipped: Bool { return true }
    
    var fillColor: NSColor
    var strokeColor: NSColor
    var lineWidth: CGFloat
    init(frame: CGRect, fillColor: NSColor, strokeColor: NSColor, lineWidth: CGFloat){
        self.fillColor = fillColor
        self.strokeColor = strokeColor
        self.lineWidth = lineWidth
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: NSRect) {
        super.draw(rect)
        
        let path = NSBezierPath()
        path.move(to: CGPoint(x: rect.width/2.0, y: lineWidth/2.0))
        path.line(to: CGPoint(x: lineWidth/2.0, y: rect.height - lineWidth/2.0))
        path.line(to: CGPoint(x: rect.width - lineWidth/2.0, y: rect.height - lineWidth/2.0))
        path.line(to: CGPoint(x: rect.width/2.0, y: lineWidth/2.0))
        path.close()
        fillColor.setFill()
        path.fill()
        path.lineWidth = lineWidth
        strokeColor.set()
        path.stroke()
    }
}

【讨论】:

  • 糟糕...我以为是。谢谢你告诉我,@Rob。
  • 我已经附加了 Cocoa 版本。
  • 谢谢!.. 是的,正如您在我上一个问题的回答中所说的那样,它的工作原理很完美。通过您的方法,我用CALayer 解决了它。但是对于这个问题,我想使用 ShapLayer 因为它的动画属性。例如,如果我更改背景颜色,我可以在CAShapeLayer 中看到平滑的颜色变化。
  • CALayer.. 对我来说仍然很有用。非常感谢!
  • 不客气。很抱歉我没有仔细阅读您的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多