IOS

IOS TutorialAirPrint tutorial in iOSCarthage iOS SetupChecking iOS versionCore SpotLight in iOSCreate a Custom framework in iOSDeep Linking in iOSExtension for rich Push Notification - iOS 10.Guideline to choose best iOS Architecture PatternsIOS - Implementation of XMPP with Robbie Hanson frameworkIOS 3D TouchIOS 10 Speech Recognition APIIOS AccessibilityIOS ADDING a SWIFT BRIDGING HEADERIOS AFNetworkingIOS AirDropIOS AlamofireIOS App Submission ProcessIOS App Transport Security (ATS)IOS App wide operationsIOS AppDelegateIOS Application rating/review requestIOS ARC (Automatic Reference Counting)IOS attributedText in UILabelIOS Auto LayoutIOS AVPlayer and AVPlayerViewControllerIOS AVSpeechSynthesizerIOS AWS SDKIOS Background ModesIOS Background Modes and EventsIOS Basic text file I/OIOS BlockIOS CAAnimationIOS Cache online imagesIOS CAGradientLayerIOS CALayerIOS CAShapeLayerIOS CategoriesIOS CGContext ReferenceIOS Chain Blocks in a Queue (with MKBlockQueue)IOS Change Status Bar ColorIOS Checking for Network ConnectivityIOS CLLocationIOS CloudKitIOS CodableIOS Code signingIOS ConcurrencyIOS Configure Beacons with CoreBluetoothIOS Contacts FrameworkIOS Content Hugging/Content Compression in AutoLayoutIOS Convert HTML to NSAttributed string and vice verseIOS Convert NSAttributedString to UIImageIOS Core DataIOS Core GraphicsIOS Core LocationIOS Core MotionIOS CoreImage FiltersIOS Create .ipa File to upload on appstore with ApplicationloaderIOS Create a video from imagesIOS Creating an App IDIOS CTCallCenterIOS Custom fontsIOS Custom KeyboardIOS Custom methods of selection of UITableViewCellsIOS Custom UITextFieldIOS Custom UIViews from XIB filesIOS Cut a UIImage into a circleIOS CydiaSubstrate tweakIOS Debugging CrashesIOS DispatchGroupIOS Dynamic TypeIOS Dynamically updating a UIStackViewIOS EventKitIOS Face Detection Using CoreImage/OpenCVIOS FacebookSDKIOS FastlaneIOS FCM Messaging in SwiftIOS FileHandleIOS GameCenter LeaderboardsIOS GameplayKitIOS GCD (Grand Central Dispatch)IOS Google Places APIIOS Graph (Coreplot)IOS Handle Multiple Environment using MacroIOS Handling URL SchemesIOS HealthkitIOS iBeaconIOS IBOutletsIOS In-App PurchaseIOS Initialization idioms



IOS Managing the Keyboard

From WikiOD

Create a custom in-app keyboard[edit | edit source]

custom keyboard animated gif

This is a basic in-app keyboard. The same method could be used to make just about any keyboard layout. Here are the main things that need to be done:

  • Create the keyboard layout in an .xib file, whose owner is a Swift or Objective-C class that is a UIView subclass.
  • Tell the UITextField to use the custom keyboard.
  • Use a delegate to communicate between the keyboard and the main view controller.

Create the .xib keyboard layout file[edit | edit source]

  • In Xcode go to File > New > File... > iOS > User Interface > View to create the .xib file.
  • I called mine Keyboard.xib
  • Add the buttons that you need.
  • Use auto layout constraints so that no matter what size the keyboard is, the buttons will resize accordingly.
  • Set the File's Owner (not the root view) to be the Keyboard class. This is a common source of error. You'll create this class in the next step. See the note at the end.

Create the .swift UIView subclass keyboard file[edit | edit source]

In Xcode go to File > New > File... > iOS > Source > Cocoa Touch Class to create the Swift or Objective-C class. Choose UIView as a superclass for newly created class

I called mine Keyboard.swift (Keyboard class in Objective-C)

Add the following code for 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
      }

  }

Add the following code for Objective-C:

Keyboard.h File

#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

Keyboard.m File

#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
  • Control drag actions from the buttons to button callback in the .xib file to the @IBAction method in the Swift or Objective-C owner to hook them all up.
  • Note that the protocol and delegate code. See this answer for a simple explanation about how delegates work.

Set up the View Controller[edit | edit source]

Add a UITextField to your main storyboard and connect it to your view controller with an IBOutlet. Call it textField.

Use the following code for the View Controller in 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)
      }
  }

Use the following code for Objective-C:

.h File

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

.m File

#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
  • Note that the view controller adopts the KeyboardDelegate protocol that we defined above.

Common error[edit | edit source]

If you are getting an EXC_BAD_ACCESS error, it is probably because you set the view's custom class as Keyboard rather than do this for the nib File's Owner.

Select Keyboard.nib and then choose File's Owner.

file's owner screenshot

Make sure that the custom class for the root view is blank.

blank root view screenshot

Notes[edit | edit source]

This example comes originally from this Stack Overflow answer.

Dismiss a keyboard with tap on view[edit | edit source]

If you want to hide a keyboard by tap outside of it, it's possible to use this hacky trick (works only with 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];
}

for Swift there will be a bit more code:

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)
}

Another Swift 3/iOS 10 example

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
    }
}

Managing the Keyboard Using a Singleton + Delegate[edit | edit source]

When I first started managing the keyboard I would use separate Notifications in each ViewController.

Notification Method (Using 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)
    }
}

My problem was that I found myself writing this code again and again for every single ViewController. After experimenting a bit I found using a Singleton + Delegate pattern allowed me to reuse a bunch of code and organize all of the Keyboard Management in a single place!

Singleton + Delegate Method:

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)
    }
}

Now when I want to manage the keyboard from a ViewController all I need to do is set the delegate to that ViewController and implement any delegate methods.

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)
    }
}

This method is very customizable too! Say we want to add functionality for UIKeyboardWillHideNotification. This is as easy as adding a method to our 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)
    }
}

Say we only want to implement func keyboardWillHide(notificationUserInfo: [NSObject: AnyObject]) in one ViewController. We can also make this method optional.

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])
}

In summary, I've found using a Singleton + Delegate to manage the keyboard is both more efficient and easier to use than using Notifications

Scrolling a UIScrollView/UITableView When Displaying the Keyboard[edit | edit source]

There are few approaches available there:

  1. You can subscribe for keyboard appearance events notifications and change offset manually:
//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);

}

Moving view up or down when keyboard is present[edit | edit source]

Note: This only works for the built-in keyboard provided by iOS[edit | edit source]

SWIFT:[edit | edit source]

In order for the view of a UIViewController to increase the origin of the frame when it is presented and decrease it when it is hidden, add the following functions to your class:

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
        }
    }
}

And in the viewDidLoad() method of your class, add the following observers:

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)

And this will work for any screen size, using the height property of the keyboard.


OBJECTIVE-C:[edit | edit source]

To do the same thing in Objective-C, this code can be used:

* (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;
    }];
}

Credit:Stack_Overflow_Documentation