mirror of
https://github.com/exyte/Macaw.git
synced 2024-11-13 05:07:24 +03:00
Fix #67. Implement SVGView
This commit is contained in:
parent
5dd06ea894
commit
606f733fa0
@ -109,6 +109,8 @@
|
||||
57CAB1361D7832E000FD8E47 /* triangle.svg in Resources */ = {isa = PBXBuildFile; fileRef = 57CAB12D1D7832E000FD8E47 /* triangle.svg */; };
|
||||
57FCD2771D76EA4600CC0FB6 /* Macaw.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FCD26C1D76EA4600CC0FB6 /* Macaw.framework */; };
|
||||
57FCD27C1D76EA4600CC0FB6 /* MacawTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */; };
|
||||
662808C41DE6BC9900A61B95 /* CGFloat+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662808C31DE6BC9900A61B95 /* CGFloat+Double.swift */; };
|
||||
66EA4AE41DE5AE030011818C /* SVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66EA4AE31DE5AE030011818C /* SVGView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -239,6 +241,8 @@
|
||||
57FCD2761D76EA4600CC0FB6 /* MacawTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacawTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
57FCD27B1D76EA4600CC0FB6 /* MacawTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacawTests.swift; sourceTree = "<group>"; };
|
||||
57FCD27D1D76EA4600CC0FB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
662808C31DE6BC9900A61B95 /* CGFloat+Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Double.swift"; sourceTree = "<group>"; };
|
||||
66EA4AE31DE5AE030011818C /* SVGView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVGView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -461,6 +465,7 @@
|
||||
children = (
|
||||
38A997E41D91888700AC4545 /* SVGParser.swift */,
|
||||
38A997E51D91888700AC4545 /* SVGParserRegexHelper.swift */,
|
||||
66EA4AE31DE5AE030011818C /* SVGView.swift */,
|
||||
);
|
||||
path = svg;
|
||||
sourceTree = "<group>";
|
||||
@ -470,6 +475,7 @@
|
||||
children = (
|
||||
38A997E71D91888700AC4545 /* CAAnimationClosure.swift */,
|
||||
38A997E81D91888700AC4545 /* NSTimer+Closure.swift */,
|
||||
662808C31DE6BC9900A61B95 /* CGFloat+Double.swift */,
|
||||
);
|
||||
path = thirdparty;
|
||||
sourceTree = "<group>";
|
||||
@ -719,6 +725,7 @@
|
||||
38A997ED1D91888700AC4545 /* AnimatableVariable.swift in Sources */,
|
||||
38A9982F1D91888700AC4545 /* Size.swift in Sources */,
|
||||
38A997FE1D91888700AC4545 /* TransformHashable.swift in Sources */,
|
||||
662808C41DE6BC9900A61B95 /* CGFloat+Double.swift in Sources */,
|
||||
38A997FF1D91888700AC4545 /* OpacityGenerator.swift in Sources */,
|
||||
38A998321D91888700AC4545 /* Image.swift in Sources */,
|
||||
38A998181D91888700AC4545 /* LinearGradient.swift in Sources */,
|
||||
@ -744,6 +751,7 @@
|
||||
38A9983B1D91888700AC4545 /* ShapeRenderer.swift in Sources */,
|
||||
38A998211D91888700AC4545 /* GeomUtils.swift in Sources */,
|
||||
38A998131D91888700AC4545 /* Effect.swift in Sources */,
|
||||
66EA4AE41DE5AE030011818C /* SVGView.swift in Sources */,
|
||||
38A998141D91888700AC4545 /* Fill.swift in Sources */,
|
||||
38A9983A1D91888700AC4545 /* RenderUtils.swift in Sources */,
|
||||
38A998201D91888700AC4545 /* Ellipse.swift in Sources */,
|
||||
|
@ -7,19 +7,25 @@ import CoreGraphics
|
||||
///
|
||||
open class SVGParser {
|
||||
|
||||
/// Parse an SVG file identified by the specified name and file extension.
|
||||
/// - returns: Root node of the corresponding Macaw scene.
|
||||
open class func parse(path: String, ofType: String = "svg") -> Node {
|
||||
let path = Bundle.main.path(forResource: path, ofType: ofType)
|
||||
let text = try! String(contentsOfFile: path!, encoding: String.Encoding.utf8)
|
||||
return SVGParser.parse(text: text)
|
||||
}
|
||||
|
||||
/// Parse the specified content of an SVG file.
|
||||
/// - returns: Root node of the corresponding Macaw scene.
|
||||
open class func parse(text: String) -> Node {
|
||||
return SVGParser(text).parse()
|
||||
}
|
||||
/// Parse an SVG file identified by the specified bundle, name and file extension.
|
||||
/// - returns: Root node of the corresponding Macaw scene.
|
||||
open class func parse(bundle: Bundle, path: String, ofType: String = "svg") -> Node {
|
||||
let path = bundle.path(forResource: path, ofType: ofType)
|
||||
let text = try! String(contentsOfFile: path!, encoding: String.Encoding.utf8)
|
||||
return SVGParser.parse(text: text)
|
||||
}
|
||||
|
||||
/// Parse an SVG file identified by the specified name and file extension.
|
||||
/// - returns: Root node of the corresponding Macaw scene.
|
||||
open class func parse(path: String, ofType: String = "svg") -> Node {
|
||||
return SVGParser.parse(bundle: Bundle.main, path: path, ofType: ofType)
|
||||
}
|
||||
|
||||
/// Parse the specified content of an SVG file.
|
||||
/// - returns: Root node of the corresponding Macaw scene.
|
||||
open class func parse(text: String) -> Node {
|
||||
return SVGParser(text).parse()
|
||||
}
|
||||
|
||||
let moveToAbsolute = Character("M")
|
||||
let moveToRelative = Character("m")
|
||||
|
142
Source/svg/SVGView.swift
Normal file
142
Source/svg/SVGView.swift
Normal file
@ -0,0 +1,142 @@
|
||||
import UIKit
|
||||
|
||||
open class SVGView: MacawView {
|
||||
|
||||
@IBInspectable var fileName: String? {
|
||||
didSet {
|
||||
render()
|
||||
}
|
||||
}
|
||||
|
||||
private func render() {
|
||||
let viewBounds = self.bounds
|
||||
let svgNode = SVGParser.parse(
|
||||
path: fileName ?? ""
|
||||
)
|
||||
if let nodeBounds = svgNode.bounds()?.cgRect() {
|
||||
let svgWidth = nodeBounds.origin.x + nodeBounds.width
|
||||
let svgHeight = nodeBounds.origin.y + nodeBounds.height
|
||||
|
||||
let viewAspectRatio = viewBounds.width / viewBounds.height
|
||||
let svgAspectRatio = svgWidth / svgHeight
|
||||
|
||||
let scaleX = viewBounds.width / svgWidth
|
||||
let scaleY = viewBounds.height / svgHeight
|
||||
|
||||
switch self.contentMode {
|
||||
case .scaleToFill:
|
||||
svgNode.place = Transform.scale(
|
||||
sx: Double(scaleX),
|
||||
sy: Double(scaleY)
|
||||
)
|
||||
case .scaleAspectFill:
|
||||
let scaleX, scaleY: CGFloat
|
||||
if viewAspectRatio > svgAspectRatio {
|
||||
scaleX = viewBounds.width / svgWidth
|
||||
scaleY = viewBounds.width / (svgWidth / svgAspectRatio)
|
||||
} else {
|
||||
scaleX = viewBounds.height / (svgHeight / svgAspectRatio)
|
||||
scaleY = viewBounds.height / svgHeight
|
||||
}
|
||||
let calculatedWidth = svgWidth * scaleX
|
||||
let calculatedHeight = svgHeight * scaleY
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.width / 2 - calculatedWidth / 2).doubleValue,
|
||||
dy: (viewBounds.height / 2 - calculatedHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
case .scaleAspectFit:
|
||||
let calculatedXWidth = scaleX * svgWidth
|
||||
let calculatedXHeight = scaleX * svgHeight
|
||||
let calculatedYWidth = scaleY * svgWidth
|
||||
let calculatedYHeight = scaleY * svgHeight
|
||||
|
||||
if calculatedXWidth <= viewBounds.width && calculatedXHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedXWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedXHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleX.doubleValue,
|
||||
sy: scaleX.doubleValue
|
||||
)
|
||||
} else if calculatedYWidth <= viewBounds.width && calculatedYHeight <= viewBounds.height {
|
||||
svgNode.place = Transform.move(
|
||||
dx: (viewBounds.midX - calculatedYWidth / 2).doubleValue,
|
||||
dy: (viewBounds.midY - calculatedYHeight / 2).doubleValue
|
||||
).scale(
|
||||
sx: scaleY.doubleValue,
|
||||
sy: scaleY.doubleValue
|
||||
)
|
||||
}
|
||||
case .center:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .top:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottom:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getMidX(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .left:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .right:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getMidY(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .topLeft:
|
||||
break
|
||||
case .topRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: 0
|
||||
)
|
||||
case .bottomLeft:
|
||||
svgNode.place = Transform.move(
|
||||
dx: 0,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .bottomRight:
|
||||
svgNode.place = Transform.move(
|
||||
dx: getRight(viewBounds, nodeBounds).doubleValue,
|
||||
dy: getBottom(viewBounds, nodeBounds).doubleValue
|
||||
)
|
||||
case .redraw:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.node = svgNode
|
||||
}
|
||||
|
||||
private func getMidX(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidX = viewBounds.midX
|
||||
let nodeMidX = nodeBounds.midX + nodeBounds.origin.x
|
||||
return viewMidX - nodeMidX
|
||||
}
|
||||
|
||||
private func getMidY(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
let viewMidY = viewBounds.midY
|
||||
let nodeMidY = nodeBounds.midY + nodeBounds.origin.y
|
||||
return viewMidY - nodeMidY
|
||||
}
|
||||
|
||||
private func getBottom(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxY - nodeBounds.maxY + nodeBounds.origin.y
|
||||
}
|
||||
|
||||
private func getRight(_ viewBounds: CGRect, _ nodeBounds: CGRect) -> CGFloat {
|
||||
return viewBounds.maxX - nodeBounds.maxX + nodeBounds.origin.x
|
||||
}
|
||||
|
||||
}
|
9
Source/thirdparty/CGFloat+Double.swift
vendored
Normal file
9
Source/thirdparty/CGFloat+Double.swift
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
internal extension CGFloat {
|
||||
|
||||
var doubleValue: Double {
|
||||
return Double(self)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user