-1

I am currently developing an iOS application with login and sign up forms. To make sure that the keyboard does not cover any UITextFields I've implemented the following solution provided by Apple and discussed in this issue.

To briefly sum it up, this solution uses a UIScrollView in which the different UI elements are placed and UIKeyboardDidShowNotification and UIKeyboardDidHideNotification to move the elements up and down when the keyboard appears/disappears so that the UITextFields aren't hidden.

This works like a charm except for one thing: for all my UIViewControllers I have to repeat the same code. To tackle my problem I have tried:

  • to create a base UIViewController, providing an implementation for the different functions, that can be subclasses by the other UIViewControllers;
  • to use a protocol and a protocol extension to provide a default implementation for the different functions and make my UIViewControllers conform to it.

Both solutions didn't solve my problem. For the first solution, I wasn't able to connect the UIScrollView of my base class through the Interface Builder although it was declared.

@IBOutlet weak var scrollView: UIScrollView!

When trying to implement the second solution, the UIViewController implementing my protocol somehow did not recognise the declared methods and their implementations.

The protocol declaration:

protocol ScrollViewProtocol {
    var scrollView: UIScrollView! { get set }
    var activeTextField: UITextField? { get set }

    func addTapGestureRecognizer()
    func singleTapGestureCaptured()

    func registerForKeyboardNotifications()
    func deregisterForKeyboardNotifications()

    func keyboardWasShown(notification: NSNotification)
    func keyboardWillBeHidden(notification: NSNotification)

    func setActiveTextField(textField: UITextField)
    func unsetActiveTextField()
}

The protocol extension implements all functions expect for the addTapGestureRecognizer() as I would like to avoid using @objc:

extension ScrollViewProtocol where Self: UIViewController {
    // The implementation for the different functions 
    // as described in the provided links expect for the following method

    func registerFromKeyboardNotifications() {
        NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil, usingBlock: { notification in
            self.keyboardWasShown(notification)
        })
        NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidHideNotification, object: nil, queue: nil, usingBlock: { notification in
            self.keyboardWillBeHidden(notification)
        })
    }
}

Does anyone have a good solution to my problem, knowingly how could I avoid repeating the code related to moving the UITextFields up and down when the keyboard appears/disappears? Or does anyone know why my solutions did not work?

Community
  • 1
  • 1
Jessy Naus
  • 54
  • 8

1 Answers1

0

I found a solution. I'll post it in case someone once to do the same thing.

So, I ended up deleting the UIScrollView outlet in my base class and replacing it with a simple property that I set in my inheriting classes. The code for my base class look as follow:

import UIKit

class ScrollViewController: UIViewController, UITextFieldDelegate {

    // MARK: Properties

    var scrollView: UIScrollView!
    var activeTextField: UITextField?

    // MARK: View cycle

    override func viewDidLoad() {
        super.viewDidLoad()

        let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapGestureCaptured))
        scrollView.addGestureRecognizer(singleTap)
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        registerForKeyboardNotifications()
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        deregisterFromKeyboardNotifications()
    }

    // MARK: Gesture recognizer

    func singleTapGestureCaptured(sender: AnyObject) {
        view.endEditing(true)
    }

    // MARK: Keyboard management

    func registerForKeyboardNotifications() {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWasShown), name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(keyboardWillBeHidden), name: UIKeyboardWillHideNotification, object: nil)
    }

    func deregisterFromKeyboardNotifications() {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
    }

    func keyboardWasShown(notification: NSNotification) {
        scrollView.scrollEnabled = true

        let info : NSDictionary = notification.userInfo!
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
        let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)

        scrollView.contentInset = contentInsets
        scrollView.scrollIndicatorInsets = contentInsets

        var aRect : CGRect = self.view.frame
        aRect.size.height -= keyboardSize!.height
        if let activeFieldPresent = activeTextField {
            if (!CGRectContainsPoint(aRect, activeFieldPresent.frame.origin)) {
                scrollView.scrollRectToVisible(activeFieldPresent.frame, animated: true)
            }
        }
    }

    func keyboardWillBeHidden(notification: NSNotification) {
        let info : NSDictionary = notification.userInfo!
        let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size
        let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)

        scrollView.contentInset = contentInsets
        scrollView.scrollIndicatorInsets = contentInsets

        view.endEditing(true)
        scrollView.scrollEnabled = false
    }

    // MARK: Text field management

    func textFieldDidBeginEditing(textField: UITextField) {
        activeTextField = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        activeTextField = nil
    }
}

And here is the inheriting class code:

class ViewController: ScrollViewController {

    @IBOutlet weak var scrollViewOutlet: UIScrollView! {
        didSet {
            self.scrollView = self.scrollViewOutlet
        }
    }

    // Your view controller functions

}

I hope this will help!

Jessy Naus
  • 54
  • 8