Search code examples
swiftcore-animationcashapelayerswiftsvgpocketsvg

How to fit CAShape Layer to UIImage View?


I am parsing SVG image file and convert it paths into CAShapeLayer, after that I am adding this CAShapeLayer to UIImage View's layer. It's work fine, but the problem is that it does not fit to the UIImage View. That's output. Image 1. That's code of adding layers to 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
}

I Searched and added these two lines of code, i got this result

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)
        }

That's is output. Image 2

I don't know how to calculate scaleX or scaleY (using in CGAffineTransform method) value according to uiImage View bounds

Edit Note: I am apply touch support to CAShapeLayer, That's code.

    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
        }
    }

Solution

  • According to your updated question, calculating scale using view bounds

    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)
        }
    }