Skip to content

Instantly share code, notes, and snippets.

@CapnSpellcheck
Last active July 28, 2020 02:58
Show Gist options
  • Save CapnSpellcheck/317de353688777879dc323738ec1a223 to your computer and use it in GitHub Desktop.
Save CapnSpellcheck/317de353688777879dc323738ec1a223 to your computer and use it in GitHub Desktop.
Extending UITextFieldDelegate and UITextViewDelegate to enable modifying edit replacements; multi-delegates provide the ability to compose delegates (filters). Note: Multi-Delegates necessarily strongref their constituents.
import Foundation
@objc protocol ExtendedUITextFieldDelegate : UITextFieldDelegate {
@objc optional func extendedTextField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> String?
}
class TextFieldMultiDelegate : NSObject, UITextFieldDelegate {
private var delegates: Array<ExtendedUITextFieldDelegate> = []
func add(delegate: ExtendedUITextFieldDelegate) {
delegates.append(delegate)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var ret = true
var replacement = string
for d in delegates {
if d.responds(to: #selector(ExtendedUITextFieldDelegate.extendedTextField(_:shouldChangeCharactersIn:replacementString:))) {
if let new = d.extendedTextField?(textField, shouldChangeCharactersIn: range, replacementString: replacement) {
ret = false
replacement = new
}
} else if d.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == false {
return false
}
}
if !ret {
textField.text = (textField.text! as NSString).replacingCharacters(in: range, with: replacement)
}
return ret
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
for d in delegates {
if d.textFieldShouldBeginEditing?(textField) == false {
return false
}
}
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
for d in delegates {
d.textFieldDidBeginEditing?(textField)
}
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
for d in delegates {
if d.textFieldShouldEndEditing?(textField) == false {
return false
}
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
for d in delegates {
d.textFieldDidEndEditing?(textField)
}
}
}
@objc protocol ExtendedUITextViewDelegate : UITextViewDelegate {
@objc optional func extendedTextView(_ textView: UITextView,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> String?
}
class TextViewMultiDelegate : NSObject, UITextViewDelegate {
private var delegates: Array<ExtendedUITextViewDelegate> = []
func add(delegate: ExtendedUITextViewDelegate) {
delegates.append(delegate)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText string: String) -> Bool {
var ret = true
var replacement = string
for d in delegates {
if d.responds(to: #selector(ExtendedUITextViewDelegate.extendedTextView(_:shouldChangeCharactersIn:replacementString:))) {
if let new = d.extendedTextView?(textView, shouldChangeCharactersIn: range, replacementString: replacement) {
ret = false
replacement = new
}
} else if d.textView?(textView, shouldChangeTextIn: range, replacementText: replacement) == false {
return false
}
}
if !ret {
textView.text = (textView.text! as NSString).replacingCharacters(in: range, with: replacement)
}
return ret
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
for d in delegates {
if d.textViewShouldBeginEditing?(textView) == false {
return false
}
}
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
for d in delegates {
d.textViewDidBeginEditing?(textView)
}
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
for d in delegates {
if d.textViewShouldEndEditing?(textView) == false {
return false
}
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
for d in delegates {
d.textViewDidEndEditing?(textView)
}
}
}
class NewlineLimitTextViewFilter : NSObject, ExtendedUITextViewDelegate {
private var newlines: Int16 = 0
private let limit: Int16
private let charset = CharacterSet.newlines
init(newlineLimit: Int16) {
limit = newlineLimit
}
func extendedTextView(_ textView: UITextView, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> String? {
NSLog("NewlineLimitFilter: limit=%d current=%d", limit, newlines)
(textView.text as NSString).substring(with: range)
.forEach { char in
if charset.contains(char.unicodeScalars.first!) {
newlines -= 1
NSLog("NewlineLimitFilter: decr")
}
}
var index = string.startIndex
while index != string.endIndex {
let char = string[index]
if charset.contains(char.unicodeScalars.first!) {
newlines += 1
NSLog("NewlineLimitFilter: decr")
if newlines >= limit {
NotificationCenter.default.post(name: .NewlineLimitExceeded, object: textView)
newlines = limit
return String(string[string.startIndex ..< index])
}
}
index = string.index(after: index)
}
return nil
}
func textViewDidBeginEditing(_ textView: UITextView) {
newlines = 0
textView.text.forEach { char in
if charset.contains(char.unicodeScalars.first!) {
newlines += 1
}
}
}
}
// length limit is of utf16 view
class LengthLimitTextFilter : NSObject, ExtendedUITextFieldDelegate, ExtendedUITextViewDelegate {
private let limit: Int
init(lengthLimit: Int) {
limit = lengthLimit
}
func extendedTextView(_ textView: UITextView, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> String? {
let utf16 = textView.text.utf16
let nsstring = string as NSString
let length = utf16.count - range.length + nsstring.length
NSLog("LengthLimitTextFilter: limit=%d current=%d range=%d replacement=%d", limit, utf16.count, range.length, nsstring.length)
if length > limit {
return nsstring.substring(to: nsstring.length - (length - limit))
}
return nil
}
func extendedTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> String? {
let utf16 = textField.text!.utf16
let nsstring = string as NSString
let length = utf16.count - range.length + nsstring.length
if length > limit {
return nsstring.substring(to: nsstring.length - (length - limit))
}
return nil
}
}
class AlphabeticTextFilter : NSObject, ExtendedUITextFieldDelegate {
private let charset: CharacterSet
init(extended: Bool) {
if extended {
let others = CharacterSet(charactersIn: " '\u{8217}\u{2D}\u{2010}\u{2011}\u{058A}\u{1806}\u{1B60}\u{30FB}\u{FE63}\u{FF0D}\u{FF65}")
charset = CharacterSet.letters.union(others)
} else {
charset = CharacterSet.letters
}
}
func extendedTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> String? {
var index = string.startIndex
while index != string.endIndex {
let char = string[index]
if !char.unicodeScalars.allSatisfy(charset.contains(_:)) {
return String(string[string.startIndex ..< index])
}
index = string.index(after: index)
}
return nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment