SwiftyMarkdown.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. //
  2. // SwiftyMarkdown.swift
  3. // SwiftyMarkdown
  4. //
  5. // Created by Simon Fairbairn on 05/03/2016.
  6. // Copyright © 2016 Voyage Travel Apps. All rights reserved.
  7. //
  8. import UIKit
  9. protocol FontProperties {
  10. var fontName : String { get set }
  11. var color : UIColor { get set }
  12. }
  13. struct BasicStyles : FontProperties {
  14. var fontName = "AvenirNextCondensed-Medium"
  15. var color = UIColor.blackColor()
  16. }
  17. enum LineType : Int {
  18. case H1, H2, H3, H4, H5, H6, Body, Italic, Bold
  19. }
  20. class SwiftyMarkdown {
  21. var h1 = BasicStyles()
  22. var h2 = BasicStyles()
  23. var h3 = BasicStyles()
  24. var h4 = BasicStyles()
  25. var h5 = BasicStyles()
  26. var h6 = BasicStyles()
  27. var body = BasicStyles()
  28. var link = BasicStyles()
  29. var italic = BasicStyles(fontName: "AvenirNextCondensed-MediumItalic", color: UIColor.blackColor())
  30. var bold = BasicStyles(fontName: "AvenirNextCondensed-Bold", color: UIColor.blackColor())
  31. let string : String
  32. init(string : String ) {
  33. self.string = string
  34. }
  35. init?(url : NSURL ) {
  36. do {
  37. self.string = try NSString(contentsOfURL: url, encoding: NSUTF8StringEncoding) as String
  38. } catch {
  39. self.string = ""
  40. return nil
  41. }
  42. }
  43. func attributedString() -> NSAttributedString {
  44. let attributedString = NSMutableAttributedString(string: "")
  45. let lines = self.string.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
  46. var lineCount = 0
  47. let headings = ["# ", "## ", "### ", "#### ", "##### ", "###### "]
  48. var skipLine = false
  49. for line in lines {
  50. lineCount++
  51. if skipLine {
  52. skipLine = false
  53. continue
  54. }
  55. var headingFound = false
  56. for heading in headings {
  57. if let range = line.rangeOfString(heading) where range.startIndex == line.startIndex {
  58. let startHeadingString = line.stringByReplacingCharactersInRange(range, withString: "")
  59. let endHeadingHash = " " + heading.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
  60. let finalHeadingString = startHeadingString.stringByReplacingOccurrencesOfString(endHeadingHash, withString: "")
  61. // Make Hx where x == current index
  62. let string = attributedStringFromString(finalHeadingString, withType: LineType(rawValue: headings.indexOf(heading)!)!)
  63. attributedString.appendAttributedString(string)
  64. headingFound = true
  65. }
  66. }
  67. if headingFound {
  68. continue
  69. }
  70. if lineCount < lines.count {
  71. let nextLine = lines[lineCount]
  72. if let range = nextLine.rangeOfString("=") where range.startIndex == nextLine.startIndex {
  73. // Make H1
  74. let string = attributedStringFromString(line, withType: .H1)
  75. attributedString.appendAttributedString(string)
  76. skipLine = true
  77. continue
  78. }
  79. if let range = nextLine.rangeOfString("-") where range.startIndex == nextLine.startIndex {
  80. // Make H1
  81. let string = attributedStringFromString(line, withType: .H2)
  82. attributedString.appendAttributedString(string)
  83. skipLine = true
  84. continue
  85. }
  86. }
  87. if line.characters.count > 0 {
  88. let scanner = NSScanner(string: line)
  89. let instructionSet = NSCharacterSet(charactersInString: "\\*_")
  90. scanner.charactersToBeSkipped = nil
  91. var finalString : String = ""
  92. while !scanner.atEnd {
  93. var followingString : NSString?
  94. var string : NSString?
  95. // Get all the characters up to the ones we are interested in
  96. scanner.scanUpToCharactersFromSet(instructionSet, intoString: &string)
  97. if let hasString = string as? String {
  98. let bodyString = attributedStringFromString(hasString, withType: .Body)
  99. attributedString.appendAttributedString(bodyString)
  100. finalString = finalString + hasString
  101. var matchedCharacters : String = ""
  102. var tempCharacters : NSString?
  103. // Scan the ones we are interested in
  104. while scanner.scanCharactersFromSet(instructionSet, intoString: &tempCharacters) {
  105. if let chars = tempCharacters as? String {
  106. matchedCharacters = matchedCharacters + chars
  107. }
  108. }
  109. print("Matched Characters: \(matchedCharacters)")
  110. let location = scanner.scanLocation
  111. // If the next string after the characters is a space, then add it to the final string and continue
  112. if !scanner.scanUpToString(" ", intoString: nil) {
  113. let charAtts = attributedStringFromString(matchedCharacters, withType: .Body)
  114. attributedString.appendAttributedString(charAtts)
  115. } else {
  116. scanner.scanLocation = location
  117. scanner.scanUpToCharactersFromSet(instructionSet, intoString: &followingString)
  118. if let hasString = followingString as? String {
  119. let attString : NSAttributedString
  120. if matchedCharacters.containsString("\\") {
  121. attString = attributedStringFromString(matchedCharacters + hasString, withType: .Body)
  122. } else if matchedCharacters == "**" || matchedCharacters == "__" {
  123. attString = attributedStringFromString(hasString, withType: .Bold)
  124. } else {
  125. attString = attributedStringFromString(hasString, withType: .Italic)
  126. }
  127. attributedString.appendAttributedString(attString)
  128. }
  129. matchedCharacters = ""
  130. while scanner.scanCharactersFromSet(instructionSet, intoString: &tempCharacters) {
  131. if let chars = tempCharacters as? String {
  132. matchedCharacters = matchedCharacters + chars
  133. }
  134. }
  135. if matchedCharacters.containsString("\\") {
  136. let attString = attributedStringFromString(matchedCharacters, withType: .Body)
  137. attributedString.appendAttributedString(attString)
  138. }
  139. }
  140. }
  141. }
  142. }
  143. attributedString.appendAttributedString(NSAttributedString(string: "\n"))
  144. }
  145. return attributedString
  146. }
  147. // Make H1
  148. func attributedStringFromString(string : String, withType type : LineType ) -> NSAttributedString {
  149. var attributes : [String : AnyObject]
  150. let textStyle : String
  151. let fontName : String
  152. var appendNewLine = true
  153. switch type {
  154. case .H1:
  155. fontName = h1.fontName
  156. textStyle = UIFontTextStyleTitle1
  157. attributes = [NSForegroundColorAttributeName : h1.color]
  158. case .H2:
  159. fontName = h2.fontName
  160. textStyle = UIFontTextStyleTitle2
  161. attributes = [NSForegroundColorAttributeName : h2.color]
  162. case .H3:
  163. fontName = h3.fontName
  164. textStyle = UIFontTextStyleTitle3
  165. attributes = [NSForegroundColorAttributeName : h3.color]
  166. case .H4:
  167. fontName = h4.fontName
  168. textStyle = UIFontTextStyleHeadline
  169. attributes = [NSForegroundColorAttributeName : h4.color]
  170. case .H5:
  171. fontName = h5.fontName
  172. textStyle = UIFontTextStyleSubheadline
  173. attributes = [NSForegroundColorAttributeName : h5.color]
  174. case .H6:
  175. fontName = h6.fontName
  176. textStyle = UIFontTextStyleFootnote
  177. attributes = [NSForegroundColorAttributeName : h6.color]
  178. case .Italic:
  179. fontName = italic.fontName
  180. attributes = [NSForegroundColorAttributeName : italic.color]
  181. textStyle = UIFontTextStyleBody
  182. appendNewLine = false
  183. case .Bold:
  184. fontName = bold.fontName
  185. attributes = [NSForegroundColorAttributeName : bold.color]
  186. appendNewLine = false
  187. textStyle = UIFontTextStyleBody
  188. default:
  189. appendNewLine = false
  190. fontName = body.fontName
  191. textStyle = UIFontTextStyleBody
  192. attributes = [NSForegroundColorAttributeName:body.color]
  193. break
  194. }
  195. let font = UIFont.preferredFontForTextStyle(textStyle)
  196. let styleDescriptor = font.fontDescriptor()
  197. let styleSize = styleDescriptor.fontAttributes()[UIFontDescriptorSizeAttribute] as? CGFloat ?? CGFloat(14)
  198. let finalFont : UIFont
  199. if let font = UIFont(name: fontName, size: styleSize) {
  200. finalFont = font
  201. } else {
  202. finalFont = UIFont.preferredFontForTextStyle(textStyle)
  203. }
  204. attributes[NSFontAttributeName] = finalFont
  205. if appendNewLine {
  206. return NSAttributedString(string: string + "\n", attributes: attributes)
  207. } else {
  208. return NSAttributedString(string: string, attributes: attributes)
  209. }
  210. }
  211. }