GalinhaVoadora
GalinhaVoadora

Reputation: 851

ScrollView and keyboard in Swift

I started creating a simple iOS app that does some operations.

But I'm having some problems when the keyboard appears, hiding one of my textfields.

I think it's a common problem and I did some research but I couldn't find anything that solved my problem.

I want to use a ScrollView rather than animate the textfield to make it visible.

Upvotes: 80

Views: 97420

Answers (17)

devshok
devshok

Reputation: 836

Intro

It's a pretty common thing we have to work with during the development process. Conceptually, if any view inside UIScrollView gets covered by the system keyboard, you have to follow a few steps to make it work perfectly:

  1. Write a callback function (selector) that will handle keyboard appearance
  2. Write a callback function (selector) that will handle keyboard dissapearance
  3. Write function to subscribe for keyboard system events (appearance and dissapearance) using written selectors (from step 1 and step 2)
  4. Write function to unsubscribe from keyboard system events
  5. Add subscription (step 3) and unsubscription (step 4) methods in appropriate view-life-cycle methods

Let's dive into

Disclaimer

The most important part to answer clearly is providing conception that you can adapt in your real code environment with any architecture you have. So, next steps assume that we have some custom UIViewController that has access to some custom UIView with UIScrollView and your accidentally covered UI (any UIView) inside UIScrollView.

How get we know about keyboard events?

NotificationCenter is a dispatch mechanism that allows us to subscribe for any information we need as observers and, luckily, for keyboard events too. So, our custom UIViewController will be an observer for keyboard events from NotificationCenter. NotificationCenter uses notifications (NSNotification) as a way of broadcasting that contains some userInfo with useful information inside. That's what we'll work with. So, let's dive into implementation finally!

Implementation

Step 1

We need to write a selector that will be used for handling income notification that will come each time the keyboard appears to fetch all the neccesary data from event:


import UIKit
import os


final class Controller: UIViewController {

    // let's assume it's your custom view
    // as instance of class `View` with
    // `UIScrollView` inside and your covered UI as well:
    private let body: View

    // step 1:
    @objc
    private func willShowKeyboard(from notification: NSNotification) {
        // #1
        guard let screen = notification.object as? UIScreen else {
            Logger().error("\(#function) invalid screen object in notification")
            return
        }
        // #2
        guard let userInfo = notification.userInfo else {
            Logger().error("\(#function) no user info in notification")
            return
        }
        // #3
        guard let frameEnd = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
            Logger().error("\(#function) no keyboard frame in user info from notification")
            return
        }
    
        // #4
    
        let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey]
        let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey]

        guard
            let animationDuration = duration as? Double,
            let animationCurve = curve as? UInt
        else {
            Logger().error("\(#function) no correct animation duration or curve in user info from notification")
            return
        }
    
        // #5

        let viewCoordinateSpace: UICoordinateSpace = self.body
        let globalCoordinatesSpace: UICoordinateSpace = screen.coordinateSpace
        let keyboardFrame = globalCoordinateSpace.convert(frame.cgRectValue, to: viewCoordinateSpace)

        // #6

        let textFieldFrame = self.body.textField.frame
        let keyboardMinY = keyboardFrame.origin.y
    
        // let's assume it's a normal space between
        // keyboard and the bottom edge of text field:
        let keyboardTopMargin: CGFloat = 20

        let haveToBeVisibleY = textFieldFrame.origin.y + textFieldFrame.height + keyboardTopMargin
        let yDifference = haveToBeVisibleY - keyboardMinY

        // is bottom edge of the text field visually lower than a top edge of the keyboard?

        if yDifference > .zero {
            // #7
            let options: UIView.AnimationOptions = .init(rawValue: animationCurve << 16)
        
            // #8
            let offsetBefore = self.body.scrollView.contentOffset
            let newOffset = CGPoint(x: offsetBefore.x, y: offsetBefore.y + offset)
        
            // #9
            UIView.animate(withDuration: animationDuration, delay: .zero, options: options) {
                self.scrollView.contentOffset = newOffset
            }
        }
    }
}

Let me try explain the code upper with a few more details below:

  1. We're trying to retrieve object as an UIScreen instance that later we use in step #
  2. As any usual notification contains of userInfo, we're trying to retrieve a hasmap of useful information
  3. We're retrieving a frame of the keyboard after animation will be completed. If you want to consider a frame of the keyboard at the start position before animation performing, check a key by using UIResponder.keyboardFrameBeginUserInfoKey.
  4. Then we retrieve duration and curve of the future keyboard appearance animation and then make type casting for them to adopt for our usage
  5. One of the most important parts why I decided to write this answer: keyboard's frame is not always in the same coordinate system comparing to your UI's coordinate system. That is why we did step #1 in the code above. We take both coordinate systems and convert keyboard's coordinate system into our system to get a valid keyboard frame to avoid calculation errors!
  6. Then we want to realize whether the bottom edge of our UI (text field, for instance but it can be anything we want) is visually lower than the top edge of the keyboard including keyboard's top margin (you may avoid top margin or set your own appropriate value to improve UI visually). For this reason, we calculate max Y of text field (including keyboard's top margin space) and min Y of the keyboard's frame because vertical axis system starts from the top of the screen due to the core logic. And, if we the max Y of the text field is bigger than the min Y of the keyboard, than we need to add inset for our scroll view to scroll it up for an appropriate Y points (yDifference).
  7. Based on step 4, we can add content offset for scroll view with the same animation and at the same time as a keyboard will appear. That's why retrieved animation duration and animation curve to immitate the keyboard's system animation. But the curve we've got from user info is a raw 32-bit unsigned integer value in fact, so to adopt our raw curve value to UIView.AnimationOptions's value we need to shift left our raw value by 16 bits to avoid zeroes on the left of the range of bits in raw value.
  8. Alright, all we gotta do is just to calculate a new offset based on current offset of scroll view. We just add a new offset by Y axis to the current one.
  9. And inside the block with all necessary and correct options we perform setting a new offset for scroll view animationally smoothly as user expected by default.

Don't worry, if you read it, all the next steps will be much easier.

Step 2

As we've written a function that handles keyboard appearance, we also need to write a function that scrolls the content back each time the keyboard will dissapear:


import UIKit
import os


final class Controller: UIViewController {

    // some code from step 1

    @objc
    private func willHideKeyboard(from notification: NSNotification) {
        // or any other value such a previous value before the keyboard has appeared.
        self.body.scrollView.contentOffset = .zero
    }
}

Step 3

Alright, since we've written the core logic when the keyboard appears and dissapears, all we gotta do is just add these selectors to the notification center:

import UIKit
import os

final class Controller: UIVIewController {

    /*
    some code from step 1, 2
    */

    private func subscribeForKeyboardEvents() {
        self.subscribeForKeyboardAppearance()
        self.subscribeForKeyboardDissapearance()
    }

    private func subscribeForKeyboardAppearance() {
        let selector = #selector(self.willShowKeyboard(from:))
        let name = UIResponder.keyboardWillShowNotification
        
        NotificationCenter
            .default
            .addObserver(self, selector: selector, name: name, object: nil)
    }

    private func subscribeForKeyboardDissapearance() {
        let selector = #selector(self.willHideKeyboard(from:))
        let name = UIResponder.keyboardWillHideNotification
        
        NotificationCenter
            .default
            .addObserver(self, selector: selector, name: name, object: nil)
    }
}

Step 4

And if the view controller and its view will be destroyed from memory, it would be great to consider prepared methods to unsubscribe from keyboard events in a similar way as we did in the previous step but vice-versa:

// some code from steps 1, 2 and 3

private func unsubscribeFromKeyboardEvents() {
    self.unsubscribeFromKeyboardAppearance()
    self.unsubscribeFromKeyboardDisappearance()
}
    
private func unsubscribeFromKeyboardAppearance() {
    let name = UIResponder.keyboardWillShowNotification
        
    NotificationCenter
        .default
        .removeObserver(self, name: name, object: nil)
}   

private func unsubscribeFromKeyboardDisappearance() {
    let name = UIResponder.keyboardWillHideNotification
    
    NotificationCenter
        .default
        .removeObserver(self, name: name, object: nil)
}

Step 5

We just call functions from steps 3 and 4 in appropriate view-life-cycle methods as below:


final class Controller: UIViewController {
    
    // code from previous steps

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.subscribeForKeyboardEvents()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.unsubscribeFromKeyboardEvents()
    }
}

Get interested?

Read more in the official documentation by Apple:

Upvotes: 1

steveluoxin
steveluoxin

Reputation: 436

There is a simple solution here

Upvotes: 0

mswlogo
mswlogo

Reputation: 149

This is a refinement on Zachary Probst solution posted above. I ran into a few issues with his solution and fixed it and enhanced it a bit.

This version does not need to pass in a list of UITextView controls. It finds the first responder in the current view. It also handles UITextView controls at any level in the View hierarchy.

I think his safeArea calculation wasn't quite right scrollView.contentOffset.y needed sign changed. It didn't show up if it was not scrolled. This fixed incremental scrolling. It might have been from other changes I made.

This works if the user jumps around to other UITextViews while the keyboard is up.

This is a Base Class I use for a bunch of ViewControllers. The inherited ViewController just needs to set the UIScrollViewer which activates this code behavior.

class ThemeAwareViewController: UIViewController
{
    var scrollViewForKeyBoard: UIScrollView? = nil
    var saveOffsetForKeyBoard: CGPoint?
    
    func findViewThatIsFirstResponder(view: UIView) -> UIView?
    {
        if view.isFirstResponder {
            return view
        }

        for subView in view.subviews {
            if let hit = findViewThatIsFirstResponder(view: subView) {
                return hit
            }
        }

        return nil
    }
    
    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            if let scrollView = scrollViewForKeyBoard {
                
                var safeArea = self.view.frame
                safeArea.size.height -= scrollView.contentOffset.y
                safeArea.size.height -= keyboardSize.height
                safeArea.size.height -= view.safeAreaInsets.bottom
                
                let activeField: UIView? = findViewThatIsFirstResponder(view: view)
                
                if let activeField = activeField {
                    // This line had me stumped for a while (I was passing in .frame)
                    let activeFrameInView = view.convert(activeField.bounds, from: activeField)
                    let distance = activeFrameInView.maxY - safeArea.size.height
                    if saveOffsetForKeyBoard == nil {
                        saveOffsetForKeyBoard = scrollView.contentOffset
                    }
                    scrollView.setContentOffset(CGPoint(x: 0, y: distance), animated: true)
                }
            }
        }
    }
    
    @objc func keyboardWillHide(notification: NSNotification) {
        guard let restoreOffset = saveOffsetForKeyBoard else {
            return
        }
        if let scrollView = scrollViewForKeyBoard {
            scrollView.setContentOffset(restoreOffset, animated: true)
            self.saveOffsetForKeyBoard = nil
        }
    }   
}

Upvotes: 3

Sudheer Kumar Palchuri
Sudheer Kumar Palchuri

Reputation: 2954

In ViewDidLoad, register the notifications:

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:UIResponder.keyboardWillHideNotification, object: nil)

Add below observer methods which does the automatic scrolling when keyboard appears.

@objc func keyboardWillShow(notification:NSNotification) {

    guard let userInfo = notification.userInfo else { return }
    var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    keyboardFrame = self.view.convert(keyboardFrame, from: nil)

    var contentInset:UIEdgeInsets = self.scrollView.contentInset
    contentInset.bottom = keyboardFrame.size.height + 20
    scrollView.contentInset = contentInset
}

@objc func keyboardWillHide(notification:NSNotification) {

    let contentInset:UIEdgeInsets = UIEdgeInsets.zero
    scrollView.contentInset = contentInset
}

Upvotes: 168

Elijah
Elijah

Reputation: 8610

Here is a complete solution, utilizing guard and concise code. Plus correct code in keyboardWillHide to only reset the bottom to 0.

@IBOutlet private weak var scrollView: UIScrollView!

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

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    scrollView.contentInset.bottom = 0
}

private func registerNotifications() {
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}

@objc private func keyboardWillShow(notification: NSNotification){
    guard let keyboardFrame = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
    scrollView.contentInset.bottom = view.convert(keyboardFrame.cgRectValue, from: nil).size.height
}

@objc private func keyboardWillHide(notification: NSNotification){
    scrollView.contentInset.bottom = 0
}

Upvotes: 41

Niki Trivedi
Niki Trivedi

Reputation: 1786

In my situation it was

ScrollView --> TableView --> TableViewCell

So I had to get y position in relative to keyboard frame and check if keyboard y position and my active field y position was intersecting or not

   @objc func keyboardWillShow(_ notification: Foundation.Notification) {

        var userInfo = notification.userInfo!
        var keyboardFrame:CGRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
        keyboardFrame = self.view.convert(keyboardFrame, from: nil)

        var contentInset:UIEdgeInsets = self.scrollView!.contentInset
        contentInset.bottom = keyboardFrame.size.height
        let loc = self.activeTextField?.convert(activeTextField!.bounds, to: self.view)

        if keyboardFrame.origin.y <  loc!.origin.y {
            self.scrollView?.contentOffset = CGPoint.init(x: (self.scrollView?.contentOffset.x)!, y: loc!.origin.y)
        }

        if self.scrollView?.contentInset.bottom == 0 {
            self.scrollView?.contentInset = contentInset
        }

    }

Upvotes: 0

Zachary Probst
Zachary Probst

Reputation: 199

Swift 5 Only adjust ScrollView when TextField is hidden by keyboard (for multiple TextFields)

Add / Remove Observers:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

Keep track of these values so you can return to your original position:

var scrollOffset : CGFloat = 0
var distance : CGFloat = 0

Adjust ScrollView contentOffset depending on TextField Location:

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {

        var safeArea = self.view.frame
        safeArea.size.height += scrollView.contentOffset.y
        safeArea.size.height -= keyboardSize.height + (UIScreen.main.bounds.height*0.04) // Adjust buffer to your liking

        // determine which UIView was selected and if it is covered by keyboard

        let activeField: UIView? = [textFieldA, textViewB, textFieldC].first { $0.isFirstResponder }
        if let activeField = activeField {
            if safeArea.contains(CGPoint(x: 0, y: activeField.frame.maxY)) {
                print("No need to Scroll")
                return
            } else {
                distance = activeField.frame.maxY - safeArea.size.height
                scrollOffset = scrollView.contentOffset.y
                self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset + distance), animated: true)
            }
        }
        // prevent scrolling while typing

        scrollView.isScrollEnabled = false
    }
}
@objc func keyboardWillHide(notification: NSNotification) {
        if distance == 0 {
            return
        }
        // return to origin scrollOffset
        self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollOffset), animated: true)
        scrollOffset = 0
        distance = 0
        scrollView.isScrollEnabled = true
}

Make sure to use [UIResponder.keyboardFrameEndUserInfoKey] to get the proper keyboard height the first time.

Upvotes: 10

Supanat Techasothon
Supanat Techasothon

Reputation: 415

for Swift 4.0

In ViewDidLoad

// setup keyboard event
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

Add below observer methods which does the automatic scrolling when keyboard appears.

@objc func keyboardWillShow(notification:NSNotification){
    var userInfo = notification.userInfo!
    var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    keyboardFrame = self.view.convert(keyboardFrame, from: nil)

    var contentInset:UIEdgeInsets = self.ui_scrollView.contentInset
    contentInset.bottom = keyboardFrame.size.height
    ui_scrollView.contentInset = contentInset
}

@objc func keyboardWillHide(notification:NSNotification){

    let contentInset:UIEdgeInsets = UIEdgeInsets.zero
    ui_scrollView.contentInset = contentInset
}

Upvotes: 17

Mubeen Qazi
Mubeen Qazi

Reputation: 177

In case anyone is looking for Objective-C code for this solution:

- (void)keyboardWasShown:(NSNotification *)notification {
        NSDictionary* info = [notification userInfo];
        CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
        UIEdgeInsets contentInsets = baseScrollView.contentInset;
        contentInsets.bottom = kbSize.height;
        baseScrollView.contentInset = contentInsets;
    }

    - (void)keyboardWillBeHidden:(NSNotification *)notification {
        UIEdgeInsets contentInsets = UIEdgeInsetsZero;
        baseScrollView.contentInset = contentInsets;
        [baseScrollView endEditing:YES];
    }

Upvotes: 0

Sam
Sam

Reputation: 466

In Swift4, just add the following extension.

extension UIViewController {

   func setupViewResizerOnKeyboardShown() {
        NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardWillShowForResizing),
                                           name: 
        Notification.Name.UIKeyboardWillShow,
                                           object: nil)
        NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.keyboardWillHideForResizing),
                                           name: Notification.Name.UIKeyboardWillHide,
                                           object: nil)
        }

   @objc func keyboardWillShowForResizing(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue,
        let window = self.view.window?.frame {
        // We're not just minusing the kb height from the view height because
        // the view could already have been resized for the keyboard before
        self.view.frame = CGRect(x: self.view.frame.origin.x,
                                 y: self.view.frame.origin.y,
                                 width: self.view.frame.width,
                                 height: window.origin.y + window.height - keyboardSize.height)
      } else {
        debugPrint("We're showing the keyboard and either the keyboard size or window is nil: panic widely.")
      }
  }

   @objc func keyboardWillHideForResizing(notification: Notification) {
     if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
        let viewHeight = self.view.frame.height
        self.view.frame = CGRect(x: self.view.frame.origin.x,
                                 y: self.view.frame.origin.y,
                                 width: self.view.frame.width,
                                 height: viewHeight + keyboardSize.height)
    } else {
        debugPrint("We're about to hide the keyboard and the keyboard size is nil. Now is the rapture.")
    }
   }
 }

Upvotes: 2

Ivan Carosati
Ivan Carosati

Reputation: 345

From the answer by Sudheer Palchuri, converted for Swift 4.

In ViewDidLoad, register the notifications:

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil)

And then:

// MARK: - Keyboard Delegates
func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

@objc func keyboardWillShow(notification:NSNotification){

    var userInfo = notification.userInfo!
    var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    keyboardFrame = self.view.convert(keyboardFrame, from: nil)

    var contentInset:UIEdgeInsets = self.scrollView.contentInset
    contentInset.bottom = keyboardFrame.size.height
    self.scrollView.contentInset = contentInset
}

@objc func keyboardWillHide(notification:NSNotification){

    let contentInset:UIEdgeInsets = UIEdgeInsets.zero
    self.scrollView.contentInset = contentInset
}

Upvotes: 6

Johan Drevet
Johan Drevet

Reputation: 225

An answer for Swift 3, based on the one proposed by Daniel Jones, but safer (thanks to the guard), more concise and with consistent scroll indicator insets:

@objc private func keyboardWillBeShown(notification: NSNotification) {
    guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else { return }
    let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
    scrollView.contentInset.bottom = keyboardFrame.size.height
    scrollView.scrollIndicatorInsets = scrollView.contentInset
}

@objc private func keyboardWillBeHidden() {
    scrollView.contentInset = .zero
    scrollView.scrollIndicatorInsets = scrollView.contentInset
}

Upvotes: 2

nikans
nikans

Reputation: 2555

contentInset doesn't work for me, because I want the scrollview move all the way up above the keyboard. So I use contentOffset:

func keyboardWillShow(notification:NSNotification) {
    guard let keyboardFrameValue = notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue else {
        return
    }
    let keyboardFrame = view.convert(keyboardFrameValue.cgRectValue, from: nil)
    scrollView.contentOffset = CGPoint(x:0, y:keyboardFrame.size.height)
}

func keyboardWillHide(notification:NSNotification) {
    scrollView.contentOffset = .zero
}

Upvotes: 11

GalinhaVoadora
GalinhaVoadora

Reputation: 851

Reading the links you sent to me, I found a way to make it work, thanks!:

func textFieldDidBeginEditing(textField: UITextField) {            
    if (textField == //your_field) {
        scrollView.setContentOffset(CGPointMake(0, field_extra.center.y-280), animated: true)
        callAnimation()
        viewDidLayoutSubviews()
    }
}

func textFieldDidEndEditing(textField: UITextField) {    
    if (textField == //your_field){
        scrollView .setContentOffset(CGPointMake(0, 0), animated: true)
        viewDidLayoutSubviews()
    }
}

Upvotes: 5

Fun Programmer
Fun Programmer

Reputation: 1082

The top answer for swift 3:

NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name:NSNotification.Name.UIKeyboardWillHide, object: nil)

And then:

func keyboardWillShow(notification:NSNotification){
    //give room at the bottom of the scroll view, so it doesn't cover up anything the user needs to tap
    var userInfo = notification.userInfo!
    var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    keyboardFrame = self.view.convert(keyboardFrame, from: nil)

    var contentInset:UIEdgeInsets = self.theScrollView.contentInset
    contentInset.bottom = keyboardFrame.size.height
    theScrollView.contentInset = contentInset
}

func keyboardWillHide(notification:NSNotification){
    let contentInset:UIEdgeInsets = UIEdgeInsets.zero
    theScrollView.contentInset = contentInset
}

Upvotes: 74

Zany
Zany

Reputation: 893

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

}

func keyboardWillShow(_ notification:Notification) {

    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardSize.height, 0)
    }
}
func keyboardWillHide(_ notification:Notification) {

    if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
    }
}

Upvotes: 0

Glynbeard
Glynbeard

Reputation: 1389

You can animate your scrollview to center on your UITextField on keyboard appearance (ie. making your textfield the first responder) via a scroll offset. Here are a couple of good resources to get you started (there are a bunch on this site):

How programmatically move a UIScrollView to focus in a control above keyboard?

How to make a UIScrollView auto scroll when a UITextField becomes a first responder

Additionally, if you simply use a UITableView with your content in cells, when the textfield becomes first responder, the UITableViewController will automatically scroll to the textfield cell for you (though I'm not sure this is what you want to do).

Upvotes: 2

Related Questions