Skip to content

Commit 9177474

Browse files
committed
Better handle some cases where PlaygroundLogger previously failed to encode empty images.
If a `NSView`, `NSImage`, or `UIImage` was zero-sized (e.g. 0x0), PlaygroundLogger would fail to encode this because it couldn't get a bitmap representation for the image. Instead, handle this case specially, and instead of failing, send zero bytes of PNG data. Xcode renders this as an empty image, which is the expectation for this case. This commit includes tests that empty views and images on all platforms generate image representations which can be encoded without throwing any errors. This is an improvement over the legacy PlaygroundLogger implementation, which would render these plus empty `UIView` instances as if they were the string "empty image". This addresses <rdar://problem/40207604>.
1 parent 063a4b0 commit 9177474

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
lines changed

PlaygroundLogger/PlaygroundLogger/OpaqueRepresentations/AppKit/NSImage+OpaqueImageRepresentable.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@
3434

3535
func encodeImage(into encoder: LogEncoder, withFormat format: LogEncoder.Format) throws {
3636
guard let bitmapRep = self.bestBitmapRepresentation else {
37-
throw LoggingError.encodingFailure(reason: "Failed to get a bitmap representation of this NSImage")
37+
if size == .zero {
38+
// If we couldn't get a bitmap representation because the image was empty, encode empty PNG data.
39+
encoder.encode(number: 0)
40+
return
41+
}
42+
else {
43+
throw LoggingError.encodingFailure(reason: "Failed to get a bitmap representation of this NSImage")
44+
}
3845
}
3946

4047
try bitmapRep.encodeImage(into: encoder, withFormat: format)

PlaygroundLogger/PlaygroundLogger/OpaqueRepresentations/AppKit/NSView+OpaqueImageRepresentable.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@
1616
extension NSView: OpaqueImageRepresentable {
1717
func encodeImage(into encoder: LogEncoder, withFormat format: LogEncoder.Format) throws {
1818
guard let bitmapRep = self.bitmapImageRepForCachingDisplay(in: self.bounds) else {
19-
throw LoggingError.encodingFailure(reason: "Unable to create a bitmap representation of this NSView")
19+
if self.bounds == .zero {
20+
// If we couldn't get a bitmap representation because the view is zero-sized, encode empty PNG data.
21+
encoder.encode(number: 0)
22+
return
23+
}
24+
else {
25+
throw LoggingError.encodingFailure(reason: "Unable to create a bitmap representation of this NSView")
26+
}
2027
}
2128

2229
self.cacheDisplay(in: self.bounds, to: bitmapRep)

PlaygroundLogger/PlaygroundLogger/OpaqueRepresentations/UIKit/UIImage+OpaqueImageRepresentable.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@
1616
extension UIImage: OpaqueImageRepresentable {
1717
func encodeImage(into encoder: LogEncoder, withFormat format: LogEncoder.Format) throws {
1818
guard let pngData = UIImagePNGRepresentation(self) else {
19-
throw LoggingError.encodingFailure(reason: "Failed to convert UIImage to PNG")
19+
if size == .zero {
20+
// We tried encoding an empty image, so it understandably failed.
21+
// In this case, simply encode empty PNG data.
22+
encoder.encode(number: 0)
23+
return
24+
}
25+
else {
26+
throw LoggingError.encodingFailure(reason: "Failed to convert UIImage to PNG")
27+
}
2028
}
2129

2230
encoder.encode(number: UInt64(pngData.count))

PlaygroundLogger/PlaygroundLoggerTests/LogEntryTests.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import XCTest
1515

1616
import Foundation
1717

18+
#if os(macOS)
19+
import AppKit
20+
#elseif os(iOS) || os(tvOS)
21+
import UIKit
22+
#endif
23+
1824
class LogEntryTests: XCTestCase {
1925
func testNilIUO() throws {
2026
let nilIUO: Int! = nil
@@ -30,4 +36,48 @@ class LogEntryTests: XCTestCase {
3036
XCTAssertEqual(totalChildrenCount, 0)
3137
XCTAssert(children.isEmpty)
3238
}
39+
40+
func testEmptyView() throws {
41+
#if os(macOS)
42+
let emptyView = NSView()
43+
#elseif os(iOS) || os(tvOS)
44+
let emptyView = UIView()
45+
#endif
46+
47+
let logEntry = try LogEntry(describing: emptyView, name: "emptyView", policy: .default)
48+
49+
guard case let .opaque(name, _, _, _, representation) = logEntry else {
50+
XCTFail("Expected an opaque log entry")
51+
return
52+
}
53+
54+
XCTAssertEqual(name, "emptyView")
55+
XCTAssert(representation is ImageOpaqueRepresentation)
56+
57+
// Try to encode the log entry. This operation shouldn't throw; if it does, it will fail the test.
58+
let encoder = LogEncoder()
59+
try logEntry.encode(with: encoder, format: .current)
60+
}
61+
62+
func testEmptyImage() throws {
63+
#if os(macOS)
64+
let emptyImage = NSImage()
65+
#elseif os(iOS) || os(tvOS)
66+
let emptyImage = UIImage()
67+
#endif
68+
69+
let logEntry = try LogEntry(describing: emptyImage, name: "emptyImage", policy: .default)
70+
71+
guard case let .opaque(name, _, _, _, representation) = logEntry else {
72+
XCTFail("Expected an opaque log entry")
73+
return
74+
}
75+
76+
XCTAssertEqual(name, "emptyImage")
77+
XCTAssert(representation is ImageOpaqueRepresentation)
78+
79+
// Try to encode the log entry. This operation shouldn't throw; if it does, it will fail the test.
80+
let encoder = LogEncoder()
81+
try logEntry.encode(with: encoder, format: .current)
82+
}
3383
}

0 commit comments

Comments
 (0)