Skip to content

Commit 3ae46fd

Browse files
committed
Refactor MarkdownStore
1 parent 8551c59 commit 3ae46fd

File tree

5 files changed

+147
-132
lines changed

5 files changed

+147
-132
lines changed

MarkdownUI.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ let package = Package(
3030
.package(
3131
name: "NetworkImage",
3232
url: "https://github.com/gonzalezreal/NetworkImage",
33-
from: "3.0.1"
33+
from: "3.1.0"
3434
),
3535
.package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.1.2"),
3636
.package(

Sources/MarkdownUI/Shared/Markdown.swift

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@
4545
@Environment(\.sizeCategory) private var sizeCategory: ContentSizeCategory
4646
@Environment(\.markdownBaseURL) private var markdownBaseURL: URL?
4747
@Environment(\.markdownStyle) private var markdownStyle: MarkdownStyle
48+
@Environment(\.networkImageLoader) private var imageLoader
49+
@Environment(\.networkImageScheduler) private var imageScheduler
4850

49-
private let document: Document
51+
@ObservedObject private var store: MarkdownStore
5052

5153
/// Creates a Markdown view that displays a CommonMark document.
5254
/// - Parameter document: The CommonMark document to display.
5355
public init(_ document: Document) {
54-
self.document = document
56+
store = MarkdownStore(document: document)
5557
}
5658

5759
#if swift(>=5.4)
@@ -61,41 +63,29 @@
6163
#endif
6264

6365
public var body: some View {
64-
PrimitiveMarkdown(
65-
document: document,
66-
baseURL: markdownBaseURL,
67-
writingDirection: NSWritingDirection(layoutDirection: layoutDirection),
68-
alignment: NSTextAlignment(
69-
layoutDirection: layoutDirection,
70-
multilineTextAlignment: multilineTextAlignment
71-
),
72-
style: markdownStyle
73-
)
74-
}
75-
}
76-
77-
@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)
78-
private struct PrimitiveMarkdown: View {
79-
@ObservedObject private var renderer: MarkdownRenderer
80-
81-
init(
82-
document: Document,
83-
baseURL: URL?,
84-
writingDirection: NSWritingDirection,
85-
alignment: NSTextAlignment,
86-
style: MarkdownStyle
87-
) {
88-
renderer = MarkdownRenderer(
89-
document: document,
90-
baseURL: baseURL,
91-
writingDirection: writingDirection,
92-
alignment: alignment,
93-
style: style
94-
)
95-
}
96-
97-
var body: some View {
98-
AttributedText(renderer.attributedString)
66+
switch store.state {
67+
case .notRendered:
68+
Color.clear
69+
.onAppear {
70+
store.send(
71+
.onAppear(
72+
environment: .init(
73+
imageLoader: imageLoader,
74+
mainQueue: imageScheduler,
75+
baseURL: markdownBaseURL,
76+
writingDirection: NSWritingDirection(layoutDirection: layoutDirection),
77+
alignment: NSTextAlignment(
78+
layoutDirection: layoutDirection,
79+
multilineTextAlignment: multilineTextAlignment
80+
),
81+
style: markdownStyle
82+
)
83+
)
84+
)
85+
}
86+
case let .attributedText(value):
87+
AttributedText(value)
88+
}
9989
}
10090
}
10191

Sources/MarkdownUI/Shared/MarkdownRenderer.swift

Lines changed: 0 additions & 92 deletions
This file was deleted.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#if canImport(Combine) && !os(watchOS)
2+
3+
import Combine
4+
import CombineSchedulers
5+
import CommonMark
6+
import Foundation
7+
import NetworkImage
8+
9+
#if os(macOS)
10+
import AppKit
11+
#elseif canImport(UIKit)
12+
import UIKit
13+
#endif
14+
15+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
16+
internal struct MarkdownEnvironment {
17+
var imageLoader: NetworkImageLoader
18+
var mainQueue: AnySchedulerOf<DispatchQueue>
19+
var baseURL: URL?
20+
var writingDirection: NSWritingDirection
21+
var alignment: NSTextAlignment
22+
var style: MarkdownStyle
23+
}
24+
25+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
26+
internal final class MarkdownStore: ObservableObject {
27+
enum Action {
28+
case onAppear(environment: MarkdownEnvironment)
29+
case didLoadTextAttachments(
30+
[String: NSTextAttachment],
31+
document: Document,
32+
environment: MarkdownEnvironment
33+
)
34+
}
35+
36+
enum State: Equatable {
37+
case notRendered(Document)
38+
case attributedText(NSAttributedString)
39+
}
40+
41+
@Published private(set) var state: State
42+
private var cancellables: Set<AnyCancellable> = []
43+
44+
init(document: Document) {
45+
state = .notRendered(document)
46+
}
47+
48+
func send(_ action: Action) {
49+
switch action {
50+
case let .onAppear(environment):
51+
guard case let .notRendered(document) = state else {
52+
return
53+
}
54+
state = .attributedText(
55+
NSAttributedString(
56+
document: document,
57+
writingDirection: environment.writingDirection,
58+
alignment: environment.alignment,
59+
style: environment.style
60+
)
61+
)
62+
let urls = document.imageURLs
63+
if !urls.isEmpty {
64+
environment.imageLoader.textAttachments(for: urls, baseURL: environment.baseURL)
65+
.map { .didLoadTextAttachments($0, document: document, environment: environment) }
66+
.receive(on: environment.mainQueue)
67+
.sink { [weak self] action in
68+
self?.send(action)
69+
}
70+
.store(in: &cancellables)
71+
}
72+
case let .didLoadTextAttachments(attachments, document, environment):
73+
state = .attributedText(
74+
NSAttributedString(
75+
document: document,
76+
attachments: attachments,
77+
writingDirection: environment.writingDirection,
78+
alignment: environment.alignment,
79+
style: environment.style
80+
)
81+
)
82+
}
83+
}
84+
}
85+
86+
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
87+
private extension NetworkImageLoader {
88+
func textAttachments(
89+
for urls: Set<String>,
90+
baseURL: URL?
91+
) -> AnyPublisher<[String: NSTextAttachment], Never> {
92+
let attachmentURLs = urls.compactMap {
93+
URL(string: $0, relativeTo: baseURL)
94+
}
95+
96+
guard !attachmentURLs.isEmpty else {
97+
return Just([:]).eraseToAnyPublisher()
98+
}
99+
100+
let textAttachmentPairs = attachmentURLs.map { url in
101+
image(for: url).map { image -> (String, NSTextAttachment) in
102+
let attachment = ImageAttachment()
103+
attachment.image = image
104+
105+
return (url.relativeString, attachment)
106+
}
107+
}
108+
109+
return Publishers.MergeMany(textAttachmentPairs)
110+
.collect()
111+
.map { Dictionary($0, uniquingKeysWith: { _, last in last }) }
112+
.replaceError(with: [:])
113+
.eraseToAnyPublisher()
114+
}
115+
}
116+
117+
#endif

0 commit comments

Comments
 (0)