Я хочу отформатировать UITextField
для ввода номера кредитной карты, чтобы он позволял вводить цифры и автоматически вставлять пробелы, чтобы число было отформатировано следующим образом:
XXXX XXXX XXXX XXXX
Как я могу это сделать?
Я хочу отформатировать UITextField
для ввода номера кредитной карты, чтобы он позволял вводить цифры и автоматически вставлять пробелы, чтобы число было отформатировано следующим образом:
XXXX XXXX XXXX XXXX
Как я могу это сделать?
Если вы используете Swift, прочитайте мой порт этого ответа для Swift 4 и используйте его вместо этого.
Если вы в Objective-C...
Во-первых, в свой UITextFieldDelegate
добавьте эти переменные экземпляра...
NSString *previousTextFieldContent;
UITextRange *previousSelection;
... и эти методы:
// Version 1.3
// Source and explanation: http://stackoverflow.com/a/19161529/1709587
-(void)reformatAsCardNumber:(UITextField *)textField
{
// In order to make the cursor end up positioned correctly, we need to
// explicitly reposition it after we inject spaces into the text.
// targetCursorPosition keeps track of where the cursor needs to end up as
// we modify the string, and at the end we set the cursor position to it.
NSUInteger targetCursorPosition =
[textField offsetFromPosition:textField.beginningOfDocument
toPosition:textField.selectedTextRange.start];
NSString *cardNumberWithoutSpaces =
[self removeNonDigits:textField.text
andPreserveCursorPosition:&targetCursorPosition];
if ([cardNumberWithoutSpaces length] > 19) {
// If the user is trying to enter more than 19 digits, we prevent
// their change, leaving the text field in its previous state.
// While 16 digits is usual, credit card numbers have a hard
// maximum of 19 digits defined by ISO standard 7812-1 in section
// 3.8 and elsewhere. Applying this hard maximum here rather than
// a maximum of 16 ensures that users with unusual card numbers
// will still be able to enter their card number even if the
// resultant formatting is odd.
[textField setText:previousTextFieldContent];
textField.selectedTextRange = previousSelection;
return;
}
NSString *cardNumberWithSpaces =
[self insertCreditCardSpaces:cardNumberWithoutSpaces
andPreserveCursorPosition:&targetCursorPosition];
textField.text = cardNumberWithSpaces;
UITextPosition *targetPosition =
[textField positionFromPosition:[textField beginningOfDocument]
offset:targetCursorPosition];
[textField setSelectedTextRange:
[textField textRangeFromPosition:targetPosition
toPosition:targetPosition]
];
}
-(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
// Note textField current state before performing the change, in case
// reformatTextField wants to revert it
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return YES;
}
/*
Removes non-digits from the string, decrementing 'cursorPosition' as
appropriate so that, for instance, if we pass in '@"1111 1123 1111"'
and a cursor position of '8', the cursor position will be changed to
'7' (keeping it between the '2' and the '3' after the spaces are removed).
*/
- (NSString *)removeNonDigits:(NSString *)string
andPreserveCursorPosition:(NSUInteger *)cursorPosition
{
NSUInteger originalCursorPosition = *cursorPosition;
NSMutableString *digitsOnlyString = [NSMutableString new];
for (NSUInteger i=0; i<[string length]; i++) {
unichar characterToAdd = [string characterAtIndex:i];
if (isdigit(characterToAdd)) {
NSString *stringToAdd =
[NSString stringWithCharacters:&characterToAdd
length:1];
[digitsOnlyString appendString:stringToAdd];
}
else {
if (i < originalCursorPosition) {
(*cursorPosition)--;
}
}
}
return digitsOnlyString;
}
/*
Detects the card number format from the prefix, then inserts spaces into
the string to format it as a credit card number, incrementing 'cursorPosition'
as appropriate so that, for instance, if we pass in '@"111111231111"' and a
cursor position of '7', the cursor position will be changed to '8' (keeping
it between the '2' and the '3' after the spaces are added).
*/
- (NSString *)insertCreditCardSpaces:(NSString *)string
andPreserveCursorPosition:(NSUInteger *)cursorPosition
{
// Mapping of card prefix to pattern is taken from
// https://baymard.com/checkout-usability/credit-card-patterns
// UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format
bool is456 = [string hasPrefix: @"1"];
// These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all
// these as 4-6-5-4 to err on the side of always letting the user type more
// digits.
bool is465 = [string hasPrefix: @"34"] ||
[string hasPrefix: @"37"] ||
// Diners Club
[string hasPrefix: @"300"] ||
[string hasPrefix: @"301"] ||
[string hasPrefix: @"302"] ||
[string hasPrefix: @"303"] ||
[string hasPrefix: @"304"] ||
[string hasPrefix: @"305"] ||
[string hasPrefix: @"309"] ||
[string hasPrefix: @"36"] ||
[string hasPrefix: @"38"] ||
[string hasPrefix: @"39"];
// In all other cases, assume 4-4-4-4-3.
// This won't always be correct; for instance, Maestro has 4-4-5 cards
// according to https://baymard.com/checkout-usability/credit-card-patterns,
// but I don't know what prefixes identify particular formats.
bool is4444 = !(is456 || is465);
NSMutableString *stringWithAddedSpaces = [NSMutableString new];
NSUInteger cursorPositionInSpacelessString = *cursorPosition;
for (NSUInteger i=0; i<[string length]; i++) {
bool needs465Spacing = (is465 && (i == 4 || i == 10 || i == 15));
bool needs456Spacing = (is456 && (i == 4 || i == 9 || i == 15));
bool needs4444Spacing = (is4444 && i > 0 && (i % 4) == 0);
if (needs465Spacing || needs456Spacing || needs4444Spacing) {
[stringWithAddedSpaces appendString:@" "];
if (i < cursorPositionInSpacelessString) {
(*cursorPosition)++;
}
}
unichar characterToAdd = [string characterAtIndex:i];
NSString *stringToAdd =
[NSString stringWithCharacters:&characterToAdd length:1];
[stringWithAddedSpaces appendString:stringToAdd];
}
return stringWithAddedSpaces;
}
Во-вторых, установите reformatCardNumber:
вызываться всякий раз, когда текстовое поле запускает событие UIControlEventEditingChanged
:
[yourTextField addTarget:yourTextFieldDelegate
action:@selector(reformatAsCardNumber:)
forControlEvents:UIControlEventEditingChanged];
(Конечно, вам нужно будет сделать это в какой-то момент после создания экземпляра вашего текстового поля и его делегата. Если вы используете раскадровки, метод viewDidLoad
вашего контроллера представления является подходящим местом.
Это обманчиво сложная проблема. Три важных вопроса, которые могут быть не сразу очевидны (и какие предыдущие ответы здесь не учитываются):
Хотя формат XXXX XXXX XXXX XXXX
для номеров кредитных и дебетовых карт является наиболее распространенным, он не единственный. Например, карты American Express имеют 15-значные номера, обычно записанные в формате XXXX XXXXXX XXXXX
, например:
Даже карты Visa могут содержать не более 16 цифр, а карты Maestro могут иметь больше:
Для пользователя существует больше способов взаимодействия с текстовым полем, чем просто ввод отдельных символов в конце существующего ввода. Вам также необходимо правильно обрабатывать добавление символов в середине строки, удаление отдельных символов, удаление нескольких выбранных символов и вставку нескольких символов. Некоторые более простые/более наивные подходы к этой проблеме не смогут правильно обрабатывать некоторые из этих взаимодействий. Наиболее извращенным случаем является вставка пользователем нескольких символов в середине строки для замены других символов, и это решение достаточно общее, чтобы справиться с этим.
Вам не нужно просто правильно переформатировать текст текстового поля после того, как пользователь его модифицирует - вам также нужно разумно расположить текстовый курсор. Наивные подходы к проблеме, которые не учитывают это, почти наверняка в некоторых случаях приведут к глупым действиям с текстовым курсором (например, к концу текстового поля после того, как пользователь добавит цифру в середине).).
Чтобы решить проблему № 1, мы используем частичное сопоставление префиксов номеров карт форматам, отобранным The Baymard Institute по адресу https://baymard.com/checkout-usability/credit-card-patterns. Мы можем автоматически определить поставщика карты по первой паре цифр и (в некоторых случаях) определить формат и соответствующим образом изменить наше форматирование. Спасибо cnotethegr8 за поддержку этой идеи в этом ответе.
Самый простой и легкий способ справиться с проблемой № 2 (и способом, использованным в приведенном выше коде) - это убрать все пробелы и вставлять их в правильные позиции каждый раз, когда изменяется содержимое текстового поля, избавляя нас от необходимости разбираться выяснить, какой вид манипуляции с текстом (вставка, удаление или замена) происходит и обрабатывать возможности по-разному.
Чтобы справиться с проблемой № 3, мы отслеживаем, как изменяется требуемый индекс курсора, когда мы удаляем нецифровые символы, а затем вставляем пробелы. Вот почему код довольно многословно выполняет эти манипуляции посимвольно, используя NSMutableString
, а не используя методы замены строк NSString
.
Наконец, есть еще одна ловушка: возвращение NO
из textField: shouldChangeCharactersInRange: replacementString
ломает кнопку "Вырезать", которую получает пользователь, когда выбирает текст в текстовом поле, поэтому я этого не делаю. Возврат NO
из этого метода приводит к тому, что "Cut" просто не обновляет буфер обмена, и я не знаю ни исправления, ни обходного пути. В результате нам нужно выполнить переформатирование текстового поля в обработчике UIControlEventEditingChanged
вместо (более очевидно) в shouldChangeCharactersInRange:
самой.
К счастью, кажется, что обработчики событий UIControl вызываются до того, как обновления пользовательского интерфейса выводятся на экран, поэтому этот подход работает нормально.
Есть также целый ряд небольших вопросов о том, как именно должно вести себя текстовое поле, которые не имеют очевидных правильных ответов:
Возможно, любой ответ на любой из этих вопросов будет адекватным, но я перечисляю их просто для того, чтобы прояснить, что на самом деле существует множество особых случаев, о которых вы могли бы подумать, если бы вы были достаточно одержимы. В приведенном выше коде я выбрал ответы на эти вопросы, которые показались мне разумными. Если вы испытываете сильные чувства к любому из этих пунктов, которые не совместимы с поведением моего кода, должно быть достаточно легко настроить его под ваши нужды.
Вероятно, вы можете оптимизировать мой код, иначе может быть проще, но этот код должен работать:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
__block NSString *text = [textField text];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789\b"];
string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
if ([string rangeOfCharacterFromSet:[characterSet invertedSet]].location != NSNotFound) {
return NO;
}
text = [text stringByReplacingCharactersInRange:range withString:string];
text = [text stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *newString = @"";
while (text.length > 0) {
NSString *subString = [text substringToIndex:MIN(text.length, 4)];
newString = [newString stringByAppendingString:subString];
if (subString.length == 4) {
newString = [newString stringByAppendingString:@" "];
}
text = [text substringFromIndex:MIN(text.length, 4)];
}
newString = [newString stringByTrimmingCharactersInSet:[characterSet invertedSet]];
if (newString.length >= 20) {
return NO;
}
[textField setText:newString];
return NO;
}
Ниже приведен рабочий порт ответа Swift 4 от Logicopolis (который, в свою очередь, является портом Swift 2 старой версии моего принятого ответа в Objective-C), расширен с помощью трюка cnotethegr8 для поддержки карт Amex, а затем улучшен для поддержки большего количества форматов карт. Я предлагаю просмотреть принятый ответ, если вы еще этого не сделали, поскольку он помогает объяснить мотивы, лежащие в основе большей части этого кода.
Обратите внимание, что минимальная последовательность шагов, необходимых для того, чтобы увидеть это в действии:
Main.storyboard
добавьте текстовое поле.ViewController
делегатом текстового поля.ViewController.swift
.IBOutlet
к текстовому полю.import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
private var previousTextFieldContent: String?
private var previousSelection: UITextRange?
@IBOutlet var yourTextField: UITextField!;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
yourTextField.addTarget(self, action: #selector(reformatAsCardNumber), for: .editingChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return true
}
@objc func reformatAsCardNumber(textField: UITextField) {
var targetCursorPosition = 0
if let startPosition = textField.selectedTextRange?.start {
targetCursorPosition = textField.offset(from: textField.beginningOfDocument, to: startPosition)
}
var cardNumberWithoutSpaces = ""
if let text = textField.text {
cardNumberWithoutSpaces = self.removeNonDigits(string: text, andPreserveCursorPosition: &targetCursorPosition)
}
if cardNumberWithoutSpaces.count > 19 {
textField.text = previousTextFieldContent
textField.selectedTextRange = previousSelection
return
}
let cardNumberWithSpaces = self.insertCreditCardSpaces(cardNumberWithoutSpaces, preserveCursorPosition: &targetCursorPosition)
textField.text = cardNumberWithSpaces
if let targetPosition = textField.position(from: textField.beginningOfDocument, offset: targetCursorPosition) {
textField.selectedTextRange = textField.textRange(from: targetPosition, to: targetPosition)
}
}
func removeNonDigits(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
var digitsOnlyString = ""
let originalCursorPosition = cursorPosition
for i in Swift.stride(from: 0, to: string.count, by: 1) {
let characterToAdd = string[string.index(string.startIndex, offsetBy: i)]
if characterToAdd >= "0" && characterToAdd <= "9" {
digitsOnlyString.append(characterToAdd)
}
else if i < originalCursorPosition {
cursorPosition -= 1
}
}
return digitsOnlyString
}
func insertCreditCardSpaces(_ string: String, preserveCursorPosition cursorPosition: inout Int) -> String {
// Mapping of card prefix to pattern is taken from
// https://baymard.com/checkout-usability/credit-card-patterns
// UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format
let is456 = string.hasPrefix("1")
// These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all these
// as 4-6-5-4 to err on the side of always letting the user type more digits.
let is465 = [
// Amex
"34", "37",
// Diners Club
"300", "301", "302", "303", "304", "305", "309", "36", "38", "39"
].contains { string.hasPrefix($0) }
// In all other cases, assume 4-4-4-4-3.
// This won't always be correct; for instance, Maestro has 4-4-5 cards according
// to https://baymard.com/checkout-usability/credit-card-patterns, but I don't
// know what prefixes identify particular formats.
let is4444 = !(is456 || is465)
var stringWithAddedSpaces = ""
let cursorPositionInSpacelessString = cursorPosition
for i in 0..<string.count {
let needs465Spacing = (is465 && (i == 4 || i == 10 || i == 15))
let needs456Spacing = (is456 && (i == 4 || i == 9 || i == 15))
let needs4444Spacing = (is4444 && i > 0 && (i % 4) == 0)
if needs465Spacing || needs456Spacing || needs4444Spacing {
stringWithAddedSpaces.append(" ")
if i < cursorPositionInSpacelessString {
cursorPosition += 1
}
}
let characterToAdd = string[string.index(string.startIndex, offsetBy:i)]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
}
Адаптация этого к другим ситуациям - например, ваш делегат не является ViewController
- оставлен читателю в качестве упражнения.
Я думаю, что это хорошо:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSLog(@"%@",NSStringFromRange(range));
// Only the 16 digits + 3 spaces
if (range.location == 19) {
return NO;
}
// Backspace
if ([string length] == 0)
return YES;
if ((range.location == 4) || (range.location == 9) || (range.location == 14))
{
NSString *str = [NSString stringWithFormat:@"%@ ",textField.text];
textField.text = str;
}
return YES;
}
Swift 3 с использованием Fawkes отвечает как базовое. Добавлена поддержка формата карты Amex. Добавлена реформация при изменении типа карты.
Сначала создайте новый класс с помощью этого кода:
extension String {
func containsOnlyDigits() -> Bool
{
let notDigits = NSCharacterSet.decimalDigits.inverted
if rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil
{
return true
}
return false
}
}
import UIKit
var creditCardFormatter : CreditCardFormatter
{
return CreditCardFormatter.sharedInstance
}
class CreditCardFormatter : NSObject
{
static let sharedInstance : CreditCardFormatter = CreditCardFormatter()
func formatToCreditCardNumber(isAmex: Bool, textField : UITextField, withPreviousTextContent previousTextContent : String?, andPreviousCursorPosition previousCursorSelection : UITextRange?) {
var selectedRangeStart = textField.endOfDocument
if textField.selectedTextRange?.start != nil {
selectedRangeStart = (textField.selectedTextRange?.start)!
}
if let textFieldText = textField.text
{
var targetCursorPosition : UInt = UInt(textField.offset(from:textField.beginningOfDocument, to: selectedRangeStart))
let cardNumberWithoutSpaces : String = removeNonDigitsFromString(string: textFieldText, andPreserveCursorPosition: &targetCursorPosition)
if cardNumberWithoutSpaces.characters.count > 19
{
textField.text = previousTextContent
textField.selectedTextRange = previousCursorSelection
return
}
var cardNumberWithSpaces = ""
if isAmex {
cardNumberWithSpaces = insertSpacesInAmexFormat(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
}
else
{
cardNumberWithSpaces = insertSpacesIntoEvery4DigitsIntoString(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
}
textField.text = cardNumberWithSpaces
if let finalCursorPosition = textField.position(from:textField.beginningOfDocument, offset: Int(targetCursorPosition))
{
textField.selectedTextRange = textField.textRange(from: finalCursorPosition, to: finalCursorPosition)
}
}
}
func removeNonDigitsFromString(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
var digitsOnlyString : String = ""
for index in stride(from: 0, to: string.characters.count, by: 1)
{
let charToAdd : Character = Array(string.characters)[index]
if isDigit(character: charToAdd)
{
digitsOnlyString.append(charToAdd)
}
else
{
if index < Int(cursorPosition)
{
cursorPosition -= 1
}
}
}
return digitsOnlyString
}
private func isDigit(character : Character) -> Bool
{
return "\(character)".containsOnlyDigits()
}
func insertSpacesInAmexFormat(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
var stringWithAddedSpaces : String = ""
for index in stride(from: 0, to: string.characters.count, by: 1)
{
if index == 4
{
stringWithAddedSpaces += " "
if index < Int(cursorPosition)
{
cursorPosition += 1
}
}
if index == 10 {
stringWithAddedSpaces += " "
if index < Int(cursorPosition)
{
cursorPosition += 1
}
}
if index < 15 {
let characterToAdd : Character = Array(string.characters)[index]
stringWithAddedSpaces.append(characterToAdd)
}
}
return stringWithAddedSpaces
}
func insertSpacesIntoEvery4DigitsIntoString(string : String, andPreserveCursorPosition cursorPosition : inout UInt) -> String {
var stringWithAddedSpaces : String = ""
for index in stride(from: 0, to: string.characters.count, by: 1)
{
if index != 0 && index % 4 == 0 && index < 16
{
stringWithAddedSpaces += " "
if index < Int(cursorPosition)
{
cursorPosition += 1
}
}
if index < 16 {
let characterToAdd : Character = Array(string.characters)[index]
stringWithAddedSpaces.append(characterToAdd)
}
}
return stringWithAddedSpaces
}
}
В вашем ViewControllerClass добавьте эту функцию
func reformatAsCardNumber(textField:UITextField){
let formatter = CreditCardFormatter()
var isAmex = false
if selectedCardType == "AMEX" {
isAmex = true
}
formatter.formatToCreditCardNumber(isAmex: isAmex, textField: textField, withPreviousTextContent: textField.text, andPreviousCursorPosition: textField.selectedTextRange)
}
Затем добавьте цель в свой текстовый элемент
youtTextField.addTarget(self, action: #selector(self.reformatAsCardNumber(textField:)), for: UIControlEvents.editingChanged)
Зарегистрировать новую переменную и тип отправленной карты
var selectedCardType: String? {
didSet{
reformatAsCardNumber(textField: yourTextField)
}
}
Спасибо Fawkes за его код!
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if textField == CardNumTxt
{
let replacementStringIsLegal = string.rangeOfCharacterFromSet(NSCharacterSet(charactersInString: "0123456789").invertedSet) == nil
if !replacementStringIsLegal
{
return false
}
let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
let components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "0123456789").invertedSet)
let decimalString = components.joinWithSeparator("") as NSString
let length = decimalString.length
let hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
if length == 0 || (length > 16 && !hasLeadingOne) || length > 19
{
let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 16) ? false : true
}
var index = 0 as Int
let formattedString = NSMutableString()
if hasLeadingOne
{
formattedString.appendString("1 ")
index += 1
}
if length - index > 4
{
let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substringWithRange(NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
let remainder = decimalString.substringFromIndex(index)
formattedString.appendString(remainder)
textField.text = formattedString as String
return false
}
else
{
return true
}
}
formattedString.appendFormat( "% @-", prefix) chage из "-" любой другой ваш выбор
Еще одна версия принятого ответа в Swift 2...
Убедитесь, что они есть в экземпляре делегата:
private var previousTextFieldContent: String?
private var previousSelection: UITextRange?
И также убедитесь, что ваше текстовое поле вызывает reformatAsCardNumber:
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)), forControlEvents: .EditingChanged)
Вам делегат текстового поля должен будет сделать это:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return true
}
Наконец, включите следующие методы:
func reformatAsCardNumber(textField: UITextField) {
var targetCursorPosition = 0
if let startPosition = textField.selectedTextRange?.start {
targetCursorPosition = textField.offsetFromPosition(textField.beginningOfDocument, toPosition: startPosition)
}
var cardNumberWithoutSpaces = ""
if let text = textField.text {
cardNumberWithoutSpaces = self.removeNonDigits(text, andPreserveCursorPosition: &targetCursorPosition)
}
if cardNumberWithoutSpaces.characters.count > 19 {
textField.text = previousTextFieldContent
textField.selectedTextRange = previousSelection
return
}
let cardNumberWithSpaces = self.insertSpacesEveryFourDigitsIntoString(cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
textField.text = cardNumberWithSpaces
if let targetPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: targetCursorPosition) {
textField.selectedTextRange = textField.textRangeFromPosition(targetPosition, toPosition: targetPosition)
}
}
func removeNonDigits(string: String, inout andPreserveCursorPosition cursorPosition: Int) -> String {
var digitsOnlyString = ""
let originalCursorPosition = cursorPosition
for i in 0.stride(to: string.characters.count, by: 1) {
let characterToAdd = string[string.startIndex.advancedBy(i)]
if characterToAdd >= "0" && characterToAdd <= "9" {
digitsOnlyString.append(characterToAdd)
}
else if i < originalCursorPosition {
cursorPosition -= 1
}
}
return digitsOnlyString
}
func insertSpacesEveryFourDigitsIntoString(string: String, inout andPreserveCursorPosition cursorPosition: Int) -> String {
var stringWithAddedSpaces = ""
let cursorPositionInSpacelessString = cursorPosition
for i in 0.stride(to: string.characters.count, by: 1) {
if i > 0 && (i % 4) == 0 {
stringWithAddedSpaces.appendContentsOf(" ")
if i < cursorPositionInSpacelessString {
cursorPosition += 1
}
}
let characterToAdd = string[string.startIndex.advancedBy(i)]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
Вот версия Swift, если это полезно для тех, кто все еще ищет этот ответ, но используя Swift вместо Objective-C. Понятия все равно остаются неизменными.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
//range.length will be greater than 0 if user is deleting text - allow it to replace
if range.length > 0
{
return true
}
//Don't allow empty strings
if string == " "
{
return false
}
//Check for max length including the spacers we added
if range.location == 20
{
return false
}
var originalText = textField.text
let replacementText = string.stringByReplacingOccurrencesOfString(" ", withString: "")
//Verify entered text is a numeric value
let digits = NSCharacterSet.decimalDigitCharacterSet()
for char in replacementText.unicodeScalars
{
if !digits.longCharacterIsMember(char.value)
{
return false
}
}
//Put an empty space after every 4 places
if originalText!.length() % 5 == 0
{
originalText?.appendContentsOf(" ")
textField.text = originalText
}
return true
}
Так что я хотел этого с меньшим количеством кода, поэтому я использовал код здесь и немного перепрофилировал его. У меня было два поля на экране: один для числа и один для даты истечения срока действия, поэтому я сделал его более многоразовым.
Альтернативный ответ Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let currentText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { return true }
if textField == cardNumberTextField {
textField.text = currentText.grouping(every: 4, with: " ")
return false
}
else { // Expiry Date Text Field
textField.text = currentText.grouping(every: 2, with: "/")
return false
}
}
extension String {
func grouping(every groupSize: String.IndexDistance, with separator: Character) -> String {
let cleanedUpCopy = replacingOccurrences(of: String(separator), with: "")
return String(cleanedUpCopy.characters.enumerated().map() {
$0.offset % groupSize == 0 ? [separator, $0.element] : [$0.element]
}.joined().dropFirst())
}
}
Определите метод ниже и вызовите его в делегатах UITextfield или где требуется
-(NSString*)processString :(NSString*)yourString
{
if(yourString == nil){
return @"";
}
int stringLength = (int)[yourString length];
int len = 4; // Length after which you need to place added character
NSMutableString *str = [NSMutableString string];
int i = 0;
for (; i < stringLength; i+=len) {
NSRange range = NSMakeRange(i, len);
[str appendString:[yourString substringWithRange:range]];
if(i!=stringLength -4){
[str appendString:@" "]; //If required string format is XXXX-XXXX-XXXX-XXX then just replace [str appendString:@"-"]
}
}
if (i < [str length]-1) { // add remaining part
[str appendString:[yourString substringFromIndex:i]];
}
//Returning required string
return str;
}
Чтобы достичь цели форматирования текста, введенного в текстовое поле таким образом XXXX XXXX XXXX XXXX важно помнить о некоторых важных вещах. Помимо того факта, что 16-значный номер карты, разделяемый каждые четыре цифры, является наиболее распространенным формат, есть карточки с 15 цифрами (формат AMX XXXX XXXXXX XXXXX) и другие с 13 цифрами или даже с 19 цифрами (https://en.wikipedia.org/wiki/Payment_card_number ). Другая важная вещь, которую вы должны рассмотреть, - это настроить текстовый экран, чтобы разрешить только цифры, настроить тип клавиатуры, поскольку numberPad - хороший старт, но удобно реализовать метод, который защищает ввод.
Отправной точкой является выбор, когда вы хотите отформатировать номер, пока пользователь вводит номер или когда пользователь покидает текстовое поле. В случае, если вы хотите отформатировать, когда пользователь покидает текстовый экран, удобно используйте метод textFieldDidEndEditing (_:) делегировать содержимое текстового поля и отформатировать его.
В случае, когда вы, когда пользователь вводит номер, полезно textField (_: shouldChangeCharactersIn: replacementString:) метод делегата, который вызывается при изменении текущего текста.
В обоих случаях все еще есть проблема, выясните, какой правильный формат для введенного числа, IMHO и на основе всех чисел, которые я видел, есть только два основных формата: формат Amex с 15 цифрами, описанными выше, и формат, который содержит номер группы карт каждые четыре цифры, которые не заботятся о том, сколько цифр там, в данном случае, как правило, например, карта с 13 цифрами будет отформатирована XXXXX XXXX XXXX X и будет выглядеть 19 цифр например XXXX XXXX XXXX XXXX XXX, это будет работать для наиболее распространенных случаев (16 цифр) и для других. Таким образом, вы могли бы выяснить, как управлять случаем AmEx с тем же алгоритмом, что и при игре с магическими числами.
Я использовал RegEx, чтобы гарантировать, что 15-значная карта - это американский экспресс, в случае других конкретных форматов.
let regex = NSPredicate(format: "SELF MATCHES %@", "3[47][A-Za-z0-9*-]{13,}" )
let isAmex = regex.evaluate(with: stringToValidate)
Я настоятельно рекомендую использовать специальный RegEx, который полезен для идентификации Эмитента и определения количества принятых цифр.
Теперь мой быстрый подход к решению с textFieldDidEndEditing -
func textFieldDidEndEditing(_ textField: UITextField) {
_=format(cardNumber: textField.text!)
}
func format(cardNumber:String)->String{
var formatedCardNumber = ""
var i :Int = 0
//loop for every character
for character in cardNumber.characters{
//in case you want to replace some digits in the middle with * for security
if(i < 6 || i >= cardNumber.characters.count - 4){
formatedCardNumber = formatedCardNumber + String(character)
}else{
formatedCardNumber = formatedCardNumber + "*"
}
//insert separators every 4 spaces(magic number)
if(i == 3 || i == 7 || i == 11 || (i == 15 && cardNumber.characters.count > 16 )){
formatedCardNumber = formatedCardNumber + "-"
// could use just " " for spaces
}
i = i + 1
}
return formatedCardNumber
}
и для shouldChangeCharactersIn: replacementString: Swift 3.0 От Jayesh Miruliya Ответ, поставьте разделитель между группой из четырех символов
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if textField == CardNumTxt
{
let replacementStringIsLegal = string.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789").inverted) == nil
if !replacementStringIsLegal
{
return false
}
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let components = newString.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted)
let decimalString = components.joined(separator: "") as NSString
let length = decimalString.length
let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)
if length == 0 || (length > 16 && !hasLeadingOne) || length > 19
{
let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 16) ? false : true
}
var index = 0 as Int
let formattedString = NSMutableString()
if hasLeadingOne
{
formattedString.append("1 ")
index += 1
}
if length - index > 4 //magic number separata every four characters
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
if length - index > 4
{
let prefix = decimalString.substring(with: NSMakeRange(index, 4))
formattedString.appendFormat("%@-", prefix)
index += 4
}
let remainder = decimalString.substring(from: index)
formattedString.append(remainder)
textField.text = formattedString as String
return false
}
else
{
return true
}
}
Swift 3.2
Небольшая коррекция в ответе и рабочем коде @Lucas в быстром 3.2. Также автоматически удаляется символ пробела.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if range.location == 19 {
return false
}
if range.length == 1 {
if (range.location == 5 || range.location == 10 || range.location == 15) {
let text = textField.text ?? ""
textField.text = text.substring(to: text.index(before: text.endIndex))
}
return true
}
if (range.location == 4 || range.location == 9 || range.location == 14) {
textField.text = String(format: "%@ ", textField.text ?? "")
}
return true
}
Решение Swift 3 на основе Mark Amery Objective-C решение:
Внедрить действия и методы делегирования:
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:))
textField.delegate = self
Методы делегата TextField и другие методы:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return true
}
func reformatAsCardNumber(_ textField: UITextField) {
var targetCursorPosition = 0
if let startPosition = textField.selectedTextRange?.start {
targetCursorPosition = textField.offset(from:textField.beginningOfDocument, to: startPosition)
}
var cardNumberWithoutSpaces = ""
if let text = textField.text {
cardNumberWithoutSpaces = removeNonDigits(string: text, andPreserveCursorPosition: &targetCursorPosition)
}
if cardNumberWithoutSpaces.characters.count > 19 {
textField.text = previousTextFieldContent
textField.selectedTextRange = previousSelection
return
}
let cardNumberWithSpaces = self.insertSpacesEveryFourDigitsIntoString(string: cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
textField.text = cardNumberWithSpaces
if let targetPosition = textField.position(from: textField.beginningOfDocument, offset: targetCursorPosition) {
textField.selectedTextRange = textField.textRange(from: targetPosition, to: targetPosition)
}
}
func removeNonDigits(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
var digitsOnlyString = ""
let originalCursorPosition = cursorPosition
for i in stride(from: 0, to: string.characters.count, by: 1) {
let characterToAdd = string[string.index(string.startIndex, offsetBy: i)]
if characterToAdd >= "0" && characterToAdd <= "9" {
digitsOnlyString.append(characterToAdd)
}
else if i < originalCursorPosition {
cursorPosition -= 1
}
}
return digitsOnlyString
}
func insertSpacesEveryFourDigitsIntoString(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
var stringWithAddedSpaces = ""
let cursorPositionInSpacelessString = cursorPosition
for i in stride(from: 0, to: string.characters.count, by: 1) {
if i > 0 && (i % 4) == 0 {
stringWithAddedSpaces.append(" ")
if i < cursorPositionInSpacelessString {
cursorPosition += 1
}
}
let characterToAdd = string[string.index(string.startIndex, offsetBy: i)]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
Вот быстрая копия принятого ответа на случай, если кому-то это понадобится. Это в основном класс-оболочка. Я не потратил слишком много времени на его оптимизацию, но он готов к использованию.
var creditCardFormatter : CreditCardFormatter
{
return CreditCardFormatter.sharedInstance
}
class CreditCardFormatter : NSObject
{
static let sharedInstance : CreditCardFormatter = CreditCardFormatter()
func formatToCreditCardNumber(textField : UITextField, withPreviousTextContent previousTextContent : String?, andPreviousCursorPosition previousCursorSelection : UITextRange?)
{
if let selectedRangeStart = textField.selectedTextRange?.start, textFieldText = textField.text
{
var targetCursorPosition : UInt = UInt(textField.offsetFromPosition(textField.beginningOfDocument, toPosition: selectedRangeStart))
let cardNumberWithoutSpaces : String = removeNonDigitsFromString(textFieldText, andPreserveCursorPosition: &targetCursorPosition)
if cardNumberWithoutSpaces.characters.count > 19
{
textField.text = previousTextContent
textField.selectedTextRange = previousCursorSelection
return
}
let cardNumberWithSpaces : String = insertSpacesIntoEvery4DigitsIntoString(cardNumberWithoutSpaces, andPreserveCursorPosition: &targetCursorPosition)
textField.text = cardNumberWithSpaces
if let finalCursorPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: Int(targetCursorPosition))
{
textField.selectedTextRange = textField.textRangeFromPosition(finalCursorPosition, toPosition: finalCursorPosition)
}
}
}
func removeNonDigitsFromString(string : String,inout andPreserveCursorPosition cursorPosition : UInt) -> String
{
var digitsOnlyString : String = ""
for index in 0.stride(to: string.characters.count, by: 1)
{
let charToAdd : Character = Array(string.characters)[index]
if isDigit(charToAdd)
{
digitsOnlyString.append(charToAdd)
}
else
{
if index < Int(cursorPosition)
{
cursorPosition -= 1
}
}
}
return digitsOnlyString
}
private func isDigit(character : Character) -> Bool
{
return "\(character)".containsOnlyDigits()
}
func insertSpacesIntoEvery4DigitsIntoString(string : String, inout andPreserveCursorPosition cursorPosition : UInt) -> String
{
var stringWithAddedSpaces : String = ""
for index in 0.stride(to: string.characters.count, by: 1)
{
if index != 0 && index % 4 == 0
{
stringWithAddedSpaces += " "
if index < Int(cursorPosition)
{
cursorPosition += 1
}
}
let characterToAdd : Character = Array(string.characters)[index]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
}
extension String
{
func containsOnlyDigits() -> Bool
{
let notDigits : NSCharacterSet = NSCharacterSet.decimalDigitCharacterSet().invertedSet
if (rangeOfCharacterFromSet(notDigits, options: NSStringCompareOptions.LiteralSearch, range: nil) == nil)
{
return true
}
return false
}
}
Вот ответ Котлина, основанный на Марке Эмери
fun formatCardNumber(cardNumber: String): String {
var trimmedCardNumber = cardNumber.replace(" ","")
// UATP cards have 4-5-6 (XXXX-XXXXX-XXXXXX) format
val is456 = trimmedCardNumber.startsWith("1")
// These prefixes reliably indicate either a 4-6-5 or 4-6-4 card. We treat all these
// as 4-6-5-4 to err on the side of always letting the user type more digits.
val is465 = listOf("34", "37", "300", "301", "302", "303", "304", "305", "309", "36", "38", "39")
.any { trimmedCardNumber.startsWith(it) }
// In all other cases, assume 4-4-4-4.
val is4444 = !(is456 || is465)
trimmedCardNumber = if (is456 || is465) {
trimmedCardNumber.take(cardNumberMaxLengthAmex)
} else {
trimmedCardNumber.take(cardNumberMaxLength)
}
var cardNumberWithAddedSpaces = ""
trimmedCardNumber.forEachIndexed { index, c ->
val needs465Spacing = is465 && (index == 4 || index == 10 || index == 15)
val needs456Spacing = is456 && (index == 4 || index == 9 || index == 15)
val needs4444Spacing = is4444 && index > 0 && index % 4 == 0
if (needs465Spacing || needs456Spacing || needs4444Spacing) {
cardNumberWithAddedSpaces += " "
}
cardNumberWithAddedSpaces += c
}
return cardNumberWithAddedSpaces
}
Затем добавьте текст измененного слушателя на текст редактирования
var flag = false
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (flag) {
flag = false
} else {
val text = formatCardNumber(s.toString())
flag = true
editText.setText(text)
editText.setSelection(text.count())
}
}
override fun afterTextChanged(s: Editable?) {}
})
Отметьте это решение. Я нашел в Autorize.net SDK Пример.
Сделайте свой UITextField
тип клавиатуры для Numeric
.
Он будет маскировать номера кредитных карт с помощью "X", а путем добавления пробелов он сделает формат 'XXXX XXXX XXXX 1234'
.
В файле заголовка .h.
#define kSpace @" "
#define kCreditCardLength 16
#define kCreditCardLengthPlusSpaces (kCreditCardLength + 3)
#define kCreditCardObscureLength (kCreditCardLength - 4)
@property (nonatomic, strong) NSString *creditCardBuf;
IBOutlet UITextField *txtCardNumber;
В .m файле
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField == txtCardNumber) {
if ([string length] > 0) { //NOT A BACK SPACE Add it
if ([self isMaxLength:textField])
return NO;
self.creditCardBuf = [NSString stringWithFormat:@"%@%@", self.creditCardBuf, string];
} else {
//Back Space do manual backspace
if ([self.creditCardBuf length] > 1) {
self.creditCardBuf = [self.creditCardBuf substringWithRange:NSMakeRange(0, [self.creditCardBuf length] - 1)];
} else {
self.creditCardBuf = @"";
}
}
[self formatValue:textField];
}
return NO;
}
- (BOOL) isMaxLength:(UITextField *)textField {
if (textField == txtCardNumber && [textField.text length] >= kCreditCardLengthPlusSpaces) {
return YES;
}
return NO;
}
- (void) formatValue:(UITextField *)textField {
NSMutableString *value = [NSMutableString string];
if (textField == txtCardNumber) {
NSInteger length = [self.creditCardBuf length];
for (int i = 0; i < length; i++) {
// Reveal only the last character.
if (length <= kCreditCardObscureLength) {
if (i == (length - 1)) {
[value appendString:[self.creditCardBuf substringWithRange:NSMakeRange(i,1)]];
} else {
[value appendString:@"X"];
}
}
// Reveal the last 4 characters
else {
if (i < kCreditCardObscureLength) {
[value appendString:@"X"];
} else {
[value appendString:[self.creditCardBuf substringWithRange:NSMakeRange(i,1)]];
}
}
//After 4 characters add a space
if ((i +1) % 4 == 0 &&
([value length] < kCreditCardLengthPlusSpaces)) {
[value appendString:kSpace];
}
}
textField.text = value;
}
}
Эти ответы - всего лишь слишком большой код для меня. Здесь решение в Swift 2.2.1
extension UITextField {
func setText(to newText: String, preservingCursor: Bool) {
if preservingCursor {
let cursorPosition = offsetFromPosition(beginningOfDocument, toPosition: selectedTextRange!.start) + newText.characters.count - (text?.characters.count ?? 0)
text = newText
if let newPosition = positionFromPosition(beginningOfDocument, offset: cursorPosition) {
selectedTextRange = textRangeFromPosition(newPosition, toPosition: newPosition)
}
}
else {
text = newText
}
}
}
Теперь просто поставьте IBAction в свой контроллер:
@IBAction func textFieldEditingChanged(sender: UITextField) {
var digits = current.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("") // remove non-digits
// add spaces as necessary or otherwise format your digits.
// for example for a phone number or zip code or whatever
// then just:
sender.setText(to: digits, preservingCursor: true)
}
Пожалуйста, используйте простую форму кредитной карты /** См. Пример использования: ### let str = "41111111111111111"
let x = yourClassname.setStringAsCardNumberWithSartNumber(4, withString: str!, withStrLenght: 8)
### output:- 4111XXXXXXXX1111
let x = yourClassname.setStringAsCardNumberWithSartNumber(0, withString: str!, withStrLenght: 12)
### output: - XXXXXXXXXXXX1111
*/
func setStringAsCardNumberWithSartNumber(Number:Int,withString str:String ,withStrLenght len:Int ) -> String{
//let aString: String = "41111111111111111"
let arr = str.characters
var CrediteCard : String = ""
if arr.count > (Number + len) {
for (index, element ) in arr.enumerate(){
if index >= Number && index < (Number + len) {
CrediteCard = CrediteCard + String("X")
}else{
CrediteCard = CrediteCard + String(element)
}
}
return CrediteCard
}else{
print("\(Number) plus \(len) are grether than strings chatarter \(arr.count)")
}
print("\(CrediteCard)")
return str
}
Я надеюсь, что это будет полезно для вас.
Пожалуйста, проверьте следующее решение, его работа отлично подходит для меня -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let subString = (textField.text as! NSString).substringWithRange(range)
if subString == " " && textField == cardNumberTextfield
{
return false // user should not be able to delete space from card field
}
else if string == ""
{
return true // user can delete any digit
}
// Expiry date formatting
if textField == expiryDateTextfield
{
let str = textField.text! + string
if str.length == 2 && Int(str) > 12
{
return false // Month should be <= 12
}
else if str.length == 2
{
textField.text = str+"/" // append / after month
return false
}
else if str.length > 5
{
return false // year should be in yy format
}
}
// Card number formatting
if textField == cardNumberTextfield
{
let str = textField.text! + string
let stringWithoutSpace = str.stringByReplacingOccurrencesOfString(" ", withString: "")
if stringWithoutSpace.length % 4 == 0 && (range.location == textField.text?.length)
{
if stringWithoutSpace.length != 16
{
textField.text = str+" " // add space after every 4 characters
}
else
{
textField.text = str // space should not be appended with last digit
}
return false
}
else if str.length > 19
{
return false
}
}
return true
}
i изменен @ilesh, поэтому он отображает только последние 4 цифры независимо от длины. Также игнорировать пробелы и символы "-". Таким образом, если у нас есть номер с форматом 0000 - 0000 - 0000 - 0000, он отображает XXXX - XXXX - XXXX - 0000
func setStringAsCardNumberWithSartNumber(Number:Int,withString str:String) -> String{
let arr = str.characters
var CrediteCard : String = ""
let len = str.characters.count-4
if arr.count > (Number + len) {
for (index, element ) in arr.enumerated(){
if index >= Number && index < (Number + len) && element != "-" && element != " " {
CrediteCard = CrediteCard + String("X")
}else{
CrediteCard = CrediteCard + String(element)
}
}
return CrediteCard
}else{
print("\(Number) plus \(len) are grether than strings chatarter \(arr.count)")
}
print("\(CrediteCard)")
return str
}
здесь есть модификация ответа от @sleeping_giant для быстрого. Это решение форматирует текст в формате "xxxx-xxxx-xxxx-xxxx-xxxx" и перестает принимать любые номера за пределами этого диапазона.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
if string == ""{
return true
}
//range.length will be greater than 0 if user is deleting text - allow it to replace
if range.length > 0
{
return true
}
//Don't allow empty strings
if string == "-"
{
return false
}
//Check for max length including the spacers we added
print(range.location)
if range.location > 23
{
return false
}
var originalText = textField.text
let replacementText = string.replacingOccurrences(of: "-", with: "")
//Verify entered text is a numeric value
let digits = NSCharacterSet.decimalDigits
for char in replacementText.unicodeScalars
{
if !(digits as NSCharacterSet).longCharacterIsMember(char.value)
{
return false
}
}
//Put an empty space after every 4 places
if (originalText?.characters.count)! > 0
{
if (originalText?.characters.count)! < 5 && (originalText?.characters.count)! % 4 == 0{
originalText?.append("-")
}else if(((originalText?.characters.count)! + 1) % 5 == 0){
originalText?.append("-")
}
}
textField.text = originalText
return true
}
Найден GIST в Github, который делает именно то, что мне нужно в Swift3 (https://gist.github.com/nunogoncalves/6a8b4b21f4f69e0fc050190df96a1e56)
Реализовано путем выполнения →
if creditCardNumberTextView.text?.characters.first == "3" {
let validator = Validator(cardType: .americanExpress, value: self.creditCardNumberTextView.text!).test()
if validator == true {
} else {
}
}
Прекрасно работает в APP, который я разрабатываю, используя кредитные карты.
в моем случае нам нужно сформировать число iban. я думаю, ниже код поможет вам
Во-первых, проверьте, что введенное пользователем значение действительно
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if(textField == self.ibanTextField){
BOOL shouldChange = ([Help checkTextFieldForIBAN:[NSString stringWithFormat:@"%@%@",textField.text,string]]);
}
}
Во-вторых, вы можете увидеть обработанный iban метод, как показано ниже. Наш ибан сформировал начало 2-х букв.
+(BOOL)checkTextFieldForIBAN:(NSString*)string{
string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
if ([string length] <= 26) {
if ([string length] > 2) {
if ([self isLetter:[string substringToIndex:2]]) {
if ([self isInteger:[string substringFromIndex:2]])
return YES;
else
return NO;
}else {
return NO;
}
}else{
return [self isLetter:string];
}
}
else {
return NO;
}
return YES;
}
Создайте новый файл swift и вставьте ниже код, измените класс текстового поля на VSTextField
import UIKit
public enum TextFieldFormatting {
case uuid
case socialSecurityNumber
case phoneNumber
case custom
case noFormatting
}
public class VSTextField: UITextField {
/**
Set a formatting pattern for a number and define a replacement string. For example: If formattingPattern would be "##-##-AB-##" and
replacement string would be "#" and user input would be "123456", final string would look like "12-34-AB-56"
*/
public func setFormatting(_ formattingPattern: String, replacementChar: Character) {
self.formattingPattern = formattingPattern
self.replacementChar = replacementChar
self.formatting = .custom
}
/**
A character which will be replaced in formattingPattern by a number
*/
public var replacementChar: Character = "*"
/**
A character which will be replaced in formattingPattern by a number
*/
public var secureTextReplacementChar: Character = "\u{25cf}"
/**
True if input number is hexadecimal eg. UUID
*/
public var isHexadecimal: Bool {
return formatting == .uuid
}
/**
Max length of input string. You don't have to set this if you set formattingPattern.
If 0 -> no limit.
*/
public var maxLength = 0
/**
Type of predefined text formatting. (You don't have to set this. It more a future feature)
*/
public var formatting : TextFieldFormatting = .noFormatting {
didSet {
switch formatting {
case .socialSecurityNumber:
self.formattingPattern = "***-**-****"
self.replacementChar = "*"
case .phoneNumber:
self.formattingPattern = "***-***-****"
self.replacementChar = "*"
case .uuid:
self.formattingPattern = "********-****-****-****-************"
self.replacementChar = "*"
default:
self.maxLength = 0
}
}
}
/**
String with formatting pattern for the text field.
*/
public var formattingPattern: String = "" {
didSet {
self.maxLength = formattingPattern.count
}
}
/**
Provides secure text entry but KEEPS formatting. All digits are replaced with the bullet character \u{25cf} .
*/
public var formatedSecureTextEntry: Bool {
set {
_formatedSecureTextEntry = newValue
super.isSecureTextEntry = false
}
get {
return _formatedSecureTextEntry
}
}
override public var text: String! {
set {
super.text = newValue
textDidChange() // format string properly even when it set programatically
}
get {
if case .noFormatting = formatting {
return super.text
} else {
// Because the UIControl target action is called before NSNotificaion (from which we fire our custom formatting), we need to
// force update finalStringWithoutFormatting to get the latest text. Otherwise, the last character would be missing.
textDidChange()
return finalStringWithoutFormatting
}
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
registerForNotifications()
}
override init(frame: CGRect) {
super.init(frame: frame)
registerForNotifications()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
/**
Final text without formatting characters (read-only)
*/
public var finalStringWithoutFormatting : String {
return _textWithoutSecureBullets.keepOnlyDigits(isHexadecimal: isHexadecimal)
}
// MARK: - INTERNAL
fileprivate var _formatedSecureTextEntry = false
// if secureTextEntry is false, this value is similar to self.text
// if secureTextEntry is true, you can find final formatted text without bullets here
fileprivate var _textWithoutSecureBullets = ""
fileprivate func registerForNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(VSTextField.textDidChange),
name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"),
object: self)
}
@objc public func textDidChange() {
var superText: String { return super.text ?? "" }
// TODO: - Isn't there more elegant way how to do this?
let currentTextForFormatting: String
if superText.count > _textWithoutSecureBullets.count {
currentTextForFormatting = _textWithoutSecureBullets + superText[superText.index(superText.startIndex, offsetBy: _textWithoutSecureBullets.count)...]
} else if superText.count == 0 {
_textWithoutSecureBullets = ""
currentTextForFormatting = ""
} else {
currentTextForFormatting = String(_textWithoutSecureBullets[..<_textWithoutSecureBullets.index(_textWithoutSecureBullets.startIndex, offsetBy: superText.count)])
}
if formatting != .noFormatting && currentTextForFormatting.count > 0 && formattingPattern.count > 0 {
let tempString = currentTextForFormatting.keepOnlyDigits(isHexadecimal: isHexadecimal)
var finalText = ""
var finalSecureText = ""
var stop = false
var formatterIndex = formattingPattern.startIndex
var tempIndex = tempString.startIndex
while !stop {
let formattingPatternRange = formatterIndex ..< formattingPattern.index(formatterIndex, offsetBy: 1)
if formattingPattern[formattingPatternRange] != String(replacementChar) {
finalText = finalText + formattingPattern[formattingPatternRange]
finalSecureText = finalSecureText + formattingPattern[formattingPatternRange]
} else if tempString.count > 0 {
let pureStringRange = tempIndex ..< tempString.index(tempIndex, offsetBy: 1)
finalText = finalText + tempString[pureStringRange]
// we want the last number to be visible
if tempString.index(tempIndex, offsetBy: 1) == tempString.endIndex {
finalSecureText = finalSecureText + tempString[pureStringRange]
} else {
finalSecureText = finalSecureText + String(secureTextReplacementChar)
}
tempIndex = tempString.index(after: tempIndex)
}
formatterIndex = formattingPattern.index(after: formatterIndex)
if formatterIndex >= formattingPattern.endIndex || tempIndex >= tempString.endIndex {
stop = true
}
}
_textWithoutSecureBullets = finalText
let newText = _formatedSecureTextEntry ? finalSecureText : finalText
if newText != superText {
super.text = _formatedSecureTextEntry ? finalSecureText : finalText
}
}
// Let check if we have additional max length restrictions
if maxLength > 0 {
if superText.count > maxLength {
super.text = String(superText[..<superText.index(superText.startIndex, offsetBy: maxLength)])
_textWithoutSecureBullets = String(_textWithoutSecureBullets[..<_textWithoutSecureBullets.index(_textWithoutSecureBullets.startIndex, offsetBy: maxLength)])
}
}
}
}
extension String {
func keepOnlyDigits(isHexadecimal: Bool) -> String {
let ucString = self.uppercased()
let validCharacters = isHexadecimal ? "0123456789ABCDEF" : "0123456789"
let characterSet: CharacterSet = CharacterSet(charactersIn: validCharacters)
let stringArray = ucString.components(separatedBy: characterSet.inverted)
let allNumbers = stringArray.joined(separator: "")
return allNumbers
}
}
// Helpers
fileprivate func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l < r
case (nil, _?):
return true
default:
return false
}
}
fileprivate func > <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l > r
default:
return rhs < lhs
}
}
Больше использования будет найдено по ссылке ниже
Спасибо парню, который предоставил отличное решение для форматирования текста в UITextField.
http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/
https://github.com/VojtaStavik/VSTextField
Работает отлично для меня.
Swift 5, Xcode 10.2
Перепробовав много решений, я столкнулся с такими проблемами, как установка правильного положения курсора и форматирование в соответствии с потребностями, и, наконец, нашел решение после объединения двух сообщений (fooobar.com/info/114624/..., fooobar.com/info/114624/...)
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let currentText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { return true }
if textField == yourTextField {
textField.setText(to: currentText.grouping(every: 4, with: "-"), preservingCursor: true)
return false
}
return true
}
И добавив это расширение
extension UITextField {
public func setText(to newText: String, preservingCursor: Bool) {
if preservingCursor {
let cursorPosition = offset(from: beginningOfDocument, to: selectedTextRange!.start) + newText.count - (text?.count ?? 0)
text = newText
if let newPosition = self.position(from: beginningOfDocument, offset: cursorPosition) {
selectedTextRange = textRange(from: newPosition, to: newPosition)
}
}
else {
text = newText
}
}