Skip to content

Commit 9fc5f2b

Browse files
committed
Clean up
1 parent e9ae342 commit 9fc5f2b

File tree

4 files changed

+192
-118
lines changed

4 files changed

+192
-118
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import SwiftUI
2+
3+
extension Binding {
4+
func transform<Target>(
5+
getter: @escaping (Value) -> Target,
6+
setter: @escaping (inout Value, Target, Transaction) -> Void
7+
) -> Binding<Target> {
8+
Binding<Target>(
9+
get: { getter(self.wrappedValue) },
10+
set: { newValue, transaction in
11+
setter(&self.wrappedValue, newValue, transaction)
12+
}
13+
)
14+
}
15+
}

Sources/LayoutInspector/DebugLayoutImpl.swift

Lines changed: 0 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -43,124 +43,6 @@ struct InspectLayout: ViewModifier {
4343
}
4444
}
4545

46-
extension View {
47-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
48-
func resizableAndDraggable(
49-
frame: Binding<CGRect>,
50-
coordinateSpace: CoordinateSpace
51-
) -> some View {
52-
modifier(ResizableAndDraggableFrame(
53-
frame: frame,
54-
coordinateSpace: coordinateSpace
55-
))
56-
}
57-
}
58-
59-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
60-
struct ResizableAndDraggableFrame: ViewModifier {
61-
@Binding var frame: CGRect
62-
var coordinateSpace: CoordinateSpace
63-
64-
@State private var isDragging: Bool = false
65-
@State private var isResizing: Bool = false
66-
67-
private static let chromeWidth: CGFloat = 10
68-
69-
func body(content: Content) -> some View {
70-
content
71-
.padding(.vertical, 20)
72-
.overlay {
73-
ZStack(alignment: .top) {
74-
Rectangle()
75-
.frame(height: 20)
76-
.foregroundStyle(isDragging ? .pink : .yellow)
77-
.draggable(isDragging: $isDragging, point: $frame.origin, coordinateSpace: coordinateSpace)
78-
79-
let resizeHandle = Rectangle()
80-
.fill(.green)
81-
.frame(width: 20, height: 20)
82-
resizeHandle
83-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
84-
.draggable(isDragging: $isResizing, point: $frame.topLeading, coordinateSpace: coordinateSpace)
85-
resizeHandle
86-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
87-
.draggable(isDragging: $isResizing, point: $frame.topTrailing, coordinateSpace: coordinateSpace)
88-
resizeHandle
89-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
90-
.draggable(isDragging: $isResizing, point: $frame.bottomLeading, coordinateSpace: coordinateSpace)
91-
resizeHandle
92-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
93-
.draggable(isDragging: $isResizing, point: $frame.bottomTrailing, coordinateSpace: coordinateSpace)
94-
}
95-
}
96-
97-
}
98-
}
99-
100-
extension Binding {
101-
func transform<Target>(
102-
getter: @escaping (Value) -> Target,
103-
setter: @escaping (inout Value, Target, Transaction) -> Void
104-
) -> Binding<Target> {
105-
Binding<Target>(
106-
get: { getter(self.wrappedValue) },
107-
set: { newValue, transaction in
108-
setter(&self.wrappedValue, newValue, transaction)
109-
}
110-
)
111-
}
112-
}
113-
114-
extension View {
115-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
116-
func draggable(isDragging: Binding<Bool>, offset: Binding<CGSize>, coordinateSpace: CoordinateSpace) -> some View {
117-
modifier(Draggable(isDragging: isDragging, offset: offset, coordinateSpace: coordinateSpace))
118-
}
119-
120-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
121-
func draggable(isDragging: Binding<Bool>, point pointBinding: Binding<CGPoint>, coordinateSpace: CoordinateSpace) -> some View {
122-
let sizeBinding = pointBinding.transform(
123-
getter: { pt -> CGSize in CGSize(width: pt.x, height: pt.y) },
124-
setter: { pt, newValue, _ in
125-
pt = CGPoint(x: newValue.width, y: newValue.height)
126-
}
127-
)
128-
return draggable(isDragging: isDragging, offset: sizeBinding, coordinateSpace: coordinateSpace)
129-
}
130-
}
131-
132-
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
133-
struct Draggable: ViewModifier {
134-
@Binding var isDragging: Bool
135-
@Binding var offset: CGSize
136-
var coordinateSpace: CoordinateSpace
137-
138-
@State private var lastTranslation: CGSize? = nil
139-
140-
func body(content: Content) -> some View {
141-
content
142-
.gesture(dragGesture)
143-
}
144-
145-
private var dragGesture: some Gesture {
146-
DragGesture(coordinateSpace: coordinateSpace)
147-
.onChanged { gv in
148-
isDragging = true
149-
if let last = lastTranslation {
150-
let delta = gv.translation - last
151-
offset = offset + delta
152-
lastTranslation = gv.translation
153-
} else {
154-
lastTranslation = gv.translation
155-
}
156-
}
157-
.onEnded { gv in
158-
lastTranslation = nil
159-
isDragging = false
160-
}
161-
}
162-
}
163-
16446
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
16547
struct DebugLayoutModifier: ViewModifier {
16648
var label: String
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import SwiftUI
2+
3+
extension View {
4+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
5+
func resizableAndDraggable(
6+
frame: Binding<CGRect>,
7+
coordinateSpace: CoordinateSpace
8+
) -> some View {
9+
modifier(ResizableAndDraggableFrame(
10+
frame: frame,
11+
coordinateSpace: coordinateSpace
12+
))
13+
}
14+
}
15+
16+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
17+
struct ResizableAndDraggableFrame: ViewModifier {
18+
@Binding var frame: CGRect
19+
var coordinateSpace: CoordinateSpace
20+
21+
@State private var isDragging: Bool = false
22+
@State private var isResizing: Bool = false
23+
24+
private static let titleBarHeight: CGFloat = 20
25+
26+
func body(content: Content) -> some View {
27+
content
28+
.padding(.vertical, Self.titleBarHeight)
29+
.overlay {
30+
ZStack(alignment: .top) {
31+
Rectangle()
32+
.frame(height: Self.titleBarHeight)
33+
.foregroundStyle(isDragging ? .pink : .yellow)
34+
.draggable(isDragging: $isDragging, point: $frame.origin, coordinateSpace: coordinateSpace)
35+
36+
let resizeHandle = Rectangle()
37+
.fill(.green)
38+
.frame(width: 20, height: 20)
39+
resizeHandle
40+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
41+
.draggable(isDragging: $isResizing, point: $frame.topLeading, coordinateSpace: coordinateSpace)
42+
resizeHandle
43+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
44+
.draggable(isDragging: $isResizing, point: $frame.topTrailing, coordinateSpace: coordinateSpace)
45+
resizeHandle
46+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
47+
.draggable(isDragging: $isResizing, point: $frame.bottomLeading, coordinateSpace: coordinateSpace)
48+
resizeHandle
49+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
50+
.draggable(isDragging: $isResizing, point: $frame.bottomTrailing, coordinateSpace: coordinateSpace)
51+
}
52+
}
53+
54+
}
55+
}
56+
57+
extension View {
58+
@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))
61+
}
62+
63+
@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 {
65+
let sizeBinding = pointBinding.transform(
66+
getter: { pt -> CGSize in CGSize(width: pt.x, height: pt.y) },
67+
setter: { pt, newValue, _ in
68+
pt = CGPoint(x: newValue.width, y: newValue.height)
69+
}
70+
)
71+
return draggable(isDragging: isDragging, offset: sizeBinding, coordinateSpace: coordinateSpace)
72+
}
73+
}
74+
75+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
76+
struct Draggable: ViewModifier {
77+
@Binding var isDragging: Bool
78+
@Binding var offset: CGSize
79+
var coordinateSpace: CoordinateSpace
80+
81+
@State private var lastTranslation: CGSize? = nil
82+
83+
func body(content: Content) -> some View {
84+
content
85+
.gesture(dragGesture)
86+
}
87+
88+
private var dragGesture: some Gesture {
89+
DragGesture(coordinateSpace: coordinateSpace)
90+
.onChanged { gv in
91+
isDragging = true
92+
if let last = lastTranslation {
93+
let delta = gv.translation - last
94+
offset = offset + delta
95+
lastTranslation = gv.translation
96+
} else {
97+
lastTranslation = gv.translation
98+
}
99+
}
100+
.onEnded { gv in
101+
lastTranslation = nil
102+
isDragging = false
103+
}
104+
}
105+
}
106+
107+
@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
108+
struct ResizableAndDraggable_Previews: PreviewProvider {
109+
static var previews: some View {
110+
WithState(CGRect(x: 20, y: 20, width: 300, height: 300)) { $frame in
111+
Color.clear
112+
.overlay {
113+
Text("This view is resizable and draggable")
114+
.padding(20)
115+
.multilineTextAlignment(.center)
116+
}
117+
.resizableAndDraggable(frame: $frame, coordinateSpace: .named("coordSpace"))
118+
.background {
119+
Rectangle()
120+
.fill(.ultraThickMaterial)
121+
.shadow(radius: 5)
122+
}
123+
.frame(width: frame.width, height: frame.height)
124+
.offset(x: frame.minX, y: frame.minY)
125+
.coordinateSpace(name: "coordSpace")
126+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
127+
}
128+
}
129+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#if canImport(SwiftUI)
2+
3+
import SwiftUI
4+
5+
/// A wrapper view that takes a constant value and provides it to its child as a mutable `Binding`.
6+
///
7+
/// Useful in Previews for previewing views that require a binding. You can't easily declare a
8+
/// `@State` variable in a PreviewProvider, and a `Binding.constant` doesn’t always cut it if you
9+
/// want to test a view’s dynamic behavior.
10+
///
11+
/// Example:
12+
///
13+
/// struct InteractiveStepper_Previews: PreviewProvider {
14+
/// static var previews: some View {
15+
/// WithState(5) { counterBinding in
16+
/// Stepper(value: counterBinding, in: 0...10) {
17+
/// Text("Counter: \(counterBinding.wrappedValue)")
18+
/// }
19+
/// }
20+
/// }
21+
/// }
22+
///
23+
public struct WithState<Value, Content: View>: View {
24+
@State private var value: Value
25+
let content: (Binding<Value>) -> Content
26+
27+
public init(_ value: Value, @ViewBuilder content: @escaping (Binding<Value>) -> Content) {
28+
self._value = State(wrappedValue: value)
29+
self.content = content
30+
}
31+
32+
public var body: some View {
33+
content($value)
34+
}
35+
}
36+
37+
struct StatefulWrapper_Previews: PreviewProvider {
38+
static var previews: some View {
39+
WithState(5) { counterBinding in
40+
Stepper(value: counterBinding, in: 0...10) {
41+
Text("Counter: \(counterBinding.wrappedValue)")
42+
}
43+
.padding()
44+
}
45+
}
46+
}
47+
48+
#endif

0 commit comments

Comments
 (0)