Skip to content

Commit 23ba756

Browse files
committed
backport SVGView to iOS 13 / macOS 10.15
1 parent 1474c46 commit 23ba756

File tree

3 files changed

+194
-19
lines changed

3 files changed

+194
-19
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// CanvasNSView.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 07/9/25.
6+
// Copyright 2025 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
#if canImport(AppKit)
33+
import AppKit
34+
import SwiftUI
35+
36+
@available(macOS, deprecated: 12.0, message: "use SwiftUI.Canvas")
37+
struct CanvasFallbackView: NSViewRepresentable {
38+
39+
var svg: SVG
40+
var capInsets: EdgeInsets
41+
var resizingMode: SVGView.ResizingMode
42+
43+
func makeNSView(context: Context) -> CanvasNSView {
44+
let nsView = CanvasNSView()
45+
nsView.wantsLayer = true
46+
nsView.layerContentsRedrawPolicy = .duringViewResize
47+
nsView.layer?.needsDisplayOnBoundsChange = true
48+
return nsView
49+
}
50+
51+
func updateNSView(_ nsView: CanvasNSView, context: Context) {
52+
nsView.svg = svg
53+
nsView.resizeMode = resizingMode
54+
nsView.capInsets = (capInsets.top, capInsets.leading, capInsets.bottom, capInsets.trailing)
55+
nsView.needsDisplay = true
56+
}
57+
}
58+
59+
final class CanvasNSView: NSView {
60+
61+
var svg: SVG?
62+
var resizeMode: SVGView.ResizingMode = .stretch
63+
var capInsets: (top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) = (0, 0, 0, 0)
64+
65+
override var isFlipped: Bool { true }
66+
67+
override func draw(_ dirtyRect: NSRect) {
68+
guard let svg,
69+
let ctx = NSGraphicsContext.current?.cgContext else { return }
70+
71+
ctx.draw(
72+
svg,
73+
in: bounds,
74+
capInsets: capInsets,
75+
byTiling: resizeMode == .tile
76+
)
77+
}
78+
}
79+
80+
#endif
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// CanvasUIView.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 07/9/25.
6+
// Copyright 2025 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
#if canImport(UIKit)
33+
import UIKit
34+
import SwiftUI
35+
36+
@available(iOS, deprecated: 15.0, message: "use SwiftUI.Canvas")
37+
struct CanvasFallbackView: UIViewRepresentable {
38+
39+
var svg: SVG
40+
var capInsets: EdgeInsets
41+
var resizingMode: SVGView.ResizingMode
42+
43+
func makeUIView(context: Context) -> CanvasUIView {
44+
let uiView = CanvasUIView()
45+
uiView.isOpaque = false
46+
uiView.contentMode = .redraw
47+
return uiView
48+
}
49+
50+
func updateUIView(_ uiView: CanvasUIView, context: Context) {
51+
uiView.svg = svg
52+
uiView.resizeMode = resizingMode
53+
uiView.capInsets = (capInsets.top, capInsets.leading, capInsets.bottom, capInsets.trailing)
54+
uiView.setNeedsDisplay()
55+
}
56+
}
57+
58+
final class CanvasUIView: UIView {
59+
60+
var svg: SVG?
61+
var resizeMode: SVGView.ResizingMode = .stretch
62+
var capInsets: (top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) = (0, 0, 0, 0)
63+
64+
override func draw(_ rect: CGRect) {
65+
guard let svg,
66+
let ctx = UIGraphicsGetCurrentContext() else { return }
67+
68+
ctx.draw(
69+
svg,
70+
in: rect,
71+
capInsets: capInsets,
72+
byTiling: resizeMode == .tile
73+
)
74+
}
75+
}
76+
77+
#endif

SwiftDraw/Sources/SVGView.swift

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
#if canImport(SwiftUI)
3333
public import SwiftUI
3434

35-
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
3635
public struct SVGView: View {
3736

3837
public init(_ name: String, bundle: Bundle = .main) {
@@ -44,12 +43,12 @@ public struct SVGView: View {
4443
}
4544

4645
private let svg: SVG?
47-
private var resizingMode: ResizingMode?
46+
private var resizable: (capInsets: EdgeInsets, mode: ResizingMode)?
4847

4948
public var body: some View {
5049
if let svg {
51-
if let resizingMode {
52-
SVGView.makeCanvas(svg: svg, resizingMode: resizingMode)
50+
if let resizable {
51+
SVGView.makeCanvas(svg: svg, capInsets: resizable.capInsets, resizingMode: resizable.mode)
5352
.frame(idealWidth: svg.size.width, idealHeight: svg.size.height)
5453
} else {
5554
SVGView.makeCanvas(svg: svg, resizingMode: .stretch)
@@ -70,26 +69,40 @@ public struct SVGView: View {
7069

7170
/// Sets the mode by which SwiftUI resizes an SVG to fit its space.
7271
/// - Parameters:
72+
/// - capInsets: Inset values that indicate a portion of the image that
73+
/// SwiftUI doesn't resize.
7374
/// - resizingMode: The mode by which SwiftUI resizes the image.
7475
/// - Returns: An SVGView, with the new resizing behavior set.
75-
public func resizable(resizingMode: ResizingMode = .stretch) -> Self {
76+
public func resizable(
77+
capInsets: EdgeInsets = EdgeInsets(),
78+
resizingMode: ResizingMode = .stretch
79+
) -> Self {
7680
var copy = self
77-
copy.resizingMode = resizingMode
81+
copy.resizable = (capInsets, resizingMode)
7882
return copy
7983
}
8084

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))
85+
@ViewBuilder
86+
private static func makeCanvas(svg: SVG, capInsets: EdgeInsets = .init(), resizingMode: ResizingMode) -> some View {
87+
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
88+
Canvas(
89+
opaque: false,
90+
colorMode: .linear,
91+
rendersAsynchronously: false
92+
) { ctx, size in
93+
ctx.draw(
94+
svg,
95+
in: CGRect(origin: .zero, size: size),
96+
capInsets: capInsets,
97+
byTiling: resizingMode == .tile
98+
)
9299
}
100+
} else {
101+
CanvasFallbackView(
102+
svg: svg,
103+
capInsets: capInsets,
104+
resizingMode: resizingMode
105+
)
93106
}
94107
}
95108
}
@@ -103,9 +116,14 @@ public extension GraphicsContext {
103116
}
104117
}
105118

106-
func draw(_ svg: SVG, in rect: CGRect, byTiling: Bool) {
119+
func draw(_ svg: SVG, in rect: CGRect, capInsets: EdgeInsets, byTiling: Bool = false) {
107120
withCGContext {
108-
$0.draw(svg, in: rect, byTiling: byTiling)
121+
$0.draw(
122+
svg,
123+
in: rect,
124+
capInsets: (capInsets.top, capInsets.leading, capInsets.bottom, capInsets.trailing),
125+
byTiling: byTiling
126+
)
109127
}
110128
}
111129
}

0 commit comments

Comments
 (0)