Skip to content

Commit 47d6f90

Browse files
committed
resizable()
1 parent af422b9 commit 47d6f90

File tree

4 files changed

+117
-22
lines changed

4 files changed

+117
-22
lines changed

Examples/Sources/GalleryView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ struct GalleryView: View {
6161
LazyVStack(spacing: 20) {
6262
ForEach(images, id: \.self) { image in
6363
SVGView(image, bundle: .samples)
64-
.aspectRatio(contentMode: .fit)
64+
.resizable()
65+
.scaledToFit()
6566
.padding([.leading, .trailing], 10)
6667
}
6768
}

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,22 @@ imageView.image = svg.rasterize() // 240x200
3838

3939
### SwiftUI
4040

41-
Display an image within `SVGView`:
41+
SVGs can be displayed within `SVGView` just like using SwiftUI's built-in `Image`:
4242

4343
```swift
44-
var body: some View {
45-
SVGView("sample.svg")
46-
.aspectRatio(contentMode: .fit)
47-
.padding()
48-
}
44+
SVGView("sample.svg")
4945
```
5046

47+
By default, SVGs are rendered at their original (intrinsic) size. To make them flexible within layouts, mark them as resizable — exactly like `Image`:
48+
49+
```swift
50+
SVGView("sample.svg")
51+
.resizable()
52+
.scaledToFit()
53+
```
54+
55+
This allows the SVG to scale proportionally to fit within its container. Use `.scaledToFill()` to completely cover the container and use `.resizable(resizingMode: .tile)` to draw the SVG in repeating tiles filling the available space.
56+
5157
When you load by name, SVGView uses an internal cache so repeated lookups are efficient.
5258
For more predictable performance (avoiding any cache lookup or parsing), you can pass an already-created SVG instance:
5359

SwiftDraw/Sources/SVG+CoreGraphics.swift

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,44 @@ public import Foundation
3535

3636
public extension CGContext {
3737

38-
func draw(_ image: SVG, in rect: CGRect? = nil) {
39-
let defaultRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
38+
func draw(_ svg: SVG, in rect: CGRect? = nil) {
39+
let defaultRect = CGRect(x: 0, y: 0, width: svg.size.width, height: svg.size.height)
4040
let renderer = CGRenderer(context: self)
4141
saveGState()
4242

4343
if let rect = rect, rect != defaultRect {
4444
translateBy(x: rect.origin.x, y: rect.origin.y)
4545
scaleBy(
46-
x: rect.width / image.size.width,
47-
y: rect.height / image.size.height
46+
x: rect.width / svg.size.width,
47+
y: rect.height / svg.size.height
4848
)
4949
}
50-
renderer.perform(image.commands)
50+
renderer.perform(svg.commands)
5151

5252
restoreGState()
5353
}
54+
55+
func draw(_ svg: SVG, in rect: CGRect, byTiling: Bool) {
56+
guard byTiling else {
57+
draw(svg, in: rect)
58+
return
59+
}
60+
61+
let cols = Int(ceil(rect.size.width / svg.size.width))
62+
let rows = Int(ceil(rect.size.height / svg.size.height))
63+
64+
for r in 0..<rows {
65+
for c in 0..<cols {
66+
let tile = CGRect(
67+
x: rect.minX + CGFloat(r) * svg.size.width,
68+
y: rect.minY + CGFloat(c) * svg.size.height,
69+
width: svg.size.width,
70+
height: svg.size.height
71+
)
72+
draw(svg, in: tile)
73+
}
74+
}
75+
}
5476
}
5577

5678
public extension SVG {

SwiftDraw/Sources/SVGView.swift

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,94 @@ public struct SVGView: View {
4444
}
4545

4646
private let svg: SVG?
47+
private var resizingMode: ResizingMode?
4748

4849
public var body: some View {
4950
if let svg {
50-
Canvas(
51-
opaque: false,
52-
colorMode: .linear,
53-
rendersAsynchronously: false
54-
) { ctx, size in
55-
ctx.draw(svg, in: CGRect(origin: .zero, size: size))
56-
}
57-
.frame(idealWidth: svg.size.width, idealHeight: svg.size.height)
51+
if let resizingMode {
52+
SVGView.makeCanvas(svg: svg, resizingMode: resizingMode)
53+
.frame(idealWidth: svg.size.width, idealHeight: svg.size.height)
54+
} else {
55+
SVGView.makeCanvas(svg: svg, resizingMode: .stretch)
56+
.frame(width: svg.size.width, height: svg.size.height)
57+
}
58+
}
59+
}
60+
61+
public enum ResizingMode: Sendable, Hashable {
62+
/// A mode to repeat the image at its original size, as many
63+
/// times as necessary to fill the available space.
64+
case tile
65+
66+
/// A mode to enlarge or reduce the size of an image so that it
67+
/// fills the available space.
68+
case stretch
69+
}
70+
71+
/// Sets the mode by which SwiftUI resizes an SVG to fit its space.
72+
/// - Parameters:
73+
/// - resizingMode: The mode by which SwiftUI resizes the image.
74+
/// - Returns: An SVGView, with the new resizing behavior set.
75+
public func resizable(resizingMode: ResizingMode = .stretch) -> Self {
76+
var copy = self
77+
copy.resizingMode = resizingMode
78+
return copy
79+
}
80+
81+
private static func makeCanvas(svg: SVG, resizingMode: ResizingMode) -> some View {
82+
Canvas(
83+
opaque: false,
84+
colorMode: .linear,
85+
rendersAsynchronously: false
86+
) { ctx, size in
87+
switch resizingMode {
88+
case .tile:
89+
ctx.draw(svg, in: CGRect(origin: .zero, size: size), byTiling: true)
90+
case .stretch:
91+
ctx.draw(svg, in: CGRect(origin: .zero, size: size))
92+
}
5893
}
5994
}
6095
}
6196

6297
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
6398
public extension GraphicsContext {
6499

65-
func draw(_ image: SVG, in rect: CGRect? = nil) {
100+
func draw(_ svg: SVG, in rect: CGRect? = nil) {
101+
withCGContext {
102+
$0.draw(svg, in: rect)
103+
}
104+
}
105+
106+
func draw(_ svg: SVG, in rect: CGRect, byTiling: Bool) {
66107
withCGContext {
67-
$0.draw(image, in: rect)
108+
$0.draw(svg, in: rect, byTiling: byTiling)
68109
}
69110
}
70111
}
112+
113+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
114+
#Preview {
115+
SVGView(svg: .circle)
116+
117+
SVGView(svg: .circle)
118+
.resizable(resizingMode: .stretch)
119+
120+
SVGView(svg: .circle)
121+
.resizable(resizingMode: .tile)
122+
}
123+
124+
#if DEBUG
125+
private extension SVG {
126+
127+
static var circle: SVG {
128+
SVG(xml: """
129+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
130+
<circle cx="50" cy="50" r="50" fill="orange" />
131+
</svg>
132+
""")!
133+
}
134+
}
135+
#endif
136+
71137
#endif

0 commit comments

Comments
 (0)