Skip to content

Commit 8d23f4b

Browse files
committed
TMP add stepper and picker
1 parent c3498e1 commit 8d23f4b

7 files changed

+211
-24
lines changed

Sources/RichTextKit/Paragraph/NSParagraphStyle+Picker.swift renamed to Sources/RichTextKit/Paragraph/NSMutableParagraphStyle+Picker.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// NSParagraphStyle+Picker.swift
2+
// NSMutableParagraphStyle+Picker.swift
33
// RichTextKit
44
//
55
// Created by Daniel Saidi on 2025-04-04.
@@ -8,28 +8,28 @@
88

99
import SwiftUI
1010

11-
public extension KeyPath where Root == NSParagraphStyle {
11+
public extension KeyPath where Root == NSMutableParagraphStyle {
1212

1313
/// Get default picker values for the key path.
1414
var defaultPickerValues: [Value]? {
1515
switch self {
1616
case \.alignment: NSTextAlignment.defaultPickerValues as? [Value]
1717
case \.allowsDefaultTighteningForTruncation: [true, false] as? [Value]
1818
case \.baseWritingDirection: NSWritingDirection.defaultPickerValues as? [Value]
19-
case \.defaultTabInterval: [28.0, 36.0, 72.0, 144.0] as? [Value]
20-
case \.firstLineHeadIndent: [0.0, 10.0, 20.0, 30.0, 40.0, 50.0] as? [Value]
21-
case \.headIndent: [0.0, 10.0, 20.0, 30.0, 40.0, 50.0] as? [Value]
19+
case \.defaultTabInterval: [28.0, 36, 72, 144] as? [Value]
20+
case \.firstLineHeadIndent: [0.0, 10, 20, 30, 40, 50] as? [Value]
21+
case \.headIndent: [0.0, 10, 20, 30, 40, 50] as? [Value]
2222
case \.hyphenationFactor: [0.0, 0.5, 1.0] as? [Value]
2323
case \.lineBreakMode: NSLineBreakMode.defaultPickerValues as? [Value]
2424
case \.lineBreakStrategy: NSParagraphStyle.LineBreakStrategy.defaultPickerValues as? [Value]
2525
case \.lineHeightMultiple: [1.0, 1.1, 1.2, 1.5, 2.0] as? [Value]
26-
case \.lineSpacing: [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] as? [Value]
27-
case \.maximumLineHeight: [0.0, 20.0, 24.0, 28.0, 32.0, 36.0] as? [Value]
28-
case \.minimumLineHeight: [0.0, 16.0, 18.0, 20.0, 22.0, 24.0] as? [Value]
29-
case \.paragraphSpacing: [0.0, 4.0, 8.0, 12.0, 16.0, 20.0] as? [Value]
30-
case \.paragraphSpacingBefore: [0.0, 4.0, 8.0, 12.0, 16.0, 20.0] as? [Value]
26+
case \.lineSpacing: [0.0, 2, 4, 6, 8, 10] as? [Value]
27+
case \.maximumLineHeight: [0.0, 20, 24, 28, 32, 36] as? [Value]
28+
case \.minimumLineHeight: [0.0, 16, 18, 20, 22, 24] as? [Value]
29+
case \.paragraphSpacing: [0.0, 4, 8, 12, 16, 20] as? [Value]
30+
case \.paragraphSpacingBefore: [0.0, 4, 8, 12, 16, 20] as? [Value]
3131
case \.tabStops: [[]] as? [Value] // Usually customized based on document needs
32-
case \.tailIndent: [0.0, 10.0, 20.0, 30.0, 40.0, 50.0] as? [Value]
32+
case \.tailIndent: [0.0, 10, 20, 30, 40, 50] as? [Value]
3333
case \.usesDefaultHyphenation: [true, false] as? [Value]
3434
default: [] as? [Value]
3535
}

Sources/RichTextKit/Paragraph/NSParagraphStyle+Stepper.swift renamed to Sources/RichTextKit/Paragraph/NSMutableParagraphStyle+Stepper.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import SwiftUI
1010

11-
public extension KeyPath where Root == NSParagraphStyle {
11+
public extension KeyPath where Root == NSMutableParagraphStyle, Value: Hashable & Strideable {
1212

1313
/// Get the default stepper interval for the key path.
1414
var defaultStepperInterval: Value? {

Sources/RichTextKit/Paragraph/NSParagraphStyle+Image.swift renamed to Sources/RichTextKit/Paragraph/NSParagraphStyle+Presentation.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// NSParagraphStyle+Image.swift
2+
// NSParagraphStyle+Presentation.swift
33
// RichTextKit
44
//
55
// Created by Daniel Saidi on 2025-04-04.
@@ -48,6 +48,15 @@ public extension NSTextAlignment {
4848
@unknown default: .richTextUnknownValueType
4949
}
5050
}
51+
52+
/// The default label for the text alignment.
53+
var defaultLabel: some View {
54+
Label {
55+
Text(title)
56+
} icon: {
57+
defaultIcon
58+
}
59+
}
5160
}
5261

5362
@ViewBuilder
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//
2+
// RichTextParagraphValuePicker.swift
3+
// RichTextKit
4+
//
5+
// Created by Daniel Saidi on 2025-04-04.
6+
// Copyright © 2025 Daniel Saidi. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
/// This picker can be used to pick a paragraph value.
12+
///
13+
/// The `label` builder is used as the main picker label and
14+
/// `valueLabel` as a label for each picker value.
15+
public struct RichTextParagraphValuePicker<ValueType: Hashable, Label: View, ValueLabel: View>: View {
16+
17+
public init(
18+
_ keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>,
19+
context: RichTextContext,
20+
values: [ValueType]? = nil,
21+
label: @escaping () -> Label,
22+
valueLabel: @escaping (ValueType) -> ValueLabel
23+
) {
24+
self.init(
25+
keyPath,
26+
binding: context.paragraphStyleValueBinding(for: keyPath),
27+
values: values,
28+
label: label,
29+
valueLabel: valueLabel
30+
)
31+
}
32+
33+
public init(
34+
_ keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>,
35+
binding: Binding<ValueType>,
36+
values: [ValueType]? = nil,
37+
label: @escaping () -> Label,
38+
valueLabel: @escaping (ValueType) -> ValueLabel
39+
) {
40+
self.keyPath = keyPath
41+
self.binding = binding
42+
self.values = values
43+
self.label = label
44+
self.valueLabel = valueLabel
45+
}
46+
47+
private let keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>
48+
private let binding: Binding<ValueType>
49+
private let values: [ValueType]?
50+
private let label: () -> Label
51+
private let valueLabel: (ValueType) -> ValueLabel
52+
53+
private var indexBinding: Binding<Int> {
54+
Binding<Int>(
55+
get: { pickerValues.firstIndex(of: binding.wrappedValue) ?? 0 },
56+
set: { binding.wrappedValue = pickerValues[$0] }
57+
)
58+
}
59+
60+
private var pickerValues: [ValueType] {
61+
values ?? keyPath.defaultPickerValues ?? []
62+
}
63+
64+
public var body: some View {
65+
Picker(selection: indexBinding) {
66+
ForEach(Array(pickerValues.enumerated()), id: \.offset) {
67+
valueLabel($0.element).tag($0.offset)
68+
}
69+
} label: {
70+
label()
71+
}
72+
}
73+
}
74+
75+
#Preview {
76+
77+
struct Preview: View {
78+
79+
@State var alignment = NSTextAlignment.left
80+
@StateObject var context = RichTextContext()
81+
82+
var body: some View {
83+
VStack {
84+
RichTextParagraphValuePicker(
85+
\.alignment,
86+
context: context
87+
) {
88+
Text("TEST")
89+
} valueLabel: { value in
90+
value.defaultLabel
91+
}
92+
.labelStyle(.iconOnly)
93+
.pickerStyle(.segmented)
94+
}
95+
}
96+
}
97+
98+
return Preview()
99+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// RichTextParagraphValueStepper.swift
3+
// RichTextKit
4+
//
5+
// Created by Daniel Saidi on 2025-04-04.
6+
// Copyright © 2025 Daniel Saidi. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
/// This stepper can be used to step a paragraph value.
12+
///
13+
/// The `label` builder is used as the main picker label.
14+
public struct RichTextParagraphValueStepper<ValueType: Hashable & Strideable & Comparable & SignedNumeric, Label: View>: View {
15+
16+
public init(
17+
_ keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>,
18+
context: RichTextContext,
19+
step: ValueType.Stride,
20+
label: @escaping () -> Label
21+
) {
22+
self.init(
23+
keyPath,
24+
binding: context.paragraphStyleValueBinding(for: keyPath),
25+
step: step,
26+
label: label
27+
)
28+
}
29+
30+
public init(
31+
_ keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>,
32+
binding: Binding<ValueType>,
33+
step: ValueType.Stride,
34+
label: @escaping () -> Label
35+
) {
36+
self.keyPath = keyPath
37+
self.binding = binding
38+
self.step = step
39+
self.label = label
40+
}
41+
42+
private let keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>
43+
private let binding: Binding<ValueType>
44+
private let step: ValueType.Stride
45+
private let label: () -> Label
46+
47+
public var body: some View {
48+
Stepper(value: binding, step: step, label: label)
49+
}
50+
}
51+
52+
#Preview {
53+
54+
struct Preview: View {
55+
56+
@StateObject var context = RichTextContext()
57+
58+
var value: Double {
59+
context.paragraphStyleValue(for: \.lineSpacing)
60+
}
61+
62+
var body: some View {
63+
VStack {
64+
Text(String(format: "%.1f", value))
65+
RichTextParagraphValueStepper(
66+
\.lineSpacing,
67+
context: context,
68+
step: 0.1
69+
) {
70+
Text("TEST")
71+
}
72+
.labelStyle(.iconOnly)
73+
.pickerStyle(.segmented)
74+
}
75+
}
76+
}
77+
78+
return Preview()
79+
}

Sources/RichTextKit/RichTextContext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public class RichTextContext: ObservableObject {
9494
) -> ValueType {
9595
paragraphStyle[keyPath: keyPath]
9696
}
97-
97+
9898
/// A binding that binds to paragraph style values.
9999
public func paragraphStyleValueBinding<ValueType>(
100100
for keyPath: WritableKeyPath<NSMutableParagraphStyle, ValueType>

Sources/RichTextKit/_Foundation/RichTextCoordinator+Subscriptions.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ extension RichTextCoordinator {
2222
}
2323
.store(in: &cancellables)
2424

25-
subscribeToAlignment()
26-
subscribeToFontName()
27-
subscribeToFontSize()
28-
subscribeToIsEditable()
29-
subscribeToIsEditingText()
25+
subscribeToContextAlignment()
26+
subscribeToContextFontName()
27+
subscribeToContextFontSize()
28+
subscribeToContextIsEditable()
29+
subscribeToContextIsEditingText()
3030
}
3131
}
3232

@@ -41,31 +41,31 @@ private extension RichTextCoordinator {
4141
.store(in: &cancellables)
4242
}
4343

44-
func subscribeToAlignment() {
44+
func subscribeToContextAlignment() {
4545
subscribe(to: context.$textAlignment) { [weak self] in
4646
self?.handle(.setAlignment($0))
4747
}
4848
}
4949

50-
func subscribeToFontName() {
50+
func subscribeToContextFontName() {
5151
subscribe(to: context.$fontName) { [weak self] in
5252
self?.textView.setRichTextFontName($0)
5353
}
5454
}
5555

56-
func subscribeToFontSize() {
56+
func subscribeToContextFontSize() {
5757
subscribe(to: context.$fontSize) { [weak self] in
5858
self?.textView.setRichTextFontSize($0)
5959
}
6060
}
6161

62-
func subscribeToIsEditable() {
62+
func subscribeToContextIsEditable() {
6363
subscribe(to: context.$isEditable) { [weak self] in
6464
self?.setIsEditable(to: $0)
6565
}
6666
}
6767

68-
func subscribeToIsEditingText() {
68+
func subscribeToContextIsEditingText() {
6969
subscribe(to: context.$isEditingText) { [weak self] in
7070
self?.setIsEditing(to: $0)
7171
}

0 commit comments

Comments
 (0)