Skip to content

Commit

Permalink
Added caret position calculation with swift range.
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandr.orlov committed Nov 14, 2020
1 parent f8abda2 commit c5df63b
Showing 6 changed files with 115 additions and 9 deletions.
6 changes: 3 additions & 3 deletions Example/iOS Example/ViewController.swift
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ class ViewController: UIViewController {
let phoneNumberFormatter = DefaultTextInputFormatter(textPattern: "### (###) ###-##-##")
let cardNumberFormatter = DefaultTextInputFormatter(textPattern: "XXXX XXXX XXXX XXXX", patternSymbol: "X")
let sumFormatter = SumTextInputFormatter(textPattern: "# ###,## $")
let placeholderPhoneNumberFormatter = PlaceholderTextInputFormatter(textPattern: "### (###) ###-##-##")
let placeholderPhoneNumberFormatter = PlaceholderTextInputFormatter(textPattern: "+## (###) ###-##-##")

// MARK: - Life Cycle
override func viewDidLoad() {
@@ -76,8 +76,8 @@ private extension ViewController {
phoneNumberField.defaultTextAttributes = convertToNSAttributedStringKeyDictionary([
NSAttributedString.Key.foregroundColor.rawValue: UIColor.white,
NSAttributedString.Key.font.rawValue: getFont()])
// phoneNumberInputController.formatter = phoneNumberFormatter
phoneNumberInputController.formatter = placeholderPhoneNumberFormatter
phoneNumberInputController.formatter = phoneNumberFormatter
// phoneNumberInputController.formatter = placeholderPhoneNumberFormatter
phoneNumberField.delegate = phoneNumberInputController
}

8 changes: 8 additions & 0 deletions Source/Extensions/String+Extension.swift
Original file line number Diff line number Diff line change
@@ -61,6 +61,14 @@ extension String {
return String(self[rangeBegin..<rangeEnd])
}

func leftSlice(end: String.Index) -> String {
return String(self[self.startIndex..<end])
}

func slice(in range: Range<String.Index>) -> String {
return String(self[range])
}

func slice(from: Int, length: Int) -> String? {
guard from < count, from + length < count else { return nil }
let fromIndex = index(startIndex, offsetBy: from)
Original file line number Diff line number Diff line change
@@ -45,13 +45,20 @@ open class DefaultTextInputFormatter: TextInputFormatter {
// MARK: - Format input

open func formatInput(currentText: String, range: NSRange, replacementString text: String) -> FormattedTextValue {
guard let swiftRange = Range(range, in: currentText) else { return .zero }

let unformattedRange = self.unformattedRange(from: range)
let unformattedRange1 = self.unformattedRange1(currentText: currentText, from: swiftRange)

let oldUnformattedText = (textFormatter.unformat(currentText) ?? "") as NSString

let newText = oldUnformattedText.replacingCharacters(in: unformattedRange, with: text)
let formattedText = textFormatter.format(newText) ?? ""

let caretOffset = getCorrectedCaretPosition(range: range, replacementString: text)
let caretOffset = getCorrectedCaretPosition(
range: swiftRange,
replacementString: text
)

return FormattedTextValue(formattedText: formattedText, caretBeginOffset: caretOffset)
}
@@ -75,11 +82,45 @@ open class DefaultTextInputFormatter: TextInputFormatter {
return newRange
}

func unformattedRange1(currentText: String, from range: Range<String.Index>) -> Range<String.Index> {
let number1 = getNumberOfFormatChars(text: currentText, before: range.lowerBound)

let number2 = getNumberOfFormatChars(text: currentText, in: range)

return range
}

private func getNumberOfFormatChars(text: String, before: String.Index) -> Int {
let textLeftSlice = text.leftSlice(end: before)
let patternLeftSlice = textPattern.leftSlice(limit: textLeftSlice.count)
var result = 0
for (textSliceChar, patternSliceChar) in zip(textLeftSlice, patternLeftSlice) {
if textSliceChar == patternSliceChar { result += 1 }
}
return result
}

private func getNumberOfFormatChars(text: String, in range: Range<String.Index>) -> Int {
let textSlice = text.slice(in: range)
let patternSlice = textPattern.slice(in: range)

return 0
}

// MARK: - Caret position calculation

private func getCorrectedCaretPosition(range: NSRange, replacementString: String) -> Int {
let offset = caretPositionCorrector.calculateCaretPositionOffset(originalRange: range, replacementFiltered: replacementString)
return offset
// private func getCorrectedCaretPosition(range: NSRange, replacementString: String) -> Int {
// return caretPositionCorrector.calculateCaretPositionOffset(
// originalRange: range,
// replacementFiltered: replacementString
// )
// }

private func getCorrectedCaretPosition(range: Range<String.Index>, replacementString: String) -> Int {
return caretPositionCorrector.calculateCaretPositionOffset(
originalRange: range,
replacementText: replacementString
)
}

}
Original file line number Diff line number Diff line change
@@ -36,6 +36,17 @@ class CaretPositionCorrector {
return offset
}

func calculateCaretPositionOffset(originalRange range: Range<String.Index>, replacementText: String) -> Int {
var offset = 0

if replacementText.isEmpty {
offset = offsetForRemove(lowerBound: range.lowerBound)
} else {
offset = offsetForInsert(lowerBound: range.lowerBound, replacementLength: replacementText.count)
}
return offset
}

/**
Find indexes of patterns symbols in range

@@ -48,7 +59,10 @@ class CaretPositionCorrector {
var indexes: [String.Index] = []
var tempRange = searchRange
while let range = textPattern.range(
of: String(patternSymbol), options: .caseInsensitive, range: tempRange, locale: nil) {
of: String(patternSymbol),
options: .caseInsensitive,
range: tempRange
) {
tempRange = range.upperBound..<tempRange.upperBound
indexes.append(range.lowerBound)
}
@@ -69,12 +83,19 @@ class CaretPositionCorrector {
let indexes = indexesOfPatternSymbols(in: searchRange)

if let lastIndex = indexes.last {
//return lastIndex.encodedOffset + 1
return lastIndex.utf16Offset(in: textPattern) + 1
}
return 0
}

func offsetForRemove(lowerBound: String.Index) -> Int {
let startIndex = textPattern.startIndex
let searchRange = startIndex..<lowerBound
let indexes = indexesOfPatternSymbols(in: searchRange)
guard let lastIndex = indexes.last else { return 0 }
return lastIndex.utf16Offset(in: textPattern) + 1
}

/**
Calculate offset for caret, when characters will insert

@@ -95,4 +116,16 @@ class CaretPositionCorrector {
return textPattern.distance(from: textPattern.startIndex, to: textPattern.endIndex)
}
}

func offsetForInsert(lowerBound: String.Index, replacementLength: Int) -> Int {
let startIndex = lowerBound
let searchRange = startIndex..<textPattern.endIndex
let indexes = indexesOfPatternSymbols(in: searchRange)

if replacementLength <= indexes.count {
return textPattern.distance(from: textPattern.startIndex, to: indexes[replacementLength - 1]) + 1
} else {
return textPattern.distance(from: textPattern.startIndex, to: textPattern.endIndex)
}
}
}
4 changes: 4 additions & 0 deletions Source/TextFormatter/TextFormatter/FormattedTextValue.swift
Original file line number Diff line number Diff line change
@@ -16,4 +16,8 @@ public struct FormattedTextValue: Equatable {
self.formattedText = formattedText
self.caretBeginOffset = caretBeginOffset
}

static var zero: FormattedTextValue {
return FormattedTextValue(formattedText: "", caretBeginOffset: 0)
}
}
Original file line number Diff line number Diff line change
@@ -90,4 +90,24 @@ class DefaultTextInputFormatterInputTests: XCTestCase {
let expectedResult = FormattedTextValue(formattedText: "12 03", caretBeginOffset: 4)
XCTAssert(actualResult == expectedResult, "\n\(actualResult) must be equal to\n\(expectedResult)")
}

// func test7() {
// let result = formatter.formatInput(
// currentText: "πŸ˜€",
// range: NSRange(location: 0, length: 0),
// replacementString: "πŸ˜€"
// )
// let expectedResult = FormattedTextValue(formattedText: "πŸ˜€", caretBeginOffset: 2)
// XCTAssertEqual(result, expectedResult)
// }
//
// func test8() {
// let result = formatter.formatInput(
// currentText: "πŸ˜€",
// range: NSRange(location: 2, length: 0),
// replacementString: "😎"
// )
// let expectedResult = FormattedTextValue(formattedText: "πŸ˜€πŸ˜Ž", caretBeginOffset: 2)
// XCTAssertEqual(result, expectedResult)
// }
}

0 comments on commit c5df63b

Please sign in to comment.