Last active
January 29, 2017 19:23
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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