PocketSVG icon indicating copy to clipboard operation
PocketSVG copied to clipboard

How to convert UIBezierpath to SVG?

Open firattamur opened this issue 3 years ago • 8 comments

Hello,

I am trying to create a coloring app. I can read svg image and fill their backgrounds. I want to save my UIBezier paths as svg after coloring process is done. I saw the function SVGStringFromCGPaths path I could not figure out how to set AttributeSet of svg? A short explanation or a small examples would be great.

Thanks,

firattamur avatar Oct 09 '21 17:10 firattamur

You'll need to explain how the "coloring process" works.

If your paths stay as SVGBezierPath instances you can just use self.SVGRepresentation.

fjolnir avatar Oct 11 '21 04:10 fjolnir

Hello,

Thank you for your response first of all. Here is my code for coloring process. I am creating CAShapeLayers from SVGBezierPaths then fill inside of them. I want to save result of coloring process as SVG. Is there any way to convert colored CAShapeLayers to SVG?

import UIKit
import PocketSVG


class ViewController: UIViewController {

private var paths: [UIBezierPath]!
private var shapeLayerIndices = [CAShapeLayer:Int]()
private var touchedshapedLayerIndices = [CAShapeLayer:Int]()
private var shapeLayers = [CAShapeLayer]()
private var affineTransform: CGAffineTransform!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    
    let url = Bundle.main.url(forResource: "simple", withExtension: "svg")!
    
    paths = SVGBezierPath.pathsFromSVG(at: url)
    let tigerLayer = CALayer()
    
    for (index, path) in paths.enumerated() {
    
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.fillColor = UIColor.white.cgColor

        shapeLayerIndices[shapeLayer] = index
        tigerLayer.addSublayer(shapeLayer)
        
    }
    
    let scale = CGAffineTransform(scaleX: 1, y: 1)
    let transform = CGAffineTransform(translationX: 100, y: 100)
    affineTransform = scale.concatenating(transform)
    tigerLayer.setAffineTransform(affineTransform)
    view.layer.addSublayer(tigerLayer)
                
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
   
    let touch = touches.first
    let point = touch!.location(in: self.view).applying(self.affineTransform.inverted())
        
    guard let sublayers = self.view.layer.sublayers?[0].sublayers else { return }

    self.touchedshapedLayerIndices = [CAShapeLayer:Int]()
    
    for (_, sublayer) in sublayers.enumerated() {
        
        if let sublayer = sublayer as? CAShapeLayer, let path = sublayer.path {
            
            if path.contains(point) {
                touchedshapedLayerIndices[sublayer] = self.shapeLayerIndices[sublayer]
            }
        }
        
    }
    
    let topShapeLayer = touchedshapedLayerIndices.max { a, b in a.value < b.value }
    
    guard topShapeLayer != nil else {
        return
    }
    
    let sublayer = topShapeLayer!.key
    let index    = topShapeLayer!.value
    
    sublayer.removeFromSuperlayer()
    sublayer.fillColor = UIColor.red.cgColor
    self.view.layer.sublayers?[0].insertSublayer(sublayer, at: UInt32(index))
    
}

}

firattamur avatar Oct 12 '21 03:10 firattamur

You'll have to iterate over the path array(and change its type to [SVGBezierPath]), then for each of the paths, use pathBySettingSVGAttributes: with the svg attributes you want to change. It'll return a new path reflecting the changes.

something like

let attributeSet = SVGMutableAttributeSet()
let paths = shapeLayers.map { layer in 
    let path = paths[shapeLayerIndices[layer]]
    let newPath = path.pathBySettingSVGAttributes(["fill": layer.backgroundColor])
    attributeSet.set(attributes: path.svgAttributes, forPath: newPath.CGPath)
    return newPath.CGPath
}

let svgString = SVGStringFromCGPaths(updatedPaths, attributeSet)

I haven't actually compiled the above, but the general idea should work.

fjolnir avatar Oct 12 '21 03:10 fjolnir

Thanks a lot! I will try it and share the results.

firattamur avatar Oct 12 '21 16:10 firattamur

Hello,

I have tried your code. I got the error in below. I tried to give color parameter as UIColor and CgColor but neither of them worked.

Screen Shot 2021-10-13 at 7 43 07 PM Screen Shot 2021-10-13 at 7 42 50 PM

Thanks,

firattamur avatar Oct 13 '21 16:10 firattamur

I had the same problem, and I modified the code myself to keep it from crashing

for(NSString *key in pathAttrs) {
      if(![pathAttrs[key] isKindOfClass:[NSString class]]) { // Color
           [svg appendFormat:@" %@=\"%@\"", key, hexTriplet((__bridge CGColorRef)pathAttrs[key]).string()];
              if (CFGetTypeID((__bridge CFTypeRef)(pathAttrs[key])) == CGColorGetTypeID()) {
                    float const alpha = CGColorGetAlpha((__bridge CGColorRef)pathAttrs[key]);
                    if(alpha < 1.0)
                        [svg appendFormat:@" %@-opacity=\"%.2g\"", key, alpha];
                }
      } else
           [svg appendFormat:@" %@=\"%@\"", key, pathAttrs[key]];
}

CodeForRabbit avatar Apr 18 '23 09:04 CodeForRabbit

@CodeForRabbit It'd be great if you could make a PR with that

fjolnir avatar Apr 18 '23 12:04 fjolnir

fixed via https://github.com/pocketsvg/PocketSVG/pull/214

arielelkin avatar Jul 17 '23 14:07 arielelkin