Skip to content

Commit

Permalink
Continued working on bullet points.
Browse files Browse the repository at this point in the history
  • Loading branch information
will-lumley committed Feb 4, 2020
1 parent 713b593 commit 6d0eab4
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 12 deletions.
6 changes: 5 additions & 1 deletion Example/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 28 additions & 9 deletions RichEditor/Classes/RichEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public class RichEditor: NSView
}
}

///The marker that will be used for bullet points
fileprivate var bulletPointMarker = NSTextList.MarkerFormat.circle

/**
Returns the FontStyling object that was derived from the selected text, or the future text if nothing is selected
- returns: The FontStyling object for the relevant text
Expand Down Expand Up @@ -138,7 +141,7 @@ extension RichEditor
*/

//Create a text list (the representation of our bullet points)
let textList = NSTextList(markerFormat: NSTextList.MarkerFormat.circle, options: 0)
let textList = NSTextList(markerFormat: self.bulletPointMarker, options: 0)
textList.startingItemNumber = 1

//Create a new paragraph style, and apply our bullet points to it
Expand All @@ -152,7 +155,7 @@ extension RichEditor
//Create an attributed string with the attributes already present in our 'future' text
//Put a \n before the bullet point, so that the bullet point is in it's own line
//Put a \t afte the bullet point so there's some space between the bullet point and the text
let attrStr = NSAttributedString(string: "\n\(textList.marker(forItemNumber: 1))\t", attributes: typingAttributes)
let attrStr = NSAttributedString(string: "\n\t\(textList.marker(forItemNumber: 1)) ", attributes: typingAttributes)
print("attrStr: \(attrStr)")

self.textView.textStorage?.append(attrStr)
Expand Down Expand Up @@ -399,16 +402,32 @@ extension RichEditor: NSTextViewDelegate
{
//Get all the lines of the text
//Get the line of text that we're on
//See if the text in the line of text that we're on has a bullet point in it

// NSInteger insertionPoint = [[[myTextView selectedRanges] objectAtIndex:0] rangeValue].location;
guard let newString = replacementString else { return true }

//guard let currentLocation = textView.selectedRanges.first?.rangeValue.location else { return true }
print("***")
textView.string.enumerateSubstrings(in: textView.string.startIndex..<textView.string.endIndex, options: .byLines) {(substring, substringRange, _, _) in
print("SubString: \(substring)")
//If the user just hit enter/newline
if newString == "\n" {
let currentLine = textView.currentLine()

//If the line that we just hit enter on contains a bullet point marker
//TODO: Check for all decimal point markers
guard var currentLineRange = currentLine.1 else { return true }
guard let currentLineStr = currentLine.2 else { return true }
if currentLineStr.contains("") {
currentLineRange.length = currentLineRange.length + 2

let attributedStr = NSMutableAttributedString(attributedString: textView.attributedString())

//Add another bullet point to this list of bullet points
guard let textList = textView.attributedString().textList(at: affectedCharRange) else { return true }

//Get the current line, and replace it with the current line AND a newline with a new bullet point ready to go
let newLine = NSAttributedString(string: "\(currentLineStr)\n\(textList.marker(forItemNumber: 2))", attributes: attributedStr.attributes)
attributedStr.replaceCharacters(in: currentLineRange, with: newLine)

return false
}
}
print("***")

return true
}
Expand Down
27 changes: 27 additions & 0 deletions RichEditor/Extensions/NSAttributedString+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,33 @@ extension NSAttributedString
return attachments
}

/**
Finds the NSTextList at a given range
- parameter range: The NSRange that we'll search for the NSTextList in
- returns: An NSTextList, if one exists at the given range
*/
public func textList(at searchRange: NSRange) -> NSTextList?
{
var textList: NSTextList?

self.enumerateAttribute(.paragraphStyle, in: self.string.fullRange, options: .longestEffectiveRangeNotRequired, using: {(value, range, finished) in
if value != nil {
if let paragraphStyle = value as? NSParagraphStyle {

//TODO: Improve search to see if this textlist is the one we're after
if paragraphStyle.textLists.count >= 1 {
textList = paragraphStyle.textLists[0]

print("TextList Range: \(range)")
print("Search Range: \(searchRange)")
}
}
}
})

return textList
}

//MARK: - Attribute Checking
/**
Iterates over every font that exists within this NSAttributedString, and checks if any of the fonts contain the desired NSFontTraitMask
Expand Down
54 changes: 52 additions & 2 deletions RichEditor/Extensions/NSTextView+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension NSTextView
}

//We've created the NSAttributedString from the HTML, let's apply it
return self.set(attrFromHTML)
return self.set(attributedString: attrFromHTML)
}

/**
Expand All @@ -53,7 +53,7 @@ extension NSTextView
- returns: A boolean value indicative of if the setting of the NSAttributedString
was successful
*/
public func set(_ attributedString: NSAttributedString) -> Bool
public func set(attributedString: NSAttributedString) -> Bool
{
guard let textStorage = self.textStorage else {
print("Error setting NSAttributedString, TextStorage is nil.")
Expand Down Expand Up @@ -91,4 +91,54 @@ extension NSTextView
print("")
}
}

/**
Calculates which line number (using a 0 based index) our caret is on, the range of this line (in comparison to the whole string), and the string that makes up that line of text.
Will return nil if there is no caret present, and a portion of text is highlighted instead.

A pain point of this function is that it cannot return the current line number when it's found, but rather
has to wait for every single line to be iterated through first. This is because the enumerateSubstrings() function
on the String is not an actual loop, and as such we cannot return or break within it.

- returns: The line number that the caret is on, the range of our line, and the string that makes up that line of text
*/
func currentLine() -> (Int?, NSRange?, String?)
{
//Bail out if the user has selected text
if self.hasSelectedText {
return (nil, nil, nil)
}

//The line number that we're currently iterating on
var lineNumber = 0

//The line number & line of text that we believe the caret to be on
var selectedLineNumber: Int?
var selectedLineRange : NSRange?
var selectedLineOfText: String?

//Iterate over every line in our TextView
self.string.enumerateSubstrings(in: self.string.startIndex..<self.string.endIndex, options: .byLines) {(substring, substringRange, _, _) in
//The range of this current line
let range = NSRange(substringRange, in: self.string)

//Calculate the start location of our line and the end location of our line, in context to our TextView.string as a whole
let startOfLine = range.location
let endOfLine = range.location + range.length

let caretLocation = self.selectedRange().location

//If the CaretLocation is between the start of this line, and the end of this line, we can assume that the caret is on this line
if caretLocation >= startOfLine && caretLocation <= endOfLine {
//Mark the line number
selectedLineNumber = lineNumber
selectedLineOfText = substring
selectedLineRange = range
}

lineNumber += 1
}

return (selectedLineNumber, selectedLineRange, selectedLineOfText)
}
}
28 changes: 28 additions & 0 deletions RichEditor/Extensions/String+Lines.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// String+Lines.swift
// Pods-RichEditor_Example
//
// Created by William Lumley on 4/2/20.
//

import Foundation

extension String
{
/**
Returns an array of strings that is made up of all the "lines" in this string.
- returns: An array of strings that is derived from this string, using a newline as a delimiter
*/
func lines() -> [String]
{
var lines = [String]()

self.enumerateSubstrings(in: self.startIndex..<self.endIndex, options: .byLines) {(substring, substringRange, _, _) in
if let str = substring {
lines.append(str)
}
}

return lines
}
}

0 comments on commit 6d0eab4

Please sign in to comment.