A customizable UI component that renders ProseMirror documents in SwiftUI.
An example app is available to showcase and enable you to test some of SwiftyProse
's features. It can be found in ./SwiftyProseDemo
.
- Deployment target iOS 14.0+
- Swift 5+
To install SwiftyProse
using Swift Package Manager, add .package(name: "SwiftyProse", url: https://github.com/MaciDE/SwiftyProse.git", from: "1.0.0"),"
to your Package.swift, then follow this integration tutorial.
Once you've installed SwiftyProse
into your project, creating a view that decodes and displays your ProseMirror-document is just a few steps.
- Blockquote
- BulletList
- CodeBlock
- HardBreak
- Heading
- HorizontalRule
- OrderedList
- Paragraph
- Text
- bold
- code
- highlight
- italic
- link
- strike
- superscript
- underline
At the top of the file where you'd like to use SwiftyProse
, import SwiftyProse
:
import SwiftyProse
Create an instance of SwiftyProseDecoder
and use the decode
function to decode a JSON string that represents your document. The decode
function returns a view of type DocNode
that you can render in a view's body.
let JSON = #"""
{
"content" : [
{
"type" : "text",
"text" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam"
},
{
"type": "horizontalRule"
},
{
"type" : "text",
"text" : "nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam"
},
],
"type" : "doc"
}
"""#
var body: some View {
if let doc = decode(str: JSON) {
doc
}
}
private func decode(str: String) -> DocNode? {
do {
let decoder = SwiftyProseDecoder()
let document = try decoder.decode(str)
return document
} catch {
NSLog("SwiftyProse: \(error.localizedDescription)")
}
return nil
}
SwiftyProse offers two options to customize the appearance of the decoded nodes.
To adjust the default style of a node you can make use of a variety of view modifiers that update EnvironmentValues
.
var body: some View {
if let doc = decode(str: json) {
doc
// Change the text color of every text node
.proseTextColor(UIColor.gray)
// Change the link color
.proseLinkColor(UIColor.blue)
// Change the text color of all heading nodes
.proseHeadingTextColor(UIColor.darkText)
// Change the spacing between nodes
.proseNodeSpacing(4)
// Change the spacing between the lines of all paragraphs
.proseParagraphLineSpacing(10)
// Change the font used in all text nodes
.proseTextFont(UIFont.systemFont(ofSize: 20))
// Change the font used in all heading nodes
.proseHeadingFont(UIFont.boldSystemFont(ofSize: 30))
// Change the font used for displaying links
.proseLinkFont(UIFont.systemFont(ofSize: 14, weight: .semibold))
}
}
If you want to replace the default style used to display the nodes you are able to create your own styles.
To create your own style for a specific node, create a struct that confirms to the desired style and create your own view in the makeBody
function. After that you are able to pass your custom style down to the desired nodes.
- BlockquoteStyle
- BulletListStyle
- CodeBlockStyle
- HeadingStyle
- HorizontalRuleStyle
- OrderedListStyle
- ParagraphStyle
Custom heading style that uses SwiftUI's text view to display an attributed string instead of the default style that utilizes a UIextView.
import SwiftyProse
import SwiftUI
@available(iOS 15, *)
public struct CustomHeadingStyle: HeadingStyle {
@Environment(\.proseTextColor) var proseTextColor
@Environment(\.proseTextFont) var proseTextFont
@Environment(\.proseLinkColor) var proseLinkColor
@Environment(\.proseLinkFont) var proseLinkFont
public func makeBody(configuration: Configuration) -> some View {
Text(AttributedString(buildAttributedString(
from: configuration.content,
attributes: configuration.attributes)))
}
private func buildAttributedString(
from nodes: [NodeWrapper],
attributes: HeadingAttributes?)
-> NSAttributedString {
let attributedString = NSMutableAttributedString(
attributedString: nodes.reducedAttributedString(
with: proseTextFont, textColor: proseTextColor))
guard let attrs = attributes else { return attributedString }
let range = NSRange(location: 0, length: attributedString.length)
var font = UIFont(name: "Asap-SemiBold", size: 16) ?? UIFont.systemFont(ofSize: 16)
switch attrs.level {
case 1:
font = font.withSize(32)
case 2:
font = font.withSize(28)
case 3:
font = font.withSize(24)
case 4:
font = font.withSize(20)
case 5:
font = font.withSize(18)
default:
break
}
attributedString.addAttribute(.font, value: font, range: range)
return attributedString
}
}
@available(iOS 15, *)
public extension HeadingStyle where Self == CustomHeadingStyle {
static var custom: Self { .init() }
}
Custom bullet list style that displays different bullets.
import SwiftyProse
import SwiftUI
public struct CustomBulletListStyle: BulletListStyle {
public func makeBody(configuration: Configuration) -> some View {
VStack(spacing: 0) {
ForEach(configuration.content.indices, id: \.self) { i in
let node = configuration.content[i]
HStack(alignment: .firstTextBaseline) {
Text(configuration.nestingLevel % 2 == 0 ? "👉" : "-")
switch node {
case .listItem(let listItem):
listItem
default:
EmptyView()
}
}
}
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.red)
)
}
}
public extension BulletListStyle where Self == CustomBulletListStyle {
static var custom: Self { .init() }
}
For more examples see the demo.