Skip to content

Commit 5eae34e

Browse files
committed
Cherry-picks pixel compare from upstream PR pointfreeco#571
1 parent 567c66a commit 5eae34e

14 files changed

+135
-59
lines changed

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ assertSnapshot(matching: vc, as: .image(on: .iPadMini(.portrait)))
7575
assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPadMini(.portrait)))
7676
```
7777

78-
> ⚠️ Warning: Snapshots must be compared using a simulator with the same OS, device gamut, and scale as the simulator that originally took the reference to avoid discrepancies between images.
78+
> ⚠️ Warning: Snapshots may differ slightly unless compared on the same OS,
79+
> device gamut, and scale as the simulator that originally took the reference.
80+
> If this cannot be avoided, acceptance in differences can be configured by
81+
> setting the `subpixelThreshold`-parameter.
82+
>
83+
> Example:
84+
> ```swift
85+
> // Allow each subpixel to deviate up to 5 byte-values
86+
> assertSnapshot(matching: vc, as: .image(on: .iPhoneX, subpixelThreshold: 5))
87+
> ```
88+
>
7989
8090
Better yet, SnapshotTesting isn't limited to views and view controllers! There are [a number of available snapshot strategies](Documentation/Available-Snapshot-Strategies.md) to choose from.
8191

SnapshotTesting.xcodeproj/project.pbxproj

+32-8
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@
195195
F3883B573DF4CAFADE5968A9 /* testUpdateSeveralSnapshotsWithLessLines.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BC721A155DB7D81BE69683 /* testUpdateSeveralSnapshotsWithLessLines.1.swift */; };
196196
F473E43FAB7DD0C5F4D02437 /* testUpdateSeveralSnapshotsWithMoreLines.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC001D7DB94ECB4F5A2CF9 /* testUpdateSeveralSnapshotsWithMoreLines.1.swift */; };
197197
F4CB3EC3E5D30B217B4D9699 /* Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10213E0E8B550596F75463C3 /* Wait.swift */; };
198+
F5B874F327B3FA5F005D3517 /* UInt8+diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B874F227B3FA5F005D3517 /* UInt8+diff.swift */; };
199+
F5B874F427B3FA5F005D3517 /* UInt8+diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B874F227B3FA5F005D3517 /* UInt8+diff.swift */; };
200+
F5B874F527B3FA5F005D3517 /* UInt8+diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B874F227B3FA5F005D3517 /* UInt8+diff.swift */; };
198201
F66FB66FCA7B884DA64ADDBE /* Diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EEE36D7D9E53E70DABA3996 /* Diff.swift */; };
199202
FBDFF661DB08CFB75DFB12C3 /* SnapshotTesting.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F9883B60A8B403A39BCF888 /* SnapshotTesting.framework */; };
200203
FE365D5C0F83CE8459CF77DC /* testUpdateSnapshotWithMoreLines.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0BC8BF7E37686D31F59B9F6 /* testUpdateSnapshotWithMoreLines.1.swift */; };
@@ -333,6 +336,7 @@
333336
EE91B5904B3F67A3A01610C8 /* testCreateSnapshotWithShorterExtendedDelimiter1.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testCreateSnapshotWithShorterExtendedDelimiter1.1.swift; sourceTree = "<group>"; };
334337
F479E2BD835B9641B85EB51E /* Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internal.swift; sourceTree = "<group>"; };
335338
F4D7D0D81B7E35C6284A3E65 /* testUpdateSnapshotWithExtendedDelimiter1.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testUpdateSnapshotWithExtendedDelimiter1.1.swift; sourceTree = "<group>"; };
339+
F5B874F227B3FA5F005D3517 /* UInt8+diff.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UInt8+diff.swift"; sourceTree = "<group>"; };
336340
F6D3BC50BC4692DC6D9FAE0F /* InlineSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineSnapshotTests.swift; sourceTree = "<group>"; };
337341
F8F831EAAFB97204ECD0B879 /* Any.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Any.swift; sourceTree = "<group>"; };
338342
FD800FD3282956340AD7706A /* AssertInlineSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertInlineSnapshot.swift; sourceTree = "<group>"; };
@@ -451,6 +455,7 @@
451455
5768A55D77F19D897206FF9F /* Extensions */ = {
452456
isa = PBXGroup;
453457
children = (
458+
F5B874F227B3FA5F005D3517 /* UInt8+diff.swift */,
454459
10213E0E8B550596F75463C3 /* Wait.swift */,
455460
);
456461
path = Extensions;
@@ -646,8 +651,6 @@
646651
isa = PBXProject;
647652
attributes = {
648653
LastUpgradeCheck = 1020;
649-
TargetAttributes = {
650-
};
651654
};
652655
buildConfigurationList = D9A4BF45876C849A308801A2 /* Build configuration list for PBXProject "SnapshotTesting" */;
653656
compatibilityVersion = "Xcode 10.0";
@@ -703,6 +706,7 @@
703706
buildActionMask = 2147483647;
704707
files = (
705708
87D3745DBD172E3E6427190F /* Any.swift in Sources */,
709+
F5B874F427B3FA5F005D3517 /* UInt8+diff.swift in Sources */,
706710
6CAB0714112F5AAE5D0FBD23 /* AssertInlineSnapshot.swift in Sources */,
707711
52B7C3800F08E80D7BA8600D /* AssertSnapshot.swift in Sources */,
708712
1420B1BBC10CFF4D03C8AA67 /* Async.swift in Sources */,
@@ -743,6 +747,7 @@
743747
buildActionMask = 2147483647;
744748
files = (
745749
E2E1BA4E82EBF5D7E7533485 /* Any.swift in Sources */,
750+
F5B874F327B3FA5F005D3517 /* UInt8+diff.swift in Sources */,
746751
90555C6EB17BD465E901043A /* AssertInlineSnapshot.swift in Sources */,
747752
AE77649A377D5328DC91D19B /* AssertSnapshot.swift in Sources */,
748753
29602B6DD2A43E1C13DF1D42 /* Async.swift in Sources */,
@@ -855,6 +860,7 @@
855860
buildActionMask = 2147483647;
856861
files = (
857862
C48A4BD0534BF1817AF91098 /* Any.swift in Sources */,
863+
F5B874F527B3FA5F005D3517 /* UInt8+diff.swift in Sources */,
858864
56551E9E2E7A3DCF41C966F1 /* AssertInlineSnapshot.swift in Sources */,
859865
6C48EAD7C6D1441EE12F907B /* AssertSnapshot.swift in Sources */,
860866
45FAA85BB7198113155FD27F /* Async.swift in Sources */,
@@ -1017,7 +1023,10 @@
10171023
DYLIB_INSTALL_NAME_BASE = "@rpath";
10181024
ENABLE_BITCODE = NO;
10191025
ENABLE_TESTING_SEARCH_PATHS = YES;
1020-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1026+
FRAMEWORK_SEARCH_PATHS = (
1027+
"$(inherited)",
1028+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1029+
);
10211030
INFOPLIST_FILE = Sources/Info.plist;
10221031
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
10231032
LD_RUNPATH_SEARCH_PATHS = (
@@ -1065,7 +1074,10 @@
10651074
DYLIB_INSTALL_NAME_BASE = "@rpath";
10661075
ENABLE_BITCODE = NO;
10671076
ENABLE_TESTING_SEARCH_PATHS = YES;
1068-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1077+
FRAMEWORK_SEARCH_PATHS = (
1078+
"$(inherited)",
1079+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1080+
);
10691081
INFOPLIST_FILE = Sources/Info.plist;
10701082
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
10711083
LD_RUNPATH_SEARCH_PATHS = (
@@ -1146,7 +1158,10 @@
11461158
DYLIB_INSTALL_NAME_BASE = "@rpath";
11471159
ENABLE_BITCODE = NO;
11481160
ENABLE_TESTING_SEARCH_PATHS = YES;
1149-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1161+
FRAMEWORK_SEARCH_PATHS = (
1162+
"$(inherited)",
1163+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1164+
);
11501165
INFOPLIST_FILE = Sources/Info.plist;
11511166
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
11521167
LD_RUNPATH_SEARCH_PATHS = (
@@ -1175,7 +1190,10 @@
11751190
DYLIB_INSTALL_NAME_BASE = "@rpath";
11761191
ENABLE_BITCODE = NO;
11771192
ENABLE_TESTING_SEARCH_PATHS = YES;
1178-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1193+
FRAMEWORK_SEARCH_PATHS = (
1194+
"$(inherited)",
1195+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1196+
);
11791197
INFOPLIST_FILE = Sources/Info.plist;
11801198
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
11811199
LD_RUNPATH_SEARCH_PATHS = (
@@ -1204,7 +1222,10 @@
12041222
DYLIB_INSTALL_NAME_BASE = "@rpath";
12051223
ENABLE_BITCODE = NO;
12061224
ENABLE_TESTING_SEARCH_PATHS = YES;
1207-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1225+
FRAMEWORK_SEARCH_PATHS = (
1226+
"$(inherited)",
1227+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1228+
);
12081229
INFOPLIST_FILE = Sources/Info.plist;
12091230
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
12101231
LD_RUNPATH_SEARCH_PATHS = (
@@ -1313,7 +1334,10 @@
13131334
DYLIB_INSTALL_NAME_BASE = "@rpath";
13141335
ENABLE_BITCODE = NO;
13151336
ENABLE_TESTING_SEARCH_PATHS = YES;
1316-
FRAMEWORK_SEARCH_PATHS = "$(inherited) $(PLATFORM_DIR)/Developer/Library/Frameworks";
1337+
FRAMEWORK_SEARCH_PATHS = (
1338+
"$(inherited)",
1339+
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
1340+
);
13171341
INFOPLIST_FILE = Sources/Info.plist;
13181342
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
13191343
LD_RUNPATH_SEARCH_PATHS = (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Foundation
2+
3+
extension UInt8 {
4+
func diff(between other: UInt8) -> UInt8 {
5+
if other > self {
6+
return other - self
7+
} else {
8+
return self - other
9+
}
10+
}
11+
}

Sources/SnapshotTesting/Snapshotting/CALayer.swift

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import Cocoa
44
extension Snapshotting where Value == CALayer, Format == NSImage {
55
/// A snapshot strategy for comparing layers based on pixel equality.
66
public static var image: Snapshotting {
7-
return .image(precision: 1)
7+
return .image(precision: 1, subpixelThreshold: 0)
88
}
99

1010
/// A snapshot strategy for comparing layers based on pixel equality.
1111
///
1212
/// - Parameter precision: The percentage of pixels that must match.
13-
public static func image(precision: Float) -> Snapshotting {
14-
return SimplySnapshotting.image(precision: precision).pullback { layer in
13+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
14+
public static func image(precision: Float, subpixelThreshold: UInt8) -> Snapshotting {
15+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold).pullback { layer in
1516
let image = NSImage(size: layer.bounds.size)
1617
image.lockFocus()
1718
let context = NSGraphicsContext.current!.cgContext
@@ -35,9 +36,10 @@ extension Snapshotting where Value == CALayer, Format == UIImage {
3536
/// A snapshot strategy for comparing layers based on pixel equality.
3637
///
3738
/// - Parameter precision: The percentage of pixels that must match.
38-
public static func image(precision: Float = 1, traits: UITraitCollection = .init())
39+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
40+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0, traits: UITraitCollection = .init())
3941
-> Snapshotting {
40-
return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).pullback { layer in
42+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold, scale: traits.displayScale).pullback { layer in
4143
renderer(bounds: layer.bounds, for: traits).image { ctx in
4244
layer.setNeedsLayout()
4345
layer.layoutIfNeeded()

Sources/SnapshotTesting/Snapshotting/CGPath.swift

+7-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ extension Snapshotting where Value == CGPath, Format == NSImage {
1010
/// A snapshot strategy for comparing bezier paths based on pixel equality.
1111
///
1212
/// - Parameter precision: The percentage of pixels that must match.
13-
public static func image(precision: Float = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
14-
return SimplySnapshotting.image(precision: precision).pullback { path in
13+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
14+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
15+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold).pullback { path in
1516
let bounds = path.boundingBoxOfPath
1617
var transform = CGAffineTransform(translationX: -bounds.origin.x, y: -bounds.origin.y)
1718
let path = path.copy(using: &transform)!
@@ -39,8 +40,10 @@ extension Snapshotting where Value == CGPath, Format == UIImage {
3940
/// A snapshot strategy for comparing bezier paths based on pixel equality.
4041
///
4142
/// - Parameter precision: The percentage of pixels that must match.
42-
public static func image(precision: Float = 1, scale: CGFloat = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
43-
return SimplySnapshotting.image(precision: precision, scale: scale).pullback { path in
43+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
44+
45+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0, scale: CGFloat = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
46+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold, scale: scale).pullback { path in
4447
let bounds = path.boundingBoxOfPath
4548
let format: UIGraphicsImageRendererFormat
4649
if #available(iOS 11.0, tvOS 11.0, *) {

Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ extension Snapshotting where Value == NSBezierPath, Format == NSImage {
1010
/// A snapshot strategy for comparing bezier paths based on pixel equality.
1111
///
1212
/// - Parameter precision: The percentage of pixels that must match.
13-
public static func image(precision: Float = 1) -> Snapshotting {
14-
return SimplySnapshotting.image(precision: precision).pullback { path in
13+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
14+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0) -> Snapshotting {
15+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold).pullback { path in
1516
// Move path info frame:
1617
let bounds = path.bounds
1718
let transform = AffineTransform(translationByX: -bounds.origin.x, byY: -bounds.origin.y)
@@ -90,4 +91,3 @@ private let defaultNumberFormatter: NumberFormatter = {
9091
return numberFormatter
9192
}()
9293
#endif
93-

Sources/SnapshotTesting/Snapshotting/NSImage.swift

+19-12
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import XCTest
44

55
extension Diffing where Value == NSImage {
66
/// A pixel-diffing strategy for NSImage's which requires a 100% match.
7-
public static let image = Diffing.image(precision: 1)
7+
public static let image: Diffing = Diffing.image(precision: 1, subpixelThreshold: 0)
88

99
/// A pixel-diffing strategy for NSImage that allows customizing how precise the matching must be.
1010
///
1111
/// - Parameter precision: A value between 0 and 1, where 1 means the images must match 100% of their pixels.
12+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
1213
/// - Returns: A new diffing strategy.
13-
public static func image(precision: Float) -> Diffing {
14+
public static func image(precision: Float, subpixelThreshold: UInt8) -> Diffing {
1415
return .init(
1516
toData: { NSImagePNGRepresentation($0)! },
1617
fromData: { NSImage(data: $0)! }
1718
) { old, new in
18-
guard !compare(old, new, precision: precision) else { return nil }
19+
guard !compare(old, new, precision: precision, subpixelThreshold: subpixelThreshold) else { return nil }
1920
let difference = SnapshotTesting.diff(old, new)
2021
let message = new.size == old.size
2122
? "Newly-taken snapshot does not match reference."
@@ -31,16 +32,17 @@ extension Diffing where Value == NSImage {
3132
extension Snapshotting where Value == NSImage, Format == NSImage {
3233
/// A snapshot strategy for comparing images based on pixel equality.
3334
public static var image: Snapshotting {
34-
return .image(precision: 1)
35+
return .image(precision: 1, subpixelThreshold: 0)
3536
}
3637

3738
/// A snapshot strategy for comparing images based on pixel equality.
3839
///
3940
/// - Parameter precision: The percentage of pixels that must match.
40-
public static func image(precision: Float) -> Snapshotting {
41+
/// - Parameter subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
42+
public static func image(precision: Float, subpixelThreshold: UInt8) -> Snapshotting {
4143
return .init(
4244
pathExtension: "png",
43-
diffing: .image(precision: precision)
45+
diffing: .image(precision: precision, subpixelThreshold: subpixelThreshold)
4446
)
4547
}
4648
}
@@ -52,7 +54,7 @@ private func NSImagePNGRepresentation(_ image: NSImage) -> Data? {
5254
return rep.representation(using: .png, properties: [:])
5355
}
5456

55-
private func compare(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool {
57+
private func compare(_ old: NSImage, _ new: NSImage, precision: Float, subpixelThreshold: UInt8) -> Bool {
5658
guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
5759
guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
5860
guard oldCgImage.width != 0 else { return false }
@@ -77,14 +79,19 @@ private func compare(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool {
7779
let newRep = NSBitmapImageRep(cgImage: newerCgImage)
7880
var differentPixelCount = 0
7981
let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh
80-
let threshold = (1 - precision) * Float(pixelCount)
82+
let threshold = Int((1 - precision) * Float(pixelCount))
8183
let p1: UnsafeMutablePointer<UInt8> = oldRep.bitmapData!
8284
let p2: UnsafeMutablePointer<UInt8> = newRep.bitmapData!
83-
for offset in 0 ..< pixelCount * 4 {
84-
if p1[offset] != p2[offset] {
85-
differentPixelCount += 1
85+
86+
var offset = 0
87+
while offset < pixelCount * 4 {
88+
if p1[offset].diff(between: p2[offset]) > subpixelThreshold {
89+
differentPixelCount += 1
90+
if differentPixelCount > threshold {
91+
return false
92+
}
8693
}
87-
if Float(differentPixelCount) > threshold { return false }
94+
offset += 1
8895
}
8996
return true
9097
}

Sources/SnapshotTesting/Snapshotting/NSView.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ extension Snapshotting where Value == NSView, Format == NSImage {
1111
///
1212
/// - Parameters:
1313
/// - precision: The percentage of pixels that must match.
14+
/// - subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
1415
/// - size: A view size override.
15-
public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting {
16-
return SimplySnapshotting.image(precision: precision).asyncPullback { view in
16+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0, size: CGSize? = nil) -> Snapshotting {
17+
return SimplySnapshotting.image(precision: precision, subpixelThreshold: subpixelThreshold).asyncPullback { view in
1718
let initialSize = view.frame.size
1819
if let size = size { view.frame.size = size }
1920
guard view.frame.width > 0, view.frame.height > 0 else {

Sources/SnapshotTesting/Snapshotting/NSViewController.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ extension Snapshotting where Value == NSViewController, Format == NSImage {
1111
///
1212
/// - Parameters:
1313
/// - precision: The percentage of pixels that must match.
14+
/// - subpixelThreshold: The byte-value threshold at which two subpixels are considered different.
1415
/// - size: A view size override.
15-
public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting {
16-
return Snapshotting<NSView, NSImage>.image(precision: precision, size: size).pullback { $0.view }
16+
public static func image(precision: Float = 1, subpixelThreshold: UInt8 = 0, size: CGSize? = nil) -> Snapshotting {
17+
return Snapshotting<NSView, NSImage>.image(precision: precision, subpixelThreshold: subpixelThreshold, size: size).pullback { $0.view }
1718
}
1819
}
1920

0 commit comments

Comments
 (0)