-
Notifications
You must be signed in to change notification settings - Fork 5
/
NiftyMarkdownFormatter.swift
93 lines (84 loc) · 3.33 KB
/
NiftyMarkdownFormatter.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//
// NiftyMarkdownFormatter.swift
//
// Created by Iiro Alhonen on 10.7.2021.
//
import SwiftUI
// MARK: Public
/**
SwiftUI view with formatted markdown. The formatted markdown is wrapped in a `VStack` with no extra view modifiers.
- Parameter markdown: The text needed to be formatted, as a `String`
- Parameter alignment: The horizontal alignment of the `VStack` in the view. Default is `.center`, like in default `VStack`.
- Parameter spacing: The distance between adjacent subviews, or `nil` if you want the stack to choose a default distance for each pair of subviews.
*/
public struct FormattedMarkdown: View {
let markdown: String
let alignment: HorizontalAlignment
let spacing: CGFloat?
public init(markdown: String, alignment: HorizontalAlignment? = nil, spacing: CGFloat? = nil) {
self.markdown = markdown
self.alignment = alignment ?? .center
self.spacing = spacing
}
public var body: some View {
let formattedStrings = formattedMarkdownArray(markdown: markdown)
VStack(alignment: alignment, spacing: spacing) {
ForEach(0..<formattedStrings.count, id: \.self) { textView in
formattedStrings[textView]
}
}
}
}
/**
Formats the markdown.
- Parameter markdown: the markdown to be formatted as a `String`.
- Returns: array of `Text` views.
*/
public func formattedMarkdownArray(markdown: String) -> [AnyView] {
var formattedViews: [AnyView] = []
let splitStrings: [String] = markdown.components(separatedBy: "\n")
for string in splitStrings {
if string.starts(with: "#") {
let heading = formatHeading(convertMarkdownHeading(string))
formattedViews.append(AnyView(heading))
} else if string.starts(with: "* ") {
formattedViews.append(
AnyView(
HStack {
VStack(alignment: .leading) {
Circle().frame(width: 5, height: 5).padding(.top, 9)
Spacer()
}
Text(formatUnorderedListItem(string))
.multilineTextAlignment(.leading)
}
)
)
} else if string.range(of: "^[0-9].") != nil {
// formattedViews.append(AnyView(Text(formatOrderedListItem(string))))
formattedViews.append(
formatOrderedListItem(string)
)
} else if string.count == 0 {
// Ignore empty lines
} else if string.starts(with: "![") {
if #available(macOS 12.0, *) {
let imageComponents = formatImageComponents(string)
formattedViews.append(AnyView(formatImage(altText: imageComponents.0, url: imageComponents.1)))
} else {
// Fallback on earlier versions
}
} else {
if #available(iOS 15, macOS 12, *) {
if let attributedString = try? AttributedString(markdown: string) {
formattedViews.append(AnyView(Text(attributedString)))
} else {
formattedViews.append(AnyView(Text(string)))
}
} else {
formattedViews.append(AnyView(Text(string)))
}
}
}
return formattedViews
}