swift4 根據路徑繪畫SVG
根據上司要求把所有的圖片都改成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:
imageView.image = image
}
}
SVGImage類將DrawSvgPathView畫出來的圖轉成Image
import UIKit
open class SVGImage:NSObject {
let svgView : DrawSvgPathView!
public overrideinit() {
// 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
}
}
}