Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38920d2e09 | |||
| cce77d50ce | |||
| 23a756f23e | |||
| 27741a1128 | |||
| a57899227d | |||
| 0de132c464 | |||
| f3d1cc33d0 | |||
| 6023b1e959 | |||
| 3a108fe096 | |||
| e6e074ed9f | |||
| ad2d909681 | |||
| a02c383230 | |||
| 9b99765a59 | |||
| 5fae172428 | |||
| 3e0ff29449 | |||
| 3c4b78254d | |||
| e7e4eda3c1 |
@@ -1,6 +1,6 @@
|
||||
# SwiftyMarkdown
|
||||
|
||||
SwiftyMarkdown converts Markdown files and strings into NSAttributedString using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size for whatever font you want
|
||||
SwiftyMarkdown converts Markdown files and strings into NSAttributedString using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size correctly with whatever font you'd like to use
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -11,17 +11,19 @@ Text string
|
||||
|
||||
URL
|
||||
|
||||
|
||||
if let url = NSBundle.mainBundle().URLForResource("file", withExtension: "md"), md = SwiftyMarkdown(url: url ) {
|
||||
md.attributedString()
|
||||
}
|
||||
|
||||
## Customisation
|
||||
|
||||
// Setting the body name will use this font for all the heading styles as well, unless explicitly overridden
|
||||
md.body.fontName = "AvenirNextCondensed-Medium"
|
||||
|
||||
md.h1.color = UIColor.redColor()
|
||||
md.h1.fontName = "AvenirNextCondensed-Bold"
|
||||
|
||||

|
||||
md.italic.color = UIColor.blueColor()
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "SwiftyMarkdown"
|
||||
s.version = "0.1.2"
|
||||
s.version = "0.2.2"
|
||||
s.summary = "Converts Markdown to NSAttributed String"
|
||||
s.homepage = "https://github.com/SimonFairbairn/Stormcloud"
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
F4CE98911C8A921300D735C1 /* SwiftyMarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98901C8A921300D735C1 /* SwiftyMarkdownTests.swift */; };
|
||||
F4CE989C1C8A922E00D735C1 /* SwiftyMarkdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4CE989B1C8A922E00D735C1 /* SwiftyMarkdown.swift */; };
|
||||
F4CE98E91C8AF01300D735C1 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98E61C8AF01300D735C1 /* LICENSE */; };
|
||||
F4CE98EA1C8AF01300D735C1 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = F4CE98E71C8AF01300D735C1 /* README.md */; };
|
||||
F4CE98EB1C8AF01300D735C1 /* SwiftyMarkdown.podspec in Resources */ = {isa = PBXBuildFile; fileRef = F4CE98E81C8AF01300D735C1 /* SwiftyMarkdown.podspec */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -208,7 +207,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4CE98EA1C8AF01300D735C1 /* README.md in Sources */,
|
||||
F4CE989C1C8A922E00D735C1 /* SwiftyMarkdown.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -251,7 +249,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -299,7 +297,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -327,7 +325,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 7;
|
||||
DYLIB_CURRENT_VERSION = 12;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = SwiftyMarkdown/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@@ -346,7 +344,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 7;
|
||||
DYLIB_CURRENT_VERSION = 12;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = SwiftyMarkdown/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.1</string>
|
||||
<string>0.2.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7</string>
|
||||
<string>12</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
|
||||
+199
-100
@@ -10,45 +10,107 @@ import UIKit
|
||||
|
||||
|
||||
public protocol FontProperties {
|
||||
var fontName : String { get set }
|
||||
var fontName : String? { get set }
|
||||
var color : UIColor { get set }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
A struct defining the styles that can be applied to the parsed Markdown. The `fontName` property is optional, and if it's not set then the `fontName` property of the Body style will be applied.
|
||||
|
||||
If that is not set, then the system default will be used.
|
||||
*/
|
||||
public struct BasicStyles : FontProperties {
|
||||
public var fontName = UIFont.preferredFontForTextStyle(UIFontTextStyleBody).fontName
|
||||
public var fontName : String? = UIFont.preferredFontForTextStyle(UIFontTextStyleBody).fontName
|
||||
public var color = UIColor.blackColor()
|
||||
}
|
||||
|
||||
enum LineType : Int {
|
||||
case H1, H2, H3, H4, H5, H6, Body, Italic, Bold, Code
|
||||
case H1, H2, H3, H4, H5, H6, Body
|
||||
}
|
||||
|
||||
enum LineStyle : Int {
|
||||
case None
|
||||
case Italic
|
||||
case Bold
|
||||
case Code
|
||||
case Link
|
||||
|
||||
static func styleFromString(string : String ) -> LineStyle {
|
||||
if string == "**" || string == "__" {
|
||||
return .Bold
|
||||
} else if string == "*" || string == "_" {
|
||||
return .Italic
|
||||
} else if string == "`" {
|
||||
return .Code
|
||||
} else if string == "[" {
|
||||
return .Link
|
||||
} else {
|
||||
return .None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A class that takes a [Markdown](https://daringfireball.net/projects/markdown/) string or file and returns an NSAttributedString with the applied styles. Supports Dynamic Type.
|
||||
public class SwiftyMarkdown {
|
||||
|
||||
/// The styles to apply to any H1 headers found in the Markdown
|
||||
public var h1 = BasicStyles()
|
||||
|
||||
/// The styles to apply to any H2 headers found in the Markdown
|
||||
public var h2 = BasicStyles()
|
||||
|
||||
/// The styles to apply to any H3 headers found in the Markdown
|
||||
public var h3 = BasicStyles()
|
||||
|
||||
/// The styles to apply to any H4 headers found in the Markdown
|
||||
public var h4 = BasicStyles()
|
||||
|
||||
/// The styles to apply to any H5 headers found in the Markdown
|
||||
public var h5 = BasicStyles()
|
||||
|
||||
/// The styles to apply to any H6 headers found in the Markdown
|
||||
public var h6 = BasicStyles()
|
||||
|
||||
/// The default body styles. These are the base styles and will be used for e.g. headers if no other styles override them.
|
||||
public var body = BasicStyles()
|
||||
|
||||
/// The styles to apply to any links found in the Markdown
|
||||
public var link = BasicStyles()
|
||||
public var italic = BasicStyles()
|
||||
public var code = BasicStyles()
|
||||
|
||||
/// The styles to apply to any bold text found in the Markdown
|
||||
public var bold = BasicStyles()
|
||||
|
||||
var previousStyle : String = UIFontTextStyleBody
|
||||
/// The styles to apply to any italic text found in the Markdown
|
||||
public var italic = BasicStyles()
|
||||
|
||||
/// The styles to apply to any code blocks or inline code text found in the Markdown
|
||||
public var code = BasicStyles()
|
||||
|
||||
|
||||
var currentType : LineType = .Body
|
||||
|
||||
|
||||
let string : String
|
||||
let instructionSet = NSCharacterSet(charactersInString: "\\*_`")
|
||||
let instructionSet = NSCharacterSet(charactersInString: "[\\*_`")
|
||||
|
||||
/**
|
||||
|
||||
- parameter string: A string containing [Markdown](https://daringfireball.net/projects/markdown/) syntax to be converted to an NSAttributedString
|
||||
|
||||
- returns: An initialized SwiftyMarkdown object
|
||||
*/
|
||||
public init(string : String ) {
|
||||
self.string = string
|
||||
}
|
||||
|
||||
/**
|
||||
A failable initializer that takes a URL and attempts to read it as a UTF-8 string
|
||||
|
||||
- parameter url: The location of the file to read
|
||||
|
||||
- returns: An initialized SwiftyMarkdown object, or nil if the string couldn't be read
|
||||
*/
|
||||
public init?(url : NSURL ) {
|
||||
|
||||
do {
|
||||
@@ -61,6 +123,11 @@ public class SwiftyMarkdown {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Generates an NSAttributedString from the string or URL passed at initialisation. Custom fonts or styles are applied to the appropriate elements when this method is called.
|
||||
|
||||
- returns: An NSAttributedString with the styles applied
|
||||
*/
|
||||
public func attributedString() -> NSAttributedString {
|
||||
let attributedString = NSMutableAttributedString(string: "")
|
||||
|
||||
@@ -70,127 +137,148 @@ public class SwiftyMarkdown {
|
||||
|
||||
let headings = ["# ", "## ", "### ", "#### ", "##### ", "###### "]
|
||||
|
||||
|
||||
var skipLine = false
|
||||
for line in lines {
|
||||
for theLine in lines {
|
||||
lineCount++
|
||||
if skipLine {
|
||||
skipLine = false
|
||||
continue
|
||||
}
|
||||
var headingFound = false
|
||||
var line = theLine
|
||||
for heading in headings {
|
||||
|
||||
if let range = line.rangeOfString(heading) where range.startIndex == line.startIndex {
|
||||
|
||||
let startHeadingString = line.stringByReplacingCharactersInRange(range, withString: "")
|
||||
let endHeadingHash = " " + heading.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
|
||||
|
||||
// Remove ending
|
||||
let endHeadingString = heading.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
|
||||
line = startHeadingString.stringByReplacingOccurrencesOfString(endHeadingString, withString: "").stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
|
||||
|
||||
let finalHeadingString = startHeadingString.stringByReplacingOccurrencesOfString(endHeadingHash, withString: "")
|
||||
|
||||
// Make Hx where x == current index
|
||||
let string = attributedStringFromString(finalHeadingString, withType: LineType(rawValue: headings.indexOf(heading)!)!)
|
||||
attributedString.appendAttributedString(string)
|
||||
headingFound = true
|
||||
currentType = LineType(rawValue: headings.indexOf(heading)!)!
|
||||
|
||||
// We found a heading so break out of the inner loop
|
||||
break
|
||||
}
|
||||
}
|
||||
if headingFound {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Look for underlined headings
|
||||
if lineCount < lines.count {
|
||||
let nextLine = lines[lineCount]
|
||||
|
||||
if let range = nextLine.rangeOfString("=") where range.startIndex == nextLine.startIndex {
|
||||
// Make H1
|
||||
let string = attributedStringFromString(line, withType: .H1)
|
||||
attributedString.appendAttributedString(string)
|
||||
currentType = .H1
|
||||
// We need to skip the next line
|
||||
skipLine = true
|
||||
continue
|
||||
}
|
||||
|
||||
if let range = nextLine.rangeOfString("-") where range.startIndex == nextLine.startIndex {
|
||||
|
||||
|
||||
// Make H1
|
||||
let string = attributedStringFromString(line, withType: .H2)
|
||||
attributedString.appendAttributedString(string)
|
||||
// Make H2
|
||||
currentType = .H2
|
||||
// We need to skip the next line
|
||||
skipLine = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If this is not an empty line...
|
||||
if line.characters.count > 0 {
|
||||
|
||||
// ...start scanning
|
||||
let scanner = NSScanner(string: line)
|
||||
|
||||
|
||||
// We want to be aware of spaces
|
||||
scanner.charactersToBeSkipped = nil
|
||||
|
||||
while !scanner.atEnd {
|
||||
var string : NSString?
|
||||
|
||||
// Get all the characters up to the ones we are interested in
|
||||
if scanner.scanUpToCharactersFromSet(instructionSet, intoString: &string) {
|
||||
|
||||
if let hasString = string as? String {
|
||||
let bodyString = attributedStringFromString(hasString, withType: .Body)
|
||||
let bodyString = attributedStringFromString(hasString, withStyle: .None)
|
||||
attributedString.appendAttributedString(bodyString)
|
||||
|
||||
let location = scanner.scanLocation
|
||||
|
||||
let matchedCharacters = tagFromScanner(scanner)
|
||||
let matchedCharacters = tagFromScanner(scanner).foundCharacters
|
||||
// If the next string after the characters is a space, then add it to the final string and continue
|
||||
if !scanner.scanUpToString(" ", intoString: nil) {
|
||||
let charAtts = attributedStringFromString(matchedCharacters, withType: .Body)
|
||||
attributedString.appendAttributedString(charAtts)
|
||||
} else {
|
||||
|
||||
let set = NSMutableCharacterSet.whitespaceCharacterSet()
|
||||
set.formUnionWithCharacterSet(NSCharacterSet.punctuationCharacterSet())
|
||||
if scanner.scanUpToCharactersFromSet(set, intoString: nil) {
|
||||
scanner.scanLocation = location
|
||||
|
||||
attributedString.appendAttributedString(self.attributedStringFromScanner(scanner))
|
||||
|
||||
} else if matchedCharacters == "[" {
|
||||
scanner.scanLocation = location
|
||||
attributedString.appendAttributedString(self.attributedStringFromScanner(scanner))
|
||||
} else {
|
||||
let charAtts = attributedStringFromString(matchedCharacters, withStyle: .None)
|
||||
attributedString.appendAttributedString(charAtts)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
attributedString.appendAttributedString(self.attributedStringFromScanner(scanner))
|
||||
attributedString.appendAttributedString(self.attributedStringFromScanner(scanner, atStartOfLine: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append a new line character to the end of the processed line
|
||||
attributedString.appendAttributedString(NSAttributedString(string: "\n"))
|
||||
currentType = .Body
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
func attributedStringFromScanner( scanner : NSScanner) -> NSAttributedString {
|
||||
func attributedStringFromScanner( scanner : NSScanner, atStartOfLine start : Bool = false) -> NSAttributedString {
|
||||
var followingString : NSString?
|
||||
var matchedCharacters = self.tagFromScanner(scanner)
|
||||
let attributedString = NSMutableAttributedString(string: "")
|
||||
scanner.scanUpToCharactersFromSet(instructionSet, intoString: &followingString)
|
||||
if let hasString = followingString as? String {
|
||||
let attString : NSAttributedString
|
||||
|
||||
if matchedCharacters.containsString("\\") {
|
||||
attString = attributedStringFromString(matchedCharacters.stringByReplacingOccurrencesOfString("\\", withString: "") + hasString, withType: .Body)
|
||||
} else if matchedCharacters == "**" || matchedCharacters == "__" {
|
||||
attString = attributedStringFromString(hasString, withType: .Bold)
|
||||
} else if matchedCharacters == "`" {
|
||||
attString = attributedStringFromString("\t" + hasString, withType: .Code)
|
||||
} else {
|
||||
attString = attributedStringFromString(hasString, withType: .Italic)
|
||||
}
|
||||
attributedString.appendAttributedString(attString)
|
||||
}
|
||||
matchedCharacters = self.tagFromScanner(scanner)
|
||||
|
||||
let results = self.tagFromScanner(scanner)
|
||||
|
||||
var style = LineStyle.styleFromString(results.foundCharacters)
|
||||
|
||||
if matchedCharacters.containsString("\\") {
|
||||
var attributes = [String : AnyObject]()
|
||||
if style == .Link {
|
||||
|
||||
let attString = attributedStringFromString(matchedCharacters.stringByReplacingOccurrencesOfString("\\", withString: ""), withType: .Body)
|
||||
var linkText : NSString?
|
||||
var linkURL : NSString?
|
||||
let linkCharacters = NSCharacterSet(charactersInString: "]()")
|
||||
|
||||
scanner.scanUpToCharactersFromSet(linkCharacters, intoString: &linkText)
|
||||
scanner.scanCharactersFromSet(linkCharacters, intoString: nil)
|
||||
scanner.scanUpToCharactersFromSet(linkCharacters, intoString: &linkURL)
|
||||
scanner.scanCharactersFromSet(linkCharacters, intoString: nil)
|
||||
|
||||
|
||||
if let hasLink = linkText, hasURL = linkURL {
|
||||
followingString = hasLink as String
|
||||
attributes[NSLinkAttributeName] = hasURL as String
|
||||
} else {
|
||||
style = .None
|
||||
}
|
||||
} else {
|
||||
scanner.scanUpToCharactersFromSet(instructionSet, intoString: &followingString)
|
||||
}
|
||||
|
||||
let attributedString = attributedStringFromString(results.escapedCharacters, withStyle: style).mutableCopy() as! NSMutableAttributedString
|
||||
if let hasString = followingString as? String {
|
||||
|
||||
|
||||
|
||||
let prefix = ( style == .Code && start ) ? "\t" : ""
|
||||
let attString = attributedStringFromString(prefix + hasString, withStyle: style, attributes: attributes)
|
||||
attributedString.appendAttributedString(attString)
|
||||
}
|
||||
let suffix = self.tagFromScanner(scanner)
|
||||
attributedString.appendAttributedString(attributedStringFromString(suffix.escapedCharacters, withStyle: style))
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
func tagFromScanner( scanner : NSScanner ) -> String {
|
||||
func tagFromScanner( scanner : NSScanner ) -> (foundCharacters : String, escapedCharacters : String) {
|
||||
var matchedCharacters : String = ""
|
||||
var tempCharacters : NSString?
|
||||
|
||||
@@ -200,20 +288,34 @@ public class SwiftyMarkdown {
|
||||
matchedCharacters = matchedCharacters + chars
|
||||
}
|
||||
}
|
||||
return matchedCharacters
|
||||
var foundCharacters : String = ""
|
||||
|
||||
while matchedCharacters.containsString("\\") {
|
||||
if let hasRange = matchedCharacters.rangeOfString("\\") {
|
||||
|
||||
let newRange = Range(start: hasRange.startIndex, end: hasRange.endIndex.advancedBy(1))
|
||||
foundCharacters = foundCharacters + matchedCharacters.substringWithRange(newRange)
|
||||
|
||||
matchedCharacters.removeRange(newRange)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (matchedCharacters, foundCharacters.stringByReplacingOccurrencesOfString("\\", withString: ""))
|
||||
}
|
||||
|
||||
|
||||
// Make H1
|
||||
|
||||
func attributedStringFromString(string : String, withType type : LineType ) -> NSAttributedString {
|
||||
var attributes : [String : AnyObject]
|
||||
func attributedStringFromString(string : String, withStyle style : LineStyle, var attributes : [String : AnyObject] = [:] ) -> NSAttributedString {
|
||||
let textStyle : String
|
||||
let fontName : String
|
||||
var fontName : String?
|
||||
|
||||
// What type are we and is there a font name set?
|
||||
|
||||
var appendNewLine = true
|
||||
|
||||
switch type {
|
||||
switch currentType {
|
||||
case .H1:
|
||||
fontName = h1.fontName
|
||||
if #available(iOS 9, *) {
|
||||
@@ -221,7 +323,7 @@ public class SwiftyMarkdown {
|
||||
} else {
|
||||
textStyle = UIFontTextStyleHeadline
|
||||
}
|
||||
attributes = [NSForegroundColorAttributeName : h1.color]
|
||||
attributes[NSForegroundColorAttributeName] = h1.color
|
||||
case .H2:
|
||||
fontName = h2.fontName
|
||||
if #available(iOS 9, *) {
|
||||
@@ -229,7 +331,7 @@ public class SwiftyMarkdown {
|
||||
} else {
|
||||
textStyle = UIFontTextStyleHeadline
|
||||
}
|
||||
attributes = [NSForegroundColorAttributeName : h2.color]
|
||||
attributes[NSForegroundColorAttributeName] = h2.color
|
||||
case .H3:
|
||||
fontName = h3.fontName
|
||||
if #available(iOS 9, *) {
|
||||
@@ -237,61 +339,62 @@ public class SwiftyMarkdown {
|
||||
} else {
|
||||
textStyle = UIFontTextStyleSubheadline
|
||||
}
|
||||
attributes = [NSForegroundColorAttributeName : h3.color]
|
||||
attributes[NSForegroundColorAttributeName] = h3.color
|
||||
case .H4:
|
||||
fontName = h4.fontName
|
||||
textStyle = UIFontTextStyleHeadline
|
||||
attributes = [NSForegroundColorAttributeName : h4.color]
|
||||
attributes[NSForegroundColorAttributeName] = h4.color
|
||||
case .H5:
|
||||
fontName = h5.fontName
|
||||
textStyle = UIFontTextStyleSubheadline
|
||||
attributes = [NSForegroundColorAttributeName : h5.color]
|
||||
attributes[NSForegroundColorAttributeName] = h5.color
|
||||
case .H6:
|
||||
fontName = h6.fontName
|
||||
textStyle = UIFontTextStyleFootnote
|
||||
attributes = [NSForegroundColorAttributeName : h6.color]
|
||||
case .Italic:
|
||||
fontName = italic.fontName
|
||||
attributes = [NSForegroundColorAttributeName : italic.color]
|
||||
textStyle = previousStyle
|
||||
appendNewLine = false
|
||||
case .Bold:
|
||||
fontName = bold.fontName
|
||||
attributes = [NSForegroundColorAttributeName : bold.color]
|
||||
appendNewLine = false
|
||||
textStyle = previousStyle
|
||||
case .Code:
|
||||
fontName = code.fontName
|
||||
attributes = [NSForegroundColorAttributeName : code.color]
|
||||
appendNewLine = false
|
||||
textStyle = previousStyle
|
||||
|
||||
attributes[NSForegroundColorAttributeName] = h6.color
|
||||
default:
|
||||
appendNewLine = false
|
||||
fontName = body.fontName
|
||||
textStyle = UIFontTextStyleBody
|
||||
attributes = [NSForegroundColorAttributeName:body.color]
|
||||
attributes[NSForegroundColorAttributeName] = body.color
|
||||
break
|
||||
}
|
||||
previousStyle = textStyle
|
||||
|
||||
// Check for code
|
||||
|
||||
if style == .Code {
|
||||
fontName = code.fontName
|
||||
attributes[NSForegroundColorAttributeName] = code.color
|
||||
}
|
||||
|
||||
if style == .Link {
|
||||
fontName = link.fontName
|
||||
attributes[NSForegroundColorAttributeName] = link.color
|
||||
}
|
||||
|
||||
// Fallback to body
|
||||
if let _ = fontName {
|
||||
|
||||
} else {
|
||||
fontName = body.fontName
|
||||
}
|
||||
|
||||
let font = UIFont.preferredFontForTextStyle(textStyle)
|
||||
let styleDescriptor = font.fontDescriptor()
|
||||
let styleSize = styleDescriptor.fontAttributes()[UIFontDescriptorSizeAttribute] as? CGFloat ?? CGFloat(14)
|
||||
|
||||
var finalFont : UIFont
|
||||
if let font = UIFont(name: fontName, size: styleSize) {
|
||||
if let finalFontName = fontName, font = UIFont(name: finalFontName, size: styleSize) {
|
||||
finalFont = font
|
||||
} else {
|
||||
finalFont = UIFont.preferredFontForTextStyle(textStyle)
|
||||
}
|
||||
|
||||
let finalFontDescriptor = finalFont.fontDescriptor()
|
||||
if type == .Italic {
|
||||
if style == .Italic {
|
||||
let italicDescriptor = finalFontDescriptor.fontDescriptorWithSymbolicTraits(.TraitItalic)
|
||||
finalFont = UIFont(descriptor: italicDescriptor, size: styleSize)
|
||||
}
|
||||
if type == .Bold {
|
||||
if style == .Bold {
|
||||
let boldDescriptor = finalFontDescriptor.fontDescriptorWithSymbolicTraits(.TraitBold)
|
||||
finalFont = UIFont(descriptor: boldDescriptor, size: styleSize)
|
||||
}
|
||||
@@ -299,10 +402,6 @@ public class SwiftyMarkdown {
|
||||
|
||||
attributes[NSFontAttributeName] = finalFont
|
||||
|
||||
if appendNewLine {
|
||||
return NSAttributedString(string: string + "\n", attributes: attributes)
|
||||
} else {
|
||||
return NSAttributedString(string: string, attributes: attributes)
|
||||
}
|
||||
return NSAttributedString(string: string, attributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
|
||||
self.textView.dataDetectorTypes = UIDataDetectorTypes.All
|
||||
if let url = NSBundle.mainBundle().URLForResource("example", withExtension: "md"), md = SwiftyMarkdown(url: url) {
|
||||
md.h2.fontName = "AvenirNextCondensed-Bold"
|
||||
md.h2.color = UIColor.redColor()
|
||||
|
||||
@@ -4,7 +4,7 @@ SwiftyMarkdown is a Swift-based *Markdown* parser that converts *Markdown* files
|
||||
|
||||
## Features
|
||||
|
||||
Customise fonts and colours easily in a Swift like way:
|
||||
Customise fonts and colours easily in a Swift-like way:
|
||||
|
||||
`md.code.fontName = "CourierNewPSMT"`
|
||||
|
||||
@@ -13,5 +13,5 @@ Customise fonts and colours easily in a Swift like way:
|
||||
|
||||
*An italic line*
|
||||
|
||||
It ignores random * and correctly handles \*escaped\* asterisks and \_underlines\_.
|
||||
It ignores random * and correctly handles escaped \*asterisks\* and \_underlines\_ and has error handling for mismatched tags (\*\*bold\* == **bold*). It also supports inline Markdown [Links](http://voyagetravelapps.com/)
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.1</string>
|
||||
<string>0.2.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7</string>
|
||||
<string>12</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -21,16 +21,260 @@ class SwiftyMarkdownTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measureBlock {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
func testThatOctothorpeHeadersAreHandledCorrectly() {
|
||||
|
||||
let headerString = "# Header 1\n## Header 2 ##\n### Header 3 ### \n#### Header 4#### \n##### Header 5\n###### Header 6"
|
||||
let headerStringWithBold = "# **Bold Header 1**"
|
||||
let headerStringWithItalic = "## Header 2 _With Italics_"
|
||||
|
||||
var md = SwiftyMarkdown(string: headerString)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 1\nHeader 2\nHeader 3\nHeader 4\nHeader 5\nHeader 6\n")
|
||||
|
||||
md = SwiftyMarkdown(string: headerStringWithBold)
|
||||
XCTAssertEqual(md.attributedString().string, "Bold Header 1\n")
|
||||
|
||||
md = SwiftyMarkdown(string: headerStringWithItalic)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 2 With Italics\n")
|
||||
|
||||
}
|
||||
|
||||
func testThatUndelinedHeadersAreHandledCorrectly() {
|
||||
let h1String = "Header 1\n===\nSome following text"
|
||||
let h2String = "Header 2\n---\nSome following text"
|
||||
|
||||
let h1StringWithBold = "Header 1 **With Bold**\n===\nSome following text"
|
||||
let h2StringWithItalic = "Header 2 _With Italic_\n---\nSome following text"
|
||||
let h2StringWithCode = "Header 2 `With Code`\n---\nSome following text"
|
||||
|
||||
var md = SwiftyMarkdown(string: h1String)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 1\nSome following text\n")
|
||||
|
||||
md = SwiftyMarkdown(string: h2String)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 2\nSome following text\n")
|
||||
|
||||
md = SwiftyMarkdown(string: h1StringWithBold)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 1 With Bold\nSome following text\n")
|
||||
|
||||
md = SwiftyMarkdown(string: h2StringWithItalic)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 2 With Italic\nSome following text\n")
|
||||
|
||||
md = SwiftyMarkdown(string: h2StringWithCode)
|
||||
XCTAssertEqual(md.attributedString().string, "Header 2 With Code\nSome following text\n")
|
||||
}
|
||||
|
||||
func testThatRegularTraitsAreParsedCorrectly() {
|
||||
let boldAtStartOfString = "**A bold string**"
|
||||
let boldWithinString = "A string with a **bold** word"
|
||||
let codeAtStartOfString = "`Code (should be indented)`"
|
||||
let codeWithinString = "A string with `code` (should not be indented)"
|
||||
let italicAtStartOfString = "*An italicised string*"
|
||||
let italicWithinString = "A string with *italicised* text"
|
||||
|
||||
let multipleBoldWords = "__A bold string__ with a **mix** **of** bold __styles__"
|
||||
let multipleCodeWords = "`A code string` with multiple `code` `instances`"
|
||||
let multipleItalicWords = "_An italic string_ with a *mix* _of_ italic *styles*"
|
||||
|
||||
let longMixedString = "_An italic string_, **follwed by a bold one**, `with some code`, \\*\\*and some\\*\\* \\_escaped\\_ \\`characters\\`, `ending` *with* __more__ variety."
|
||||
|
||||
|
||||
var md = SwiftyMarkdown(string: boldAtStartOfString)
|
||||
XCTAssertEqual(md.attributedString().string, "A bold string\n")
|
||||
|
||||
md = SwiftyMarkdown(string: boldWithinString)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with a bold word\n")
|
||||
|
||||
md = SwiftyMarkdown(string: codeAtStartOfString)
|
||||
XCTAssertEqual(md.attributedString().string, "\tCode (should be indented)\n")
|
||||
|
||||
md = SwiftyMarkdown(string: codeWithinString)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with code (should not be indented)\n")
|
||||
|
||||
md = SwiftyMarkdown(string: italicAtStartOfString)
|
||||
XCTAssertEqual(md.attributedString().string, "An italicised string\n")
|
||||
|
||||
md = SwiftyMarkdown(string: italicWithinString)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with italicised text\n")
|
||||
|
||||
md = SwiftyMarkdown(string: multipleBoldWords)
|
||||
XCTAssertEqual(md.attributedString().string, "A bold string with a mix of bold styles\n")
|
||||
|
||||
md = SwiftyMarkdown(string: multipleCodeWords)
|
||||
XCTAssertEqual(md.attributedString().string, "\tA code string with multiple code instances\n")
|
||||
|
||||
md = SwiftyMarkdown(string: multipleItalicWords)
|
||||
XCTAssertEqual(md.attributedString().string, "An italic string with a mix of italic styles\n")
|
||||
|
||||
md = SwiftyMarkdown(string: longMixedString)
|
||||
XCTAssertEqual(md.attributedString().string, "An italic string, follwed by a bold one, with some code, **and some** _escaped_ `characters`, ending with more variety.\n")
|
||||
|
||||
}
|
||||
|
||||
func testThatMarkdownMistakesAreHandledAppropriately() {
|
||||
let mismatchedBoldCharactersAtStart = "**This should be bold*"
|
||||
let mismatchedBoldCharactersWithin = "A string *that should be italic**"
|
||||
|
||||
var md = SwiftyMarkdown(string: mismatchedBoldCharactersAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "This should be bold\n")
|
||||
|
||||
md = SwiftyMarkdown(string: mismatchedBoldCharactersWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string that should be italic\n")
|
||||
|
||||
}
|
||||
|
||||
func testThatEscapedCharactersAreEscapedCorrectly() {
|
||||
let escapedBoldAtStart = "\\*\\*A normal string\\*\\*"
|
||||
let escapedBoldWithin = "A string with \\*\\*escaped\\*\\* asterisks"
|
||||
|
||||
let escapedItalicAtStart = "\\_A normal string\\_"
|
||||
let escapedItalicWithin = "A string with \\_escaped\\_ underscores"
|
||||
|
||||
let escapedBackticksAtStart = "\\`A normal string\\`"
|
||||
let escapedBacktickWithin = "A string with \\`escaped\\` backticks"
|
||||
|
||||
let oneEscapedAsteriskOneNormalAtStart = "\\**A normal string\\**"
|
||||
let oneEscapedAsteriskOneNormalWithin = "A string with \\**escaped\\** asterisks"
|
||||
|
||||
let oneEscapedAsteriskTwoNormalAtStart = "\\***A normal string*\\**"
|
||||
let oneEscapedAsteriskTwoNormalWithin = "A string with *\\**escaped**\\* asterisks"
|
||||
|
||||
var md = SwiftyMarkdown(string: escapedBoldAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "**A normal string**\n")
|
||||
|
||||
md = SwiftyMarkdown(string: escapedBoldWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with **escaped** asterisks\n")
|
||||
|
||||
md = SwiftyMarkdown(string: escapedItalicAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "_A normal string_\n")
|
||||
|
||||
md = SwiftyMarkdown(string: escapedItalicWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with _escaped_ underscores\n")
|
||||
|
||||
md = SwiftyMarkdown(string: escapedBackticksAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "`A normal string`\n")
|
||||
|
||||
md = SwiftyMarkdown(string: escapedBacktickWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with `escaped` backticks\n")
|
||||
|
||||
md = SwiftyMarkdown(string: oneEscapedAsteriskOneNormalAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "*A normal string*\n")
|
||||
|
||||
md = SwiftyMarkdown(string: oneEscapedAsteriskOneNormalWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with *escaped* asterisks\n")
|
||||
|
||||
md = SwiftyMarkdown(string: oneEscapedAsteriskTwoNormalAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "*A normal string*\n")
|
||||
|
||||
md = SwiftyMarkdown(string: oneEscapedAsteriskTwoNormalWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A string with *escaped* asterisks\n")
|
||||
|
||||
}
|
||||
|
||||
func testThatAsterisksAndUnderscoresNotAttachedToWordsAreNotRemoved() {
|
||||
let asteriskSpace = "An asterisk followed by a space: * "
|
||||
let backtickSpace = "A backtick followed by a space: ` "
|
||||
let underscoreSpace = "An underscore followed by a space: _ "
|
||||
|
||||
let asteriskFullStop = "Two asterisks followed by a full stop: **."
|
||||
let backtickFullStop = "Two backticks followed by a full stop: ``."
|
||||
let underscoreFullStop = "Two underscores followed by a full stop: __."
|
||||
|
||||
let asteriskComma = "An asterisk followed by a full stop: *, *"
|
||||
let backtickComma = "A backtick followed by a space: `, `"
|
||||
let underscoreComma = "An underscore followed by a space: _, _"
|
||||
|
||||
let asteriskWithBold = "A **bold** word followed by an asterisk * "
|
||||
let backtickWithCode = "A `code` word followed by a backtick ` "
|
||||
let underscoreWithItalic = "An _italic_ word followed by an underscore _ "
|
||||
|
||||
var md = SwiftyMarkdown(string: asteriskSpace)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskSpace + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickSpace)
|
||||
XCTAssertEqual(md.attributedString().string, backtickSpace + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreSpace)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreSpace + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskFullStop + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, backtickFullStop + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreFullStop)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreFullStop + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskComma)
|
||||
XCTAssertEqual(md.attributedString().string, asteriskComma + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickComma)
|
||||
XCTAssertEqual(md.attributedString().string, backtickComma + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreComma)
|
||||
XCTAssertEqual(md.attributedString().string, underscoreComma + "\n")
|
||||
|
||||
md = SwiftyMarkdown(string: asteriskWithBold)
|
||||
XCTAssertEqual(md.attributedString().string, "A bold word followed by an asterisk * \n")
|
||||
|
||||
md = SwiftyMarkdown(string: backtickWithCode)
|
||||
XCTAssertEqual(md.attributedString().string, "A code word followed by a backtick ` \n")
|
||||
|
||||
md = SwiftyMarkdown(string: underscoreWithItalic)
|
||||
XCTAssertEqual(md.attributedString().string, "An italic word followed by an underscore _ \n")
|
||||
|
||||
}
|
||||
|
||||
|
||||
func testForLinks() {
|
||||
|
||||
let linkAtStart = "[Link at start](http://voyagetravelapps.com/)"
|
||||
let linkWithin = "A [Link](http://voyagetravelapps.com/)"
|
||||
let headerLink = "## [Header link](http://voyagetravelapps.com/)"
|
||||
|
||||
let multipleLinks = "[Link 1](http://voyagetravelapps.com/), [Link 2](http://voyagetravelapps.com/)"
|
||||
|
||||
let mailtoAndTwitterLinks = "Email us at [simon@voyagetravelapps.com](mailto:simon@voyagetravelapps.com) Twitter [@VoyageTravelApp](twitter://user?screen_name=VoyageTravelApp)"
|
||||
|
||||
let syntaxErrorSquareBracketAtStart = "[Link with missing square(http://voyagetravelapps.com/)"
|
||||
let syntaxErrorSquareBracketWithin = "A [Link(http://voyagetravelapps.com/)"
|
||||
|
||||
let syntaxErrorParenthesisAtStart = "[Link with missing parenthesis](http://voyagetravelapps.com/"
|
||||
let syntaxErrorParenthesisWithin = "A [Link](http://voyagetravelapps.com/"
|
||||
|
||||
var md = SwiftyMarkdown(string: linkAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "Link at start\n")
|
||||
|
||||
md = SwiftyMarkdown(string: linkWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A Link\n")
|
||||
|
||||
md = SwiftyMarkdown(string: headerLink)
|
||||
XCTAssertEqual(md.attributedString().string, "Header link\n")
|
||||
|
||||
md = SwiftyMarkdown(string: multipleLinks)
|
||||
XCTAssertEqual(md.attributedString().string, "Link 1, Link 2\n")
|
||||
|
||||
md = SwiftyMarkdown(string: syntaxErrorSquareBracketAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "Link with missing square\n")
|
||||
|
||||
md = SwiftyMarkdown(string: syntaxErrorSquareBracketWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A Link\n")
|
||||
|
||||
md = SwiftyMarkdown(string: syntaxErrorParenthesisAtStart)
|
||||
XCTAssertEqual(md.attributedString().string, "Link with missing parenthesis\n")
|
||||
|
||||
md = SwiftyMarkdown(string: syntaxErrorParenthesisWithin)
|
||||
XCTAssertEqual(md.attributedString().string, "A Link\n")
|
||||
|
||||
md = SwiftyMarkdown(string: mailtoAndTwitterLinks)
|
||||
XCTAssertEqual(md.attributedString().string, "Email us at simon@voyagetravelapps.com Twitter @VoyageTravelApp\n")
|
||||
|
||||
|
||||
|
||||
// let mailtoAndTwitterLinks = "Twitter [@VoyageTravelApp](twitter://user?screen_name=VoyageTravelApp)"
|
||||
// let md = SwiftyMarkdown(string: mailtoAndTwitterLinks)
|
||||
// XCTAssertEqual(md.attributedString().string, "Twitter @VoyageTravelApp\n")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user