iOS
Manejando el teclado
Buscar..
Desplazando un UIScrollView / UITableView al visualizar el teclado
Hay pocos enfoques disponibles allí:
- Puede suscribirse para recibir notificaciones de eventos de apariencia de teclado y cambiar la compensación manualmente:
//Swift 2.0+
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourVCClassName.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourVCClassName.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardHeight = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue.size.height {
tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
}
}
}
func keyboardWillHide(notification: NSNotification) {
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
//Objective-C
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
if (userInfo) {
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardEndFrame.size.height, 0);
}
}
- (void)keyboardWillHide:(NSNotification *)notification {
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
}
- O use soluciones ya hechas como TPKeyboardAvoidingTableView o TPKeyboardAvoidingScrollView https://github.com/michaeltyson/TPKeyboardAvoiding
Descartar un teclado con toque en la vista
Si desea ocultar un teclado tocando fuera de él, es posible utilizar este truco pirateado (solo funciona con Objective-C):
- (void)viewDidLoad {
[super viewDidLoad];
// dismiss keyboard when tap outside a text field
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self.view action:@selector(endEditing:)];
[tapGestureRecognizer setCancelsTouchesInView:NO];
[self.view addGestureRecognizer:tapGestureRecognizer];
}
Para Swift habrá un poco más de código:
override func viewDidLoad() {
super.viewDidLoad()
// dismiss keyboard when tap outside a text field
let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YourVCName.dismissKeyboard))
view.addGestureRecognizer(tapGestureRecognizer)
}
//Calls this function when the tap is recognized.
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
Otro ejemplo de Swift 3 / iOS 10
class vc: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
txtSomeField.delegate = self
}
}
extension vc: UITextFieldDelegate {
//Hide the keyboard for any text field when the UI is touched outside of the keyboard.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
self.view.endEditing(true) //Hide the keyboard
}
}
Crear un teclado personalizado en la aplicación
Este es un teclado básico en la aplicación. El mismo método podría usarse para hacer casi cualquier distribución de teclado. Aquí están las principales cosas que deben hacerse:
- Cree la distribución del teclado en un archivo .xib, cuyo propietario es una clase Swift o Objective-C que es una subclase
UIView
. - Dígale al
UITextField
que use el teclado personalizado. - Utilice un delegado para comunicarse entre el teclado y el controlador de vista principal.
Crear el archivo de diseño de teclado .xib
- En Xcode vaya a Archivo> Nuevo> Archivo ...> iOS> Interfaz de usuario> Ver para crear el archivo .xib.
- Llamé al mío teclado.xib
- Añade los botones que necesites.
- Use restricciones de diseño automático para que, independientemente del tamaño del teclado, los botones cambien de tamaño según corresponda.
- Establezca el propietario del archivo (no la vista raíz) para que sea la clase de
Keyboard
. Esta es una fuente común de error. Vas a crear esta clase en el siguiente paso. Ver la nota al final.
Cree el archivo de teclado de subclase .swift UIView
En Xcode vaya a Archivo> Nuevo> Archivo ...> iOS> Fuente> Cocoa Touch Class para crear la clase Swift o Objective-C. Elija
UIView
como una superclase para la clase recién creadaLlamé a la mía
Keyboard.swift
(clase deKeyboard
en Objective-C)Agregue el siguiente código para Swift:
import UIKit // The view controller will adopt this protocol (delegate) // and thus must contain the keyWasTapped method protocol KeyboardDelegate: class { func keyWasTapped(character: String) } class Keyboard: UIView { // This variable will be set as the view controller so that // the keyboard can send messages to the view controller. weak var delegate: KeyboardDelegate? // MARK:- keyboard initialization required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initializeSubviews() } override init(frame: CGRect) { super.init(frame: frame) initializeSubviews() } func initializeSubviews() { let xibFileName = "Keyboard" // xib extention not included let view = NSBundle.mainBundle().loadNibNamed(xibFileName, owner: self, options: nil)[0] as! UIView self.addSubview(view) view.frame = self.bounds } // MARK:- Button actions from .xib file @IBAction func keyTapped(sender: UIButton) { // When a button is tapped, send that information to the // delegate (ie, the view controller) self.delegate?.keyWasTapped(sender.titleLabel!.text!) // could alternatively send a tag value } }
Agregue el siguiente código para Objective-C:
Archivo keyboard.h
#import <UIKit/UIKit.h> // The view controller will adopt this protocol (delegate) // and thus must contain the keyWasTapped method @protocol KeyboardDelegate<NSObject> - (void)keyWasTapped:(NSString *)character; @end @interface Keyboard : UIView @property (nonatomic, weak) id<KeyboardDelegate> delegate; @end
Archivo keyboard.m
#import "Keyboard.h" @implementation Keyboard - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; [self initializeSubviews]; return self; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; [self initializeSubviews]; return self; } - (void)initializeSubviews { NSString *xibFileName = @"Keyboard"; // xib extention not included UIView *view = [[[NSBundle mainBundle] loadNibNamed:xibFileName owner:self options:nil] firstObject]; [self addSubview:view]; view.frame = self.bounds; } // MARK:- Button actions from .xib file -(IBAction)keyTapped:(UIButton *)sender { // When a button is tapped, send that information to the // delegate (ie, the view controller) [self.delegate keyWasTapped:sender.titleLabel.text]; // could alternatively send a tag value } @end
- Controle las acciones de arrastre desde los botones hasta la devolución de llamada en el archivo
@IBAction
método@IBAction
en el propietario de Swift o Objective-C para conectarlos todos. - Tenga en cuenta que el protocolo y el código delegado. Vea esta respuesta para una explicación simple sobre cómo trabajan los delegados.
Configurar el controlador de vista
Agregue un
UITextField
a su guión gráfico principal y conéctelo a su controlador de vista con unIBOutlet
. LlámalotextField
.Use el siguiente código para View Controller en Swift:
import UIKit class ViewController: UIViewController, KeyboardDelegate { @IBOutlet weak var textField: UITextField! override func viewDidLoad() { super.viewDidLoad() // initialize custom keyboard let keyboardView = Keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: 300)) keyboardView.delegate = self // the view controller will be notified by the keyboard whenever a key is tapped // replace system keyboard with custom keyboard textField.inputView = keyboardView } // required method for keyboard delegate protocol func keyWasTapped(character: String) { textField.insertText(character) } }
Usa el siguiente código para Objective-C:
archivo .h
#import <UIKit/UIKit.h> @interface ViewController : UIViewController @end
archivo .m
#import "ViewController.h" #import "Keyboard.h" @interface ViewController ()<KeyboardDelegate> @property (nonatomic, weak) IBOutlet UITextField *textField; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // initialize custom keyboard Keyboard *keyboardView = [[Keyboard alloc] initWithFrame:CGRectMake(0, 0, 0, 300)]; keyboardView.delegate = self; // the view controller will be notified by the keyboard whenever a key is tapped // replace system keyboard with custom keyboard self.textField.inputView = keyboardView; } - (void)keyWasTapped:(NSString *)character { [self.textField insertText:character]; } @end
- Tenga en cuenta que el controlador de vista adopta el protocolo
KeyboardDelegate
que definimos anteriormente.
Error común
Si EXC_BAD_ACCESS
un error EXC_BAD_ACCESS
, probablemente se deba a que configura la clase personalizada de la vista como Keyboard
lugar de hacerlo para el propietario del archivo de plumilla.
Seleccione Keyboard.nib
y luego elija Propietario del archivo.
Asegúrese de que la clase personalizada para la vista raíz esté en blanco.
Notas
Este ejemplo proviene originalmente de esta respuesta de desbordamiento de pila .
Administrar el teclado usando un delegado de Singleton +
Cuando empecé a administrar el teclado, usaba notificaciones separadas en cada ViewController.
Método de notificación (mediante NSNotification):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardNotification(_:)), name: UIKeyboardWillChangeFrameNotification, object: nil)
}
func keyboardNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let duration: NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseOut.rawValue
let animationCurve: UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
if endFrame?.origin.y >= UIScreen.mainScreen().bounds.size.height {
lowerViewBottomConstraint.constant = 0
} else {
lowerViewBottomConstraint.constant = endFrame?.size.height ?? 0.0
}
view.animateConstraintWithDuration(duration, delay: NSTimeInterval(0), options: animationCurve, completion: nil)
}
}
Mi problema fue que me encontré escribiendo este código una y otra vez para cada ViewController. Después de experimentar un poco, descubrí que usar un patrón Singleton + Delegate me permitió reutilizar un montón de código y organizar toda la administración del teclado en un solo lugar.
Singleton + Método de Delegado:
protocol KeyboardManagerDelegate: class {
func keyboardWillChangeFrame(endFrame: CGRect?, duration: NSTimeInterval, animationCurve: UIViewAnimationOptions)
}
class KeyboardManager {
weak var delegate: KeyboardManagerDelegate?
class var sharedInstance: KeyboardManager {
struct Singleton {
static let instance = KeyboardManager()
}
return Singleton.instance
}
init() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(KeyboardManager.keyboardWillChangeFrameNotification(_:)), name: UIKeyboardWillChangeFrameNotification, object: nil)
}
@objc func keyboardWillChangeFrameNotification(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }
let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue()
let duration: NSTimeInterval = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
let animationCurveRawNSN = userInfo[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber
let animationCurveRaw = animationCurveRawNSN?.unsignedLongValue ?? UIViewAnimationOptions.CurveEaseOut.rawValue
let animationCurve: UIViewAnimationOptions = UIViewAnimationOptions(rawValue: animationCurveRaw)
delegate?.keyboardWillChangeFrame(endFrame, duration: duration, animationCurve: animationCurve)
}
}
Ahora, cuando quiero administrar el teclado desde un ViewController, todo lo que tengo que hacer es configurar el delegado en ese ViewController e implementar cualquier método de delegado.
class ViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
KeyboardManager.sharedInstance.delegate = self
}
}
// MARK: - Keyboard Manager
extension ViewController: KeyboardManagerDelegate {
func keyboardWillChangeFrame(endFrame: CGRect?, duration: NSTimeInterval, animationCurve: UIViewAnimationOptions) {
if endFrame?.origin.y >= UIScreen.mainScreen().bounds.size.height {
lowerViewBottomConstraint.constant = 0
} else {
lowerViewBottomConstraint.constant = (endFrame?.size.height ?? 0.0)
}
view.animateConstraintWithDuration(duration, delay: NSTimeInterval(0), options: animationCurve, completion: nil)
}
}
Este método es muy personalizable también! Digamos que queremos agregar funcionalidad para UIKeyboardWillHideNotification
. Esto es tan fácil como agregar un método a nuestro KeyboardManagerDelegate
.
KeyboardManagerDelegate
with UIKeyboardWillHideNotification
:
protocol KeyboardManagerDelegate: class {
func keyboardWillChangeFrame(endFrame: CGRect?, duration: NSTimeInterval, animationCurve: UIViewAnimationOptions)
func keyboardWillHide(notificationUserInfo: [NSObject: AnyObject])
}
class KeyboardManager {
init() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(KeyboardManager.keyboardWillChangeFrameNotification(_:)), name: UIKeyboardWillChangeFrameNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(KeyboardManager.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillHide(notification: NSNotification) {
guard let userInfo = notification.userInfo else { return }
delegate?.keyboardWillHide(userInfo)
}
}
Digamos que solo queremos implementar func keyboardWillHide(notificationUserInfo: [NSObject: AnyObject])
en un ViewController. También podemos hacer este método opcional.
typealias KeyboardManagerDelegate = protocol<KeyboardManagerModel, KeyboardManagerConfigureable>
protocol KeyboardManagerModel: class {
func keyboardWillChangeFrame(endFrame: CGRect?, duration: NSTimeInterval, animationCurve: UIViewAnimationOptions)
}
@objc protocol KeyboardManagerConfigureable {
optional func keyboardWillHide(userInfo: [NSObject: AnyObject])
}
* Tenga en cuenta que este patrón ayuda a evitar el uso excesivo de @objc
. ¡Vea http://www.jessesquires.com/avoiding-objc-in-swift/ para más detalles!
En resumen, he encontrado que usar un Delegado de Singleton + para administrar el teclado es más eficiente y fácil de usar que usar Notificaciones
Mover la vista hacia arriba o hacia abajo cuando el teclado está presente
Nota: esto solo funciona para el teclado incorporado provisto por iOS
RÁPIDO:
Para que la vista de un UIViewController aumente el origen del marco cuando se presenta y lo disminuya cuando esté oculto, agregue las siguientes funciones a su clase:
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0{
self.view.frame.origin.y -= keyboardSize.height
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != 0{
self.view.frame.origin.y += keyboardSize.height
}
}
}
Y en el método viewDidLoad()
de su clase, agregue los siguientes observadores:
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(Login.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
Y esto funcionará para cualquier tamaño de pantalla, usando la propiedad de altura del teclado.
C OBJETIVO:
Para hacer lo mismo en Objective-C, este código se puede usar:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
[UIView animateWithDuration:0.3 animations:^{
CGRect f = self.view.frame;
f.origin.y = -keyboardSize.height;
self.view.frame = f;
}];
}
-(void)keyboardWillHide:(NSNotification *)notification
{
[UIView animateWithDuration:0.3 animations:^{
CGRect f = self.view.frame;
f.origin.y = 0.0f;
self.view.frame = f;
}];
}