swift4 根据路径绘画SVG

来源:互联网 发布:文明5 for mac 编辑:程序博客网 时间:2024/05/14 09:29

根据上司要求把所有的图片都改成SVG格式的,然后我就在网上找了一些类库,比如说SVGKit类库,网上也有好多它的使用,但是我用的swift,SVGKit是OC的库,这时也有人会说SVGKit也可以在swift项目中使用,但是我搞了一天没成功,可能是我太笨了鄙视,然后我就自己写了一个根据路径串在画板上把这张图画出来,话不多说,直接上代码。

ViewController类中创建一个ImageView,将画出的svgImage放到ImageView上

import UIKit


class ViewController: UIViewController {


    var imageView : UIImageView!

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.\

        

         imageView = UIImageView(frame: CGRect(x: 40, y: 70, width: 250, height: 250))

        

        self.view.addSubview(imageView)

    }

 override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }


    @IBAction func onClick(_ sender:Any) {

        let image =SVGImage().convertViewToSVGImage(width:imageView.bounds.width,height:imageView.bounds.height,data:"M2,21.625 C2,21.625 30,21.625 30,21.625 31.105,21.625 32,22.52 32,23.625 32,23.625 32,25 32,25 32,26.105 31.105,27 30,27 30,27 2,27 2,27 0.89499998,27 0,26.105 0,25 0,25 0,23.625 0,23.625 0,22.52 0.89499998,21.625 2,21.625 z M2,11.125 C2,11.125 30,11.125 30,11.125 31.105,11.125 32,12.02 32,13.125 32,13.125 32,14.5 32,14.5 32,15.605 31.105,16.5 30,16.5 30,16.5 2,16.5 2,16.5 0.89499998,16.5 0,15.605 0,14.5 0,14.5 0,13.125 0,13.125 0,12.02 0.89499998,11.125 2,11.125 z M2,0 C2,0 30,0 30,0 31.105,0 32,0.89499998 32,2 32,2 32,3.375 32,3.375 32,4.48 31.105,5.375 30,5.375 30,5.375 2,5.375 2,5.375 0.89499998,5.375 0,4.48 0,3.375 0,3.375 0,2 0,2 0,0.89499998 0.89499998,0 2,0 z", lineColor:UIColor.red,fillColor:UIColor,blue)

        imageView.image = image

    }

    

}


SVGImage类将DrawSvgPathView画出来的图转成Image

import UIKit


open class SVGImage:NSObject {

    

    let svgView : DrawSvgPathView!

    

    public override init() {

        //        super.init()

        svgView = DrawSvgPathView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))

        

    }

    //将视图转成Image

    open func convertViewToSVGImage(width:CGFloat,height:CGFloat,data:String, lineColor:UIColor, fillColor: UIColor? = UIColor.clear) ->UIImage?{

        

        svgView.layer.sublayers =nil

        svgView.setPathFromSvg(width: width,height: height,data: data, lineColor: lineColor, fillColor: fillColor)

        UIGraphicsBeginImageContextWithOptions(CGSize(width:width,height:height),false,UIScreen.main.scale)

        svgView.layer.render(in:UIGraphicsGetCurrentContext()!)

        let image =UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        

        return image

        

    }

}


DrawSvgPathView类根据PocketSVG解析出来的路径绘画图片。

import UIKit


 class DrawSvgPathView: UIView {


    var _shapeView :CAShapeLayer =CAShapeLayer()

    var _strokeLineColor : UIColor!

    var _strokeFillColor : UIColor!

    

    override init(frame:CGRect) {

        super.init(frame: frame)

    }

    

    required publicinit?(coder aDecoder:NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }

    

    open func setPathFromSvg(width:CGFloat,height:CGFloat,data:String, lineColor:UIColor, fillColor: UIColor? = UIColor.clear){

        

        self.layer.sublayers =nil

        self.bounds.size =CGSize(width: width, height: height)

        self._strokeLineColor = lineColor

        self._strokeFillColor = fillColor

        let pocketSVG = PocketSVG(str: data)

        self._shapeView.path = pocketSVG.bezier.cgPath

        

        self.scale()

        

        

    }

    

    func scale(){

      

        let boundingBox : CGRect = (_shapeView.path?.boundingBoxOfPath)!

        let boundingBoxAspectRatio : CGFloat = boundingBox.width/boundingBox.height

        let viewAspectRatio : CGFloat = self.frame.size.width /self.frame.size.height

        

        var scaleFactor : CGFloat = 1.0

        if boundingBoxAspectRatio > viewAspectRatio {

            

            scaleFactor = self.frame.size.width / boundingBox.width

        }else{

            scaleFactor = self.frame.size.height / boundingBox.height

        }

        //// Scaling the path ...

        var scaleTransform :CGAffineTransform =CGAffineTransform()

        //Scale down the path first

        scaleTransform = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)

        //然后将路径的左上角

        scaleTransform = scaleTransform.translatedBy(x: -boundingBox.minX, y: -boundingBox.minY)

        

        

        let scaledSize : CGSize = __CGSizeApplyAffineTransform(boundingBox.size,CGAffineTransform(scaleX: scaleFactor, y: scaleFactor))

        let centerOffset : CGSize = CGSize(width: (self.frame.size.width - scaledSize.width) / (scaleFactor * 2.0), height: (self.frame.size.height - scaledSize.height) / (scaleFactor * 2.0))

        

       scaleTransform = scaleTransform.translatedBy(x:centerOffset.width, y: centerOffset.height)


        let scaledPath = _shapeView.path?.copy(using: &scaleTransform)

        

        let scaledShapeLayer = CAShapeLayer.init()

        scaledShapeLayer.path = scaledPath

        scaledShapeLayer.strokeColor = _strokeLineColor.cgColor

        if_strokeFillColor ==UIColor.clear {

            scaledShapeLayer.fillColor = UIColor.clear.cgColor

        }else{

            scaledShapeLayer.fillColor = _strokeFillColor.cgColor

        }

        scaledShapeLayer.lineWidth = 1.0

        ////add the shape layer to our custom view

        self.layer.addSublayer(scaledShapeLayer)

        

        

    }


}


PocketSVG类主要是解析ViewController类中的那一串路径字符串,解析成UIBezierPath。

import UIKit



let separatorCharString  = ", CcMmLlHhVvZzqQaAsS"

let commandCharString    = "CcMmLlHhVvZzqQaAsS"


class Token: NSObject {

    var command : String = ""

    var values  : [CGFloat] = [CGFloat]()

    

    init(commandChar : String) {

        super.init()

        command = commandChar

        values = [CGFloat]()

    }

    

    func addValue(value: CGFloat){

        values.append(value)

    }

    

    func valence() -> Int{

        return values.count

    }

    

    func parameter(index : Int) -> CGFloat?{

        if index < values.count {

            return values[index]

        }else{

            return nil

        }

    }

    

}


class PocketSVG: NSObject {

    


    let invalidCommand       = "*"

    

    var pathScale : Float = 0.0

    

    var lastPoint   : CGPoint!

    

    var lastControlPoint    : CGPoint!

    

    var validLastControlPoint   : Bool!

    

    var separatorSet    : CharacterSet!

    

    var commandSet      : CharacterSet!

    

    var tokens  : [Token] = [Token]()

    

    var bezier  :UIBezierPath =UIBezierPath()

    

    

    

    init(str: String) {

        super.init()

        

        self.pathScale =0

        self.reset()

        self.separatorSet =CharacterSet.init(charactersIn:separatorCharString)

        self.commandSet   =CharacterSet.init(charactersIn:commandCharString)

        if let t =self.parsePath(attr: str) {

            

            self.tokens = t

        }

        

        bezier = self.generateBezier(inTokens:self.tokens)

        

        

    }

    

    func reset(){

        self.lastPoint =CGPoint(x:0, y:0)

        self.validLastControlPoint =false

    }

    

    func parsePath(attr : String) -> [Token]?{

        var stringTokens : [String] = [String]()

        

        var sTokens = [Token]()

        

        var i : Int =0

        while i < attr.characters.count {

            var charAtIndex = (attr as NSString).substring(with:NSRange.init(location: i, length:1))

            //如果是空格 i+1

            if charAtIndex == " "{

                i += 1

                continue


            }

            

            var stringToken = ""

            if charAtIndex != ","{

                stringToken += charAtIndex

            }

            

            if !commandSet.isSuperset(of:CharacterSet(charactersIn: charAtIndex)) && charAtIndex !=","{

                

                i += 1

                charAtIndex = (attr as NSString).substring(with: NSRange.init(location: i, length: 1))

                

                while i < attr.characters.count && !separatorSet.isSuperset(of:CharacterSet(charactersIn: charAtIndex)) {

                    stringToken += charAtIndex

                    i += 1

                    charAtIndex = (attr as NSString).substring(with: NSRange.init(location: i, length: 1))

                }

            }else{

                i += 1

            }

            if !stringToken.isEmpty{

                stringTokens.append(stringToken)

            }

            

        }

        

        if stringTokens.count ==0 {

            return nil

        }

        

        // turn the stringTokens array into Tokens, checking validity of tokens as we go

        

        i = 0

        var stringToken = stringTokens[i]

        var command = (stringToken as NSString).substring(with:NSRange.init(location:0, length:1))

        while i < stringTokens.count {

            

            if !commandSet.isSuperset(of:CharacterSet.init(charactersIn: command)){

                return nil

            }

        

        // There can be any number of floats after a command. Suck them in until the next command.

            let token = Token(commandChar: command)

            i += 1

            if i < stringTokens.count{

                stringToken = stringTokens[i]

                command = (stringToken as NSString).substring(with: NSRange.init(location: 0, length: 1))

                while i < stringTokens.count &&  !commandSet.isSuperset(of:CharacterSet.init(charactersIn: command)) {

                    

                    

                    let floatScanner = Scanner(string: stringToken)

                    var value : Float = 0.0

                    if !floatScanner.scanFloat(&value){

                        return nil

                    }

                    pathScale = Float(abs(Int32(value))) >pathScale ?Float(abs(Int32(value))) :pathScale

                    

                    token.addValue(value: CGFloat(value))

                    

                    i += 1

                    if i < stringTokens.count{

                        

                        stringToken = stringTokens[i]

                        command = (stringToken asNSString).substring(with:NSRange.init(location:0, length: 1))

                    }

                }

            }

            sTokens.append(token)

        }

        

        return sTokens

    }

    

    

    func generateBezier(inTokens:[Token]) ->UIBezierPath{

        

//        var bezier = UIBezierPath()

        self.reset()

        for thisToken in inTokens {

            

            let command = thisToken.command

            switch command{

            case "M","m" :

                self.appendSVGMCommand(token: thisToken)

                break

            case "L","l","H","h","V","v":

                self.appendSVGLCommand(token: thisToken)

                break

            case "c","C":

                self.appendSVGCCommand(token: thisToken)

                break

            case "s","S":

                self.appendSVGSCommand(token: thisToken)

                break

            case "Z","z":

                self.bezier.close()

                break

            default:

                break

            }

        }

        

        return bezier

        

    }

    

    func appendSVGMCommand(token: Token){

        

        self.validLastControlPoint =false

        var index = 0

        var first = true

        while index < token.valence() {

           

            var x : CGFloat = 0

            if let value = token.parameter(index: index){

                x = value + (token.command == "m" ? lastPoint.x :0)

            }

            

            index += 1

            if index == token.valence(){

                return

            }

            

            var y : CGFloat = 0

            if let value = token.parameter(index: index){

                y = value + (token.command == "m" ? lastPoint.y :0)

            }

            lastPoint = CGPoint(x: x, y: y)

            

            if first{

                bezier.move(to:lastPoint)

                first = false

            }else{

                bezier.addLine(to:lastPoint)

            }

            index += 1

            

        }

    }

   

    func appendSVGLCommand(token: Token){

        

        self.validLastControlPoint =false

        var index = 0

        while index < token.valence() {

            

            var x : CGFloat = 0

            var y : CGFloat = 0

            

            switch token.command{

            case "l":

                    x = lastPoint.x

                    y = lastPoint.y

            case "L":

                if let valueX = token.parameter(index: index){

                    x += valueX

                }

                index += 1

                if index == token.valence(){

                    return

                }

                

                if let valueY = token.parameter(index: index){

                    y += valueY

                }

                break

            case "h":

                x = lastPoint.x

            case "H":

                if let valueX = token.parameter(index: index){

                    x += valueX

                }

                y = lastPoint.y

                break

            case "v":

                y = lastPoint.y

            case "V":

                if let valueY = token.parameter(index: index){

                    y += valueY

                }

                x = lastPoint.x

                break

                

            default:

                return

            }

            lastPoint = CGPoint(x: x, y: y)

            bezier.addLine(to:lastPoint)

            index += 1

        }

    }

    

    func appendSVGCCommand(token: Token){

        

        var index = 0

        while index +5 < token.valence() {// we must have 6 floats here (x1, y1, x2, y2, x, y).

            

            var x1 : CGFloat = 0

            if let value = token.parameter(index: index){

                x1 = value + (token.command =="c" ?lastPoint.x :0)

            }

            index += 1

            

            var y1 : CGFloat = 0

            if let value = token.parameter(index: index){

                y1 = value + (token.command =="c" ?lastPoint.y :0)

            }

            index += 1

            

            var x2 : CGFloat = 0

            if let value = token.parameter(index: index){

                x2 = value + (token.command =="c" ?lastPoint.x :0)

            }

            index += 1

            

            var y2 : CGFloat = 0

            if let value = token.parameter(index: index){

                y2 = value + (token.command =="c" ?lastPoint.y :0)

            }

            index += 1

            

            var x : CGFloat = 0

            if let value = token.parameter(index: index){

                x = value + (token.command == "c" ? lastPoint.x :0)

            }

            index += 1

            

            var y : CGFloat = 0

            if let value = token.parameter(index: index){

                y = value + (token.command == "c" ? lastPoint.y :0)

            }

            index += 1

            

            lastPoint = CGPoint(x: x, y: y)

            

            bezier.addCurve(to:lastPoint, controlPoint1:CGPoint(x:x1,y:y1), controlPoint2:CGPoint(x:x2,y:y2))

            self.lastControlPoint =CGPoint(x: x2, y: y2)

            self.validLastControlPoint =true

        }

    }

    

    func appendSVGSCommand(token : Token){

        

        var index = 0

        while index + 3 < token.valence() {

            

            let x1 :CGFloat =lastPoint.x + (lastPoint.x -lastControlPoint.x)

            let y1 :CGFloat =lastPoint.y + (lastPoint.y -lastControlPoint.y)

            

            var x2 : CGFloat = 0

            if let value = token.parameter(index: index){

                x2 = value + (token.command =="s" ?lastPoint.x :0)

            }

            index += 1

            

            var y2 : CGFloat = 0

            if let value = token.parameter(index: index){

                y2 = value + (token.command =="s" ?lastPoint.y :0)

            }

            index += 1

            

            var x : CGFloat = 0

            if let value = token.parameter(index: index){

                x = value + (token.command == "s" ? lastPoint.x :0)

            }

            index += 1

            

            var y : CGFloat = 0

            if let value = token.parameter(index: index){

                y = value + (token.command == "s" ? lastPoint.y :0)

            }

            index += 1

            

            lastPoint = CGPoint(x: x, y: y)

            

            bezier.addCurve(to:lastPoint, controlPoint1:CGPoint(x:x1,y:y1), controlPoint2:CGPoint(x:x2,y:y2))

            self.lastControlPoint =CGPoint(x: x2, y: y2)

            self.validLastControlPoint =true

            

        }

    }

    

}