Commit c693872f by Daniel Dahan

editor: reworked TextStorage and Text into Editor

parent b55650de
......@@ -7,6 +7,19 @@
objects = {
/* Begin PBXBuildFile section */
961276631DCD8B1800A7D920 /* CharacterAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961276621DCD8B1800A7D920 /* CharacterAttribute.swift */; };
961276641DCD8CF200A7D920 /* PresenterCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9631A7C01D95E3AC00CFB109 /* PresenterCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961276651DCD8CF200A7D920 /* Capture.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0D1DBE6AF600DA84DB /* Capture.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961276661DCD8CF200A7D920 /* CapturePreview.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0F1DBE6AF600DA84DB /* CapturePreview.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961276671DCD8CF200A7D920 /* CaptureController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B0E1DBE6AF600DA84DB /* CaptureController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961276681DCD8CF200A7D920 /* Editor.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961DED451DCC40C500F425B6 /* Editor.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961276691DCD8CF200A7D920 /* EditorController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961DED4A1DCC546100F425B6 /* EditorController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766A1DCD8CF200A7D920 /* HeightPreset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CB9A1DAD3D1D003E2611 /* HeightPreset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766B1DCD8CF200A7D920 /* PhotoLibrary.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B161DBE6B1800DA84DB /* PhotoLibrary.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766C1DCD8CF200A7D920 /* PhotoLibraryController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96717B171DBE6B1800DA84DB /* PhotoLibraryController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766D1DCD8CF200A7D920 /* Pulse.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9631A7C61D95E5D900CFB109 /* Pulse.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766E1DCD8CF200A7D920 /* Display.swift in Headers */ = {isa = PBXBuildFile; fileRef = 9626CA961DAB53A8003E2611 /* Display.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9612766F1DCD8CF200A7D920 /* CharacterAttribute.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961276621DCD8B1800A7D920 /* CharacterAttribute.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961DED461DCC40C500F425B6 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961DED451DCC40C500F425B6 /* Editor.swift */; };
961DED4B1DCC546100F425B6 /* EditorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961DED4A1DCC546100F425B6 /* EditorController.swift */; };
961EFC581D738FF600E84652 /* SnackbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961EFC571D738FF600E84652 /* SnackbarController.swift */; };
......@@ -99,7 +112,6 @@
96BCB7E11CB40DC500C806FE /* Bar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7981CB40DC500C806FE /* Bar.swift */; };
96BCB7E21CB40DC500C806FE /* RootController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7991CB40DC500C806FE /* RootController.swift */; };
96BCB7E31CB40DC500C806FE /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79A1CB40DC500C806FE /* TabBar.swift */; };
96BCB7E41CB40DC500C806FE /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79B1CB40DC500C806FE /* Text.swift */; };
96BCB7E51CB40DC500C806FE /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79C1CB40DC500C806FE /* TextField.swift */; };
96BCB7E61CB40DC500C806FE /* TextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79D1CB40DC500C806FE /* TextStorage.swift */; };
96BCB7E71CB40DC500C806FE /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB79E1CB40DC500C806FE /* TextView.swift */; };
......@@ -159,7 +171,6 @@
96BCB8461CB4115200C806FE /* NavigationBar.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7901CB40DC500C806FE /* NavigationBar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8471CB4115200C806FE /* NavigationController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7911CB40DC500C806FE /* NavigationController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8481CB4115200C806FE /* NavigationItem.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7921CB40DC500C806FE /* NavigationItem.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8491CB4115200C806FE /* Text.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB79B1CB40DC500C806FE /* Text.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB84A1CB4115200C806FE /* TextField.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB79C1CB40DC500C806FE /* TextField.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB84B1CB4115200C806FE /* TextStorage.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB79D1CB40DC500C806FE /* TextStorage.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB84C1CB4115200C806FE /* TextView.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB79E1CB40DC500C806FE /* TextView.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -205,6 +216,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; };
961DED451DCC40C500F425B6 /* Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = "<group>"; };
961DED4A1DCC546100F425B6 /* EditorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorController.swift; sourceTree = "<group>"; };
961EFC571D738FF600E84652 /* SnackbarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnackbarController.swift; sourceTree = "<group>"; };
......@@ -288,7 +300,6 @@
96BCB7981CB40DC500C806FE /* Bar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bar.swift; sourceTree = "<group>"; };
96BCB7991CB40DC500C806FE /* RootController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootController.swift; sourceTree = "<group>"; };
96BCB79A1CB40DC500C806FE /* TabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
96BCB79B1CB40DC500C806FE /* Text.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Text.swift; sourceTree = "<group>"; };
96BCB79C1CB40DC500C806FE /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
96BCB79D1CB40DC500C806FE /* TextStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStorage.swift; sourceTree = "<group>"; };
96BCB79E1CB40DC500C806FE /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
......@@ -772,7 +783,7 @@
96BCB80E1CB4110E00C806FE /* TextView */ = {
isa = PBXGroup;
children = (
96BCB79B1CB40DC500C806FE /* Text.swift */,
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */,
96BCB79D1CB40DC500C806FE /* TextStorage.swift */,
96BCB79E1CB40DC500C806FE /* TextView.swift */,
);
......@@ -845,7 +856,6 @@
96BCB8461CB4115200C806FE /* NavigationBar.swift in Headers */,
96BCB8471CB4115200C806FE /* NavigationController.swift in Headers */,
96BCB8481CB4115200C806FE /* NavigationItem.swift in Headers */,
96BCB8491CB4115200C806FE /* Text.swift in Headers */,
96BCB84A1CB4115200C806FE /* TextField.swift in Headers */,
96BCB84B1CB4115200C806FE /* TextStorage.swift in Headers */,
96BCB84C1CB4115200C806FE /* TextView.swift in Headers */,
......@@ -875,6 +885,18 @@
9697F7C61D8F2573004741EC /* PageTabBarController.swift in Headers */,
9697F7CB1D8F2573004741EC /* Snackbar.swift in Headers */,
9697F7CC1D8F2573004741EC /* SnackbarController.swift in Headers */,
961276641DCD8CF200A7D920 /* PresenterCard.swift in Headers */,
961276651DCD8CF200A7D920 /* Capture.swift in Headers */,
961276661DCD8CF200A7D920 /* CapturePreview.swift in Headers */,
961276671DCD8CF200A7D920 /* CaptureController.swift in Headers */,
961276681DCD8CF200A7D920 /* Editor.swift in Headers */,
961276691DCD8CF200A7D920 /* EditorController.swift in Headers */,
9612766A1DCD8CF200A7D920 /* HeightPreset.swift in Headers */,
9612766B1DCD8CF200A7D920 /* PhotoLibrary.swift in Headers */,
9612766C1DCD8CF200A7D920 /* PhotoLibraryController.swift in Headers */,
9612766D1DCD8CF200A7D920 /* Pulse.swift in Headers */,
9612766E1DCD8CF200A7D920 /* Display.swift in Headers */,
9612766F1DCD8CF200A7D920 /* CharacterAttribute.swift in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -1143,9 +1165,9 @@
96BCB7CD1CB40DC500C806FE /* PulseView.swift in Sources */,
96BCB7DA1CB40DC500C806FE /* NavigationController.swift in Sources */,
96BCB7A81CB40DC500C806FE /* FabButton.swift in Sources */,
96BCB7E41CB40DC500C806FE /* Text.swift in Sources */,
96717B121DBE6AF600DA84DB /* CaptureController.swift in Sources */,
96717B181DBE6B1800DA84DB /* PhotoLibrary.swift in Sources */,
961276631DCD8B1800A7D920 /* CharacterAttribute.swift in Sources */,
96BCB7AB1CB40DC500C806FE /* ImageCard.swift in Sources */,
96BCB7CB1CB40DC500C806FE /* PulseAnimation.swift in Sources */,
96BCB7AE1CB40DC500C806FE /* Material+UIFont.swift in Sources */,
......
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
public enum CharacterAttribute: String {
case font = "NSFontAttributeName"
case paragraphStyle = "NSParagraphStyleAttributeName"
case forgroundColor = "NSForegroundColorAttributeName"
case backgroundColor = "NSBackgroundColorAttributeName"
case ligature = "NSLigatureAttributeName"
case kern = "NSKernAttributeName"
case strikethroughStyle = "NSStrikethroughStyleAttributeName"
case underlineStyle = "NSUnderlineStyleAttributeName"
case strokeColor = "NSStrokeColorAttributeName"
case strokeWidth = "NSStrokeWidthAttributeName"
case shadow = "NSShadowAttributeName"
case textEffect = "NSTextEffectAttributeName"
case attachment = "NSAttachmentAttributeName"
case link = "NSLinkAttributeName"
case baselineOffset = "NSBaselineOffsetAttributeName"
case underlineColor = "NSUnderlineColorAttributeName"
case strikethroughColor = "NSStrikethroughColorAttributeName"
case obliqueness = "NSObliquenessAttributeName"
case expansion = "NSExpansionAttributeName"
case writingDirection = "NSWritingDirectionAttributeName"
case verticalGlyphForm = "NSVerticalGlyphFormAttributeName"
}
public func CharacterAttributeToValue(attribute: CharacterAttribute) -> String {
switch attribute {
case .font:
return NSFontAttributeName
case .paragraphStyle:
return NSParagraphStyleAttributeName
case .forgroundColor:
return NSForegroundColorAttributeName
case .backgroundColor:
return NSBackgroundColorAttributeName
case .ligature:
return NSLigatureAttributeName
case .kern:
return NSKernAttributeName
case .strikethroughStyle:
return NSStrikethroughStyleAttributeName
case .underlineStyle:
return NSUnderlineStyleAttributeName
case .strokeColor:
return NSStrokeColorAttributeName
case .strokeWidth:
return NSStrokeWidthAttributeName
case .shadow:
return NSShadowAttributeName
case .textEffect:
return NSTextEffectAttributeName
case .attachment:
return NSAttachmentAttributeName
case .link:
return NSLinkAttributeName
case .baselineOffset:
return NSBaselineOffsetAttributeName
case .underlineColor:
return NSUnderlineColorAttributeName
case .strikethroughColor:
return NSStrikethroughColorAttributeName
case .obliqueness:
return NSObliquenessAttributeName
case .expansion:
return NSExpansionAttributeName
case .writingDirection:
return NSWritingDirectionAttributeName
case .verticalGlyphForm:
return NSVerticalGlyphFormAttributeName
}
}
extension NSAttributedString {
/**
Retrieves the character attributes that are currently applied.
- Parameter at location: An Int.
- Parameter effectiveRange: An optional NSRangePointer.
- Returns: A Dictionary of CharacterAttribute type keys and Any type values.
*/
open func characterAttributes(at location: Int, effectiveRange: NSRangePointer?) -> [CharacterAttribute: Any] {
var ca = [CharacterAttribute: Any]()
attributes(at: location, effectiveRange: effectiveRange).forEach { (key, value) in
guard let attribute = CharacterAttribute.init(rawValue: key) else {
return
}
ca[attribute] = value
}
return ca
}
}
extension NSMutableAttributedString {
/**
Adds a character attribute to a given range.
- Parameter characterAttribute: A CharacterAttribute.
- Parameter value: Any type.
- Parameter range: A NSRange.
*/
open func addAttribute(characterAttribute: CharacterAttribute, value: Any, range: NSRange) {
addAttribute(characterAttribute.rawValue, value: value, range: range)
}
/**
Removes a character attribute from a given range.
- Parameter characterAttribute: A CharacterAttribute.
- Parameter range: A NSRange.
*/
open func removeAttribute(characterAttribute: CharacterAttribute, range: NSRange) {
removeAttribute(characterAttribute.rawValue, range: range)
}
}
/*
* Copyright (C) 2015 - 2016, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(TextDelegate)
public protocol TextDelegate {
/**
An optional delegation method that is executed when
text will be processed during editing.
- Parameter text: The Text instance assodicated with the
delegation object.
- Parameter textStorage: The TextStorage instance
associated with the delegation object.
- Parameter string: The string value that is currently
being edited.
- Parameter range: The range of characters that are being
edited.
*/
@objc
optional func textWillProcessEdit(text: Text, textStorage: TextStorage, string: String, range: NSRange)
/**
An optional delegation method that is executed after
the edit processing has completed.
- Parameter text: The Text instance assodicated with the
delegation object.
- Parameter textStorage: The TextStorage instance
associated with the delegation object.
- Parameter string: The string value that was edited.
- Parameter result: A NSTextCheckingResult associated
with the processing result.
- Parameter flags: Matching flags.
- Parameter stop: Halts a service which is either
publishing or resolving.
*/
@objc
optional func textDidProcessEdit(text: Text, textStorage: TextStorage, string: String, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>)
}
@objc(Text)
public class Text: NSObject {
/// The string pattern to match within the textStorage.
public var pattern: String = "(^|\\s)#[\\d\\w_\u{203C}\u{2049}\u{20E3}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{23E9}-\u{23EC}\u{23F0}\u{23F3}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2601}\u{260E}\u{2611}\u{2614}-\u{2615}\u{261D}\u{263A}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2693}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270C}\u{270F}\u{2712}\u{2714}\u{2716}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E7}-\u{1F1EC}\u{1F1EE}-\u{1F1F0}\u{1F1F3}\u{1F1F5}\u{1F1F7}-\u{1F1FA}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F320}\u{1F330}-\u{1F335}\u{1F337}-\u{1F37C}\u{1F380}-\u{1F393}\u{1F3A0}-\u{1F3C4}\u{1F3C6}-\u{1F3CA}\u{1F3E0}-\u{1F3F0}\u{1F400}-\u{1F43E}\u{1F440}\u{1F442}-\u{1F4F7}\u{1F4F9}-\u{1F4FC}\u{1F500}-\u{1F507}\u{1F509}-\u{1F53D}\u{1F550}-\u{1F567}\u{1F5FB}-\u{1F640}\u{1F645}-\u{1F64F}\u{1F680}-\u{1F68A}]+" {
didSet {
prepareTextStorageExpression()
}
}
/// TextStorage instance that is observed while editing.
public private(set) var textStorage: TextStorage = TextStorage()
/// Delegation object for pre and post text processing.
open weak var delegate: TextDelegate?
/// Initializer.
public override init() {
super.init()
prepareTextStorageExpression()
prepareTextStorageProcessingCallbacks()
}
/**
A convenience property that accesses the textStorage
string.
*/
public var string: String {
return textStorage.string
}
/// An Array of matches that match the pattern expression.
public var matches: Array<String> {
return textStorage.expression!.matches(in: string, options: [], range: NSMakeRange(0, string.utf16.count)).map { [unowned self] in
(self.string as NSString).substring(with: $0.range).trimmed
}
}
/**
An Array of unique matches that match the pattern
expression.
*/
public var uniqueMatches: Array<String> {
var seen: [String: Bool] = [:]
return matches.filter { nil == seen.updateValue(true, forKey: $0) }
}
/// Prepares the TextStorage regular expression for matching.
private func prepareTextStorageExpression() {
textStorage.expression = try? NSRegularExpression(pattern: pattern, options: [])
}
/// Prepares the pre and post processing callbacks.
private func prepareTextStorageProcessingCallbacks() {
textStorage.textWillProcessEdit = { [weak self] (textStorage: TextStorage, string: String, range: NSRange) -> Void in
if let s: Text = self {
s.delegate?.textWillProcessEdit?(text: s, textStorage: textStorage, string: string, range: range)
}
}
textStorage.textDidProcessEdit = { [weak self] (textStorage: TextStorage, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
if let s: Text = self {
s.delegate?.textDidProcessEdit?(text: s, textStorage: textStorage, string: textStorage.string, result: result, flags: flags, stop: stop)
}
}
}
}
......@@ -30,21 +30,37 @@
import UIKit
internal typealias TextWillProcessEdit = (TextStorage, String, NSRange) -> Void
internal typealias TextDidProcessEdit = (TextStorage, NSTextCheckingResult?, NSRegularExpression.MatchingFlags, UnsafeMutablePointer<ObjCBool>) -> Void
@objc(TextStorageDelegate)
public protocol TextStorageDelegate: NSTextStorageDelegate {
/**
A delegation method that is executed when text will be
processed during editing.
- Parameter textStorage: A TextStorage.
- Parameter willProcessEditing text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func textStorage(textStorage: TextStorage, willProcessEditing text: String, range: NSRange)
/**
A delegation method that is executed when text has been
processed after editing.
- Parameter textStorage: A TextStorage.
- Parameter didProcessEditing text: A String.
- Parameter result: An optional NSTextCheckingResult.
- Parameter flags: NSRegularExpression.MatchingFlags.
- Parameter top: An UnsafeMutablePointer<ObjCBool>.
*/
@objc
optional func textStorage(textStorage: TextStorage, didProcessEditing text: String, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>)
}
public class TextStorage: NSTextStorage {
/// A callback that is executed when a process edit will happen.
internal var textWillProcessEdit: TextWillProcessEdit?
/// A callback that is executed when a process edit did happen.
internal var textDidProcessEdit: TextDidProcessEdit?
open class TextStorage: NSTextStorage {
/// A storage facility for attributed text.
public lazy var store: NSMutableAttributedString = NSMutableAttributedString()
open fileprivate(set) var store: NSMutableAttributedString!
/// The regular expression to match text fragments against.
public var expression: NSRegularExpression?
open var expression: NSRegularExpression?
/// Initializer.
public required init?(coder aDecoder: NSCoder) {
......@@ -54,60 +70,78 @@ public class TextStorage: NSTextStorage {
/// Initializer.
public override init() {
super.init()
prepareStore()
}
}
extension TextStorage {
/// Prepare the store.
fileprivate func prepareStore() {
store = NSMutableAttributedString()
}
}
extension TextStorage {
/// A String value of the attirbutedString property.
public override var string: String {
open override var string: String {
return store.string
}
/// Processes the text when editing.
public override func processEditing() {
open override func processEditing() {
let range: NSRange = (string as NSString).paragraphRange(for: editedRange)
textWillProcessEdit?(self, string, range)
expression!.enumerateMatches(in: string, options: [], range: range) { (result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
self.textDidProcessEdit?(self, result, flags, stop)
(delegate as? TextStorageDelegate)?.textStorage?(textStorage: self, willProcessEditing: string, range: range)
expression!.enumerateMatches(in: string, options: [], range: range) { [weak self] (result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
guard let s = self else {
return
}
(s.delegate as? TextStorageDelegate)?.textStorage!(textStorage: s, didProcessEditing: s.string, result: result, flags: flags, stop: stop)
}
super.processEditing()
}
/**
Returns the attributes for the character at a given index.
- Parameter location: The index for which to return attributes.
This value must lie within the bounds of the receiver.
- Parameter range: Upon return, the range over which the
attributes and values are the same as those at index. This range
isn’t necessarily the maximum range covered, and its extent is
implementation-dependent. If you need the maximum range, use
attributesAtIndex:longestEffectiveRange:inRange:.
If you don't need this value, pass NULL.
- Returns: The attributes for the character at index.
*/
public override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [String : Any] {
Returns the attributes for the character at a given index.
- Parameter location: The index for which to return attributes.
This value must lie within the bounds of the receiver.
- Parameter range: Upon return, the range over which the
attributes and values are the same as those at index. This range
isn’t necessarily the maximum range covered, and its extent is
implementation-dependent. If you need the maximum range, use
attributesAtIndex:longestEffectiveRange:inRange:.
If you don't need this value, pass NULL.
- Returns: The attributes for the character at index.
*/
open override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [String : Any] {
return store.attributes(at: location, effectiveRange: range)
}
/**
Replaces a range of text with a string value.
- Parameter range: The character range to replace.
- Parameter str: The string value that the characters
will be replaced with.
*/
public override func replaceCharacters(in range: NSRange, with str: String) {
Replaces a range of text with a string value.
- Parameter range: The character range to replace.
- Parameter str: The string value that the characters
will be replaced with.
*/
open override func replaceCharacters(in range: NSRange, with str: String) {
store.replaceCharacters(in: range, with: str)
edited(NSTextStorageEditActions.editedCharacters, range: range, changeInLength: str.utf16.count - range.length)
edited(.editedCharacters, range: range, changeInLength: str.utf16.count - range.length)
}
/**
Sets the attributedString attribute values.
- Parameter attrs: The attributes to set.
- Parameter range: A range of characters that will have their
attributes updated.
*/
public override func setAttributes(_ attrs: [String : Any]?, range: NSRange) {
Sets the attributedString attribute values.
- Parameter attrs: The attributes to set.
- Parameter range: A range of characters that will have their
attributes updated.
*/
open override func setAttributes(_ attrs: [String : Any]?, range: NSRange) {
store.setAttributes(attrs, range: range)
edited(NSTextStorageEditActions.editedAttributes, range: range, changeInLength: 0)
edited(.editedAttributes, range: range, changeInLength: 0)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment