254 lines
8.7 KiB
Swift
254 lines
8.7 KiB
Swift
//
|
||
// PageContentView.swift
|
||
// DNSPageView
|
||
//
|
||
// Created by Daniels on 2018/2/24.
|
||
// Copyright © 2018 Daniels. All rights reserved.
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
// of this software and associated documentation files (the "Software"), to deal
|
||
// in the Software without restriction, including without limitation the rights
|
||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the Software is
|
||
// furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included in
|
||
// all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
// THE SOFTWARE.
|
||
//
|
||
|
||
import UIKit
|
||
|
||
public protocol PageContentViewDelegate: AnyObject {
|
||
func contentView(_ contentView: PageContentView, didEndScrollAt index: Int)
|
||
func contentView(_ contentView: PageContentView, scrollingWith sourceIndex: Int, targetIndex: Int, progress: CGFloat)
|
||
}
|
||
|
||
|
||
private let CellID = "CellID"
|
||
public class PageContentView: UIView {
|
||
|
||
public weak var delegate: PageContentViewDelegate?
|
||
|
||
public weak var container: PageViewContainer?
|
||
|
||
public weak var eventHandler: PageEventHandleable?
|
||
|
||
private (set) public var style: PageStyle = PageStyle() {
|
||
didSet {
|
||
collectionView.semanticContentAttribute = style.isRTL ? .forceRightToLeft : .forceLeftToRight
|
||
}
|
||
}
|
||
|
||
private (set) public var childViewControllers : [UIViewController] = [UIViewController]()
|
||
|
||
/// 初始化后,默认显示的页数
|
||
private (set) public var currentIndex: Int {
|
||
didSet {
|
||
guard delegate == nil else { return }
|
||
container?.updateCurrentIndex(currentIndex)
|
||
}
|
||
}
|
||
|
||
private var startOffsetX: CGFloat = 0
|
||
|
||
private var isForbidDelegate: Bool = false
|
||
|
||
private (set) public lazy var collectionView: UICollectionView = {
|
||
let layout = PageCollectionViewFlowLayout()
|
||
layout.minimumLineSpacing = 0
|
||
layout.minimumInteritemSpacing = 0
|
||
layout.scrollDirection = .horizontal
|
||
|
||
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||
collectionView.showsHorizontalScrollIndicator = false
|
||
collectionView.isPagingEnabled = true
|
||
collectionView.scrollsToTop = false
|
||
collectionView.dataSource = self
|
||
collectionView.delegate = self
|
||
collectionView.bounces = false
|
||
if #available(iOS 10, *) {
|
||
collectionView.isPrefetchingEnabled = false
|
||
}
|
||
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: CellID)
|
||
return collectionView
|
||
}()
|
||
|
||
|
||
public init(frame: CGRect, style: PageStyle, childViewControllers: [UIViewController], currentIndex: Int = 0) {
|
||
assert(currentIndex >= 0 && currentIndex < childViewControllers.count,
|
||
"currentIndex < 0 or currentIndex >= childViewControllers.count")
|
||
self.currentIndex = currentIndex
|
||
super.init(frame: frame)
|
||
addSubview(collectionView)
|
||
configure(childViewControllers: childViewControllers, style: style, currentIndex: currentIndex)
|
||
}
|
||
|
||
required public init?(coder aDecoder: NSCoder) {
|
||
self.currentIndex = 0
|
||
super.init(coder: aDecoder)
|
||
addSubview(collectionView)
|
||
}
|
||
|
||
public override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
collectionView.frame = CGRect(origin: CGPoint.zero, size: frame.size)
|
||
let layout = collectionView.collectionViewLayout as! PageCollectionViewFlowLayout
|
||
layout.itemSize = frame.size
|
||
layout.offset = CGFloat(currentIndex) * frame.size.width
|
||
layout.invalidateLayout()
|
||
}
|
||
}
|
||
|
||
|
||
extension PageContentView {
|
||
internal func configure(childViewControllers: [UIViewController]? = nil, style: PageStyle? = nil, currentIndex: Int? = nil) {
|
||
if let childViewControllers = childViewControllers {
|
||
self.childViewControllers = childViewControllers
|
||
}
|
||
if let style = style {
|
||
self.style = style
|
||
}
|
||
if let currentIndex = currentIndex {
|
||
collectionView.collectionViewLayout.invalidateLayout()
|
||
self.currentIndex = currentIndex
|
||
}
|
||
configureSubViews()
|
||
collectionView.reloadData()
|
||
setNeedsLayout()
|
||
}
|
||
|
||
private func configureSubViews() {
|
||
collectionView.backgroundColor = style.contentViewBackgroundColor
|
||
collectionView.isScrollEnabled = style.isContentScrollEnabled
|
||
}
|
||
}
|
||
|
||
|
||
extension PageContentView: UICollectionViewDataSource {
|
||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||
return childViewControllers.count
|
||
}
|
||
|
||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellID, for: indexPath)
|
||
|
||
for subview in cell.contentView.subviews {
|
||
subview.removeFromSuperview()
|
||
}
|
||
let childViewController = childViewControllers[indexPath.item]
|
||
|
||
eventHandler = childViewController as? PageEventHandleable
|
||
childViewController.view.frame = CGRect(origin: CGPoint.zero, size: cell.contentView.frame.size)
|
||
|
||
cell.contentView.addSubview(childViewController.view)
|
||
|
||
return cell
|
||
}
|
||
}
|
||
|
||
|
||
extension PageContentView: UICollectionViewDelegate {
|
||
|
||
|
||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||
isForbidDelegate = false
|
||
startOffsetX = scrollView.contentOffset.x
|
||
|
||
}
|
||
|
||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||
updateUI(scrollView)
|
||
}
|
||
|
||
|
||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||
if !decelerate {
|
||
collectionViewDidEndScroll(scrollView)
|
||
}
|
||
}
|
||
|
||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||
collectionViewDidEndScroll(scrollView)
|
||
}
|
||
|
||
|
||
private func collectionViewDidEndScroll(_ scrollView: UIScrollView) {
|
||
|
||
let index = Int(round(scrollView.contentOffset.x / scrollView.frame.width))
|
||
|
||
delegate?.contentView(self, didEndScrollAt: index)
|
||
|
||
if index != currentIndex {
|
||
let childViewController = childViewControllers[currentIndex]
|
||
(childViewController as? PageEventHandleable)?.contentViewDidDisappear()
|
||
}
|
||
|
||
currentIndex = index
|
||
|
||
eventHandler = childViewControllers[currentIndex] as? PageEventHandleable
|
||
|
||
eventHandler?.contentViewDidEndScroll()
|
||
|
||
}
|
||
|
||
|
||
|
||
private func updateUI(_ scrollView: UIScrollView) {
|
||
if isForbidDelegate {
|
||
return
|
||
}
|
||
|
||
var progress: CGFloat = 0
|
||
var targetIndex = 0
|
||
var sourceIndex = 0
|
||
|
||
|
||
progress = scrollView.contentOffset.x.truncatingRemainder(dividingBy: scrollView.frame.width) / scrollView.frame.width
|
||
if progress == 0 || progress.isNaN {
|
||
return
|
||
}
|
||
let index = Int(scrollView.contentOffset.x / scrollView.frame.width)
|
||
if collectionView.contentOffset.x > startOffsetX { // 左滑动
|
||
sourceIndex = index
|
||
targetIndex = index + 1
|
||
} else {
|
||
sourceIndex = index + 1
|
||
targetIndex = index
|
||
progress = 1 - progress
|
||
}
|
||
guard targetIndex < childViewControllers.count && targetIndex >= 0 else { return }
|
||
|
||
|
||
if progress > 0.998 {
|
||
progress = 1
|
||
}
|
||
|
||
delegate?.contentView(self, scrollingWith: sourceIndex, targetIndex: targetIndex, progress: progress)
|
||
}
|
||
}
|
||
|
||
|
||
extension PageContentView: PageTitleViewDelegate {
|
||
public func titleView(_ titleView: PageTitleView, didSelectAt index: Int) {
|
||
isForbidDelegate = true
|
||
|
||
guard currentIndex < childViewControllers.count else { return }
|
||
|
||
currentIndex = index
|
||
|
||
let indexPath = IndexPath(item: index, section: 0)
|
||
|
||
collectionView.scrollToItem(at: indexPath, at: .left, animated: false)
|
||
}
|
||
}
|
||
|
||
|