Can*_*soy 68 credit-card objective-c uitextfield ios swift
我想格式化UITextField输入信用卡号码,使其只允许输入数字并自动插入空格,以便数字的格式如下:
XXXX XXXX XXXX XXXX
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?
Mar*_*ery 143
如果您正在使用Swift,请阅读我对Swift 4的答案的端口并使用它.
如果你在Objective-C ...
首先,给你UITextFieldDelegate,添加这些实例变量......
NSString *previousTextFieldContent;
UITextRange *previousSelection;
Run Code Online (Sandbox Code Playgroud)
......以及这些方法:
// 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;
}
Run Code Online (Sandbox Code Playgroud)
其次,reformatCardNumber:只要文本字段触发UIControlEventEditingChanged事件,就设置为调用:
[yourTextField addTarget:yourTextFieldDelegate
action:@selector(reformatAsCardNumber:)
forControlEvents:UIControlEventEditingChanged];
Run Code Online (Sandbox Code Playgroud)
(当然,您需要在文本字段及其委托实例化之后的某个时刻执行此操作.如果您使用的是故事板,则viewDidLoad视图控制器的方法是合适的位置.
这是一个看似复杂的问题.可能不会立即明显的三个重要问题(以及之前的答案都没有考虑到):
虽然XXXX XXXX XXXX XXXX信用卡和借记卡号码的格式是最常见的格式,但它并不是唯一的格式.例如,美国运通卡通常以15位数字XXXX XXXXXX XXXXX格式编写,如下所示:

即使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更新刷新到屏幕之前被调用,因此这种方法很好.
还有一大堆关于文本字段应该表现得如何没有明显正确答案的小问题:
可能任何这些问题的答案都是足够的,但我列出它们只是为了表明实际上有许多特殊情况你可能想在这里仔细思考,如果你足够痴迷.在上面的代码中,我已经选择了对我来说似乎合理的这些问题的答案.如果您对这些与我的代码行为方式不兼容的要点有强烈的感受,那么应该很容易根据您的需要进行调整.
Seb*_*ssi 26
您可以优化我的代码,或者可能有一种更简单的方法,但这段代码应该有效:
-(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;
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ery 22
下面是一个工作的Swift 4端口Logicopolis的答案(这是我在Objective-C中接受的旧版本的Swift 2端口)增强了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
}
}
Run Code Online (Sandbox Code Playgroud)
适应其他情况 - 比如你的代表不是ViewController- 被留给读者练习.
Luc*_*cas 12
我觉得这个很好:
-(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;
}
Run Code Online (Sandbox Code Playgroud)
Dmi*_*try 11
使用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
}
}
Run Code Online (Sandbox Code Playgroud)
在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)
}
Run Code Online (Sandbox Code Playgroud)
然后将target添加到textField
youtTextField.addTarget(self, action: #selector(self.reformatAsCardNumber(textField:)), for: UIControlEvents.editingChanged)
Run Code Online (Sandbox Code Playgroud)
注册新变量并将卡片类型发送给它
var selectedCardType: String? {
didSet{
reformatAsCardNumber(textField: yourTextField)
}
}
Run Code Online (Sandbox Code Playgroud)
谢谢福克斯的代码!
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
}
}
Run Code Online (Sandbox Code Playgroud)
formattedString.appendFormat("%@ - ",前缀)chage of" - "您选择的任何其他
在 Swift 5 中:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == cardNumberTextField {
return formatCardNumber(textField: textField, shouldChangeCharactersInRange: range, replacementString: string)
}
return true
}
func formatCardNumber(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == cardNumberTextField {
let replacementStringIsLegal = string.rangeOfCharacter(from: NSCharacterSet(charactersIn: "0123456789").inverted) == nil
if !replacementStringIsLegal {
return false
}
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let components = newString.components(separatedBy: NSCharacterSet(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 {
let prefix = decimalString.substring(with: NSRange(location: index, length: 4))
formattedString.appendFormat("%@ ", prefix)
index += 4
}
if length - index > 4 {
let prefix = decimalString.substring(with: NSRange(location: index, length: 4))
formattedString.appendFormat("%@ ", prefix)
index += 4
}
if length - index > 4 {
let prefix = decimalString.substring(with: NSRange(location: index, length: 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
}
}
Run Code Online (Sandbox Code Playgroud)
这是Mark Amery 接受的答案的Swift 5版本。
在您的类中添加这些变量:
@IBOutlet weak var cardNumberTextField: UITextField!
private var previousTextFieldContent: String?
private var previousSelection: UITextRange?
Run Code Online (Sandbox Code Playgroud)
还要确保您的文本字段调用reformatAsCardNumber: from viewDidLoad():
cardNumberTextField.addTarget(self, action: #selector(reformatAsCardNumber), for: .editingChanged)
Run Code Online (Sandbox Code Playgroud)
将其添加到您的UITextFieldDelegate:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == cardNumberTextField {
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
}
return true
}
Run Code Online (Sandbox Code Playgroud)
最后在您的中包含以下方法viewController:
@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.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 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 insertSpacesEveryFourDigitsIntoString(string: String, andPreserveCursorPosition cursorPosition: inout Int) -> String {
var stringWithAddedSpaces = ""
let cursorPositionInSpacelessString = cursorPosition
for i in Swift.stride(from: 0, to: string.count, by: 1) {
if i > 0 && (i % 4) == 0 {
stringWithAddedSpaces.append(contentsOf: " ")
if i < cursorPositionInSpacelessString {
cursorPosition += 1
}
}
let characterToAdd = string[string.index(string.startIndex, offsetBy: i)]
stringWithAddedSpaces.append(characterToAdd)
}
return stringWithAddedSpaces
}
Run Code Online (Sandbox Code Playgroud)
小智 5
Swift 2中另一个接受的答案版本......
确保在委托实例中包含以下内容:
private var previousTextFieldContent: String?
private var previousSelection: UITextRange?
Run Code Online (Sandbox Code Playgroud)
并确保您的文本字段调用reformatAsCardNumber:
textField.addTarget(self, action: #selector(reformatAsCardNumber(_:)), forControlEvents: .EditingChanged)
Run Code Online (Sandbox Code Playgroud)
您的文本字段委托将需要这样做:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
previousTextFieldContent = textField.text;
previousSelection = textField.selectedTextRange;
return true
}
Run Code Online (Sandbox Code Playgroud)
最后包括以下方法:
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
}
Run Code Online (Sandbox Code Playgroud)
所以我想用更少的代码来做到这一点,所以我在这里使用了代码,并重新调整了用途。屏幕上有两个字段,一个是数字,一个是到期日,所以我使它更具可重用性。
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())
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
50606 次 |
| 最近记录: |