I found that making sure textfields peek over the top of the keyboard when editing was a very wearisome task. You need to add the delegate methods, manage the keyboard notifications and also the animations - pretty soon my classes were riddled with all of this duplicated logic!

So I created a class which manages all of this for me - the only thing I have to do is create an instance of this class, passing in the UIView I want to move, and the UIView that contains all of my text fields.

Please note! This can only be used on UIViews that are not the root view of the view controller, or a view without top and bottom constraints!

Reference: http://kingscocoa.com/tutorials/autolayout-animations/

Objective-C:

    textFieldController = [[TextFieldInteractivity alloc] initWithTheView:self.myShiftingContent withShiftingView:self.myShiftingContent withMainView:self.view];

    [textFieldController setAsDelegate:self];

Then place this in your code and make sure the class conforms to the UITextFieldDelegate protocol:

    - (void)textFieldDidBeginEditing:(UITextField *)textField{

    [self.textFieldController textFieldDidBeginEditing:textField];
    }

Swift:

    var textFieldController =  TextFieldInteractivity(theView:self.myShiftingContent, withShiftingView:self.myShiftingContent, withMainView:self.view)

    textFieldController.setAsDelegate(self)

    func textFieldDidBeginEditing(textField: UITextField) {

    self.textFieldController.textFieldDidBeginEditing:textField()
    }

Here is the class:

    //
//  TextFieldInteractivity.swift
//
//  Created by Paul Jeremy Malouf on 12/08/2014.
//

import Foundation  
import UIKit

@objc class TextFieldInteractivity: NSObject, UITextFieldDelegate, UITextViewDelegate {

    weak var myFieldContainerView:UIView?
    weak var myBackgroundView:UIView?
    var originalPosition:CGFloat
    var adjustedYValue:CGFloat = 0
    var myKeyboardPosition:CGFloat = 0
    var tap:UITapGestureRecognizer
    var moveConstraints = true
    var theBufferingSpace:CGFloat = 10

    var currentViewOffset:CGFloat {

        if let theScrollView = self.myFieldContainerView as? UIScrollView {

            return theScrollView.contentOffset.y
        }

        return 0
    }

    class func debug(string:String){

        print("TextFieldInteractivity: \(string)")
    }

    init (theFieldContainerView:UIView, withBackgroundView theBackgroundView:UIView? = nil){

        myFieldContainerView = theFieldContainerView
        myBackgroundView = theBackgroundView ?? theFieldContainerView.superview

        originalPosition = myFieldContainerView?.superview?.convertPoint(myFieldContainerView!.frame.origin, toView: nil).y ?? 0

        adjustedYValue = originalPosition
        tap = UITapGestureRecognizer()
        super.init()

        TextFieldInteractivity.debug("self.originalPosition: \(self.originalPosition)")

        tap = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: "UIKeyboardWillShowNotification", object: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: "UIKeyboardDidHideNotification", object: nil)
    }

    func cleanupNotifications(){

        NSNotificationCenter.defaultCenter().removeObserver(self, name: "UIKeyboardWillShowNotification", object: nil)

        NSNotificationCenter.defaultCenter().removeObserver(self, name: "UIKeyboardDidHideNotification", object: nil)
    }

    func replaceNotifications(){

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: "UIKeyboardWillShowNotification", object: nil)

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: "UIKeyboardDidHideNotification", object: nil)
    }

    func keyboardWillShow (theNote:NSNotification) {

        if myFieldContainerView != nil && adjustedYValue == originalPosition {

            adjustedYValue = originalPosition
        }

        if let noteInfo:NSDictionary = theNote.userInfo {

            if let kbFrame:CGRect = noteInfo[UIKeyboardFrameBeginUserInfoKey]?.CGRectValue {

                let screenBounds: CGRect = UIScreen.mainScreen().bounds

                var kbFrameHeight = kbFrame.size.width
                var screenBoundsHeight = screenBounds.size.width

                if HelperFunctions.isHigherThaniOSVersion("7.9"){

                    kbFrameHeight = kbFrame.size.height
                    screenBoundsHeight = screenBounds.size.height
                }

                myKeyboardPosition = screenBoundsHeight - kbFrameHeight;
            }
        }

        self.myBackgroundView?.addGestureRecognizer(tap)
    }

    func updateViewPosition (view:UIView){

        if abs(myKeyboardPosition) > 0 {

            adjustedYValue = originalPosition

            if let pointInBackgroundView = view.superview?.convertPoint(view.frame.origin, toView: nil){

                let fieldActualBottomEdge = (pointInBackgroundView.y + view.frame.size.height)+theBufferingSpace

                let newYPosition = originalPosition + (myKeyboardPosition - fieldActualBottomEdge)

                TextFieldInteractivity.debug("pointInBackgroundView: \(pointInBackgroundView)")
                TextFieldInteractivity.debug("fieldActualBottomEdge: \(fieldActualBottomEdge)")
                TextFieldInteractivity.debug("currentViewOffset: \(currentViewOffset)")
                TextFieldInteractivity.debug("myKeyboardPosition: \(myKeyboardPosition)")
                TextFieldInteractivity.debug("newYPosition: \(newYPosition)")

                if newYPosition < originalPosition {

                    adjustedYValue = newYPosition
                }
            }

            TextFieldInteractivity.debug("adjustedYValue: \(adjustedYValue)")

            moveView()
        }
    }

    func textFieldDidBeginEditing(textField: UITextField) {

        updateViewPosition(textField)
    }

    func textViewDidBeginEditing(textView: UITextView) {

        updateViewPosition(textView)
    }

    func keyboardWillHide (theNote:NSNotification) {

        self.attemptKeyboardDefaultPosition();

        self.myBackgroundView?.removeGestureRecognizer(tap)
    }

    func moveView(isReturning:Bool = false){

        if myFieldContainerView != nil {

            if moveConstraints == true {

                TextFieldInteractivity.debug("moveConstraints is true")

                replaceConstraintOnView(myFieldContainerView!, withConstant: adjustedYValue)
            }

            var delayAmount = 0.0

            if isReturning == false {

                delayAmount = 0.2
            }

            UIView.animateWithDuration(0.2, delay: delayAmount, usingSpringWithDamping: 10, initialSpringVelocity: 10, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {

                if self.moveConstraints == true {

                    self.myFieldContainerView?.layoutIfNeeded()
                }else{

                    self.myFieldContainerView?.frame.origin.y = self.adjustedYValue
                }

                }, completion:{
                    (completed:Bool) in

                    //COMPLETION TASKS HERE
            })
        }
    }

    func replaceConstraintOnView(theView:UIView, withConstant theConstant:CGFloat){

        if let theSuperView = theView.superview {

            if let superViewYPosition = theSuperView.superview?.convertPoint(theSuperView.frame.origin, toView: nil){

                _ = theSuperView.constraints.map{
                    (theConstraint)->AnyObject in

                    if  (theConstraint.firstItem as? UIView == theView) {
                        if (theConstraint.firstAttribute == NSLayoutAttribute.Top){

                            TextFieldInteractivity.debug("Setting top constraint to \(theConstant)")

                            theConstraint.constant = theConstant - superViewYPosition.y
                        }
                    }

                    if  (theConstraint.secondItem as? UIView == theView) {

                        if (theConstraint.secondAttribute == NSLayoutAttribute.Bottom){

                            TextFieldInteractivity.debug("superViewYPosition: \(superViewYPosition)")

                            TextFieldInteractivity.debug("theView.frame.size.height: \(theView.frame.size.height)")

                            TextFieldInteractivity.debug("theSuperView.frame.size.height: \(theSuperView.frame.size.height)")

                            let bottomConstraint = (theSuperView.frame.size.height - theView.frame.size.height) - (theConstant - superViewYPosition.y)

                            TextFieldInteractivity.debug("Setting bottom constraint to \(bottomConstraint)")

                            theConstraint.constant = bottomConstraint
                        }
                    }

                    return theConstraint
                }
            }
        }
    }

    func dismissKeyboard() {

        self.myFieldContainerView?.endEditing(true)

        self.attemptKeyboardDefaultPosition();
    }

    func attemptKeyboardDefaultPosition(){

        TextFieldInteractivity.debug("attemptKeyboardDefaultPosition adjustedYValue: \(adjustedYValue), originalPosition: \(originalPosition)")

        if returnFirstResponder() == nil {

            if adjustedYValue != originalPosition{

                adjustedYValue = originalPosition
                moveView(true)
            }
        }
    }

    func forceKeyboardDefaultPosition(){

        TextFieldInteractivity.debug("forceKeyboardDefaultPosition adjustedYValue: \(adjustedYValue), originalPosition: \(originalPosition)")

        if adjustedYValue != originalPosition{

            adjustedYValue = originalPosition

            if myFieldContainerView != nil {

                if moveConstraints == true {

                    TextFieldInteractivity.debug("moveConstraints is true")

                    replaceConstraintOnView(myFieldContainerView!, withConstant: adjustedYValue)
                }

                if self.moveConstraints == true {

                    self.myFieldContainerView?.layoutIfNeeded()
                }else{

                    self.myFieldContainerView?.frame.origin.y = self.adjustedYValue
                }
            }
        }
    }

    func returnFirstResponder()->UIView?{

        for sub in self.myFieldContainerView!.subviews
        {
            if let theEditingField = sub as? UITextField {

                if theEditingField.editing {

                    return theEditingField
                }
            }else if let theEditingView = sub as? UITextView {

                if theEditingView.isFirstResponder() {

                    return theEditingView
                }
            }
        }

        return nil
    }
}