如何使 CAShape 层适合 UIImage 视图?

Posted

技术标签:

【中文标题】如何使 CAShape 层适合 UIImage 视图?【英文标题】:How to fit CAShape Layer to UIImage View? 【发布时间】:2021-10-18 10:03:47 【问题描述】:

我正在解析 SVG 图像文件并将其路径转换为 ​​CAShapeLayer,之后我将此 CAShapeLayer 添加到 UIImage View 的图层。 它工作正常,但问题是它不适合 UIImage 视图。 那是输出。 Image 1。 这是向 UIImageView 添加图层的代码

        if let svgURL = Bundle.main.url(forResource: "image", withExtension: "svg") 
        let paths = SVGBezierPath.pathsFromSVG(at: svgURL)
        for path in paths 
            items.append(path)
            let layer = createLayer(path: path)
            layer.frame = self.backgroundIV.bounds
            self.backgroundIV.layer.addSublayer(layer)
        
        
        
       // Create Layer from Paths Method


    fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer 
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    if let any = path.svgAttributes["stroke"] 
        shapeLayer.strokeColor = (any as! CGColor)
    
    
    if let any = path.svgAttributes["fill"] 
        shapeLayer.fillColor = (any as! CGColor)
    
    return shapeLayer

我搜索并添加了这两行代码,我得到了这个结果

let scale = CGFloat(0.5) 
        for path in paths 
            path.apply(CGAffineTransform(scaleX: scale, y: scale))
            items.append(path)
            let layer = createLayer(path: path)
            layer.frame = self.backgroundIV.bounds
            self.backgroundIV.layer.addSublayer(layer)
        

这是输出。 Image 2

我不知道如何根据uiImage View bounds计算scaleXscaleY(在CGAffineTransform方法中使用)值

编辑 注意:我对 CAShapeLayer 应用了触摸支持, 那是代码。

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
    
        guard let point = touches.first?.location(in: self.backgroundIV),
              let layers = self.backgroundIV.layer.sublayers
        else  return 
        var hitLayers: [CAShapeLayer] = []
        selectedLayer?.lineWidth = CGFloat(0)
        for subLayer in layers 
            if let thisLayer = subLayer as? CAShapeLayer,
               let pth = thisLayer.path 
                let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
                if pth.contains(layerPoint) 
//                    undoModel.append()
                    hitLayers.append(thisLayer)
                
            
        
        
        selectedLayer = hitLayers.last
        selectedLayer?.strokeColor = UIColor.red.cgColor
        selectedLayer?.lineWidth = CGFloat(3)
        undo.append(.init(stokeColor: nil, selectedLayer: selectedLayer, points: []))
        
        if ((selectedLayer?.frame.contains(point)) != nil) 
            isDragged = true
        
    

【问题讨论】:

我想知道是否不是因为您正在检查的尺寸:self.backgroundIV.bounds 不是最后一个。或者它是?你能打印出来检查一下吗?如果不是最后一个,请检查 layoutSubviews,并在此处更新图层框架/比例。 我相信您使用的是 PocketSVG,那么最好使用提供的 SVGImageView。 @ChanOnly123 在我的情况下它不起作用。 @AliAkbar - 假设您使用的是PocketSVG(基于您之前的问题),您是否有理由不使用它的SVGImageView 【参考方案1】:

根据您更新的问题,使用视图边界计算比例

var paths = [SVGBezierPath]()

override func viewDidLoad() 
    super.viewDidLoad()

    if let svgURL = Bundle.main.url(forResource: "Freesample", withExtension: "svg") 
        paths = SVGBezierPath.pathsFromSVG(at: svgURL)
        backgroundIV.layer.borderColor = UIColor.black.withAlphaComponent(0.3).cgColor
        backgroundIV.layer.borderWidth = 1
    


override func viewDidLayoutSubviews() 
    super.viewDidLayoutSubviews()
    let svgBounds = SVGBoundingRectForPaths(paths)
    var scale = CGFloat.zero
    if backgroundIV.bounds.width < backgroundIV.bounds.height 
        scale = backgroundIV.bounds.width / svgBounds.width
     else 
        scale = backgroundIV.bounds.height / svgBounds.height
    
    
    self.backgroundIV.layer.sublayers?.forEach  $0.removeFromSuperlayer() 
    for path in paths 
        path.apply(.init(scaleX: scale, y: scale))
        let layer = createLayer(path: path)
        layer.frame = self.backgroundIV.bounds
        self.backgroundIV.layer.addSublayer(layer)
    

【讨论】:

感谢您的回复,它可以工作,但我忘了告诉我我对图层应用了触摸支持,所以 UIImage 不会处理它。 谢谢,一切正常。【参考方案2】:

这与我在上一个关于检测多层触摸的问题中给出的答案基本相同。

不同之处在于,此示例使用SVGImageView,而不是使用您的代码来创建/添加图层,它是PocketSVG 的一部分:

class BoxesViewController: UIViewController 
    
    var backgroundIV: SVGImageView!
    
    var detectMode: DetectMode = .All
    var detectType: DetectType = .ShapePath
    
    override func viewDidLoad() 
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        guard let svgURL = Bundle.main.url(forResource: "roundedboxes", withExtension: "svg") else 
            fatalError("SVG file not found!!!")
        
        
        backgroundIV = SVGImageView.init(contentsOf: svgURL)
        
        backgroundIV.frame = view.bounds
        backgroundIV.contentMode = .scaleAspectFit
        backgroundIV.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        
        let modeControl = UISegmentedControl(items: ["All", "Top Most", "Bottom Most"])
        let typeControl = UISegmentedControl(items: ["Shape Path", "Shape Bounding Box"])
        
        modeControl.translatesAutoresizingMaskIntoConstraints = false
        typeControl.translatesAutoresizingMaskIntoConstraints = false
        backgroundIV.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(modeControl)
        view.addSubview(typeControl)
        view.addSubview(backgroundIV)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            modeControl.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            modeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            modeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            typeControl.topAnchor.constraint(equalTo: modeControl.bottomAnchor, constant: 40.0),
            typeControl.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            typeControl.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            backgroundIV.topAnchor.constraint(equalTo: typeControl.bottomAnchor, constant: 40.0),
            backgroundIV.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            backgroundIV.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            backgroundIV.heightAnchor.constraint(equalTo: backgroundIV.widthAnchor),
            
        ])
        
        modeControl.addTarget(self, action: #selector(modeChanged(_:)), for: .valueChanged)
        typeControl.addTarget(self, action: #selector(typeChanged(_:)), for: .valueChanged)
        
        modeControl.selectedSegmentIndex = 0
        typeControl.selectedSegmentIndex = 0
        
        // so we can see the frame of the image view
        backgroundIV.backgroundColor = .white
        
    
    
    @objc func modeChanged(_ sender: UISegmentedControl) -> Void 
        switch sender.selectedSegmentIndex 
        case 0:
            detectMode = .All
        case 1:
            detectMode = .TopMost
        case 2:
            detectMode = .BottomMost
        default:
            ()
        
    
    
    @objc func typeChanged(_ sender: UISegmentedControl) -> Void 
        switch sender.selectedSegmentIndex 
        case 0:
            detectType = .ShapePath
        case 1:
            detectType = .ShapeBounds
        default:
            ()
        
    
    
    fileprivate func createLayer(path: SVGBezierPath) -> CAShapeLayer 
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        if let any = path.svgAttributes["stroke"] 
            shapeLayer.strokeColor = (any as! CGColor)
        
        
        if let any = path.svgAttributes["fill"] 
            shapeLayer.fillColor = (any as! CGColor)
        
        return shapeLayer
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        
        guard let point = touches.first?.location(in: self.backgroundIV),
              // make sure backgroundIV has sublayers
              let layers = self.backgroundIV.layer.sublayers
        else  return 
        
        var hitLayers: [CAShapeLayer] = []
        
        // loop through all sublayers
        for subLayer in layers 
            // make sure
            //  it is a CAShapeLayer
            //  it has a path
            if let thisLayer = subLayer as? CAShapeLayer,
               let pth = thisLayer.path 
                // clear the lineWidth... we'll reset it after getting the hit layers
                thisLayer.lineWidth = 0
                
                // convert touch point from backgroundIV.layer to thisLayer
                let layerPoint: CGPoint = thisLayer.convert(point, from: self.backgroundIV.layer)
                
                if detectType == .ShapePath 
                    // does the path contain the point?
                    if pth.contains(layerPoint) 
                        hitLayers.append(thisLayer)
                    
                 else if detectType == .ShapeBounds 
                    if pth.boundingBox.contains(layerPoint) 
                        hitLayers.append(thisLayer)
                    
                
            
        
        
        if detectMode == .All 
            hitLayers.forEach  layer in
                layer.strokeColor = UIColor.cyan.cgColor
                layer.lineWidth = 3
            
         else if detectMode == .TopMost 
            if let layer = hitLayers.last 
                layer.strokeColor = UIColor.cyan.cgColor
                layer.lineWidth = 3
            
         else if detectMode == .BottomMost 
            if let layer = hitLayers.first 
                layer.strokeColor = UIColor.cyan.cgColor
                layer.lineWidth = 3
            
        
        
    
    

现在 SVG 文件被缩放以适应视图:

【讨论】:

以上是关于如何使 CAShape 层适合 UIImage 视图?的主要内容,如果未能解决你的问题,请参考以下文章

如何在导航栏中放置 UIImage 使其成为徽标?

iPhone 编程:如何使 UIImageView 中的 UIImage 以实际大小显示?

如何使 UIScrollView 缩放到 UIImage 的大小?

如何使 UIImage 在 Objective-c 的 Xcode 中工作

如何在iOS中更改UIImage的SHAPE

UIImage 调整大小并裁剪以适合