diff --git a/Sources/MarkdownView/Helper/ContentUpdater.swift b/Sources/MarkdownView/Helper/ContentUpdater.swift index a7d51b6d..d37e8ee3 100644 --- a/Sources/MarkdownView/Helper/ContentUpdater.swift +++ b/Sources/MarkdownView/Helper/ContentUpdater.swift @@ -39,12 +39,30 @@ struct MarkdownRenderingModeKey: EnvironmentKey { static var defaultValue: MarkdownRenderingMode = .immediate } +/// Thread to render markdown content on. +public enum MarkdownRenderingThread { + /// Render & Update markdown content on main thread. + case main + /// Render markdown content on background thread, while updating view on main thread. + case background +} + +struct MarkdownRenderingThreadKey: EnvironmentKey { + static var defaultValue: MarkdownRenderingThread = .background +} + extension EnvironmentValues { /// Markdown rendering mode var markdownRenderingMode: MarkdownRenderingMode { get { self[MarkdownRenderingModeKey.self] } set { self[MarkdownRenderingModeKey.self] = newValue } } + + /// Markdown rendering thread + var markdownRenderingThread: MarkdownRenderingThread { + get { self[MarkdownRenderingThreadKey.self] } + set { self[MarkdownRenderingThreadKey.self] = newValue } + } } // MARK: - Markdown Rendering Mode @@ -56,4 +74,11 @@ extension View { public func markdownRenderingMode(_ renderingMode: MarkdownRenderingMode) -> some View { environment(\.markdownRenderingMode, renderingMode) } + + /// The thread to render content. + /// + /// - Parameter thread: The thread for rendering markdown content on. + public func markdownRenderingThread(_ thread: MarkdownRenderingThread) -> some View { + environment(\.markdownRenderingThread, thread) + } } diff --git a/Sources/MarkdownView/MarkdownView.swift b/Sources/MarkdownView/MarkdownView.swift index 78737923..83a4b220 100644 --- a/Sources/MarkdownView/MarkdownView.swift +++ b/Sources/MarkdownView/MarkdownView.swift @@ -11,6 +11,7 @@ public struct MarkdownView: View { @State private var scrollViewRef = ScrollProxyRef.shared @Environment(\.markdownRenderingMode) private var renderingMode + @Environment(\.markdownRenderingThread) private var renderingThread @Environment(\.lineSpacing) private var lineSpacing @Environment(\.fontGroup) private var fontGroup @Environment(\.markdownViewRole) private var role @@ -53,28 +54,32 @@ public struct MarkdownView: View { public var body: some View { ScrollViewReader { scrollProxy in - ZStack { - switch configuration.role { - case .normal: representedView - case .editor: - representedView - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + if renderingThread == .main { + _makeView(text: text) + } else { + ZStack { + switch configuration.role { + case .normal: representedView + case .editor: + representedView + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } } + .onAppear { scrollViewRef.proxy = scrollProxy } } - .onAppear { scrollViewRef.proxy = scrollProxy } } .sizeOfView($viewSize) .containerSize(viewSize) .updateCodeBlocksWhenColorSchemeChanges() .font(fontGroup.body) // Default font - .if(renderingMode == .optimized) { content in + .if(renderingMode == .optimized && renderingThread == .background) { content in content // Received a debouncedText, we need to reload MarkdownView. .onReceive(contentUpdater.textUpdater, perform: makeView(text:)) // Push current text, waiting for next update. .onChange(of: text, perform: contentUpdater.push(_:)) } - .if(renderingMode == .immediate) { content in + .if(renderingMode == .immediate && renderingThread == .background) { content in content // Immediately update MarkdownView when text changes. .onChange(of: text, perform: makeView(text:)) @@ -86,26 +91,26 @@ public struct MarkdownView: View { } private func makeView(text: String) { - func view() -> AnyView { - var renderer = Renderer( - text: text, - configuration: configuration, - interactiveEditHandler: { text in - Task { @MainActor in - self.text = text - self.makeView(text: text) - } - }, - blockDirectiveRenderer: blockDirectiveRenderer, - imageRenderer: imageRenderer - ) - let parseBD = !blockDirectiveRenderer.providers.isEmpty - return renderer.representedView(parseBlockDirectives: parseBD) - } - - representedView = view() + representedView = _makeView(text: text) MarkdownTextStorage.default.text = text } + + private func _makeView(text: String) -> AnyView { + var renderer = Renderer( + text: text, + configuration: configuration, + interactiveEditHandler: { text in + Task { @MainActor in + self.text = text + self.makeView(text: text) + } + }, + blockDirectiveRenderer: blockDirectiveRenderer, + imageRenderer: imageRenderer + ) + let parseBD = !blockDirectiveRenderer.providers.isEmpty + return renderer.representedView(parseBlockDirectives: parseBD) + } } extension MarkdownView {