133 lines
5.6 KiB
Swift
133 lines
5.6 KiB
Swift
//
|
|
// ScrollingContentViewController.swift
|
|
// ScrollingContentViewController
|
|
//
|
|
// Created by Drew Olbrich on 1/6/19.
|
|
// Copyright 2019 Oath Inc.
|
|
//
|
|
// Licensed under the terms of the MIT License. See the file LICENSE for the full terms.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
/// A view controller that manages a single scrolling content view.
|
|
///
|
|
/// `ScrollingContentViewController` is a subclass of `UIViewController` that
|
|
/// provides a `contentView` outlet which can be assigned in Interface Builder or
|
|
/// programmatically. The width and height of the content view are constrained to be
|
|
/// greater than or equal to the dimensions of the root view's safe area. If the
|
|
/// content view's Auto Layout constraints or intrinsic content size require it to
|
|
/// exceed the size of the safe area, the content view will scroll freely.
|
|
///
|
|
/// The view controller's root view acts as a background view. Scrolling content
|
|
/// should be added to `contentView` instead of `view`.
|
|
///
|
|
/// The scroll view that hosts the content view is exposed via the `scrollView`
|
|
/// property.
|
|
///
|
|
/// See [https://github.com/drewolbrich/ScrollingContentViewController](https://github.com/drewolbrich/ScrollingContentViewController/blob/master/README.md) for full documentation.
|
|
open class ScrollingContentViewController: UIViewController {
|
|
|
|
/// The scrolling content view.
|
|
///
|
|
/// This view is the subview of `scrollView`.
|
|
@IBOutlet public var contentView: UIView! {
|
|
didSet {
|
|
if !isViewLoaded {
|
|
// If the view controller's root view hasn't been loaded yet, the assignment of
|
|
// scrollingContentViewManager.contentView must be deferred until viewDidLoad is
|
|
// called, because otherwise, no view hierarchy will exist to parent the content
|
|
// view and the scroll view to. This is the code path that is executed when the
|
|
// contentView outlet value defined in Interface Builder is assigned.
|
|
} else {
|
|
scrollingContentViewManager.contentView = contentView
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The `UIScrollView` to which `contentView` is parented.
|
|
///
|
|
/// This view is typically the subview of `ScrollingContentViewController.view`, but
|
|
/// it may be an arbitrary descendant of that view.
|
|
public var scrollView: ScrollingContentScrollView {
|
|
return scrollingContentViewManager.scrollView
|
|
}
|
|
|
|
/// If `true`, the content view should be resized to compensate for the portion of
|
|
/// the scroll view obscured by the presented keyboard, if possible.
|
|
///
|
|
/// The default value is `false`.
|
|
@IBInspectable public var shouldResizeContentViewForKeyboard: Bool {
|
|
set {
|
|
scrollingContentViewManager.shouldResizeContentViewForKeyboard = newValue
|
|
}
|
|
get {
|
|
return scrollingContentViewManager.shouldResizeContentViewForKeyboard
|
|
}
|
|
}
|
|
|
|
/// If `true`, the view controller's `additionalSafeAreaInsets` property is adjusted
|
|
/// when the keyboard is presented.
|
|
///
|
|
/// The default value is `true`.
|
|
@IBInspectable public var shouldAdjustAdditionalSafeAreaInsetsForKeyboard: Bool {
|
|
set {
|
|
scrollingContentViewManager.shouldAdjustAdditionalSafeAreaInsetsForKeyboard = newValue
|
|
}
|
|
get {
|
|
return scrollingContentViewManager.shouldAdjustAdditionalSafeAreaInsetsForKeyboard
|
|
}
|
|
}
|
|
|
|
/// An object that manages adding a content view to a scroll view.
|
|
private lazy var scrollingContentViewManager = ScrollingContentViewManager(hostViewController: self)
|
|
|
|
/// If you override this method, you must call `super` at some point in your
|
|
/// implementation.
|
|
open override func loadView() {
|
|
// Load all controls and connect all outlets defined by Interface Builder.
|
|
super.loadView()
|
|
|
|
scrollingContentViewManager.loadView(forContentView: contentView)
|
|
}
|
|
|
|
/// If you override this method, you must call `super` at some point in your
|
|
/// implementation.
|
|
open override func viewDidLoad() {
|
|
// If the content view has been assigned by Interface Builder, add it and the
|
|
// scroll view to the view hierarchy automatically. Otherwise, it is the caller's
|
|
// responsibility to manually assign the `contentView` property in their own
|
|
// implementation of viewDidLoad.
|
|
if let contentView = contentView {
|
|
scrollingContentViewManager.contentView = contentView
|
|
}
|
|
}
|
|
|
|
/// If you override this method, you must call `super` at some point in your
|
|
/// implementation.
|
|
open override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
assert(contentView != nil, "Either contentView must be assigned in viewDidLoad, or the contentView outlet must be connected in Interface Builder")
|
|
|
|
assert(scrollingContentViewManager.contentView != nil, "The content view was not added to the view hierarchy. Did you forget to call super in viewDidLoad?")
|
|
}
|
|
|
|
/// If you override this method, you must call `super` at some point in your
|
|
/// implementation.
|
|
open override func viewSafeAreaInsetsDidChange() {
|
|
super.viewSafeAreaInsetsDidChange()
|
|
|
|
scrollingContentViewManager.viewSafeAreaInsetsDidChange()
|
|
}
|
|
|
|
/// If you override this method, you must call `super` at some point in your
|
|
/// implementation.
|
|
open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
|
super.viewWillTransition(to: size, with: coordinator)
|
|
|
|
scrollingContentViewManager.viewWillTransition(to: size, with: coordinator)
|
|
}
|
|
|
|
}
|