Skip to content

Commit fedf8e6

Browse files
author
Itai Ferber
committed
Add Codable conformance to common CG types
Give custom Codable implementations for CGAffineTransform, CGPoint, CGSize, CGRect, and CGVector, along with unit tests.
1 parent 1ca142e commit fedf8e6

File tree

2 files changed

+250
-2
lines changed

2 files changed

+250
-2
lines changed

stdlib/public/SDK/CoreGraphics/CoreGraphics.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,21 @@ extension CGPoint : Equatable {
250250
}
251251
}
252252

253+
extension CGPoint : Codable {
254+
public init(from decoder: Decoder) throws {
255+
var container = try decoder.unkeyedContainer()
256+
let x = try container.decode(CGFloat.self)
257+
let y = try container.decode(CGFloat.self)
258+
self.init(x: x, y: y)
259+
}
260+
261+
public func encode(to encoder: Encoder) throws {
262+
var container = encoder.unkeyedContainer()
263+
try container.encode(x)
264+
try container.encode(y)
265+
}
266+
}
267+
253268
public extension CGSize {
254269
static var zero: CGSize {
255270
@_transparent // @fragile
@@ -302,6 +317,21 @@ extension CGSize : Equatable {
302317
}
303318
}
304319

320+
extension CGSize : Codable {
321+
public init(from decoder: Decoder) throws {
322+
var container = try decoder.unkeyedContainer()
323+
let width = try container.decode(CGFloat.self)
324+
let height = try container.decode(CGFloat.self)
325+
self.init(width: width, height: height)
326+
}
327+
328+
public func encode(to encoder: Encoder) throws {
329+
var container = encoder.unkeyedContainer()
330+
try container.encode(width)
331+
try container.encode(height)
332+
}
333+
}
334+
305335
public extension CGVector {
306336
static var zero: CGVector {
307337
@_transparent // @fragile
@@ -332,6 +362,21 @@ extension CGVector : CustomDebugStringConvertible {
332362
}
333363
}
334364

365+
extension CGVector : Codable {
366+
public init(from decoder: Decoder) throws {
367+
var container = try decoder.unkeyedContainer()
368+
let dx = try container.decode(CGFloat.self)
369+
let dy = try container.decode(CGFloat.self)
370+
self.init(dx: dx, dy: dy)
371+
}
372+
373+
public func encode(to encoder: Encoder) throws {
374+
var container = encoder.unkeyedContainer()
375+
try container.encode(dx)
376+
try container.encode(dy)
377+
}
378+
}
379+
335380
public extension CGRect {
336381
static var zero: CGRect {
337382
@_transparent // @fragile
@@ -411,13 +456,51 @@ extension CGRect : Equatable {
411456
}
412457
}
413458

459+
extension CGRect : Codable {
460+
public init(from decoder: Decoder) throws {
461+
var container = try decoder.unkeyedContainer()
462+
let origin = try container.decode(CGPoint.self)
463+
let size = try container.decode(CGSize.self)
464+
self.init(origin: origin, size: size)
465+
}
466+
467+
public func encode(to encoder: Encoder) throws {
468+
var container = encoder.unkeyedContainer()
469+
try container.encode(origin)
470+
try container.encode(size)
471+
}
472+
}
473+
414474
extension CGAffineTransform {
415475
public static var identity: CGAffineTransform {
416476
@_transparent // @fragile
417477
get { return CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0) }
418478
}
419479
}
420480

481+
extension CGAffineTransform : Codable {
482+
public init(from decoder: Decoder) throws {
483+
var container = try decoder.unkeyedContainer()
484+
let a = try container.decode(CGFloat.self)
485+
let b = try container.decode(CGFloat.self)
486+
let c = try container.decode(CGFloat.self)
487+
let d = try container.decode(CGFloat.self)
488+
let tx = try container.decode(CGFloat.self)
489+
let ty = try container.decode(CGFloat.self)
490+
self.init(a: a, b: b, c: c, d: d, tx: tx, ty: ty)
491+
}
492+
493+
public func encode(to encoder: Encoder) throws {
494+
var container = encoder.unkeyedContainer()
495+
try container.encode(a)
496+
try container.encode(b)
497+
try container.encode(c)
498+
try container.encode(d)
499+
try container.encode(tx)
500+
try container.encode(ty)
501+
}
502+
}
503+
421504
//===----------------------------------------------------------------------===//
422505
// CGImage
423506
//===----------------------------------------------------------------------===//

test/stdlib/CodableTests.swift

Lines changed: 167 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// REQUIRES: objc_interop
1212

1313
import Foundation
14+
import CoreGraphics
1415

1516
#if FOUNDATION_XCTEST
1617
import XCTest
@@ -57,12 +58,21 @@ func expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Dat
5758
}
5859

5960
func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T) where T : Equatable {
61+
let inf = "INF", negInf = "-INF", nan = "NaN"
6062
let encode = { (_ value: T) throws -> Data in
61-
return try JSONEncoder().encode(value)
63+
let encoder = JSONEncoder()
64+
encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf,
65+
negativeInfinity: negInf,
66+
nan: nan)
67+
return try encoder.encode(value)
6268
}
6369

6470
let decode = { (_ data: Data) throws -> T in
65-
return try JSONDecoder().decode(T.self, from: data)
71+
let decoder = JSONDecoder()
72+
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: inf,
73+
negativeInfinity: negInf,
74+
nan: nan)
75+
return try decoder.decode(T.self, from: data)
6676
}
6777

6878
expectRoundTripEquality(of: value, encode: encode, decode: decode)
@@ -186,6 +196,151 @@ class TestCodable : TestCodableSuper {
186196
}
187197
}
188198

199+
// MARK: - CGAffineTransform
200+
lazy var cg_affineTransformValues: [CGAffineTransform] = {
201+
var values = [
202+
CGAffineTransform.identity,
203+
CGAffineTransform(),
204+
CGAffineTransform(translationX: 2.0, y: 2.0),
205+
CGAffineTransform(scaleX: 2.0, y: 2.0),
206+
CGAffineTransform(a: 1.0, b: 2.5, c: 66.2, d: 40.2, tx: -5.5, ty: 3.7),
207+
CGAffineTransform(a: -55.66, b: 22.7, c: 1.5, d: 0.0, tx: -22, ty: -33),
208+
CGAffineTransform(a: 4.5, b: 1.1, c: 0.025, d: 0.077, tx: -0.55, ty: 33.2),
209+
CGAffineTransform(a: 7.0, b: -2.3, c: 6.7, d: 0.25, tx: 0.556, ty: 0.99),
210+
CGAffineTransform(a: 0.498, b: -0.284, c: -0.742, d: 0.3248, tx: 12, ty: 44)
211+
]
212+
213+
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
214+
values.append(CGAffineTransform(rotationAngle: .pi / 2))
215+
}
216+
217+
return values
218+
}()
219+
220+
func test_CGAffineTransform_JSON() {
221+
for transform in cg_affineTransformValues {
222+
expectRoundTripEqualityThroughJSON(for: transform)
223+
}
224+
}
225+
226+
func test_CGAffineTransform_Plist() {
227+
for transform in cg_affineTransformValues {
228+
expectRoundTripEqualityThroughPlist(for: transform)
229+
}
230+
}
231+
232+
// MARK: - CGPoint
233+
lazy var cg_pointValues: [CGPoint] = {
234+
var values = [
235+
CGPoint.zero,
236+
CGPoint(x: 10, y: 20)
237+
]
238+
239+
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
240+
// Limit on magnitude in JSON. See rdar://problem/12717407
241+
values.append(CGPoint(x: CGFloat.greatestFiniteMagnitude,
242+
y: CGFloat.greatestFiniteMagnitude))
243+
}
244+
245+
return values
246+
}()
247+
248+
func test_CGPoint_JSON() {
249+
for point in cg_pointValues {
250+
expectRoundTripEqualityThroughJSON(for: point)
251+
}
252+
}
253+
254+
func test_CGPoint_Plist() {
255+
for point in cg_pointValues {
256+
expectRoundTripEqualityThroughPlist(for: point)
257+
}
258+
}
259+
260+
// MARK: - CGSize
261+
lazy var cg_sizeValues: [CGSize] = {
262+
var values = [
263+
CGSize.zero,
264+
CGSize(width: 30, height: 40)
265+
]
266+
267+
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
268+
// Limit on magnitude in JSON. See rdar://problem/12717407
269+
values.append(CGSize(width: CGFloat.greatestFiniteMagnitude,
270+
height: CGFloat.greatestFiniteMagnitude))
271+
}
272+
273+
return values
274+
}()
275+
276+
func test_CGSize_JSON() {
277+
for size in cg_sizeValues {
278+
expectRoundTripEqualityThroughJSON(for: size)
279+
}
280+
}
281+
282+
func test_CGSize_Plist() {
283+
for size in cg_sizeValues {
284+
expectRoundTripEqualityThroughPlist(for: size)
285+
}
286+
}
287+
288+
// MARK: - CGRect
289+
lazy var cg_rectValues: [CGRect] = {
290+
var values = [
291+
CGRect.zero,
292+
CGRect.null,
293+
CGRect(x: 10, y: 20, width: 30, height: 40)
294+
]
295+
296+
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
297+
// Limit on magnitude in JSON. See rdar://problem/12717407
298+
values.append(CGRect.infinite)
299+
}
300+
301+
return values
302+
}()
303+
304+
func test_CGRect_JSON() {
305+
for rect in cg_rectValues {
306+
expectRoundTripEqualityThroughJSON(for: rect)
307+
}
308+
}
309+
310+
func test_CGRect_Plist() {
311+
for rect in cg_rectValues {
312+
expectRoundTripEqualityThroughPlist(for: rect)
313+
}
314+
}
315+
316+
// MARK: - CGVector
317+
lazy var cg_vectorValues: [CGVector] = {
318+
var values = [
319+
CGVector.zero,
320+
CGVector(dx: 0.0, dy: -9.81)
321+
]
322+
323+
if #available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
324+
// Limit on magnitude in JSON. See rdar://problem/12717407
325+
values.append(CGVector(dx: CGFloat.greatestFiniteMagnitude,
326+
dy: CGFloat.greatestFiniteMagnitude))
327+
}
328+
329+
return values
330+
}()
331+
332+
func test_CGVector_JSON() {
333+
for vector in cg_vectorValues {
334+
expectRoundTripEqualityThroughJSON(for: vector)
335+
}
336+
}
337+
338+
func test_CGVector_Plist() {
339+
for vector in cg_vectorValues {
340+
expectRoundTripEqualityThroughPlist(for: vector)
341+
}
342+
}
343+
189344
// MARK: - DateComponents
190345
lazy var dateComponents: Set<Calendar.Component> = [
191346
.era, .year, .month, .day, .hour, .minute, .second, .nanosecond,
@@ -458,6 +613,16 @@ CodableTests.test("test_Calendar_JSON") { TestCodable().test_Calendar_JSON() }
458613
CodableTests.test("test_Calendar_Plist") { TestCodable().test_Calendar_Plist() }
459614
CodableTests.test("test_CharacterSet_JSON") { TestCodable().test_CharacterSet_JSON() }
460615
CodableTests.test("test_CharacterSet_Plist") { TestCodable().test_CharacterSet_Plist() }
616+
CodableTests.test("test_CGAffineTransform_JSON") { TestCodable().test_CGAffineTransform_JSON() }
617+
CodableTests.test("test_CGAffineTransform_Plist") { TestCodable().test_CGAffineTransform_Plist() }
618+
CodableTests.test("test_CGPoint_JSON") { TestCodable().test_CGPoint_JSON() }
619+
CodableTests.test("test_CGPoint_Plist") { TestCodable().test_CGPoint_Plist() }
620+
CodableTests.test("test_CGSize_JSON") { TestCodable().test_CGSize_JSON() }
621+
CodableTests.test("test_CGSize_Plist") { TestCodable().test_CGSize_Plist() }
622+
CodableTests.test("test_CGRect_JSON") { TestCodable().test_CGRect_JSON() }
623+
CodableTests.test("test_CGRect_Plist") { TestCodable().test_CGRect_Plist() }
624+
CodableTests.test("test_CGVector_JSON") { TestCodable().test_CGVector_JSON() }
625+
CodableTests.test("test_CGVector_Plist") { TestCodable().test_CGVector_Plist() }
461626
CodableTests.test("test_DateComponents_JSON") { TestCodable().test_DateComponents_JSON() }
462627
CodableTests.test("test_DateComponents_Plist") { TestCodable().test_DateComponents_Plist() }
463628

0 commit comments

Comments
 (0)