Files
ContainerController/ContainerControllerSwift/ContainerController.swift
T
2020-06-09 23:01:58 +03:00

1336 lines
42 KiB
Swift

//
// ContainerView.swift
// PatternsSwift
//
// Created by mrustaa on 21/04/2020.
// Copyright © 2020 mrustaa. All rights reserved.
//
import UIKit
open class ContainerController: NSObject {
// MARK: Views
public var view: ContainerView!
public var shadowButton: UIButton!
public var controller: UIViewController?
public var scrollView: UIScrollView?
public var headerView: UIView?
public var footerView: UIView?
// MARK: Layout
public var layout: ContainerLayout = ContainerLayout()
// MARK: Delegate
public var delegate: ContainerControllerDelegate?
// MARK: Current Move Type
public var moveType: ContainerMoveType = .hide
public var oldMoveType: ContainerMoveType = .hide
// MARK: - Properties Scroll
private var oldTransform: CGAffineTransform = .identity
private var oldPosition: CGFloat = 0.0
private var panGesture: UIPanGestureRecognizer?
private var panBeginSavePosition: CGFloat = 0.0
private var oldOrientation: ContainerDevice.Orientation = ContainerDevice.orientation
private var isScrolling: Bool = false
private var scrollBordersRunContainer: Bool = false
private var scrollOnceBeginDragging: Bool = false
private var scrollOnceEnded: Bool = false
private var scrollBegin: Bool = false
private var scrollStartPosition: CGFloat = 0.0
private var scrollTransform = CGAffineTransform.identity
// MARK: - Properties Position
public var topBarHeight: CGFloat {
var result: CGFloat = 0.0
if let vc = controller?.navigationController, !vc.isNavigationBarHidden {
let statusBarHeight: CGFloat = ContainerDevice.statusBarHeight
let navBarHeight: CGFloat = vc.navigationBar.frame.height
result = statusBarHeight + navBarHeight
}
return result
}
public var topTranslucent: Bool {
return controller?.navigationController?.navigationBar.isTranslucent ?? false
}
private var isPortrait: Bool {
return ContainerDevice.isPortrait
}
private var deviceHeight: CGFloat {
var height: CGFloat = 0.0
if isPortrait {
height = ContainerDevice.screenMax
} else {
height = ContainerDevice.screenMin
}
height -= topBarHeight
return height
}
private var deviceWidth: CGFloat {
var width: CGFloat = 0.0
if isPortrait {
width = ContainerDevice.screenMin
} else {
width = ContainerDevice.screenMax
}
return width
}
// MARK: - Positions Move
private var positionTop: CGFloat {
var top = layout.positions.top
if !isPortrait {
if let landscape = layout.landscapePositions {
top = landscape.top
}
}
return top
}
public var positionMiddle: CGFloat {
var middle = layout.positions.middle ?? layout.positions.bottom
if !isPortrait {
if let landscapeMid = layout.landscapePositions?.middle {
middle = landscapeMid
}
}
return deviceHeight - middle
}
private var positionBottom: CGFloat {
var bottom = layout.positions.bottom
if !isPortrait {
if let landscape = layout.landscapePositions {
bottom = landscape.bottom
}
}
return deviceHeight - bottom
}
private var insetsLeft: CGFloat {
var left: CGFloat = layout.insets.left
if !isPortrait {
if let inset = layout.landscapeInsets {
left = inset.left
}
}
return left
}
private var insetsRight: CGFloat {
var right: CGFloat = layout.insets.right
if !isPortrait {
if let inset = layout.landscapeInsets {
right = inset.right
}
}
return right
}
private var middleEnable: Bool {
if isPortrait {
return layout.positions.middle != nil
} else {
if let landscapePositions = layout.landscapePositions {
return landscapePositions.middle != nil
} else {
return layout.positions.middle != nil
}
}
}
// MARK: - Init
public init(addTo controller: UIViewController, layout: ContainerLayout) {
super.init()
self.controller = controller
set(layout: layout)
NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
createShadowButton()
createContainerView()
move(type: layout.startPosition, animation: false)
}
// MARK: - Remove
public func remove(completion: (() -> Void)? = nil) {
NotificationCenter.default.removeObserver(self)
move(type: .hide, completion:
{ [weak self] in
guard let _self = self else { return }
_self.scrollView?.removeFromSuperview()
_self.headerView?.removeFromSuperview()
_self.footerView?.removeFromSuperview()
_self.shadowButton.removeFromSuperview()
_self.view.removeFromSuperview()
completion?()
})
}
// MARK: - Rotated
@objc func rotated() {
let orint = UIDevice.current.orientation
if orint == .faceUp || orint == .faceDown { return }
if ContainerDevice.orientation == oldOrientation { return }
oldOrientation = ContainerDevice.orientation
if isPortrait {
shadowButton.isHidden = !layout.backgroundShadowShow
} else {
if let landscapeShadowShow = layout.landscapeBackgroundShadowShow {
shadowButton.isHidden = !landscapeShadowShow
} else {
shadowButton.isHidden = !layout.backgroundShadowShow
}
}
delegate?.containerControllerRotation(self)
calculationView()
calculationScrollViewHeight(from: .rotation)
move(type: moveType, from: .rotation)
}
// MARK: - Update Layout
func set(layout: ContainerLayout) {
self.layout = layout
calculationViews()
}
// MARK: Set
func set(movingEnabled: Bool) {
layout.movingEnabled = movingEnabled
scrollView?.isScrollEnabled = movingEnabled
panGesture?.isEnabled = movingEnabled
}
func set(trackingPosition: Bool) {
layout.trackingPosition = trackingPosition
}
func set(footerPadding: CGFloat) {
layout.footerPadding = footerPadding
calculationViews()
}
// MARK: Scroll Insets
func set(scrollIndicatorTop: CGFloat) {
layout.scrollIndicatorInsets = UIEdgeInsets(top: scrollIndicatorTop, left: 0, bottom: layout.scrollIndicatorInsets.bottom, right: 0)
calculationViews()
}
func set(scrollIndicatorBottom: CGFloat) {
layout.scrollIndicatorInsets = UIEdgeInsets(top: layout.scrollIndicatorInsets.top, left: 0, bottom: scrollIndicatorBottom, right: 0)
calculationViews()
}
func set(scrollInsetsTop: CGFloat) {
layout.scrollInsets = UIEdgeInsets(top: scrollInsetsTop, left: 0, bottom: layout.scrollInsets.bottom, right: 0)
calculationViews()
}
func set(scrollInsetsBottom: CGFloat) {
layout.scrollInsets = UIEdgeInsets(top: layout.scrollInsets.top, left: 0, bottom: scrollInsetsBottom, right: 0)
calculationViews()
}
// MARK: Portrait
func set(top: CGFloat) {
layout.positions.top = top
}
func set(middle: CGFloat?) {
layout.positions.middle = middle
}
func set(bottom: CGFloat) {
layout.positions.bottom = bottom
}
func set(right: CGFloat) {
layout.insets.right = right
if isPortrait { calculationViews() }
}
func set(left: CGFloat) {
layout.insets.left = left
if isPortrait { calculationViews() }
}
func set(backgroundShadowShow: Bool) {
layout.backgroundShadowShow = backgroundShadowShow
if isPortrait { move(type: moveType) }
}
// MARK: Landscape
func updateLandscapeLayout() {
if layout.landscapePositions == nil {
layout.landscapePositions = ContainerPosition.zero
}
if layout.landscapeInsets == nil {
layout.landscapeInsets = ContainerInsets.zero
}
}
func setLandscape(top: CGFloat) {
updateLandscapeLayout()
layout.landscapePositions?.top = top
}
func setLandscape(middle: CGFloat?) {
updateLandscapeLayout()
layout.landscapePositions?.middle = middle
}
func setLandscape(bottom: CGFloat) {
updateLandscapeLayout()
layout.landscapePositions?.bottom = bottom
}
func setLandscape(right: CGFloat) {
updateLandscapeLayout()
layout.landscapeInsets?.right = right
if !isPortrait { calculationViews() }
}
func setLandscape(left: CGFloat) {
updateLandscapeLayout()
layout.landscapeInsets?.left = left
if !isPortrait { calculationViews() }
}
func setLandscape(backgroundShadowShow: Bool) {
layout.landscapeBackgroundShadowShow = backgroundShadowShow
if !isPortrait { move(type: moveType) }
}
// MARK: - Create Shadow-Button
private func createShadowButton() {
shadowButton = UIButton(frame: CGRect(x: 0, y: 0, width: ContainerDevice.screenMax, height: ContainerDevice.screenMax))
shadowButton.isUserInteractionEnabled = false
shadowButton.backgroundColor = .black
shadowButton.alpha = 0.0
shadowButton.addTarget(self, action: #selector(shadowButtonAction), for: .touchUpInside)
controller?.view.addSubview(shadowButton)
}
@objc private func shadowButtonAction() {
delegate?.containerControllerShadowClick(self)
}
// MARK: - Create Container-View
private func createContainerView() {
let frame = CGRect(x: 0, y: 0, width: deviceWidth, height: deviceHeight * 2)
view = ContainerView(frame: frame)
view.backgroundColor = .white
controller?.view.addSubview(view)
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGesture?.isEnabled = layout.movingEnabled
if let panGesture = panGesture {
view.addGestureRecognizer(panGesture)
}
}
// MARK: - Add Header
public func removeHeaderView() {
if let headerView = self.headerView {
headerView.removeFromSuperview()
}
headerView = nil
calculationViews()
}
public func add(headerView: UIView) {
removeHeaderView()
self.headerView = headerView
view.contentView?.addSubview(headerView)
calculationViews()
}
// MARK: - Add Footer
public func removeFooterView() {
if let footerView = self.footerView {
footerView.removeFromSuperview()
}
footerView = nil
calculationViews()
}
public func add(footerView: UIView) {
removeFooterView()
self.footerView = footerView
controller?.view.addSubview(footerView)
calculationViews()
}
// MARK: - Add ScrollView
public func removeScrollView() {
if let scroll = self.scrollView {
scroll.removeFromSuperview()
}
scrollView = nil
calculationViews()
}
public func add(scrollView: UIScrollView) {
removeScrollView()
self.scrollView = scrollView
scrollView.isScrollEnabled = layout.movingEnabled
scrollView.autoresizingMask = [.flexibleLeftMargin,
.flexibleWidth,
.flexibleRightMargin,
.flexibleTopMargin,
.flexibleHeight,
.flexibleBottomMargin]
scrollView.isScrollEnabled = (moveType == .top)
if scrollView.delegate == nil {
scrollView.delegate = self
}
if let tableAdapterView = scrollView as? TableAdapterView {
tableAdapterView.delegate = self
tableAdapterView.dataSource = self
}
if let collectionAdapterView = scrollView as? CollectionAdapterView {
collectionAdapterView.delegate = self
collectionAdapterView.dataSource = self
}
view.contentView?.addSubview(scrollView)
calculationViews()
}
// MARK: - Pan Gesture
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
view.layer.removeAllAnimations()
scrollView?.layer.removeAllAnimations()
switch gesture.state {
case .began:
panBeginSavePosition = view.transform.ty
case .changed:
var transform = view.transform
transform.ty = (panBeginSavePosition + gesture.translation(in: view).y)
if transform.ty < 0 {
transform.ty = (positionTop / 2)
} else if transform.ty < positionTop {
transform.ty = ((positionTop / 2) + (transform.ty / 2))
}
let position = transform.ty
let type: ContainerMoveType = moveType
let from: ContainerFromType = .pan
let animation = false
changeView(transform: transform)
shadowLevelAlpha(position: position, animation: false)
changeFooterView(position: position)
calculationScrollViewHeight(from: from)
changeMove(position: position, type: type, animation: animation)
case .ended:
let velocityY = gesture.velocity(in: view).y
let type = calculatePositionTypeFrom(velocity: velocityY)
move(type: type, animation: true, velocity: velocityY, from: .pan)
default: break
}
}
// MARK: - Calculation Views Size
public func calculationViews() {
calculationView()
calculationScrollViewHeight()
}
private func calculationView() {
guard let view = view else { return }
let x: CGFloat = insetsLeft
let width: CGFloat = (deviceWidth - insetsRight - insetsLeft)
view.frame.origin.x = x
view.frame.size.width = width
view.frame.size.height = deviceHeight * 2
if let headerView = headerView {
headerView.frame.origin.x = 0.0
headerView.frame.origin.y = 0.0
headerView.frame.size.width = width
}
if let footerView = footerView {
footerView.frame.origin.x = x
footerView.frame.size.width = width
changeFooterView()
}
}
// MARK: - Calculation ScrollView Size
private func calculationScrollViewHeight(position: CGFloat = -1.0,
animation: Bool = false,
from: ContainerFromType = .custom,
velocity: CGFloat = 0.0,
moveType: ContainerMoveType = .custom,
moveTypeOld: ContainerMoveType = .custom) {
guard let scrollView = scrollView else { return }
scrollView.layer.removeAllAnimations()
let headerHeight: CGFloat = headerView?.frame.height ?? 0.0
var footerInsets: CGFloat = 0.0
if let footerView = footerView {
footerInsets = deviceHeight - footerView.frame.origin.y
}
var scrollInsetsBottom: CGFloat = ContainerDevice.isIphoneXBottom
if scrollInsetsBottom < footerInsets {
scrollInsetsBottom = 0.0
}
let top: CGFloat = layout.scrollInsets.top
let bottom: CGFloat = layout.scrollInsets.bottom + scrollInsetsBottom
let indicatorTop: CGFloat = layout.scrollIndicatorInsets.top
let indicatorBottom: CGFloat = layout.scrollIndicatorInsets.bottom + scrollInsetsBottom
var containerViewPositionY: CGFloat = 0.0
if position == -1.0 {
containerViewPositionY = view.transform.ty
} else {
containerViewPositionY = position
}
let width: CGFloat = (deviceWidth - insetsRight - insetsLeft)
var height: CGFloat = (deviceHeight - (headerHeight + footerInsets + containerViewPositionY))
if height < 0 {
height = 0
}
if animation ,
!isScrolling,
footerView == nil,
oldPosition < position,
((moveType == .middle) && (0 < velocity)) || (moveType == .bottom) {
height = scrollView.frame.height
}
scrollView.frame = CGRect(x: 0, y: headerHeight, width: width, height: height)
scrollView.scrollIndicatorInsets = UIEdgeInsets(top: indicatorTop , left: 0, bottom: indicatorBottom, right: 0)
scrollView.contentInset = UIEdgeInsets(top: top, left: 0, bottom: bottom, right: 0)
}
// MARK: - Position-Type From Velocity
private func calculatePositionTypeFrom(velocity: CGFloat) -> ContainerMoveType {
var type: ContainerMoveType
let position = view.transform.ty
if middleEnable {
if position < positionTop { /// <<< (70 Top)
if 750 < velocity {
if 2500 < velocity {
type = .bottom ///
} else {
type = .middle ///
}
} else {
type = .top /// Default
}
} else if position > positionBottom { /// (300 Bottom) >>>
if velocity < -750 {
if velocity < -2000 {
type = .top ///
} else {
type = .middle ///
}
} else {
type = .bottom /// Default
}
} else {
let centerMiddleTop = (((positionMiddle - positionTop) / 2) + positionTop)
let centerBottomMiddle = (((positionBottom - positionMiddle) / 2) + positionMiddle)
if position < centerMiddleTop { /// top ...70
if 150 < velocity {
if 2500 < velocity {
type = .bottom ///
} else {
type = .middle ///
}
} else {
type = .top /// Default
}
} else if position < centerBottomMiddle { /// ---
if velocity < 0 {
if velocity < -150 {
type = .top ///
} else {
type = .middle ///
}
} else {
if 150 < velocity {
type = .bottom ///
} else {
type = .middle ///
}
}
} else { ///
if velocity < -150 {
if velocity < -2000 {
type = .top ///
} else {
type = .middle ///
}
} else {
type = .bottom /// Default
}
}
}
} else {
if position < positionTop { /// <<< (70 Top)
if 750 < velocity {
type = .bottom ///
} else {
type = .top /// Default
}
} else if position > positionBottom { /// (300 Bottom) >>>
if velocity < -750 {
type = .top ///
} else {
type = .bottom /// Default
}
} else { /// (pos 150) - Center top...!...bottom
/// (((300 - 70 = 230) / 2 = 115) + 70) = 185
let centerTopBottom = (((positionBottom - positionTop) / 2) + positionTop)
if position < centerTopBottom { ///
if 150 < velocity {
type = .bottom
} else {
type = .top /// Default
}
} else { ///
if velocity < -150 {
type = .top
} else {
type = .bottom /// Default
}
}
}
}
return type
}
// MARK: - Move
public func move(type: ContainerMoveType,
animation: Bool = true,
velocity: CGFloat = 0.0,
from: ContainerFromType = .custom,
completion: (() -> Void)? = nil) {
let position = positionMoveFrom(type: type)
move(position: position,
animation: animation,
type: type,
velocity: velocity,
from: from,
completion: completion)
}
// MARK: - Move Position
public func positionMoveFrom(type: ContainerMoveType) -> CGFloat {
switch type {
case .top: return positionTop
case .middle:
if !middleEnable { return positionBottom }
else { return positionMiddle }
case .bottom: return positionBottom
case .hide: return deviceHeight
case .custom: return 0.0
}
}
// MARK: - Move Animtaion
private var displayVelocity: CGFloat = 0.0
public func move(position: CGFloat,
animation: Bool,
type: ContainerMoveType,
velocity: CGFloat = 0.0,
from: ContainerFromType,
completion: (() -> Void)? = nil) {
if layout.movingEnabled {
scrollView?.isScrollEnabled = (type == .top)
} else {
scrollView?.isScrollEnabled = false
}
displayVelocity = velocity
oldMoveType = moveType
let oldMove = moveType
moveType = type
shadowLevelAlpha(position: position, animation: true)
let transform = CGAffineTransform(translationX: 0, y: position)
let animationComp = { [weak self] in
guard let _self = self else { return }
_self.changeView(transform: transform)
if !_self.layout.trackingPosition {
_self.changeFooterView(position: position)
_self.calculationScrollViewHeight(position: position, animation: animation, from: from, velocity: velocity, moveType: type, moveTypeOld: oldMove)
}
_self.changeMove(position: position, type: type, animation: true)
}
if animation {
animationSpringFrom(force: velocity, type: type, animation: animationComp, completion: completion)
} else {
changeFooterView(position: position)
changeView(transform: transform)
calculationScrollViewHeight(position: position, animation: animation, from: from, velocity: velocity, moveType: type, moveTypeOld: oldMove)
changeMove(position: position, type: type, animation: false)
completion?()
}
}
// MARK: - Tracking Position
@objc func animationDidUpdate(displayLink: CADisplayLink) {
guard layout.trackingPosition else { return }
guard let presentationLayer = self.view.layer.presentation() else { return }
let position = presentationLayer.frame.origin.y
changeFooterView(position: position)
calculationScrollViewHeight(position: position, from: .tracking, velocity: displayVelocity, moveType: moveType, moveTypeOld: oldMoveType)
}
private func changeView(transform: CGAffineTransform) {
oldPosition = view.frame.origin.y
oldTransform = view.transform
view.transform = transform
}
private func changeMove(position: CGFloat,
type: ContainerMoveType,
animation: Bool) {
delegate?.containerControllerMove(self, position: position, type: type, animation: animation)
}
//MARK: - Shadow Alpha Level
func shadowLevelAlpha(position: CGFloat,
animation: Bool) {
if animation {
animationSpring(duration: 0.45) { [weak self] in
guard let _self = self else { return }
_self.shadowLevelAlpha(positionY: position)
}
} else {
shadowLevelAlpha(positionY: position)
}
}
func animationSpring(duration: CGFloat = 0.45, animations: @escaping () -> Void) {
UIView.animate(withDuration: TimeInterval(duration),
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 6.0,
options: [.allowUserInteraction],
animations: animations,
completion: nil)
}
func shadowLevelAlpha(positionY: CGFloat) {
if isPortrait {
shadowButton.isHidden = !layout.backgroundShadowShow
} else {
if let landscapeShadowShow = layout.landscapeBackgroundShadowShow {
shadowButton.isHidden = !landscapeShadowShow
} else {
shadowButton.isHidden = !layout.backgroundShadowShow
}
}
let alphaMax: CGFloat = 0.45
if positionY < positionTop {
shadowButton.alpha = alphaMax
} else if positionY < positionMiddle {
let m = positionMiddle - positionTop
let p = positionY - positionTop
let percent = (1.0 - (p / m))
let result = percent * alphaMax
shadowButton.alpha = result
} else {
shadowButton.alpha = 0.0
}
}
//MARK: - Change FooterView Position
func changeFooterView(position: CGFloat? = nil) {
guard let footer = footerView else { return }
let pos = position ?? positionMoveFrom(type: moveType)
let header = headerView?.frame.height ?? 0.0
let rr = deviceHeight - header - footer.frame.height
let result = ((rr - pos) - layout.footerPadding)
let footerViewPositionDefault = (deviceHeight - footer.frame.height)
if result < 0 {
footer.frame.origin.y = (footerViewPositionDefault + (result * (-1)))
} else {
footer.frame.origin.y = footerViewPositionDefault
}
}
// MARK: - Animation Spring + Force
func animationSpringFrom(force: CGFloat,
type: ContainerMoveType,
animation: @escaping (() -> Void),
completion: (() -> Void)? = nil) {
var velocity: CGFloat = 0
let transformY = view.transform.ty
if type == .top {
if force < 0 {
velocity = force * (-1)
}
} else if type == .bottom {
velocity = force
} else if type == .middle {
if force < 0 {
velocity = force * (-1)
} else {
velocity = force
}
}
velocity = velocity / 10000
velocity = velocity * 300
if type == .top {
if (transformY - positionTop) < 0 {
velocity = velocity * (-1)
}
} else if type == .bottom {
if 0 < (transformY - positionBottom) {
velocity = velocity * (-1)
}
}
var positionY: CGFloat = 0
if type == .top {
positionY = (transformY - positionTop)
} else if type == .bottom {
positionY = (transformY - positionBottom)
} else if type == .middle {
positionY = (transformY - positionMiddle)
} else if type == .hide {
positionY = (transformY - deviceHeight)
}
if positionY < 0 {
positionY = positionY * (-1)
}
var damping: CGFloat = 0.75
var duration: CGFloat = 0.65
let percent = (1.0 - (transformY / deviceHeight))
if 350 < positionY { /// 350...
velocity = (velocity * percent) / 3.5
if 6.5...13.5 ~= velocity {
if velocity < 9.0 {
velocity = 6.5
} else {
velocity = 13.5
}
}
damping = 0.8
duration = 0.45
} else if 200 < positionY { /// 200...350
velocity = (velocity * percent) / 2.0
if 6.5...13.5 ~= velocity {
if velocity < 9.0 {
velocity = 6.5
} else {
velocity = 13.5
}
}
damping = 0.8
duration = 0.45
} else if 150 < positionY { /// 150...200
damping = 0.7
duration = 0.55
velocity = (velocity * percent) / 2.5
if 4.7...8.6 ~= velocity {
if velocity < 6.5 {
velocity = 8.6
} else {
velocity = 4.7
}
}
} else if 100 < positionY { /// 100...150
velocity = (velocity * percent) / 2.0
} else if 50 < positionY { /// 50...100
velocity = velocity / 1.5
} else if 25 < positionY { /// 25...50
// velocity = velocity * 1.5
} else if 10 < positionY { /// 10...25
velocity = velocity * 1.5
} else { /// ...10
velocity = velocity * 3.0
}
if duration == 0.65, damping == 0.75 {
if 4.3...7.4 ~= velocity {
if velocity < 5.85 {
velocity = 7.4
} else {
velocity = 4.3
}
}
}
var displayLink: CADisplayLink?
if layout.trackingPosition {
displayLink = CADisplayLink(target: self, selector: #selector(animationDidUpdate))
displayLink?.preferredFramesPerSecond = 60
displayLink?.add(to: .main, forMode: .default)
}
UIView.animate( withDuration: TimeInterval(duration),
delay: 0.0,
usingSpringWithDamping: damping,
initialSpringVelocity: velocity,
options: [ .allowUserInteraction ],
animations: animation,
completion: { _ in
if let displayLink = displayLink {
displayLink.invalidate()
}
completion?()
})
}
}
// MARK: - Gesture Delegate
extension ContainerController: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
}
// MARK: - Table Delegate
extension ContainerController: UITableViewDelegate {
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let tableAdapterView = scrollView as? TableAdapterView {
return tableAdapterView.tableView(tableView, heightForRowAt: indexPath)
}
return 0
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let tableAdapterView = scrollView as? TableAdapterView {
return tableAdapterView.tableView(tableView, didSelectRowAt: indexPath)
}
}
}
// MARK: - Table DataSource
extension ContainerController: UITableViewDataSource {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let tableAdapterView = scrollView as? TableAdapterView {
return tableAdapterView.tableView(tableView, numberOfRowsInSection: section)
}
return 0
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let tableAdapterView = scrollView as? TableAdapterView {
return tableAdapterView.tableView(tableView, cellForRowAt: indexPath)
}
return UITableViewCell()
}
}
// MARK: - Collection Delegate
extension ContainerController: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let collectionAdapterView = scrollView as? CollectionAdapterView {
collectionAdapterView.collectionView(collectionView, didSelectItemAt: indexPath)
}
}
}
// MARK: - Collection DataSource
extension ContainerController: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let collectionAdapterView = scrollView as? CollectionAdapterView {
return collectionAdapterView.collectionView(collectionView, numberOfItemsInSection: section)
}
return 0
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let collectionAdapterView = scrollView as? CollectionAdapterView {
return collectionAdapterView.collectionView(collectionView, cellForItemAt: indexPath)
}
return UICollectionViewCell()
}
}
// MARK: - Collection DelegateFlowLayout
extension ContainerController: UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let collectionAdapterView = scrollView as? CollectionAdapterView {
return collectionAdapterView.collectionView(collectionView, layout: collectionViewLayout, sizeForItemAt: indexPath)
}
return CGSize.zero
}
}
// MARK: - Scroll Delegate
extension ContainerController: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let tableAdapterView = scrollView as? TableAdapterView {
tableAdapterView.scrollViewDidScroll(tableAdapterView)
}
let gesture: UIPanGestureRecognizer = scrollView.panGestureRecognizer
let inViewVelocityY: CGFloat = gesture.velocity(in: controller?.view).y
let inViewTranslationY: CGFloat = gesture.translation(in: controller?.view).y
if gesture.state != .possible, scrollView.contentOffset.y <= 0 {
scrollView.showsVerticalScrollIndicator = false
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0)
} else {
scrollView.showsVerticalScrollIndicator = true
}
if scrollView.contentOffset.y == 0, 0 < inViewVelocityY {
scrollBordersRunContainer = true
} else {
scrollBordersRunContainer = false
}
scrollTransform = view.transform
let top: CGFloat = positionTop
if gesture.state == .ended {
scrollOnceBeginDragging = false
}
if scrollBordersRunContainer {
view.layer.removeAllAnimations()
scrollOnceEnded = false
scrollOnceBeginDragging = false
scrollTransform.ty = ((top - scrollStartPosition) + inViewTranslationY)
if scrollTransform.ty < top {
scrollTransform.ty = top
}
if scrollBegin {
animationSpring(duration: 0.325) { [weak self] in
guard let _self = self else { return }
_self.changeView(transform: _self.scrollTransform)
}
scrollBegin = false
} else {
changeView(transform: scrollTransform)
}
let position = scrollTransform.ty
let type: ContainerMoveType = .top
let from: ContainerFromType = .scrollBorder
let animation = false
shadowLevelAlpha(position: position, animation: false)
changeFooterView(position: position)
calculationScrollViewHeight(from: from)
changeMove(position: position, type: type, animation: animation)
if gesture.state == .ended {
move(type: moveType, animation: true, velocity: inViewVelocityY, from: from)
}
} else {
if top == scrollTransform.ty, !scrollOnceBeginDragging {
scrollOnceBeginDragging = true
}
if top < scrollTransform.ty {
if inViewVelocityY < 0.0 {
if moveType == .top {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0)
}
scrollTransform = view.transform
scrollTransform.ty = (top - scrollStartPosition) + inViewTranslationY
if scrollTransform.ty < top {
scrollTransform.ty = top
}
let position = scrollTransform.ty
let type: ContainerMoveType = .top
let from: ContainerFromType = .scroll
let animation = false
changeView(transform: scrollTransform)
shadowLevelAlpha(position: position, animation: false)
changeFooterView(position: position)
calculationScrollViewHeight(from: from)
changeMove(position: position, type: type, animation: animation)
}
}
}
}
// MARK: - Scroll Begin/End Dragging
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isScrolling = true
scrollStartPosition = scrollView.contentOffset.y
scrollBegin = true
if scrollStartPosition < 0 {
scrollStartPosition = 0.0
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
isScrolling = false
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
isScrolling = false
}
let gesture: UIPanGestureRecognizer = scrollView.panGestureRecognizer
let inViewVelocityY: CGFloat = gesture.velocity(in: controller?.view).y
if !scrollOnceEnded {
scrollOnceEnded = true
let type = calculatePositionTypeFrom(velocity: inViewVelocityY)
move(type: type, velocity: inViewVelocityY)
}
}
}