Skip to content

Instantly share code, notes, and snippets.

@stami
Created November 12, 2021 15:57
Show Gist options
  • Save stami/54d78be217f9335d0ba2110daaab3edf to your computer and use it in GitHub Desktop.
Save stami/54d78be217f9335d0ba2110daaab3edf to your computer and use it in GitHub Desktop.
Reusable base view controller to extend
// Use as you wish!
import UIKit
import SnapKit
class ScrollStackViewController: UIViewController {
let scrollView = UIScrollView()
let contentView = UIView()
let stackView = UIStackView()
private lazy var keyboardAvoiding = KeyboardAvoiding(scrollView: self.scrollView, rootView: self.view, dismissKeyboardOnTap: true)
/// Override to set insets for the stackview
var stackViewInset: UIEdgeInsets {
return UIEdgeInsets(top: view.statusBarHeight() + 30, left: 30, bottom: 30, right: 30)
}
var avoidKeyboard: Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
scrollView.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalTo(view)
}
stackView.axis = .vertical
stackView.alignment = .fill
contentView.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(stackViewInset.top)
make.left.equalToSuperview().offset(stackViewInset.left)
make.right.equalToSuperview().offset(-stackViewInset.right)
make.bottom.equalToSuperview().offset(-stackViewInset.bottom)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if avoidKeyboard {
keyboardAvoiding.activate()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if avoidKeyboard {
keyboardAvoiding.deactivate()
}
}
func wrapWithCenteringContainer(_ contentView: UIView) -> UIView {
let container = UIView()
container.addSubview(contentView)
contentView.snp.makeConstraints { make in
make.top.bottom.centerX.equalToSuperview()
make.left.greaterThanOrEqualToSuperview()
make.right.lessThanOrEqualToSuperview()
}
return container
}
}
class KeyboardAvoiding {
private let scrollView: UIScrollView
private let rootView: UIView
private let dismissKeyboardOnTap: Bool
private var originalBottomInset: CGFloat?
private lazy var tapGestureRecognizer: UITapGestureRecognizer = {
let recognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
recognizer.cancelsTouchesInView = false
return recognizer
}()
init(scrollView: UIScrollView, rootView: UIView, dismissKeyboardOnTap: Bool) {
self.scrollView = scrollView
self.rootView = rootView
self.dismissKeyboardOnTap = dismissKeyboardOnTap
}
func activate() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
if dismissKeyboardOnTap {
rootView.addGestureRecognizer(tapGestureRecognizer)
}
}
func deactivate() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
rootView.removeGestureRecognizer(tapGestureRecognizer)
}
@objc
private func dismissKeyboard() {
rootView.endEditing(true)
}
@objc
private func keyboardWillShow(_ notification: Notification) {
guard let userInfo = notification.userInfo as? [String: AnyObject],
let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey]?.cgRectValue
else { return }
let convertedFrame = rootView.convert(keyboardFrame, from: nil)
let keyboardInset = scrollView.frame.maxY - convertedFrame.origin.y
var insets = scrollView.contentInset
// Set only once
if originalBottomInset == nil {
originalBottomInset = insets.bottom
}
insets.bottom = (originalBottomInset ?? 0) + keyboardInset
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
@objc
private func keyboardWillHide(_ notification: Notification) {
var insets = scrollView.contentInset
insets.bottom = originalBottomInset ?? 0
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment