Skip to content

Instantly share code, notes, and snippets.

@Erkened
Last active January 29, 2017 19:23
Show Gist options
  • Save Erkened/c33e1d8e24cab137cfdbd7a5262d982a to your computer and use it in GitHub Desktop.
Save Erkened/c33e1d8e24cab137cfdbd7a5262d982a to your computer and use it in GitHub Desktop.
A UIScrollView which scrolls when the keyboard is displayed/hidden. The keyboard is dismissed when tapping outside a responding view like a textfield or textview. Swift 3.0
//
// UIScrollViewWithKeyboard.swift
// Repnote
//
// Created by John Neumann on 29/01/2017.
// Copyright © 2017 Audioy. All rights reserved.
//
import UIKit
class UIScrollViewWithKeyboard: UIScrollView {
var keyboardIsUp = false
override init(frame: CGRect) {
super.init(frame: frame)
// Listen to notifications from the keyboard
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
//NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow", name: UIKeyboardDidShowNotification, object: nil)
//NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide", name: UIKeyboardDidHideNotification, object: nil)
//NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChangeFrame:", name: UIKeyboardWillChangeFrameNotification, object: nil)
//NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidChangeFrame:", name: UIKeyboardDidChangeFrameNotification, object: nil)
}
/*
convenience init(){
self.init(frame: CGRect.zero)
}
*/
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func keyboardWillShow(notification: NSNotification){
//print("Keyboard will show")
if
let keyboardRect = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue,
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? UInt {
// It may be required to translate the rect to the proper reference frame if rotates. This code does it. Keep it for reference
//let properlyRotatedRect:CGRect = self.window!.convertRect(keyboardRect, toView: self.window!.rootViewController!.view)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardRect.height, right: 0)
})
// Scroll the view to the frame that became first responder, if found
if
let responderView = findFirstResponder(inView: self),
let responderSuperview = responderView.superview{
let translatedFrame = responderSuperview.convert(responderView.frame, to: self)
self.scrollRectToVisible(translatedFrame, animated: true)
}
keyboardIsUp = true
}
}
func keyboardWillHide(notification: NSNotification){
//print("Keyboard will hide")
if
let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? UInt {
// It may be required to translate the rect to the proper reference frame if rotates. This code does it. Keep it for reference
//let properlyRotatedRect:CGRect = self.window!.convertRect(keyboardRect, toView: self.window!.rootViewController!.view)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: UIViewKeyframeAnimationOptions(rawValue: curve), animations: {
self.contentInset = UIEdgeInsets.zero
})
keyboardIsUp = false
}
}
// Search recursively for the view that is first responder within a view and its subviews
func findFirstResponder(inView view: UIView) -> UIView?{
for subview:UIView in view.subviews{
if subview.responds(to: #selector(getter: isFirstResponder)) && subview.isFirstResponder{
return subview
}
if let result = findFirstResponder(inView: subview){
return result
}
}
return nil
}
// WARNING!! For touchesEnded to be called, make sure that any tap recogniser added to this scrollView defines tapGestureRecogniser.cancelsTouchesInView = false;
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// Exit if keyboard is down, locationTouch is nil or there is no responder view or responder superview
guard
keyboardIsUp,
let locationTouch = touches.first,
let responderView = findFirstResponder(inView: self),
let responderSuperview = responderView.superview else{
return
}
// Get the location point
let locationPoint = locationTouch.location(in: self)
// Get the frame of the first responder
let translatedFrame = responderSuperview.convert(responderView.frame, to: self)
// If the touch point is outside the first responder, dismiss the keyboard
if !translatedFrame.contains(locationPoint){
responderView.resignFirstResponder()
}
}
deinit{
NotificationCenter.default.removeObserver(self)
}
}
// will change frame
// will show
// did change frame
// did show
// will change frame
// will hide
// did change frame
// did hide
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment