Skip to content

Commit 48cd05d

Browse files
authored
Support dark mode (#45)
* Updated bundle identifier * Fix updating view within view update created undefine behaviour warning * support dark mode
1 parent 1d4673b commit 48cd05d

File tree

8 files changed

+201
-28
lines changed

8 files changed

+201
-28
lines changed

RichEditorDemo/RichEditorDemo.xcodeproj/project.pbxproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@
310310
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
311311
MACOSX_DEPLOYMENT_TARGET = 14.0;
312312
MARKETING_VERSION = 1.0;
313-
PRODUCT_BUNDLE_IDENTIFIER = com.canopas.nolonely.RichEditorDemo;
313+
PRODUCT_BUNDLE_IDENTIFIER = com.canopas.RichEditorDemo;
314314
PRODUCT_NAME = "$(TARGET_NAME)";
315315
SDKROOT = auto;
316316
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -347,7 +347,7 @@
347347
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
348348
MACOSX_DEPLOYMENT_TARGET = 14.0;
349349
MARKETING_VERSION = 1.0;
350-
PRODUCT_BUNDLE_IDENTIFIER = com.canopas.nolonely.RichEditorDemo;
350+
PRODUCT_BUNDLE_IDENTIFIER = com.canopas.RichEditorDemo;
351351
PRODUCT_NAME = "$(TARGET_NAME)";
352352
SDKROOT = auto;
353353
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

RichEditorDemo/RichEditorDemo/ContentView.swift

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import SwiftUI
99
import RichEditorSwiftUI
1010

1111
struct ContentView: View {
12+
@Environment(\.colorScheme) var colorScheme
13+
1214
@ObservedObject var state: RichEditorState
1315

1416
init(state: RichEditorState? = nil) {
@@ -27,7 +29,11 @@ struct ContentView: View {
2729
var body: some View {
2830
NavigationStack {
2931
VStack {
32+
EditorToolBarView(state: state)
33+
3034
RichEditor(state: _state)
35+
.textPadding(12)
36+
.cornerRadius(10)
3137
}
3238
.padding(10)
3339
.toolbar {
@@ -41,6 +47,7 @@ struct ContentView: View {
4147
})
4248
}
4349
}
50+
.background(colorScheme == .dark ? .black : .gray.opacity(0.07))
4451
.navigationTitle("Rich Editor")
4552
.navigationBarTitleDisplayMode(.inline)
4653
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Buildable.swift
3+
// RichEditorSwiftUI
4+
//
5+
// Created by Divyesh Vekariya on 24/09/24.
6+
//
7+
8+
import Foundation
9+
10+
/// Adds a helper function to mutate a properties and help implement _Builder_ pattern
11+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
12+
protocol Buildable {}
13+
14+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
15+
extension Buildable {
16+
/// Mutates a property of the instance
17+
///
18+
/// - Parameter keyPath: `WritableKeyPath` to the instance property to be modified
19+
/// - Parameter value: value to overwrite the instance property
20+
func mutating<T>(keyPath: WritableKeyPath<Self, T>, value: T) -> Self {
21+
var newSelf = self
22+
newSelf[keyPath: keyPath] = value
23+
return newSelf
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// RichEditor+Buildable.swift
3+
// RichEditorSwiftUI
4+
//
5+
// Created by Divyesh Vekariya on 24/09/24.
6+
//
7+
8+
import SwiftUI
9+
10+
extension RichEditor: Buildable {
11+
12+
///A Boolean value that indicates whether the text view is editable.
13+
public func isEditable(_ isEditable: Bool = true) -> Self {
14+
return mutating(keyPath: \.isEditable, value: isEditable)
15+
}
16+
17+
///A Boolean value that determines whether user events are ignored and removed from the event queue.
18+
public func isUserInteractionEnabled(_ enabled: Bool = true) -> Self {
19+
return mutating(keyPath: \.isUserInteractionEnabled, value: enabled)
20+
}
21+
22+
///A Boolean value that determines whether scrolling is enabled.
23+
public func isScrollEnabled(_ enabled: Bool = true) -> Self {
24+
return mutating(keyPath: \.isScrollEnabled, value: enabled)
25+
}
26+
27+
///The maximum number of lines that the text container can store.
28+
public func linelimit(_ linelimit: Int?) -> Self {
29+
return mutating(keyPath: \.linelimit, value: linelimit)
30+
}
31+
32+
///The color of the text.
33+
public func fontColor(_ color: Color? = nil) -> Self {
34+
return mutating(keyPath: \.fontColor, value: color)
35+
}
36+
37+
///The view’s background color.
38+
public func backgroundColor(_ color: Color? = nil) -> Self {
39+
return mutating(keyPath: \.backgroundColor, value: color)
40+
}
41+
42+
///Add padding to all side of textContent
43+
public func textPadding(_ padding: CGFloat? = nil) -> Self {
44+
return mutating(keyPath: \.textPadding, value: padding)
45+
}
46+
47+
///An integer that you can use to identify view objects in your application.
48+
public func tagId(_ tag: Int? = nil) -> Self {
49+
return mutating(keyPath: \.tag, value: tag)
50+
}
51+
}

Sources/RichEditorSwiftUI/UI/Editor/RichEditor.swift

+36-9
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,46 @@ import SwiftUI
1010
public struct RichEditor: View {
1111
@ObservedObject var state: RichEditorState
1212

13+
///A Boolean value that indicates whether the text view is editable.
14+
var isEditable: Bool = true
15+
16+
///A Boolean value that determines whether user events are ignored and removed from the event queue.
17+
var isUserInteractionEnabled: Bool = true
18+
19+
///A Boolean value that determines whether scrolling is enabled.
20+
var isScrollEnabled: Bool = true
21+
22+
///The maximum number of lines that the text container can store.
23+
var linelimit: Int?
24+
25+
///The color of the text.
26+
var fontColor: Color? = nil
27+
28+
///The view’s background color.
29+
var backgroundColor:Color? = nil
30+
31+
///Add padding to all side of textContent
32+
var textPadding: CGFloat? = nil
33+
34+
///An integer that you can use to identify view objects in your application.
35+
var tag: Int? = nil
36+
1337
public init(state: ObservedObject<RichEditorState>) {
1438
self._state = state
1539
}
1640

1741
public var body: some View {
18-
VStack(content: {
19-
EditorToolBarView(state: state)
20-
21-
TextViewWrapper(state: _state,
22-
attributesToApply: $state.attributesToApply,
23-
isScrollEnabled: true,
24-
fontStyle: state.currentFont,
25-
onTextViewEvent: state.onTextViewEvent(_:))
26-
})
42+
TextViewWrapper(state: _state,
43+
attributesToApply: $state.attributesToApply,
44+
isEditable: isEditable,
45+
isUserInteractionEnabled: isUserInteractionEnabled,
46+
isScrollEnabled: isScrollEnabled,
47+
linelimit: linelimit,
48+
fontStyle: state.currentFont,
49+
fontColor: fontColor,
50+
backGroundColor: backgroundColor,
51+
tag: tag,
52+
textPadding: textPadding,
53+
onTextViewEvent: state.onTextViewEvent(_:))
2754
}
2855
}

Sources/RichEditorSwiftUI/UI/Editor/RichEditorState.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import SwiftUI
910

1011
public class RichEditorState: ObservableObject {
1112
private var adapter: EditorAdapter = DefaultAdapter()
@@ -295,7 +296,9 @@ extension RichEditorState {
295296
private func updateAttributes(spans: [(RichTextSpanInternal, shouldApply: Bool)]) {
296297
if attributesToApply == nil {
297298
attributesToApply = (spans: spans, onCompletion: { [weak self] in
298-
self?.attributesToApply = nil
299+
Task { @MainActor in
300+
self?.attributesToApply = nil
301+
}
299302
if let updateQueue = self?.updateAttributesQueue, !updateQueue.isEmpty {
300303
self?.updateAttributes(spans: updateQueue)
301304
self?.updateAttributesQueue.removeAll(where: { item in

Sources/RichEditorSwiftUI/UI/EditorToolBar/EditorToolBarView.swift

+39-10
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@
77

88
import SwiftUI
99

10-
struct EditorToolBarView: View {
10+
public struct EditorToolBarView: View {
11+
@Environment(\.colorScheme) var colorScheme
12+
1113
@ObservedObject var state: RichEditorState
1214

13-
var body: some View {
15+
var selectedColor: Color {
16+
colorScheme == .dark ? .white.opacity(0.3) : .gray.opacity(0.1)
17+
}
18+
19+
public init(state: RichEditorState) {
20+
self.state = state
21+
}
22+
23+
public var body: some View {
1424
LazyHStack(spacing: 5, content: {
1525
ForEach(EditorTool.allCases, id: \.self) { tool in
1626
if tool.isContainManu {
@@ -21,12 +31,15 @@ struct EditorToolBarView: View {
2131
}
2232
})
2333
.frame(height: 50)
34+
.padding(.horizontal, 15)
2435
.frame(maxWidth: .infinity, alignment: .leading)
25-
.background(Color.gray.opacity(0.1))
36+
.background(selectedColor)
37+
.clipShape(.capsule)
2638
}
2739
}
2840

2941
private struct ToggleStyleButton: View {
42+
@Environment(\.colorScheme) var colorScheme
3043

3144
let tool: EditorTool
3245
let appliedTools: Set<TextSpanStyle>
@@ -36,6 +49,13 @@ private struct ToggleStyleButton: View {
3649
tool.isSelected(appliedTools)
3750
}
3851

52+
var normalDarkColor: Color {
53+
colorScheme == .dark ? .white : .black
54+
}
55+
56+
var selectedColor: Color {
57+
colorScheme == .dark ? .gray.opacity(0.4) : .gray.opacity(0.1)
58+
}
3959

4060
var body: some View {
4161
Button(action: {
@@ -45,15 +65,18 @@ private struct ToggleStyleButton: View {
4565
Image(systemName: tool.systemImageName)
4666
.font(.title)
4767
})
48-
.foregroundColor(isSelected ? .blue : .black)
49-
.frame(width: 45, height: 50, alignment: .center)
50-
.padding(.horizontal, 3)
51-
.background(isSelected ? Color.gray.opacity(0.1) : Color.clear)
68+
.foregroundColor(isSelected ? .blue : normalDarkColor)
69+
.frame(width: 40, height: 40, alignment: .center)
70+
.background(isSelected ? selectedColor : Color.clear)
71+
.cornerRadius(5)
72+
.padding(.vertical, 5)
5273
})
5374
}
5475
}
5576

5677
struct TitleStyleButton: View {
78+
@Environment(\.colorScheme) var colorScheme
79+
5780
let tool: EditorTool
5881
let appliedTools: Set<TextSpanStyle>
5982
let setStyle: (TextSpanStyle) -> Void
@@ -64,6 +87,10 @@ struct TitleStyleButton: View {
6487

6588
@State var isExpanded: Bool = false
6689

90+
var normalDarkColor: Color {
91+
colorScheme == .dark ? .white : .black
92+
}
93+
6794
var body: some View {
6895

6996
Menu(content: {
@@ -74,7 +101,7 @@ struct TitleStyleButton: View {
74101
}, label: {
75102
if hasStyle(header.getTextSpanStyle()) {
76103
Label(header.title, systemImage:"checkmark")
77-
.foregroundColor(.blue)
104+
.foregroundColor(normalDarkColor)
78105
} else {
79106
Text(header.title)
80107
}
@@ -88,10 +115,12 @@ struct TitleStyleButton: View {
88115
Image(systemName: "chevron.down")
89116
.font(.subheadline)
90117
})
91-
.foregroundColor(isSelected ? .blue : .black)
92-
.frame(width: 60, height: 50, alignment: .center)
118+
.foregroundColor(isSelected ? .blue : normalDarkColor)
119+
.frame(width: 50, height: 40, alignment: .center)
93120
.padding(.horizontal, 3)
94121
.background(isSelected ? Color.gray.opacity(0.1) : Color.clear)
122+
.cornerRadius(5)
123+
.padding(.vertical, 5)
95124
})
96125
.onTapGesture {
97126
isExpanded.toggle()

0 commit comments

Comments
 (0)