Skip to content

Instantly share code, notes, and snippets.

@dermotos
Last active March 25, 2024 21:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotos/7db325bf464417733fda7b6d3d4be181 to your computer and use it in GitHub Desktop.
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.
#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