ios guidelines - Comment afficher le bouton "Terminé" sur le pavé numérique de l'iPhone





menu green (18)


Pour Swift 2.2 j'utilise ceci

func addDoneButtonOnKeyboard() {
    let doneToolbar: UIToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 50))

    let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
    let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Done, target: self, action: #selector(DetailViewController.finishDecimalKeypad))

    var items: [UIBarButtonItem]? = [UIBarButtonItem]()
    items?.append(flexSpace)
    items?.append(done)

    doneToolbar.items = items
    doneToolbar.sizeToFit()
    self.productPrice.inputAccessoryView=doneToolbar
}

func finishDecimalKeypad() {
    self.productPrice?.resignFirstResponder()
}

Il n'y a pas de bouton "Terminé" sur le pavé numérique. Lorsqu'un utilisateur finit d'entrer des informations numériques dans un champ de texte, comment puis-je faire disparaître le pavé numérique?

Je pourrais obtenir un bouton "Terminé" en utilisant le clavier par défaut, mais alors les utilisateurs devraient passer aux touches numériques pour entrer des nombres. Est-il possible d'afficher un bouton "Terminé" sur le pavé numérique?




Une solution Swift 3 utilisant une extension. Idéal si vous avez plusieurs objets UITextField numériques dans votre application, car cela donne la UITextField de décider, pour chaque UITextField , d'effectuer ou non une action personnalisée lorsque Done ou Cancel est sélectionné.

//
//  UITextField+DoneCancelToolbar.swift
//

import UIKit

extension UITextField {
    func addDoneCancelToolbar(onDone: (target: Any, action: Selector)? = nil, onCancel: (target: Any, action: Selector)? = nil) {     
        let onCancel = onCancel ?? (target: self, action: #selector(cancelButtonTapped))
        let onDone = onDone ?? (target: self, action: #selector(doneButtonTapped))

        let toolbar: UIToolbar = UIToolbar()
        toolbar.barStyle = .default
        toolbar.items = [
            UIBarButtonItem(title: "Cancel", style: .plain, target: onCancel.target, action: onCancel.action),
            UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
            UIBarButtonItem(title: "Done", style: .done, target: onDone.target, action: onDone.action)
        ]
        toolbar.sizeToFit()

        self.inputAccessoryView = toolbar
    }

    // Default actions:  
    func doneButtonTapped() { self.resignFirstResponder() }
    func cancelButtonTapped() { self.resignFirstResponder() }
}

Exemple d'utilisation en utilisant les actions par défaut:

//
// MyViewController.swift
//

@IBOutlet weak var myNumericTextField: UITextField! {
    didSet { myNumericTextField?.addDoneCancelToolbar() }
}

Exemple d'utilisation à l'aide d'une action Done personnalisée:

//
// MyViewController.swift
//

@IBOutlet weak var myNumericTextField: UITextField! {
    didSet { 
        myNumericTextField?.addDoneCancelToolbar(onDone: (target: self, action: #selector(doneButtonTappedForMyNumericTextField))) 
    }
}

func doneButtonTappedForMyNumericTextField() { 
    print("Done"); 
    myNumericTextField.resignFirstResponder() 
}



SWIFT 3.0 Une saveur différente, en utilisant des parties de certaines réponses précédentes.

func addToolbarToNumberPad()
{
    let numberPadToolbar: UIToolbar = UIToolbar()

    numberPadToolbar.isTranslucent = true
    numberPadToolbar.items=[
        UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelAction)),
        UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
        UIBarButtonItem(title: "Custom", style: .done, target: self, action: #selector(self.customAction)),
        UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.doneAction)),
    ]

    numberPadToolbar.sizeToFit()

    textField.inputAccessoryView = numberPadToolbar
}

func cancelAction()
{
    textField.resignFirstResponder()
}

func customAction()
{
    textField.resignFirstResponder()
}

func doneAction()
{
    textField.resignFirstResponder()
}

override func viewDidLoad()
{
    super.viewDidLoad()

    self.addToolbarToNumberPad()
}



Voici le code le plus récent. Il suffit d'inclure #import "UIViewController + NumPadReturn.h" dans votre viewController.

Voici le .h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface UIViewController (NumPadReturn)



@end

Et eux

#import "UIViewController+NumPadReturn.h"


@implementation UIViewController (NumPadReturn)

-(void) viewDidLoad{
    // add observer for the respective notifications (depending on the os version)
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2) {
        [[NSNotificationCenter defaultCenter] addObserver:self 
                                                 selector:@selector(keyboardDidShow:) 
                                                     name:UIKeyboardDidShowNotification 
                                                   object:nil];     
    } else {
        [[NSNotificationCenter defaultCenter] addObserver:self 
                                                 selector:@selector(keyboardWillShow:) 
                                                     name:UIKeyboardWillShowNotification 
                                                   object:nil];
    }

}


- (void)keyboardWillShow:(NSNotification *)note {
    // if clause is just an additional precaution, you could also dismiss it
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 3.2) {
        [self addButtonToKeyboard];
    }
}

- (void)keyboardDidShow:(NSNotification *)note {
    // if clause is just an additional precaution, you could also dismiss it
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2) {
        [self addButtonToKeyboard];
    }
}

- (void)addButtonToKeyboard {
    // create custom button
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 163, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.0) {
        [doneButton setImage:[UIImage imageNamed:@"DoneUp3.png"] forState:UIControlStateNormal];
        [doneButton setImage:[UIImage imageNamed:@"DoneDown3.png"] forState:UIControlStateHighlighted];
    } else {        
        [doneButton setImage:[UIImage imageNamed:@"DoneUp.png"] forState:UIControlStateNormal];
        [doneButton setImage:[UIImage imageNamed:@"DoneDown.png"] forState:UIControlStateHighlighted];
    }
    [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
    // locate keyboard view
    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
        keyboard = [tempWindow.subviews objectAtIndex:i];
        // keyboard found, add the button
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2) {
            if([[keyboard description] hasPrefix:@"<UIPeripheralHost"] == YES)
                [keyboard addSubview:doneButton];
        } else {
            if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)
                [keyboard addSubview:doneButton];
        }
    }
}

- (void)doneButton:(id)sender {
    NSLog(@"doneButton");
    [self.view endEditing:TRUE];
}



@end



Je décris ici une solution pour iOS 4.2+ mais le bouton de rejet apparaît après l'apparition du clavier. Ce n'est pas terrible, mais pas idéal non plus.

La solution décrite dans la question ci-dessus comprend une illusion plus élégante pour fermer le bouton, où je fondu et déplace verticalement le bouton pour donner l'impression que le clavier et le bouton rejettent ensemble.




J'ai trouvé la réponse de @ user1258240 assez concise étant donné que ce n'est pas aussi simple que de définir une propriété returnKeyType .

Je voulais juste apporter ma propre approche "réutilisable" à cela:

func SetDoneToolbar(field:UITextField) {
    let doneToolbar:UIToolbar = UIToolbar()

    doneToolbar.items=[
        UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: self, action: nil),
        UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.dismissKeyboard))
    ]

    doneToolbar.sizeToFit()
    field.inputAccessoryView = doneToolbar
}

override func viewDidLoad() {
    super.viewDidLoad()

    SetDoneToolbar(field: UITextField_1)
    SetDoneToolbar(field: UITextField_2)
    SetDoneToolbar(field: UITextField_3)
    SetDoneToolbar(field: UITextField_N)
}



Si vous avez plusieurs champs numériques, je suggère de sous-classer UITextField pour créer un NumericTextField qui affiche toujours un clavier numérique avec un bouton terminé. Ensuite, associez simplement vos champs numériques avec cette classe dans l'Interface Builder et vous n'aurez besoin d'aucun code supplémentaire dans vos contrôleurs View. Ce qui suit est la classe Swift 3.0 que j'utilise dans Xcode 8.0.

class NumericTextField: UITextField {
   let numericKbdToolbar = UIToolbar()

    // MARK: Initilization
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.initialize()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.initialize()
    }

    // Sets up the input accessory view with a Done button that closes the keyboard
    func initialize()
    {
        self.keyboardType = UIKeyboardType.numberPad

        numericKbdToolbar.barStyle = UIBarStyle.default
        let space = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
        let callback = #selector(NumericTextField.finishedEditing)
        let donebutton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: callback)
        numericKbdToolbar.setItems([space, donebutton], animated: false)
        numericKbdToolbar.sizeToFit()
        self.inputAccessoryView = numericKbdToolbar
    }

    // MARK: On Finished Editing Function
    func finishedEditing()
    {
        self.resignFirstResponder()
    }
}



L'astuce que j'ai vu utilisé est de faire un bouton transparent personnalisé de la taille de l'ensemble de la vue, puis dans sa méthode de clic, avoir le champ de texte démissionner premier répondeur. Ainsi, l'utilisateur peut cliquer n'importe où en dehors du champ pour fermer le clavier.




Toutes ces implémentations concernant la recherche de la vue du clavier et l'ajout du bouton terminé à la 3e rangée (c'est pourquoi la hauteur du clavier de button.y = 163 b / c est de 216) sont fragiles car iOS ne cesse de changer la hiérarchie des vues. Par exemple, aucun des codes ci-dessus ne fonctionne pour iOS9.

Je pense qu'il est plus sûr de simplement trouver la vue supérieure, par [[[UIApplication sharedApplication] windows] lastObject], et juste ajouter le bouton en bas à gauche, doneButton.frame = CGRectMake (0, SCREEN_HEIGHT-53, 106 , 53) // mode portrait




Nous pouvons également simplifier la solution «utilisateur touché ailleurs» si nous indiquons simplement au contrôleur de notre vue de mettre fin à l'édition:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
 { 
      [super touchesBegan:touches withEvent:event];

      [self.view endEditing:YES]; //YES ignores any textfield refusal to resign
 }

... en supposant que "toucher ailleurs supprime le clavier" est le comportement souhaité pour tous les autres champs modifiables sur la vue.




Une autre solution. Parfait s'il y a d'autres champs de texte non-pavé numérique sur l'écran.

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIToolbar* numberToolbar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320, 50)];
    numberToolbar.barStyle = UIBarStyleBlackTranslucent;
    numberToolbar.items = @[[[UIBarButtonItem alloc]initWithTitle:@"Cancel" style:UIBarButtonItemStyleBordered target:self action:@selector(cancelNumberPad)],
                         [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
                         [[UIBarButtonItem alloc]initWithTitle:@"Apply" style:UIBarButtonItemStyleDone target:self action:@selector(doneWithNumberPad)]];
    [numberToolbar sizeToFit];
    numberTextField.inputAccessoryView = numberToolbar;
}

-(void)cancelNumberPad{
    [numberTextField resignFirstResponder];
    numberTextField.text = @"";
}

-(void)doneWithNumberPad{
    NSString *numberFromTheKeyboard = numberTextField.text;
    [numberTextField resignFirstResponder];
}



Ce message du forum décrit comment personnaliser le UIKeyboard pour ajouter votre vue personnalisée sur le clavier.




Si vous connaissez à l'avance le nombre de numéros à entrer (par exemple un code PIN à 4 chiffres), vous pouvez annuler automatiquement après 4 pressions sur les touches, comme indiqué dans ma réponse à cette question similaire:

renvoyer Number Pad

Pas besoin d'un bouton supplémentaire fait dans ce cas.




Swift 2.2 / J'ai utilisé la réponse de Dx_. Cependant, je voulais cette fonctionnalité sur tous les claviers. Donc, dans ma classe de base, j'ai mis le code:

func addDoneButtonForTextFields(views: [UIView]) {
    for view in views {
        if let textField = view as? UITextField {
            let doneToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 50))

            let flexSpace = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil)
            let done = UIBarButtonItem(title: "Done", style: .Done, target: self, action: #selector(dismissKeyboard))

            var items = [UIBarButtonItem]()
            items.append(flexSpace)
            items.append(done)

            doneToolbar.items = items
            doneToolbar.sizeToFit()

            textField.inputAccessoryView = doneToolbar
        } else {
            addDoneButtonForTextFields(view.subviews)
        }
    }
}

func dismissKeyboard() {
    dismissKeyboardForTextFields(self.view.subviews)
}

func dismissKeyboardForTextFields(views: [UIView]) {
    for view in views {
        if let textField = view as? UITextField {
            textField.resignFirstResponder()
        } else {
            dismissKeyboardForTextFields(view.subviews)
        }
    }
}

Ensuite, appelez addDoneButtonForTextFields sur self.view.subviews dans viewDidLoad (ou willDisplayCell si vous utilisez une vue tabulaire) pour ajouter le bouton Terminé à tous les claviers.




Voici une adaptation pour la réponse de Luda pour Swift:

Dans la déclaration de votre sous-classe UIViewController

let numberToolbar: UIToolbar = UIToolbar()

dans ViewDidLoad mettre:

    numberToolbar.barStyle = UIBarStyle.BlackTranslucent
    numberToolbar.items=[
        UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Bordered, target: self, action: "hoopla"),
        UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil),
        UIBarButtonItem(title: "Apply", style: UIBarButtonItemStyle.Bordered, target: self, action: "boopla")
    ]

    numberToolbar.sizeToFit()

    textField.inputAccessoryView = numberToolbar //do it for every relevant textfield if there are more than one 

et ajoutez les fonctions hoopla et hoopla (n'hésitez pas à choisir d'autres noms, changez simplement les noms des sélecteurs dans ViewDidLoad en conséquence

func boopla () {
    textField.resignFirstResponder()
}

func hoopla () {
    textField.text=""
    textField.resignFirstResponder()
}



Voici une révision de la réponse de Luda avec les changements suivants:

  • la vue des accessoires est automatiquement dimensionnée à la largeur du cadre de l'application

  • la constante obsolète UIBarButtonItemStyleBordered est évitée

  • le bouton "Terminé" est instancié en tant que UIBarButtonSystemItemDone

Actuellement, le bouton "Terminé" est centré dans la vue des accessoires. Vous pouvez le positionner à gauche ou à droite en supprimant l'espace du côté pertinent.

J'ai omis un bouton "Annuler" car le clavier par défaut n'en a pas non plus. Si vous voulez un bouton "Annuler", je vous suggère de l'instancier comme un UIBarButtonSystemItemCancel et de vous assurer que vous ne jetez pas la valeur d'origine dans votre champ de texte. Le comportement "Annuler" implémenté dans la réponse de Luda, qui écrase la valeur avec une chaîne vide, peut ne pas être ce que vous voulez.

- (void)viewDidLoad {
  [super viewDidLoad];
  float appWidth = CGRectGetWidth([UIScreen mainScreen].applicationFrame);
  UIToolbar *accessoryView = [[UIToolbar alloc]
                              initWithFrame:CGRectMake(0, 0, appWidth, 0.1 * appWidth)];
  UIBarButtonItem *space = [[UIBarButtonItem alloc]
                            initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                            target:nil
                            action:nil];
  UIBarButtonItem *done = [[UIBarButtonItem alloc]
                           initWithBarButtonSystemItem:UIBarButtonSystemItemDone
                           target:self
                           action:@selector(selectDoneButton)];
  accessoryView.items = @[space, done, space];
  self.valueField.inputAccessoryView = accessoryView;
}

- (void)selectDoneButton {
  [self.valueField resignFirstResponder];
}

Pour plus d'informations sur la création de vues d'accessoires, consultez la documentation d'Apple sur les vues personnalisées pour la saisie de données . Vous voudrez probablement consulter les pages de référence sur UIToolbar et UIBarButtonItem .




J'ai modifié la solution de Bryan pour qu'elle soit un peu plus robuste, afin qu'elle fonctionne bien avec d'autres types de claviers qui pourraient apparaître dans la même vue. C'est décrit ici:

Créer un bouton DONE sur le pavé numérique iOS UIKeyboard

J'essaierais de l'expliquer ici, mais la plupart du code est à regarder, ce qui ne serait pas facile ici




Le moyen le plus simple d'y parvenir serait de mettre la méthode d' input dans une boucle while. Utilisez continue quand vous obtenez une mauvaise entrée, et sortez de la boucle lorsque vous êtes satisfait.

Lorsque votre entrée peut provoquer une exception

Utilisez try and catch pour détecter quand l'utilisateur entre des données qui ne peuvent pas être analysées.

while True:
    try:
        # Note: Python 2.x users should use raw_input, the equivalent of 3.x's input
        age = int(input("Please enter your age: "))
    except ValueError:
        print("Sorry, I didn't understand that.")
        #better try again... Return to the start of the loop
        continue
    else:
        #age was successfully parsed!
        #we're ready to exit the loop.
        break
if age >= 18: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

Mettre en place vos propres règles de validation

Si vous voulez rejeter les valeurs que Python peut analyser avec succès, vous pouvez ajouter votre propre logique de validation.

while True:
    data = input("Please enter a loud message (must be all caps): ")
    if not data.isupper():
        print("Sorry, your response was not loud enough.")
        continue
    else:
        #we're happy with the value given.
        #we're ready to exit the loop.
        break

while True:
    data = input("Pick an answer from A to D:")
    if data.lower() not in ('a', 'b', 'c', 'd'):
        print("Not an appropriate choice.")
    else:
        break

Combinaison du traitement des exceptions et de la validation personnalisée

Les deux techniques ci-dessus peuvent être combinées en une seule boucle.

while True:
    try:
        age = int(input("Please enter your age: "))
    except ValueError:
        print("Sorry, I didn't understand that.")
        continue

    if age < 0:
        print("Sorry, your response must not be negative.")
        continue
    else:
        #age was successfully parsed, and we're happy with its value.
        #we're ready to exit the loop.
        break
if age >= 18: 
    print("You are able to vote in the United States!")
else:
    print("You are not able to vote in the United States.")

Encapsuler tout dans une fonction

Si vous devez demander beaucoup de valeurs à votre utilisateur, il peut être utile de placer ce code dans une fonction, vous n'avez donc pas besoin de le retaper à chaque fois.

def get_non_negative_int(prompt):
    while True:
        try:
            value = int(input(prompt))
        except ValueError:
            print("Sorry, I didn't understand that.")
            continue

        if value < 0:
            print("Sorry, your response must not be negative.")
            continue
        else:
            break
    return value

age = get_non_negative_int("Please enter your age: ")
kids = get_non_negative_int("Please enter the number of children you have: ")
salary = get_non_negative_int("Please enter your yearly earnings, in dollars: ")

Mettre tous ensemble

Vous pouvez étendre cette idée pour créer une fonction d'entrée très générique:

def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None):
    if min_ is not None and max_ is not None and max_ < min_:
        raise ValueError("min_ must be less than or equal to max_.")
    while True:
        ui = input(prompt)
        if type_ is not None:
            try:
                ui = type_(ui)
            except ValueError:
                print("Input type must be {0}.".format(type_.__name__))
                continue
        if max_ is not None and ui > max_:
            print("Input must be less than or equal to {0}.".format(max_))
        elif min_ is not None and ui < min_:
            print("Input must be greater than or equal to {0}.".format(min_))
        elif range_ is not None and ui not in range_:
            if isinstance(range_, range):
                template = "Input must be between {0.start} and {0.stop}."
                print(template.format(range_))
            else:
                template = "Input must be {0}."
                if len(range_) == 1:
                    print(template.format(*range_))
                else:
                    print(template.format(" or ".join((", ".join(map(str,
                                                                     range_[:-1])),
                                                       str(range_[-1])))))
        else:
            return ui

Avec une utilisation telle que:

age = sanitised_input("Enter your age: ", int, 1, 101)
answer = sanitised_input("Enter your answer: ", str.lower, range_=('a', 'b', 'c', 'd'))

Pièges communs, et pourquoi vous devriez les éviter

L'utilisation redondante input instructions d' input redondantes

Cette méthode fonctionne mais est généralement considérée comme un mauvais style:

data = input("Please enter a loud message (must be all caps): ")
while not data.isupper():
    print("Sorry, your response was not loud enough.")
    data = input("Please enter a loud message (must be all caps): ")

Il peut sembler attrayant au départ, car il est plus court que la méthode while True , mais il viole le principe de ne pas répéter vous-même du développement logiciel. Cela augmente la probabilité de bogues dans votre système. Que faire si vous voulez revenir à 2.7 en changeant l' input à raw_input , mais changer accidentellement seulement la première input ci-dessus? C'est une SyntaxError qui n'attend que de se produire.

La récursion va souffler votre pile

Si vous venez d'apprendre à propos de la récursivité, vous pourriez être tenté de l'utiliser dans get_non_negative_int afin de pouvoir disposer de la boucle while.

def get_non_negative_int(prompt):
    try:
        value = int(input(prompt))
    except ValueError:
        print("Sorry, I didn't understand that.")
        return get_non_negative_int(prompt)

    if value < 0:
        print("Sorry, your response must not be negative.")
        return get_non_negative_int(prompt)
    else:
        return value

Cela semble fonctionner correctement la plupart du temps, mais si l'utilisateur entre des fois des données non valides, le script se terminera avec RuntimeError: maximum recursion depth exceeded . Vous pouvez penser "pas de folie ferait 1000 erreurs d'affilée", mais vous sous-estimez l'ingéniosité des imbéciles!





ios iphone user-input