Skip to content

Commit f579cf7

Browse files
committed
Initial position of Inspector UI, info button, make isDragging optional
1 parent 0f4da85 commit f579cf7

File tree

3 files changed

+114
-32
lines changed

3 files changed

+114
-32
lines changed

Sources/LayoutInspector/DebugLayoutImpl.swift

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ struct InspectLayout: ViewModifier {
55
@StateObject private var logStore: LogStore = .init()
66
@State private var selectedView: String? = nil
77
@State private var generation: Int = 0
8-
@State private var frame: CGRect = CGRect(x: 0, y: 0, width: 300, height: 300)
8+
@State private var inspectorFrame: CGRect = CGRect(x: 0, y: 0, width: 300, height: 300)
9+
@State private var contentSize: CGSize? = nil
10+
@State private var isPresentingInfoPanel: Bool = false
911

1012
private static let coordSpaceName = "InspectLayout"
1113

@@ -14,33 +16,60 @@ struct InspectLayout: ViewModifier {
1416
content
1517
.id(generation)
1618
.environment(\.debugLayoutSelectedViewID, selectedView)
19+
.measureSize { size in
20+
// Move inspector UI below the inspected view initially
21+
if contentSize == nil {
22+
inspectorFrame.origin.y = size.height + 8
23+
}
24+
contentSize = size
25+
}
1726
}
1827
.overlay(alignment: .topLeading) {
1928
LogEntriesGrid(logEntries: logStore.log, highlight: $selectedView)
2029
.safeAreaInset(edge: .bottom) {
21-
Button("Reset layout cache") {
22-
generation &+= 1
23-
}
24-
.buttonStyle(.bordered)
25-
.frame(maxWidth: .infinity)
26-
.background()
27-
.backgroundStyle(.thickMaterial)
30+
toolbar
2831
}
2932
.resizableAndDraggable(
30-
frame: $frame,
33+
frame: $inspectorFrame,
3134
coordinateSpace: .named(Self.coordSpaceName)
3235
)
3336
.background {
34-
Rectangle()
35-
.fill(.thickMaterial)
37+
Rectangle().fill(.thickMaterial)
3638
.shadow(radius: 5)
3739
}
38-
.frame(width: frame.width, height: frame.height)
39-
.offset(x: frame.minX, y: frame.minY)
40+
.frame(width: inspectorFrame.width, height: inspectorFrame.height)
41+
.offset(x: inspectorFrame.minX, y: inspectorFrame.minY)
4042
.coordinateSpace(name: Self.coordSpaceName)
4143
}
4244
.environmentObject(logStore)
4345
}
46+
47+
@ViewBuilder private var toolbar: some View {
48+
HStack {
49+
Button("Reset layout cache") {
50+
generation &+= 1
51+
}
52+
Spacer()
53+
Button {
54+
isPresentingInfoPanel.toggle()
55+
} label: {
56+
Image(systemName: "info.circle")
57+
}
58+
.popover(isPresented: $isPresentingInfoPanel) {
59+
VStack(alignment: .leading) {
60+
Text("SwiftUI Layout Inspector")
61+
.font(.headline)
62+
Link("GitHub", destination: URL(string: "https://github.com/ole/swiftui-layout-inspector")!)
63+
}
64+
.padding()
65+
}
66+
.presentationDetents([.medium])
67+
}
68+
.padding()
69+
.frame(maxWidth: .infinity)
70+
.background()
71+
.backgroundStyle(.thinMaterial)
72+
}
4473
}
4574

4675
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)

Sources/LayoutInspector/ResizableAndDraggableView.swift

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,63 +18,89 @@ struct ResizableAndDraggableFrame: ViewModifier {
1818
@Binding var frame: CGRect
1919
var coordinateSpace: CoordinateSpace
2020

21-
@State private var isDragging: Bool = false
22-
@State private var isResizing: Bool = false
23-
2421
private static let titleBarHeight: CGFloat = 20
2522

2623
func body(content: Content) -> some View {
2724
content
28-
.padding(.vertical, Self.titleBarHeight)
25+
.padding(.top, Self.titleBarHeight)
2926
.overlay {
3027
ZStack(alignment: .top) {
3128
Rectangle()
3229
.frame(height: Self.titleBarHeight)
33-
.foregroundStyle(isDragging ? .pink : .yellow)
34-
.draggable(isDragging: $isDragging, point: $frame.origin, coordinateSpace: coordinateSpace)
30+
.foregroundStyle(.tertiary)
31+
.draggable(point: $frame.origin, coordinateSpace: coordinateSpace)
3532

36-
let resizeHandle = Rectangle()
37-
.fill(.green)
33+
let resizeHandle = ResizeHandle()
34+
.fill(.secondary)
3835
.frame(width: 20, height: 20)
3936
resizeHandle
4037
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
41-
.draggable(isDragging: $isResizing, point: $frame.topLeading, coordinateSpace: coordinateSpace)
38+
.draggable(point: $frame.topLeading, coordinateSpace: coordinateSpace)
4239
resizeHandle
40+
.rotationEffect(.degrees(90))
4341
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
44-
.draggable(isDragging: $isResizing, point: $frame.topTrailing, coordinateSpace: coordinateSpace)
42+
.draggable(point: $frame.topTrailing, coordinateSpace: coordinateSpace)
4543
resizeHandle
44+
.rotationEffect(.degrees(-90))
4645
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
47-
.draggable(isDragging: $isResizing, point: $frame.bottomLeading, coordinateSpace: coordinateSpace)
46+
.draggable(point: $frame.bottomLeading, coordinateSpace: coordinateSpace)
4847
resizeHandle
48+
.rotationEffect(.degrees(180))
4949
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
50-
.draggable(isDragging: $isResizing, point: $frame.bottomTrailing, coordinateSpace: coordinateSpace)
50+
.draggable(point: $frame.bottomTrailing, coordinateSpace: coordinateSpace)
5151
}
5252
}
53+
}
54+
}
5355

56+
struct ResizeHandle: Shape {
57+
func path(in rect: CGRect) -> Path {
58+
var path = Path()
59+
path.move(to: rect.topLeading)
60+
path.addLine(to: rect.topTrailing)
61+
path.addLine(to: rect.bottomLeading)
62+
path.closeSubpath()
63+
return path
5464
}
5565
}
5666

5767
extension View {
5868
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
59-
func draggable(isDragging: Binding<Bool>, offset: Binding<CGSize>, coordinateSpace: CoordinateSpace) -> some View {
60-
modifier(Draggable(isDragging: isDragging, offset: offset, coordinateSpace: coordinateSpace))
69+
func draggable(
70+
isDragging: Binding<Bool>? = nil,
71+
offset: Binding<CGSize>,
72+
coordinateSpace: CoordinateSpace
73+
) -> some View {
74+
modifier(Draggable(
75+
isDragging: isDragging,
76+
offset: offset,
77+
coordinateSpace: coordinateSpace
78+
))
6179
}
6280

6381
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
64-
func draggable(isDragging: Binding<Bool>, point pointBinding: Binding<CGPoint>, coordinateSpace: CoordinateSpace) -> some View {
82+
func draggable(
83+
isDragging: Binding<Bool>? = nil,
84+
point pointBinding: Binding<CGPoint>,
85+
coordinateSpace: CoordinateSpace
86+
) -> some View {
6587
let sizeBinding = pointBinding.transform(
6688
getter: { pt -> CGSize in CGSize(width: pt.x, height: pt.y) },
6789
setter: { pt, newValue, _ in
6890
pt = CGPoint(x: newValue.width, y: newValue.height)
6991
}
7092
)
71-
return draggable(isDragging: isDragging, offset: sizeBinding, coordinateSpace: coordinateSpace)
93+
return draggable(
94+
isDragging: isDragging,
95+
offset: sizeBinding,
96+
coordinateSpace: coordinateSpace
97+
)
7298
}
7399
}
74100

75101
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
76102
struct Draggable: ViewModifier {
77-
@Binding var isDragging: Bool
103+
var isDragging: Binding<Bool>?
78104
@Binding var offset: CGSize
79105
var coordinateSpace: CoordinateSpace
80106

@@ -88,7 +114,7 @@ struct Draggable: ViewModifier {
88114
private var dragGesture: some Gesture {
89115
DragGesture(coordinateSpace: coordinateSpace)
90116
.onChanged { gv in
91-
isDragging = true
117+
isDragging?.wrappedValue = true
92118
if let last = lastTranslation {
93119
let delta = gv.translation - last
94120
offset = offset + delta
@@ -99,7 +125,7 @@ struct Draggable: ViewModifier {
99125
}
100126
.onEnded { gv in
101127
lastTranslation = nil
102-
isDragging = false
128+
isDragging?.wrappedValue = false
103129
}
104130
}
105131
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import SwiftUI
2+
3+
extension View {
4+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
5+
func measureSize(onChange: @escaping (CGSize) -> Void) -> some View {
6+
self
7+
.background {
8+
GeometryReader { geometry in
9+
Color.clear
10+
.preference(key: SizePreferenceKey.self, value: geometry.size)
11+
}
12+
}
13+
.onPreferenceChange(SizePreferenceKey.self) { size in
14+
if let size {
15+
onChange(size)
16+
}
17+
}
18+
}
19+
}
20+
21+
enum SizePreferenceKey: PreferenceKey {
22+
static var defaultValue: CGSize? = nil
23+
24+
static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
25+
value = value ?? nextValue()
26+
}
27+
}

0 commit comments

Comments
 (0)