263 lines
12 KiB
Swift
263 lines
12 KiB
Swift
//
|
|
// MVMCoreUIThreeLayerViewController.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Scott Pfeil on 2/13/19.
|
|
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
|
//
|
|
// This class has three views, a top, middle, and bottom. The top view is just the first view in the scrollview. The middle can be aligned center, top, or bottom depending on if spaceBetweenTopAndMiddle() or spaceBetweenMiddleAndBottom() return values. The bottom view, if inside the scrollview, appears pinned to the bottom unless content pushes it off the screen. If outside the scroll, it's pinned to the bottom under the scrollview.
|
|
|
|
import UIKit
|
|
import MVMAnimationFramework
|
|
|
|
open class ThreeLayerViewController: ProgrammaticScrollViewController {
|
|
|
|
// The three main views
|
|
var topView: UIView?
|
|
var middleView: UIView?
|
|
var bottomView: UIView?
|
|
var useMargins: Bool = false
|
|
|
|
// The bottom view can be put outside of the scrolling area.
|
|
var bottomViewOutsideOfScroll = false
|
|
|
|
private var safeAreaView: UIView?
|
|
var heightConstraint: NSLayoutConstraint?
|
|
|
|
open override func updateViews() {
|
|
super.updateViews()
|
|
let width = view.bounds.width
|
|
MFStyler.setDefaultMarginsFor(contentView, size: width)
|
|
if let topView = topView as? MVMCoreViewProtocol {
|
|
topView.updateView(width)
|
|
}
|
|
if let middleView = middleView as? MVMCoreViewProtocol {
|
|
middleView.updateView(width)
|
|
}
|
|
if let bottomView = bottomView as? MVMCoreViewProtocol {
|
|
bottomView.updateView(width)
|
|
}
|
|
}
|
|
|
|
open override func updateViewConstraints() {
|
|
super.updateViewConstraints()
|
|
guard let scrollView = scrollView else {
|
|
return
|
|
}
|
|
|
|
if #available(iOS 11.0, *), scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic {
|
|
heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom
|
|
} else {
|
|
heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom
|
|
}
|
|
}
|
|
|
|
open override func loadView() {
|
|
super.loadView()
|
|
// The height is used to keep the bottom view at the bottom.
|
|
if let contentView = contentView, let scrollView = scrollView {
|
|
heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0)
|
|
heightConstraint?.priority = UILayoutPriority.defaultLow
|
|
}
|
|
}
|
|
|
|
open override func newDataBuildScreen() {
|
|
super.newDataBuildScreen()
|
|
|
|
// Removes the views
|
|
topView?.removeFromSuperview()
|
|
middleView?.removeFromSuperview()
|
|
bottomView?.removeFromSuperview()
|
|
safeAreaView?.removeFromSuperview()
|
|
MVMCoreUIStackableViewController.remove(contentView?.subviews)
|
|
|
|
// Reset constraints
|
|
bottomConstraint?.isActive = true
|
|
heightConstraint?.isActive = false
|
|
|
|
setupLayers()
|
|
}
|
|
|
|
//MARK:-Functions to subclass
|
|
// Subclass for a top view.
|
|
open func viewForTop() -> UIView? {
|
|
return nil
|
|
}
|
|
|
|
// Subclass for a middle view.
|
|
open func viewForMiddle() -> UIView? {
|
|
return nil
|
|
}
|
|
|
|
// Subclass for a bottom view.
|
|
open func viewForBottom() -> UIView? {
|
|
return nil
|
|
}
|
|
|
|
// If a value is set, the middle view is pinned this value below the top view, if not, space is left to fill.
|
|
open func spaceBetweenTopAndMiddle() -> CGFloat? {
|
|
return nil
|
|
}
|
|
|
|
// If a value is set, the middle view is pinned this value above the bottom view, if not, space is left to fill.
|
|
open func spaceBetweenMiddleAndBottom() -> CGFloat? {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
//MARK:-Setup
|
|
extension ThreeLayerViewController {
|
|
func setupViewAsTop() -> UIView? {
|
|
if let topView = viewForTop() {
|
|
self.topView = topView
|
|
} else {
|
|
topView = MVMCoreUICommonViewsUtility.commonView()
|
|
topView?.heightAnchor.constraint(equalToConstant: 0).isActive = true
|
|
}
|
|
guard let topView = topView, let contentView = contentView else {
|
|
return nil
|
|
}
|
|
contentView.addSubview(topView)
|
|
NSLayoutConstraint.pinViewTop(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewLeft(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewRight(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true
|
|
return topView
|
|
}
|
|
|
|
func setupViewAsMiddle() -> UIView? {
|
|
if let middleView = viewForMiddle() {
|
|
self.middleView = middleView
|
|
} else {
|
|
middleView = MVMCoreUICommonViewsUtility.commonView()
|
|
middleView?.heightAnchor.constraint(equalToConstant: 0).isActive = true
|
|
}
|
|
guard let middleView = middleView, let contentView = contentView else {
|
|
return nil
|
|
}
|
|
contentView.addSubview(middleView)
|
|
NSLayoutConstraint.pinViewLeft(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewRight(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true
|
|
middleView.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical)
|
|
return middleView
|
|
}
|
|
|
|
func setupViewAsBottom() -> UIView? {
|
|
if let bottomView = viewForBottom() {
|
|
self.bottomView = bottomView
|
|
} else {
|
|
bottomView = MVMCoreUICommonViewsUtility.commonView()
|
|
bottomView?.heightAnchor.constraint(equalToConstant: 0).isActive = true
|
|
}
|
|
guard let bottomView = bottomView else {
|
|
return nil
|
|
}
|
|
|
|
// Adds the bottom view outside the scroll if directed.
|
|
if bottomViewOutsideOfScroll {
|
|
bottomConstraint?.isActive = false;
|
|
addViewInsideOfScrollViewBottom(ViewConstrainingView.empty())
|
|
addViewOutsideOfScrollViewBottom(bottomView)
|
|
} else {
|
|
bottomConstraint?.isActive = true;
|
|
addViewInsideOfScrollViewBottom(bottomView)
|
|
}
|
|
return bottomView
|
|
}
|
|
|
|
func setupLayers() {
|
|
guard let contentView = contentView, let topView = setupViewAsTop(), let middleView = setupViewAsMiddle(), let bottomView = setupViewAsBottom() else {
|
|
return
|
|
}
|
|
let spaceAbove = spaceBetweenTopAndMiddle()
|
|
let spaceBelow = spaceBetweenMiddleAndBottom()
|
|
if let spaceAbove = spaceAbove, let spaceBelow = spaceBelow {
|
|
// Both top and bottom space set, buttons not pinned to bottom.
|
|
middleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceAbove).isActive = true
|
|
bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true
|
|
} else {
|
|
heightConstraint?.isActive = true
|
|
|
|
if let spaceAbove = spaceAbove {
|
|
// Space above is set, space below is free.
|
|
let bottomSpacer = MVMCoreUICommonViewsUtility.commonView()
|
|
contentView.addSubview(bottomSpacer)
|
|
bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
|
|
contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true
|
|
bottomSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true
|
|
middleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceAbove).isActive = true
|
|
bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true
|
|
bottomView.topAnchor.constraint(equalTo: bottomSpacer.bottomAnchor).isActive = true
|
|
} else if let spaceBelow = spaceBelow {
|
|
// Space below is set, space above is free.
|
|
let topSpacer = MVMCoreUICommonViewsUtility.commonView()
|
|
contentView.addSubview(topSpacer)
|
|
topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
|
|
contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true
|
|
topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true
|
|
topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
|
|
middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true
|
|
bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true
|
|
} else {
|
|
// No set space above or below, make the spacers the same height with a default minimum.
|
|
let topSpacer = MVMCoreUICommonViewsUtility.commonView()
|
|
let bottomSpacer = MVMCoreUICommonViewsUtility.commonView()
|
|
contentView.addSubview(topSpacer)
|
|
contentView.addSubview(bottomSpacer)
|
|
topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
|
|
contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true
|
|
bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
|
|
contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true
|
|
topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor).isActive = true
|
|
topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true
|
|
topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
|
|
middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true
|
|
bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true
|
|
bottomView.topAnchor.constraint(equalTo: bottomSpacer.bottomAnchor).isActive = true
|
|
}
|
|
}
|
|
}
|
|
|
|
func addViewInsideOfScrollViewBottom(_ view: UIView) {
|
|
guard let contentView = contentView else {
|
|
return
|
|
}
|
|
contentView.addSubview(view);
|
|
NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewBottom(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
}
|
|
|
|
func addViewOutsideOfScrollViewBottom(_ view: UIView) {
|
|
self.view?.addSubview(view)
|
|
if let scrollView = scrollView, let parentView = self.view {
|
|
view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
|
|
NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
if #available(iOS 11.0, *) {
|
|
parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
|
if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) {
|
|
safeAreaView.backgroundColor = bottomView?.backgroundColor
|
|
self.safeAreaView = safeAreaView
|
|
}
|
|
} else {
|
|
NSLayoutConstraint.pinViewBottom(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//MARK:-Animation
|
|
extension ThreeLayerViewController {
|
|
open override func setupIntroAnimations() {
|
|
if let topView = topView, topView.subviews.count > 0 {
|
|
introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: topView))
|
|
}
|
|
if let middleView = middleView, middleView.subviews.count > 0 {
|
|
introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: middleView))
|
|
}
|
|
if let bottomView = bottomView, bottomView.subviews.count > 0 {
|
|
introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: bottomView))
|
|
}
|
|
}
|
|
}
|