(xxxx xxxx xxxx xxxx)와 같은 신용 카드 입력을위한 UITextField 서식 지정
UITextField
신용 카드 번호를 입력 하기 위해 a를 형식화하여 숫자 만 입력하고 자동으로 공백을 삽입하여 숫자 형식이 다음과 같이 지정되도록하고 싶습니다.
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's 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 카드에는 일반적으로 다음XXXX XXXXXX XXXXX
과 같은 형식 으로 작성된 15 자리 숫자가 있습니다 .Visa 카드도 16 자리 미만일 수 있으며 Maestro 카드에는 다음과 같은 숫자가 더 포함될 수 있습니다.
사용자가 기존 입력 끝에 단일 문자를 입력하는 것보다 텍스트 필드와 상호 작용하는 방법이 더 많습니다. 또한 제대로 사용자가 처리해야 중간에 문자를 추가 , 문자열의 삭제 , 하나의 문자를 선택한 여러 문자를 삭제 및 붙여 넣기 여러 문자. 이 문제에 대한 더 간단하고 순진한 접근 방식은 이러한 상호 작용 중 일부를 제대로 처리하지 못합니다. 가장 비뚤어진 경우는 사용자가 문자열 중간에 여러 문자를 붙여 다른 문자를 대체하는 것이며이 솔루션은이를 처리 할 수있을만큼 일반적입니다.
사용자가 텍스트 필드를 수정 한 후 텍스트 필드의 텍스트를 올바르게 다시 포맷 할 필요가 없습니다 . 텍스트 커서를 현명하게 배치해야합니다 . 이를 고려하지 않는 문제에 대한 순진한 접근 방식은 일부 경우에 텍스트 커서로 어리석은 작업을 수행하게 될 것입니다 (예 : 사용자가 중간에 숫자를 추가 한 후 텍스트 필드 끝에 놓는 것과 같이 ).
문제 # 1을 처리하기 위해 Baymard Institute ( https://baymard.com/checkout-usability/credit-card-patterns) 에서 선별 한 형식에 카드 번호 접두사의 부분 매핑을 사용합니다 . 우리는 자동으로 (에서 첫 번째 자리의 커플에서 카드 제공 감지 할 수있는 몇 가지 형식 추론의 경우)를 우리가 따라 서식을 조정합니다. 이 아이디어를이 답변에 기여한 cnotethegr8 에게 감사드립니다 .
문제 # 2 (및 위 코드에서 사용 된 방법)를 처리하는 가장 간단하고 쉬운 방법 은 텍스트 필드의 내용이 변경 될 때마다 모든 공백을 제거하고 올바른 위치에 다시 삽입하는 것입니다. 어떤 종류의 텍스트 조작 (삽입, 삭제 또는 대체)이 진행되고 있는지 파악하고 가능성을 다르게 처리합니다.
문제 # 3 을 처리하기 위해 숫자가 아닌 부분을 제거하고 공백을 삽입 할 때 커서의 원하는 인덱스가 어떻게 변경되는지 추적합니다. 이것이 코드가 의 문자열 대체 방법을 NSMutableString
사용하는 대신을 사용하여 문자별로 이러한 조작을 다소 장황하게 수행하는 이유 NSString
입니다.
반환 : 마지막으로, 숨어 또 하나 개의 함정 거기 NO
에서 textField: shouldChangeCharactersInRange: replacementString
그들은 내가 그것을하지 않는 이유는 텍스트 필드에서 텍스트를 선택하면 사용자가 얻을 수 넘김 '잘라 내기'버튼을. NO
해당 메서드에서 돌아 오면 'Cut'이 단순히 클립 보드를 전혀 업데이트하지 않으며 수정 또는 해결 방법이 없음을 알고 있습니다. 결과적으로 우리 UIControlEventEditingChanged
는 (더 분명하게) shouldChangeCharactersInRange:
자체가 아닌 핸들러 에서 텍스트 필드의 형식을 다시 지정해야 합니다.
다행히 UIControl 이벤트 핸들러는 UI 업데이트가 화면에 플러시되기 전에 호출되는 것처럼 보이므로이 방법은 제대로 작동합니다.
또한 분명한 정답이없는 텍스트 필드가 정확히 어떻게 작동해야하는지에 대한 사소한 질문이 많이 있습니다.
- 사용자가 텍스트 필드의 내용이 19 자리를 초과하는 내용을 붙여 넣으려고 할 경우 붙여 넣은 문자열의 시작 부분을 삽입하고 (19 자리에 도달 할 때까지) 나머지 부분이 잘 리거나 아무것도 삽입하지 않아야합니다. ?
- 사용자가 커서를 뒤에 놓고 백 스페이스 키를 눌러 단일 공백을 삭제하려고하면 아무 일도 일어나지 않고 커서가 그대로 남아 있거나 커서가 한 문자 왼쪽으로 이동해야하거나 (공백 앞에 배치) 커서가 이미 공백에 남아있는 것처럼 공백의 왼쪽에있는 숫자가 삭제됩니까?
- 사용자가 네 번째, 여덟 번째 또는 열두 번째 자리를 입력 할 때 공백을 즉시 삽입하고 커서를 그 뒤로 이동해야합니까? 아니면 사용자가 다섯 번째, 아홉 번째 또는 열세 번째 자리를 입력 한 후에 만 공백을 삽입해야합니까?
- 사용자가 공백 뒤의 첫 번째 숫자를 삭제할 때 공백이 완전히 제거되지 않으면 커서가 공백 앞이나 뒤에 위치해야합니까?
아마도 이러한 질문에 대한 답은 충분할 것입니다. 그러나 충분히 강박 적이라면 여기에서 신중하게 생각하고 싶을 수있는 특별한 경우가 실제로 많이 있음을 분명히하기 위해 나열합니다. 위의 코드에서 나는이 질문에 대해 나에게 합리적이라고 생각되는 답을 골랐다. 내 코드가 작동하는 방식과 호환되지 않는 이러한 점에 대해 강한 감정을 느끼는 경우 필요에 맞게 쉽게 조정할 수 있습니다.
내 코드를 최적화하거나 더 쉬운 방법이있을 수 있지만이 코드는 작동합니다.
-(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;
}
아래의 작업 스위프트 4 포트 Logicopolis의 대답 (내 이전 버전의 스위프트 2 포트 설정에 허용 대답 향상 목표 - C에서)를 cnotethegr8 아멕스 카드를 지원하고 더 많은 카드를 지원하도록 향상을 위해 '의 비결은 형식. 이 코드의 많은이면에있는 동기를 설명하는 데 도움이되므로 아직 확인하지 않은 경우 허용 된 답변을 살펴 보는 것이 좋습니다.
이를 확인하는 데 필요한 최소한의 단계는 다음과 같습니다.
- Swift에서 새 Single View 앱 을 만듭니다 .
- 에
Main.storyboard
하는 추가 텍스트 필드 . - Text Field
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;
}
Fawkes 답변을 기본으로 사용하는 Swift 3 솔루션 . 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)
}
그런 다음 textField에 대상을 추가하십시오.
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) 다른 "-"의 변경 사항
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
}
이것이 여전히이 답변을 찾고 있지만 Objective-C 대신 Swift를 사용하는 모든 사람에게 유용한 경우 Swift 버전이 있습니다. 개념은 여전히 동일합니다.
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가 몇 가지 중요한 사항을 염두에 두어야합니다. 4 자리마다 구분 된 16 자리 카드 번호가 가장 일반적으로 사용되는 형식이라는 사실 외에도 15 자리 (AmEx 형식 XXXX XXXXXX XXXXX)와 13 자리 또는 심지어 19 자리 ( https : // en. wikipedia.org/wiki/Payment_card_number ). 고려해야 할 다른 중요한 사항은 숫자 만 허용하도록 textField를 구성하고 numberPad가 좋은 시작이지만 입력을 보호하는 메서드를 구현하는 것이 편리하므로 키보드 유형을 구성하는 것입니다.
시작점은 사용자가 숫자를 입력하는 동안 또는 사용자가 텍스트 필드를 떠날 때 숫자 형식을 지정할시기를 결정합니다. 사용자가 textField를 떠날 때 서식을 지정하려는 경우 textFieldDidEndEditing (_ :) delegate의 메서드를 사용하면 편리합니다. textField의 내용을 가져와 서식을 지정합니다.
사용자가 번호를 입력하는 동안 현재 텍스트가 변경 될 때마다 호출되는 textField (_ : shouldChangeCharactersIn : replacementString :) 델리게이트 메서드가 유용합니다.
두 경우 모두 여전히 문제가 있습니다. 입력 한 번호 IMHO에 대해 올바른 형식이 무엇인지 알아 내고 내가 본 모든 번호를 기반으로하여 두 가지 주요 형식 만 있습니다. 위에서 설명한 15 자리 숫자의 Amex 형식과 4 자리마다 그룹 카드 번호를 형식화하십시오.이 경우에는 일반적인 규칙과 같습니다. 예를 들어 13 자리 카드는 XXXXX XXXX XXXX X 형식이되고 19 자리 숫자는 XXXX 형식이됩니다. XXXX XXXX XXXX XXX, 가장 일반적인 경우 (16 자리 숫자) 및 다른 경우에도 작동합니다. 따라서 매직 넘버를 가지고 노는 아래의 동일한 알고리즘으로 AmEx 케이스를 관리하는 방법을 알아낼 수 있습니다.
다른 특정 형식의 경우 RegEx를 사용하여 15 자리 카드가 American Express인지 확인했습니다.
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 : a Swift 3.0의 경우 Jayesh Miruliya 답변에서 4 개의 문자 그룹 사이에 구분 기호를 넣습니다.
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
}
}
스위프트 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
}
Mark Amery의 Objective-C 솔루션을 기반으로 한 Swift 3 솔루션 :
작업 및 위임 메서드 구현 :
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)) textField.delegate = self
TextField Delegate 메서드 및 기타 메서드 :
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
}
}
Mark Amery를 기반으로 한 Kotlin 답변입니다.
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?) {}
})
Swift 5.1, Xcode 11
많은 솔루션을 시도한 후 올바른 커서 위치 설정 및 필요에 따라 형식 지정과 같은 문제에 직면했고 마침내 2 개의 게시물을 결합한 후 솔루션을 찾았습니다 ( https://stackoverflow.com/a/38838740/10579134 , https : // stackoverflow. com / a / 45297778 / 10579134 )
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
}
}
이 답변은 모두 나에게 너무 많은 코드입니다. 다음은 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
}
도움이 되었기를 바랍니다.
@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
}
Swift3 ( https://gist.github.com/nunogoncalves/6a8b4b21f4f69e0fc050190df96a1e56 ) 에서 필요한 작업을 수행하는 Github에서 GIST를 찾았습니다.
수행하여 구현->
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 형식의 방법을 볼 수 있습니다. 우리의 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;
}
여기에 @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
}
이 솔루션을 확인하십시오. 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;
}
}
저에게 잘 작동하는 벨로우즈 솔루션을 확인하십시오.
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
}
새 신속한 파일을 만들고 코드 아래에 붙여넣고 텍스트 필드 클래스를 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's 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's 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's 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
나를 위해 잘 작동합니다.
'IT박스' 카테고리의 다른 글
작동하지 않는 SQL NOT IN (0) | 2020.11.07 |
---|---|
파이썬에서 문자열이 숫자로 시작하는지 확인하는 방법은 무엇입니까? (0) | 2020.11.07 |
django runserver를 다시 시작할 때 포트를 '삭제'하는 방법 (0) | 2020.11.06 |
Arduino를 사용하여 serial.read ()를 사용 가능한 문자열로 변환 하시겠습니까? (0) | 2020.11.06 |
Android에서 기본 경고 대화 상자의 너비와 높이를 제어하는 방법은 무엇입니까? (0) | 2020.11.06 |