Comments (11)
Yeah it fixed it, thanks!
from input-mask-ios.
@MrJox while I'm working on some improvements and fixes required by our last library update, I've put together a quick solution for your problem.
Effectively, it's a PolyMaskTextFieldDelegate
modification with a custom affinity calculation method. Instead of an overall text affinity with the mask, this method compares the length of the prefixes intersection, the count of characters.
This code or its modified version might find its way to the library sources eventually.
import Foundation
import UIKit
import InputMask
@IBDesignable
class PrefixAffinityMaskedTextFieldDelegate: MaskedTextFieldDelegate {
public var affineFormats: [String]
public init(primaryFormat: String, affineFormats: [String]) {
self.affineFormats = affineFormats
super.init(format: primaryFormat)
}
public override init(format: String) {
self.affineFormats = []
super.init(format: format)
}
override open func put(text: String, into field: UITextField) {
let mask: Mask = pickMask(
forText: text,
caretPosition: text.endIndex,
autocomplete: autocomplete
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.endIndex
),
autocomplete: autocomplete
)
field.text = result.formattedText.string
field.caretPosition = result.formattedText.string.distance(
from: result.formattedText.string.startIndex,
to: result.formattedText.caretPosition
)
listener?.textField?(field, didFillMandatoryCharacters: result.complete, didExtractValue: result.extractedValue)
}
override open func deleteText(inRange range: NSRange, inTextInput field: UITextInput) -> Mask.Result {
let text: String = replaceCharacters(
inText: field.allText,
range: range,
withCharacters: ""
)
let mask: Mask = pickMask(
forText: text,
caretPosition: text.index(text.startIndex, offsetBy: range.location),
autocomplete: false
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: text,
caretPosition: text.index(text.startIndex, offsetBy: range.location)
),
autocomplete: false
)
field.allText = result.formattedText.string
field.caretPosition = range.location
return result
}
override open func modifyText(
inRange range: NSRange,
inTextInput field: UITextInput,
withText text: String
) -> Mask.Result {
let updatedText: String = replaceCharacters(
inText: field.allText,
range: range,
withCharacters: text
)
let mask: Mask = pickMask(
forText: updatedText,
caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count),
autocomplete: autocomplete
)
let result: Mask.Result = mask.apply(
toText: CaretString(
string: updatedText,
caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count)
),
autocomplete: autocomplete
)
field.allText = result.formattedText.string
field.caretPosition = result.formattedText.string.distance(
from: result.formattedText.string.startIndex,
to: result.formattedText.caretPosition
)
return result
}
func pickMask(forText text: String, caretPosition: String.Index, autocomplete: Bool) -> Mask {
let primaryAffinity: Int = calculateAffinity(
ofMask: mask,
forText: text,
caretPosition: caretPosition,
autocomplete: autocomplete
)
var masks: [(Mask, Int)] = affineFormats.map { (affineFormat: String) -> (Mask, Int) in
let mask: Mask = try! Mask.getOrCreate(withFormat: affineFormat, customNotations: customNotations)
let affinity: Int = calculateAffinity(
ofMask: mask,
forText: text,
caretPosition: caretPosition,
autocomplete: autocomplete
)
return (mask, affinity)
}
masks.sort { (left: (Mask, Int), right: (Mask, Int)) -> Bool in
return left.1 > right.1
}
var insertIndex: Int = -1
for (index, maskAffinity) in masks.enumerated() {
if primaryAffinity >= maskAffinity.1 {
insertIndex = index
break
}
}
if (insertIndex >= 0) {
masks.insert((mask, primaryAffinity), at: insertIndex)
} else {
masks.append((mask, primaryAffinity))
}
return masks.first!.0
}
func calculateAffinity(
ofMask mask: Mask,
forText text: String,
caretPosition: String.Index,
autocomplete: Bool
) -> Int {
return mask.apply(
toText: CaretString(
string: text,
caretPosition: caretPosition
),
autocomplete: autocomplete
).formattedText.string.prefixIntersection(with: text).count
}
func replaceCharacters(inText text: String, range: NSRange, withCharacters newText: String) -> String {
if 0 < range.length {
let result = NSMutableString(string: text)
result.replaceCharacters(in: range, with: newText)
return result as String
} else {
let result = NSMutableString(string: text)
result.insert(newText, at: range.location)
return result as String
}
}
}
extension String {
func prefixIntersection(with string: String) -> Substring {
let lhsStartIndex = startIndex
var lhsEndIndex = startIndex
let rhsStartIndex = string.startIndex
var rhsEndIndex = string.startIndex
while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex
if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
return self[lhsStartIndex..<lhsEndIndex]
}
}
return self[lhsStartIndex..<lhsEndIndex]
}
}
extension UITextInput {
var allText: String {
get {
guard let all: UITextRange = allTextRange
else { return "" }
return self.text(in: all) ?? ""
}
set(newText) {
guard let all: UITextRange = allTextRange
else { return }
self.replace(all, withText: newText)
}
}
var caretPosition: Int {
get {
if let responder = self as? UIResponder {
// Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
guard responder.isFirstResponder
else { return allText.count }
}
if let range: UITextRange = selectedTextRange {
let selectedTextLocation: UITextPosition = range.start
return offset(from: beginningOfDocument, to: selectedTextLocation)
} else {
return 0
}
}
set(newPosition) {
if let responder = self as? UIResponder {
// Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
guard responder.isFirstResponder
else { return }
}
if newPosition > allText.count {
return
}
let from: UITextPosition = position(from: beginningOfDocument, offset: newPosition)!
let to: UITextPosition = position(from: from, offset: 0)!
selectedTextRange = textRange(from: from, to: to)
}
}
var allTextRange: UITextRange? {
return self.textRange(from: self.beginningOfDocument, to: self.endOfDocument)
}
}
from input-mask-ios.
@MrJox I'm not sure I understood you correctly.
Be noticed, there's no Del
key on iOS, only Backspace
. Don't let virtual keyboard emulation fool you.
Regarding your last error, you may consider following patch for the edge case:
extension String {
func prefixIntersection(with string: String) -> Substring {
guard !self.isEmpty && !string.isEmpty
else { return "" }
let lhsStartIndex = startIndex
var lhsEndIndex = startIndex
let rhsStartIndex = string.startIndex
var rhsEndIndex = string.startIndex
while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex
if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
return self[lhsStartIndex..<lhsEndIndex]
}
}
return self[lhsStartIndex..<lhsEndIndex]
}
}
from input-mask-ios.
@MrJox optimized & simplified version:
extension String {
func prefixIntersection(with string: String) -> Substring {
var lhsIndex = startIndex
var rhsIndex = string.startIndex
while lhsIndex != endIndex && rhsIndex != string.endIndex {
if self[...lhsIndex] == string[...rhsIndex] {
lhsIndex = index(after: lhsIndex)
rhsIndex = string.index(after: rhsIndex)
} else {
return self[..<lhsIndex]
}
}
return self[..<lhsIndex]
}
}
from input-mask-ios.
Maybe there is a way to "lock" mask in textField once it was initialised with any of the masks?
from input-mask-ios.
Hi @MrJox, thanks for your report.
Could you please provide the exact states of the text inside your text field when this error occurs?
I'll be able to trace it later today and then help you with the solution.
from input-mask-ios.
Not absolutely sure what you mean by states but here's what happens:
I have a phone number read from cache and inserted into textField (It's a custom JVFloat textField).
The phone number is +7 (926) 000-00-00 and then I set the pointer after '2' and delete this number. As a result my textView content converts into 8 (796) 000-00-00.
So for some reason it deletes + and treats 7 not as mask but as an actual part of phone number and puts it into brackets and then adds the 8 mask.
from input-mask-ios.
Yeah, your description works for me, thanks!
Stay tuned.
from input-mask-ios.
Hm, so now i do this:
@IBOutlet var loginListener: PrefixAffinityMaskedTextFieldDelegate!
But it still won't work. What i noticed, if you delete digits within brackets with Del key, the digit gets deleted and now it's one less digit within brackets however when I delete with Backspace button, it places/moves either the digit from the left or digit from the right so the amount of digits inside brackets stay unchanged.
What I mean:
original: +7 (916) 000-00-00
with del key: +7 (96) 000-00-00
with backspace key: 8 (796) 000-00-00
Correction:
Now it seems to work however when I try to delete the string in the textField, I have only '+' character remaining and then my app crashes when i try to delete it with
Thread 1: Fatal error: cannot increment beyond endIndex
from input-mask-ios.
@MrJox I'll close this issue with the next library update.
I'm considering adding this to the library sources as an alternative strategy.
from input-mask-ios.
4.0.0
contains this as a feature.
from input-mask-ios.
Related Issues (20)
- can you add tailplaceholder as parameter, like MaskedTexfieldDelegate(primaryFormat: [99] / [99], isTailPlaceholderVisible: true) HOT 3
- Move the list of countries to an external file⦠maybe?
- fix put when primaryMaskFormat "7 ([999])", if we use put("788", textfield) and if string first char is 7 mask will remove it, output: "7 88" HOT 8
- Update the wikis
- Obtain text field value without input-mask HOT 1
- first number of format is wrong with prefix HOT 2
- Being able to use the `UITextField` delegates HOT 11
- First number of mask(#104) HOT 17
- Automatic Caps Lock Activation Issue in MaskedTextField HOT 7
- [Question] How do I initially fill in the mask having only symbols for blanks? HOT 10
- [Question] Character Limit HOT 2
- IOS Objective-C Programmatically example HOT 1
- [!] Unable to determine Swift version for the following pods HOT 1
- Unable to use with single textField HOT 2
- How to define ${dynamic amount }/month in input mask HOT 1
- How to write uppercased text? HOT 2
- Number Formatting (Thousand Separator) HOT 3
- Poor performance when user types fast HOT 3
- can you make mask template always visible while user input text HOT 2
- My repo
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from input-mask-ios.