Last active
March 25, 2024 21:33
-
-
Save dermotos/7db325bf464417733fda7b6d3d4be181 to your computer and use it in GitHub Desktop.
View modifier to help debug framing issues in SwiftUI. Draws a box with measurements around the view this is added to.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if DEBUG | |
import Foundation | |
import SwiftUI | |
// MARK: - Public Interface | |
public extension View { | |
// swiftlint:disable identifier_name | |
/// Draw a frame around the view and print its size on screen. | |
/// | |
/// For debug use only. Remove before publishing your pull request. | |
/// - Parameter label: Optionally Add a label to the center of the view. | |
/// - Parameter color: Optionally change the color used to debug the view. | |
/// - Parameter fontSize: Optionally change the font size used to debug the view. | |
func _debugFrame(label: String? = nil, color: Color = .green, fontSize: CGFloat? = nil) -> some View { | |
modifier(DebugFrameModifier(label: label, color: color, fontSize: fontSize ?? Constants.defaultFontSize)) | |
} | |
// swiftlint:enable identifier_name | |
} | |
// MARK: - Private Implementation | |
private struct DebugFrameModifier: ViewModifier { | |
@State var xAxisTextSize: CGSize = .zero | |
@State var yAxisTextSize: CGSize = .zero | |
var label: String? | |
var color: Color | |
var fontSize: CGFloat | |
func body(content: Content) -> some View { | |
content | |
.overlay { | |
GeometryReader(content: { viewGeometry in | |
Text("← \(Int(viewGeometry.size.width)) →") | |
.styleText( | |
orientation: .horizontal, | |
color: color, | |
fontSize: fontSize | |
) | |
.onPreferenceChange(ViewSizeKey.self) { | |
xAxisTextSize = $0 | |
} | |
.position( | |
x: viewGeometry.size.width / Constants.two, | |
y: -(Constants.infoTextOffset + xAxisTextSize.height) | |
) | |
Text("← \(Int(viewGeometry.size.height)) →") | |
.styleText( | |
orientation: .vertical, | |
color: color, | |
fontSize: fontSize | |
) | |
.onPreferenceChange(ViewSizeKey.self) { | |
yAxisTextSize = $0 | |
} | |
.position( | |
x: -(yAxisTextSize.width + Constants.infoTextOffset), | |
y: viewGeometry.size.height / Constants.two | |
) | |
ZStack { | |
Color.clear | |
.border(color) | |
if let label { | |
Text(label) | |
.padding(.all, Constants.labelPadding) | |
.styleText( | |
orientation: .horizontal, | |
color: color, | |
fontSize: fontSize + Constants.labelFontSizeAddition | |
) | |
} | |
} | |
}) | |
} | |
} | |
} | |
private struct TextStyleModifier: ViewModifier { | |
var orientation: TextOrientation | |
var color: Color | |
var fontSize: CGFloat | |
func body(content: Content) -> some View { | |
content | |
.lineLimit(Constants.oneLine) | |
.bold() | |
.kerning(.zero) | |
.foregroundColor(color) | |
.font(.system(size: fontSize)) | |
.monospaced() | |
.background { | |
Color.black | |
.cornerRadius( | |
Constants.cornerRadius, | |
corners: .allCorners | |
) | |
} | |
.rotationEffect(.degrees(orientation.degrees)) | |
} | |
} | |
private extension View { | |
func styleText(orientation: TextOrientation, color: Color, fontSize: CGFloat) -> some View { | |
modifier(TextStyleModifier(orientation: orientation, color: color, fontSize: fontSize)) | |
} | |
} | |
private enum TextOrientation { | |
case horizontal | |
case vertical | |
var degrees: CGFloat { | |
switch self { | |
case .horizontal: .zero | |
case .vertical: Constants.ninetyDegrees | |
} | |
} | |
} | |
private struct ViewSizeKey: PreferenceKey { | |
static var defaultValue: CGSize = .zero | |
static func reduce(value: inout CGSize, nextValue: () -> CGSize) { | |
value = nextValue() | |
} | |
} | |
private struct ViewGeometry: View { | |
var body: some View { | |
GeometryReader(content: { geometry in | |
Color.clear | |
.preference(key: ViewSizeKey.self, value: geometry.size) | |
}) | |
} | |
} | |
private enum Constants { | |
static let oneLine: Int = 1 | |
static let labelFontSizeAddition: CGFloat = 2 | |
static let labelPadding: CGFloat = 4 | |
static let cornerRadius: CGFloat = 2.5 | |
static let minimumScale: CGFloat = 0.1 | |
static let defaultFontSize: CGFloat = 12 | |
static let infoTextOffset: CGFloat = -10 | |
static let ninetyDegrees: CGFloat = -90 | |
static let two: CGFloat = 2 | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment