Commit 09b0f7a4 by Daniel Dahan

development: moving Editor to TextView only

parent d1f9360b
...@@ -207,6 +207,17 @@ open class Editor: View { ...@@ -207,6 +207,17 @@ open class Editor: View {
/// A reference to the NSLayoutManager. /// A reference to the NSLayoutManager.
open fileprivate(set) var layoutManager: NSLayoutManager! open fileprivate(set) var layoutManager: NSLayoutManager!
/// A reference to the textView inputAccessoryView.
@IBInspectable
open override var inputAccessoryView: UIView? {
get {
return textView.inputAccessoryView
}
set(value) {
textView.inputAccessoryView = value
}
}
/// A preset wrapper around textViewEdgeInsets. /// A preset wrapper around textViewEdgeInsets.
open var textViewEdgeInsetsPreset = EdgeInsetsPreset.none { open var textViewEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet { didSet {
...@@ -225,6 +236,85 @@ open class Editor: View { ...@@ -225,6 +236,85 @@ open class Editor: View {
/// Reference to the TextView. /// Reference to the TextView.
open fileprivate(set) var textView: TextView! open fileprivate(set) var textView: TextView!
/// The placeholderLabel font value.
@IBInspectable
open var font: UIFont? {
get {
return placeholderLabel.font
}
set(value) {
placeholderLabel.font = value
textView.font = value
}
}
/// The placeholderLabel text value.
@IBInspectable
open var placeholder: String? {
get {
return placeholderLabel.text
}
set(value) {
placeholderLabel.text = value
layoutSubviews()
}
}
/// The placeholder UILabel.
@IBInspectable
open let placeholderLabel = UILabel()
/// Placeholder normal text
@IBInspectable
open var placeholderNormalColor = Color.darkText.others {
didSet {
updatePlaceholderLabelColor()
}
}
/// Placeholder active text
@IBInspectable
open var placeholderActiveColor = Color.blue.base {
didSet {
updatePlaceholderLabelColor()
}
}
/// Placeholder UILabel EdgeInsets preset property.
open var placeholderLabelEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet {
placeholderLabelEdgeInsets = EdgeInsetsPresetToValue(preset: placeholderLabelEdgeInsetsPreset)
}
}
/// Placeholder UILabel EdgeInsets property.
open var placeholderLabelEdgeInsets = EdgeInsets.zero {
didSet {
layoutSubviews()
}
}
/// An override to the text property.
@IBInspectable
open var text: String? {
get {
return textView.text
}
set(value) {
textView.text = value
}
}
/// An override to the attributedText property.
open var attributedText: NSAttributedString! {
get {
return textView.attributedText
}
set(value) {
textView.attributedText = value
}
}
/// A reference to an EditorDelegate. /// A reference to an EditorDelegate.
open weak var delegate: EditorDelegate? open weak var delegate: EditorDelegate?
...@@ -254,7 +344,7 @@ open class Editor: View { ...@@ -254,7 +344,7 @@ open class Editor: View {
An Array of unique matches that match the pattern An Array of unique matches that match the pattern
expression. expression.
*/ */
public var uniqueMatches: [String] { open var uniqueMatches: [String] {
var seen = [String: Bool]() var seen = [String: Bool]()
return matches.filter { nil == seen.updateValue(true, forKey: $0) } return matches.filter { nil == seen.updateValue(true, forKey: $0) }
} }
...@@ -266,6 +356,14 @@ open class Editor: View { ...@@ -266,6 +356,14 @@ open class Editor: View {
} }
textView.frame = CGRect(x: textViewEdgeInsets.left, y: textViewEdgeInsets.top, width: width - textViewEdgeInsets.left - textViewEdgeInsets.right, height: height - textViewEdgeInsets.top - textViewEdgeInsets.bottom) textView.frame = CGRect(x: textViewEdgeInsets.left, y: textViewEdgeInsets.top, width: width - textViewEdgeInsets.left - textViewEdgeInsets.right, height: height - textViewEdgeInsets.top - textViewEdgeInsets.bottom)
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2
layout(placeholderLabel).edges(
top: placeholderLabelEdgeInsets.top,
left: placeholderLabelEdgeInsets.left + textContainer.lineFragmentPadding,
bottom: placeholderLabelEdgeInsets.bottom,
right: placeholderLabelEdgeInsets.right + textContainer.lineFragmentPadding)
} }
/// Deinitializer. /// Deinitializer.
...@@ -287,6 +385,7 @@ open class Editor: View { ...@@ -287,6 +385,7 @@ open class Editor: View {
prepareTextStorage() prepareTextStorage()
prepareRegularExpression() prepareRegularExpression()
prepareTextView() prepareTextView()
preparePlaceholderLabel()
prepareKeyboardNotificationObservers() prepareKeyboardNotificationObservers()
} }
} }
...@@ -322,11 +421,30 @@ extension Editor { ...@@ -322,11 +421,30 @@ extension Editor {
textStorage.expression = try? NSRegularExpression(pattern: pattern, options: []) textStorage.expression = try? NSRegularExpression(pattern: pattern, options: [])
} }
/// prepares the placeholderLabel property.
fileprivate func preparePlaceholderLabel() {
placeholderLabel.font = font
placeholderLabel.textAlignment = textView.textAlignment
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = .clear
}
/// Prepares the keyboard notification center observers. /// Prepares the keyboard notification center observers.
fileprivate func prepareKeyboardNotificationObservers() { fileprivate func prepareKeyboardNotificationObservers() {
let defaultCenter = NotificationCenter.default let defaultCenter = NotificationCenter.default
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil) defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
defaultCenter.addObserver(textView, selector: #selector(handleTextViewTextDidBegin), name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self)
defaultCenter.addObserver(textView, selector: #selector(handleTextViewTextDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.addObserver(textView, selector: #selector(handleTextViewTextDidEnd), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
}
}
extension Editor {
/// Updates the placeholderLabel text color.
fileprivate func updatePlaceholderLabelColor() {
tintColor = placeholderActiveColor
placeholderLabel.textColor = textView.isEditing ? placeholderActiveColor : placeholderNormalColor
} }
} }
...@@ -368,6 +486,24 @@ extension Editor { ...@@ -368,6 +486,24 @@ extension Editor {
delegate?.editor?(editor: self, willHideKeyboard: v) delegate?.editor?(editor: self, willHideKeyboard: v)
} }
/// Notification handler for when text editing began.
@objc
fileprivate func handleTextViewTextDidBegin() {
}
/// Notification handler for when text changed.
@objc
fileprivate func handleTextViewTextDidChange() {
}
/// Notification handler for when text editing ended.
@objc
fileprivate func handleTextViewTextDidEnd() {
}
} }
extension Editor: TextStorageDelegate { extension Editor: TextStorageDelegate {
......
...@@ -31,7 +31,45 @@ ...@@ -31,7 +31,45 @@
import UIKit import UIKit
@objc(TextViewDelegate) @objc(TextViewDelegate)
public protocol TextViewDelegate : UITextViewDelegate {} public protocol TextViewDelegate : UITextViewDelegate {
/**
A delegation method that is executed when the keyboard will open.
- Parameter textView: A TextView.
- Parameter willShowKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, willShowKeyboard value: NSValue)
/**
A delegation method that is executed when the keyboard will close.
- Parameter textView: A TextView.
- Parameter willHideKeyboard value: A NSValue.
*/
@objc
optional func textView(textView: TextView, willHideKeyboard value: NSValue)
/**
A delegation method that is executed when text will be
processed during editing.
- Parameter textView: A TextView.
- Parameter willProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func textView(textView: TextView, willProcessEditing textStorage: TextStorage, text: String, range: NSRange)
/**
A delegation method that is executed when text has been
processed after editing.
- Parameter textView: A TextView.
- Parameter didProcessEditing textStorage: A TextStorage.
- Parameter text: A String.
- Parameter range: A NSRange.
*/
@objc
optional func textView(textView: TextView, didProcessEditing textStorage: TextStorage, text: String, range: NSRange)
}
@objc(TextView) @objc(TextView)
open class TextView: UITextView { open class TextView: UITextView {
...@@ -43,6 +81,9 @@ open class TextView: UITextView { ...@@ -43,6 +81,9 @@ open class TextView: UITextView {
/// A boolean indicating whether the text is in edit mode. /// A boolean indicating whether the text is in edit mode.
open fileprivate(set) var isEditing = true open fileprivate(set) var isEditing = true
/// Is the keyboard hidden.
open fileprivate(set) var isKeyboardHidden = true
/// A property that accesses the backing layer's background /// A property that accesses the backing layer's background
@IBInspectable @IBInspectable
open override var backgroundColor: UIColor? { open override var backgroundColor: UIColor? {
...@@ -50,8 +91,8 @@ open class TextView: UITextView { ...@@ -50,8 +91,8 @@ open class TextView: UITextView {
layer.backgroundColor = backgroundColor?.cgColor layer.backgroundColor = backgroundColor?.cgColor
} }
} }
/// The placeholderLabel font value. /// The placeholderLabel font value.
@IBInspectable @IBInspectable
open override var font: UIFont? { open override var font: UIFont? {
didSet { didSet {
...@@ -67,7 +108,6 @@ open class TextView: UITextView { ...@@ -67,7 +108,6 @@ open class TextView: UITextView {
} }
set(value) { set(value) {
placeholderLabel.text = value placeholderLabel.text = value
layoutSubviews()
} }
} }
...@@ -90,38 +130,23 @@ open class TextView: UITextView { ...@@ -90,38 +130,23 @@ open class TextView: UITextView {
updatePlaceholderLabelColor() updatePlaceholderLabelColor()
} }
} }
/// An override to the text property. /// NSTextContainer EdgeInsets preset property.
@IBInspectable open var textContainerInsetsPreset = EdgeInsetsPreset.none {
open override var text: String? { didSet {
didSet { textContainerInsets = EdgeInsetsPresetToValue(preset: textContainerInsetsPreset)
handleTextViewTextDidChange() }
} }
}
/// NSTextContainer EdgeInsets property.
/// An override to the attributedText property. open var textContainerInsets: EdgeInsets {
open override var attributedText: NSAttributedString! { get {
didSet { return textContainerInset
handleTextViewTextDidChange() }
} set(value) {
} textContainerInset = value
}
/** }
Text container UIEdgeInset preset property. This updates the
textContainerInset property with a preset value.
*/
open var textContainerEdgeInsetsPreset: EdgeInsetsPreset = .none {
didSet {
textContainerInset = EdgeInsetsPresetToValue(preset: textContainerEdgeInsetsPreset)
}
}
/// Text container UIEdgeInset property.
open override var textContainerInset: EdgeInsets {
didSet {
reload()
}
}
/** /**
An initializer that initializes the object with a NSCoder object. An initializer that initializes the object with a NSCoder object.
...@@ -131,6 +156,52 @@ open class TextView: UITextView { ...@@ -131,6 +156,52 @@ open class TextView: UITextView {
super.init(coder: aDecoder) super.init(coder: aDecoder)
prepare() prepare()
} }
/// The string pattern to match within the textStorage.
open var pattern = "(^|\\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 {
prepareRegularExpression()
}
}
/// A reference to the textView text.
open override var text: String! {
didSet {
setContentOffset(.zero, animated: true)
updatePlaceholderVisibility()
}
}
/**
A convenience property that accesses the textStorage
string.
*/
open var string: String {
return textStorage.string
}
/// An Array of matches that match the pattern expression.
open var matches: [String] {
guard let v = (textStorage as? TextStorage)?.expression else {
return []
}
return v.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.
*/
open var uniqueMatches: [String] {
var set = Set<String>()
for x in matches {
set.insert(x)
}
return Array<String>(set)
}
/** /**
An initializer that initializes the object with a CGRect object. An initializer that initializes the object with a CGRect object.
...@@ -151,6 +222,22 @@ open class TextView: UITextView { ...@@ -151,6 +222,22 @@ open class TextView: UITextView {
public convenience init(textContainer: NSTextContainer?) { public convenience init(textContainer: NSTextContainer?) {
self.init(frame: .zero, textContainer: textContainer) self.init(frame: .zero, textContainer: textContainer)
} }
/// A convenience initializer that constructs all aspects of the textView.
public convenience init() {
let textContainer = NSTextContainer(size: .zero)
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = TextStorage()
textStorage.addLayoutManager(layoutManager)
self.init(textContainer: textContainer)
textContainer.size = bounds.size
textStorage.delegate = self
}
/** /**
Denitializer. This should never be called unless you know Denitializer. This should never be called unless you know
...@@ -164,38 +251,9 @@ open class TextView: UITextView { ...@@ -164,38 +251,9 @@ open class TextView: UITextView {
super.layoutSubviews() super.layoutSubviews()
layoutShape() layoutShape()
layoutShadowPath() layoutShadowPath()
layoutPlaceholderLabel()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2
}
/// Reloads necessary components when the view has changed.
open func reload() {
removeConstraints(constraints)
layout(placeholderLabel).edges(
top: textContainerInset.top,
left: textContainerInset.left + textContainer.lineFragmentPadding,
bottom: textContainerInset.bottom,
right: textContainerInset.right + textContainer.lineFragmentPadding)
} }
/// Notification handler for when text editing began.
@objc
fileprivate func handleTextViewTextDidBegin() {
}
/// Notification handler for when text changed.
@objc
fileprivate func handleTextViewTextDidChange() {
placeholderLabel.isHidden = !isEmpty
}
/// Notification handler for when text editing ended.
@objc
fileprivate func handleTextViewTextDidEnd() {
}
/** /**
Prepares the view instance when intialized. When subclassing, Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method it is recommended to override the prepare method
...@@ -206,27 +264,37 @@ open class TextView: UITextView { ...@@ -206,27 +264,37 @@ open class TextView: UITextView {
open func prepare() { open func prepare() {
contentScaleFactor = Screen.scale contentScaleFactor = Screen.scale
textContainerInset = .zero textContainerInset = .zero
backgroundColor = .white backgroundColor = nil
clipsToBounds = false prepareNotificationHandlers()
prepareRegularExpression()
preparePlaceholderLabel() preparePlaceholderLabel()
prepareNotificationHandlers()
} }
}
/// prepares the placeholderLabel property.
fileprivate func preparePlaceholderLabel() { extension TextView {
/// Prepares the Notification handlers.
fileprivate func prepareNotificationHandlers() {
let defaultCenter = NotificationCenter.default
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleKeyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidBegin), name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidEnd), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
}
/// Prepares the regular expression for matching.
fileprivate func prepareRegularExpression() {
(textStorage as? TextStorage)?.expression = try? NSRegularExpression(pattern: pattern, options: [])
}
/// prepares the placeholderLabel property.
fileprivate func preparePlaceholderLabel() {
placeholderLabel.font = font placeholderLabel.font = font
placeholderLabel.textAlignment = textAlignment placeholderLabel.textAlignment = textAlignment
placeholderLabel.numberOfLines = 0 placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = .clear placeholderLabel.backgroundColor = .clear
} addSubview(placeholderLabel)
}
/// Prepares the Notification handlers.
fileprivate func prepareNotificationHandlers() {
let defaultCenter = NotificationCenter.default
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidBegin), name: NSNotification.Name.UITextViewTextDidBeginEditing, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: self)
defaultCenter.addObserver(self, selector: #selector(handleTextViewTextDidEnd), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self)
}
} }
extension TextView { extension TextView {
...@@ -235,4 +303,99 @@ extension TextView { ...@@ -235,4 +303,99 @@ extension TextView {
tintColor = placeholderActiveColor tintColor = placeholderActiveColor
placeholderLabel.textColor = isEditing ? placeholderActiveColor : placeholderNormalColor placeholderLabel.textColor = isEditing ? placeholderActiveColor : placeholderNormalColor
} }
/// Updates the placeholderLabel visibility.
fileprivate func updatePlaceholderVisibility() {
placeholderLabel.isHidden = !isEmpty
}
}
extension TextView {
/// Laysout the placeholder UILabel.
fileprivate func layoutPlaceholderLabel() {
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2
let x = textContainerInset.left + textContainer.lineFragmentPadding
let y = textContainerInset.top
placeholderLabel.sizeToFit()
placeholderLabel.frame.origin.x = x
placeholderLabel.frame.origin.y = y
placeholderLabel.frame.size.width = textContainer.size.width - textContainerInset.right - textContainer.lineFragmentPadding
}
}
extension TextView {
/**
Handler for when the keyboard will open.
- Parameter notification: A Notification.
*/
@objc
fileprivate func handleKeyboardWillShow(notification: Notification) {
guard isKeyboardHidden else {
return
}
isKeyboardHidden = false
guard let v = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
return
}
(delegate as? TextViewDelegate)?.textView?(textView: self, willShowKeyboard: v)
}
/**
Handler for when the keyboard will close.
- Parameter notification: A Notification.
*/
@objc
fileprivate func handleKeyboardWillHide(notification: Notification) {
guard !isKeyboardHidden else {
return
}
isKeyboardHidden = true
guard let v = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
return
}
(delegate as? TextViewDelegate)?.textView?(textView: self, willHideKeyboard: v)
}
/// Notification handler for when text editing began.
@objc
fileprivate func handleTextViewTextDidBegin() {
isEditing = true
}
/// Notification handler for when text changed.
@objc
fileprivate func handleTextViewTextDidChange() {
updatePlaceholderVisibility()
}
/// Notification handler for when text editing ended.
@objc
fileprivate func handleTextViewTextDidEnd() {
isEditing = false
updatePlaceholderVisibility()
}
}
extension TextView: TextStorageDelegate {
@objc
open func textStorage(textStorage: TextStorage, willProcessEditing text: String, range: NSRange) {
(delegate as? TextViewDelegate)?.textView?(textView: self, willProcessEditing: textStorage, text: string, range: range)
}
@objc
open func textStorage(textStorage: TextStorage, didProcessEditing text: String, result: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop: UnsafeMutablePointer<ObjCBool>) {
guard let range = result?.range else {
return
}
(delegate as? TextViewDelegate)?.textView?(textView: self, didProcessEditing: textStorage, text: string, range: range)
}
} }
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