クレジットカード番号を入力するための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'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;
}
次に、テキストフィールドがUIControlEventEditingChanged
イベントを発生させるたびに呼び出されるようにreformatCardNumber:
を設定します。
[yourTextField addTarget:yourTextFieldDelegate
action:@selector(reformatAsCardNumber:)
forControlEvents:UIControlEventEditingChanged];
(もちろん、テキストフィールドとそのデリゲートがインスタンス化された後のある時点でこれを行う必要があります。ストーリーボードを使用している場合、View ControllerのviewDidLoad
メソッドが適切な場所です。
これは一見複雑な問題です。すぐには明らかにならないかもしれない3つの重要な問題(およびここでの以前の回答はすべて考慮に入れていない)
クレジットカード番号とデビットカード番号のXXXX XXXX XXXX XXXX
形式は最も一般的な形式ですが、それだけではありません。たとえば、American Expressカードには、次のように通常XXXX XXXXXX XXXXX
形式で記述された15桁の数字があります。
Visaカードでさえ 以下 16桁であり、Maestroカードはさらに多くを持つことができます:
既存の入力の最後に単一の文字を入力するだけでなく、ユーザーがテキストフィールドと対話する方法が多くあります。また、ユーザーを適切に処理する必要があります文字列の中央に文字を追加する、deleting単一の文字、選択した複数の文字の削除、および複数の文字での貼り付け。この問題に対するいくつかのより単純/より素朴なアプローチは、これらの相互作用のいくつかを適切に処理できません。最もひねくれたケースは、ユーザーが他の文字を置き換えるために文字列の中央に複数の文字を貼り付けることであり、この解決策はそれを処理するのに十分一般的です。
ユーザーがテキストフィールドを変更した後、テキストフィールドのテキストを適切に再フォーマットするだけでなく、テキストカーソルを適切に配置する必要もあります。これを考慮しない問題への素朴なアプローチは、ほとんどの場合、テキストカーソルでばかげたことをすることになるでしょう(ユーザーが途中で数字を追加した後にテキストフィールドの最後に置くなど) )。
問題#1に対処するために、カード番号のプレフィックスの部分マッピングを使用して、 https://baymard.com/checkout-usability/credit-card-patterns のThe Baymard Instituteによってキュレーションされた形式にします。最初の数桁からカードプロバイダーを自動的に検出し、(someの場合)形式を推測し、それに応じて形式を調整できます。 cnotethegr8 に感謝します。このアイデアをこの回答に提供してくれました。
問題#2(および上記のコードで使用されている方法)に対処する最も簡単で簡単な方法は、すべてのスペースを取り除き、コンテンツが挿入されるたびに正しい位置に再挿入することですテキストフィールドの変更を確認し、どのような種類のテキスト操作(挿入、削除、または置換)が行われているかを把握し、可能性を異なる方法で処理する必要性を回避します。
問題#3に対処するために、数字以外を削除してからスペースを挿入するときにカーソルの目的のインデックスがどのように変化するかを追跡します。これが、コードがNSMutableString
の文字列置換メソッドを使用するのではなく、NSString
を使用して文字ごとにこれらの操作をかなり冗長に実行する理由です。
最後に、もう1つトラップが潜んでいます。textField: shouldChangeCharactersInRange: replacementString
からNO
を返すと、ユーザーがテキストフィールドでテキストを選択したときに取得する[切り取り]ボタンが壊れてしまいます。そのメソッドからNO
を返すと、 'Cut'はクリップボードをまったく更新しないことになり、修正も回避策もありません。その結果、(より明らかに)shouldChangeCharactersInRange:
自体ではなく、UIControlEventEditingChanged
ハンドラーでテキストフィールドの再フォーマットを行う必要があります。
幸いなことに、UIControlのイベントハンドラーは、UIの更新が画面にフラッシュされる前に呼び出されるように見えるため、このアプローチはうまく機能します。
また、明らかな正解を持たない、テキストフィールドの正確な動作についての小さな質問も多数あります。
おそらくこれらの質問への答えはどれでも十分でしょうが、もしあなたが十分に執着しているなら、ここで慎重に考えたい特別なケースが実際にたくさんあることを明確にするためにそれらをリストします。上記のコードでは、これらの質問に対する答えを選びました。私のコードの振る舞いと互換性のないこれらの点について強い思いを持っている場合、それをあなたのニーズに合わせて微調整するのは簡単です。
あなたはおそらく私のコードを最適化するか、もっと簡単な方法があるかもしれませんが、このコードは動作するはずです:
-(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ポートの古いバージョンの accepted answer Objective-Cで) cnotethegr8 のAmexカードをサポートするトリックで強化され、さらに多くのカード形式をサポートするようにさらに強化されました。このコードの多くの背後にある動機を説明するのに役立つので、まだありません。
この動作を確認するために必要な最小限の手順は次のとおりです。
Main.storyboard
で、Text Fieldを追加します。ViewController
をText Fieldのデリゲートにします。ViewController.Swift
に貼り付けます。IBOutlet
をText Fieldに接続します。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を使用したソリューション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)
}
次に、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
}
}
フォーマットされたString.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
}
これはSwiftバージョンです。これは、まだこの答えを探しているが、Objective-Cの代わりに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
}
そこで、より少ないコードでこれを望んでいたので、コード here を使用し、少し再利用しました。画面には2つのフィールドがありました。1つは番号用で、もう1つは有効期限用でした。そのため、より再利用しやすくしました。
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())
}
}
Swift 3.2
@Lucasの答えとSwift 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
}
以下のメソッドを定義し、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(_ :)デリゲートのメソッドを使用すると便利です。textFieldのコンテンツを取得してフォーマットします。
ユーザーが数値を入力しているときに役立つのは、現在のテキストが変更されるたびに呼び出されるtextField(_:shouldChangeCharactersIn:replacementString :)デリゲートメソッドです。
どちらの場合もまだ問題があり、入力された数字の正しい形式であるIMHOを把握し、私が見たすべての数字に基づいて、主な形式は2つのみです:上記の15桁のAmex形式と4桁ごとにカード番号をグループ化するフォーマット。桁数を気にせず、この場合は一般的なルールのようになります。たとえば、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)
発行者を特定し、受け入れられる桁数を把握するのに役立つ特定の正規表現を使用することを強くお勧めします。
今、私のSwift 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 Answerから、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
}
}
Mark AmeryのObjective-Cソリューション に基づくSwift 3ソリューション
アクションを実装し、メソッドを委任します。
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
}
Swift 5、Xcode 10.3
多くのソリューションを試した後、必要に応じて正しいカーソル位置の設定や書式設定などの問題に直面し、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
}
}
マーク・アメリーに基づいたコトリンの回答はこちら
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誰かがそれを必要とする場合に受け入れられた答えのコピーです。それは基本的にはラッパークラスです。
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
}
}
@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
}
新しい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'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
私にとっては素晴らしい仕事です。
このソリューションをご覧ください。 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;
}
}
Swift3で必要なことを正確に行うGithubでGistを見つけました( 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で素晴らしい動作をします。
//: Playground - noun: a place where people can play
import UIKit
let cardNumber = "1234 8940 8941 5678"
let aString = cardNumber.components(separatedBy:" ")
var starString:String = ""
for i in 0...(aString.count - 2) {
starString = "\(starString) \(getStars(aString[i]))"
}
let finalCardString = starString + " " + aString.last!
print(finalCardString)
func getStars(_ digitString:String) -> String {
let digits = digitString.flatMap{Int(String($0))}
switch digits.count {
case 1:
return "*"
case 2:
return "**"
case 3:
return "***"
case 4:
return "****"
case 5:
return "*****"
case 6:
return "******"
case 7:
return "*******"
case 8:
return "********"
case 9:
return "*********"
default:
return ""
}
}
//出力**** **** **** 5678
これは、Swiftの@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
}
クレジットカードの簡単な形式を使用してください/ **サンプル使用法を参照してください:### 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
}
これがあなたのお役に立てば幸いです。
これらの答えはすべて私にとってあまりにも多くのコードです。 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
}
}
}
次に、ViewControllerに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)
}
以下のソリューションを確認してください。
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
}
私の場合、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;
}