Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d60e40f7 | ||
|
|
938a7a13c0 | ||
|
|
8c28b92a17 | ||
|
|
2c2f770889 | ||
|
|
019a239ee3 |
@@ -0,0 +1 @@
|
||||
4.0
|
||||
+9
-2
@@ -1,6 +1,13 @@
|
||||
osx_image: xcode8
|
||||
osx_image: xcode9.2
|
||||
language: objective-c
|
||||
|
||||
xcode_project: DemoExpandingCollection/DemoExpandingCollection.xcodeproj
|
||||
xcode_scheme: ExpandingCollection
|
||||
xcode_sdk: iphonesimulator
|
||||
xcode_sdk: iphonesimulator11.2
|
||||
|
||||
# SWIFT_VERSION: 4.0
|
||||
|
||||
# whitelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
@@ -11,30 +11,29 @@ import UIKit
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
configureNavigationTabBar()
|
||||
return true
|
||||
}
|
||||
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
configureNavigationTabBar()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
fileprivate func configureNavigationTabBar() {
|
||||
//transparent background
|
||||
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
|
||||
UINavigationBar.appearance().shadowImage = UIImage()
|
||||
UINavigationBar.appearance().isTranslucent = true
|
||||
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowOffset = CGSize(width: 0, height: 2)
|
||||
shadow.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1)
|
||||
|
||||
UINavigationBar.appearance().titleTextAttributes = [
|
||||
NSAttributedStringKey.foregroundColor : UIColor.white,
|
||||
NSAttributedStringKey.shadow: shadow
|
||||
]
|
||||
}
|
||||
|
||||
fileprivate func configureNavigationTabBar() {
|
||||
//transparent background
|
||||
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
|
||||
UINavigationBar.appearance().shadowImage = UIImage()
|
||||
UINavigationBar.appearance().isTranslucent = true
|
||||
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowOffset = CGSize(width: 0, height: 2)
|
||||
shadow.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1)
|
||||
|
||||
UINavigationBar.appearance().titleTextAttributes = [
|
||||
NSAttributedStringKey.foregroundColor: UIColor.white,
|
||||
NSAttributedStringKey.shadow: shadow,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
@@ -13,15 +19,35 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2018 Ramotion. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fcf-sN-ku0">
|
||||
<rect key="frame" x="16" y="630" width="343" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Expanding Collection" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bCd-3b-lHA">
|
||||
<rect key="frame" x="101.5" y="323" width="173" height="21"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="fcf-sN-ku0" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leadingMargin" id="811-hI-Egq"/>
|
||||
<constraint firstItem="bCd-3b-lHA" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="T4x-O3-JqD"/>
|
||||
<constraint firstItem="bCd-3b-lHA" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="VFr-tp-sPw"/>
|
||||
<constraint firstItem="fcf-sN-ku0" firstAttribute="trailing" secondItem="Ze5-6b-2t3" secondAttribute="trailingMargin" id="dS9-pv-ebe"/>
|
||||
<constraint firstItem="xb3-aO-Qok" firstAttribute="top" secondItem="fcf-sN-ku0" secondAttribute="bottom" constant="20" id="kbD-dX-t3N"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
<point key="canvasLocation" x="52" y="374.66266866566718"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
import UIKit.UIImage
|
||||
typealias Image = UIImage
|
||||
import UIKit.UIImage
|
||||
typealias Image = UIImage
|
||||
#elseif os(OSX)
|
||||
import AppKit.NSImage
|
||||
typealias Image = NSImage
|
||||
import AppKit.NSImage
|
||||
typealias Image = NSImage
|
||||
#endif
|
||||
|
||||
// swiftlint:disable file_length
|
||||
@@ -13,47 +13,48 @@
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
enum Asset: String {
|
||||
case backgroundImage = "BackgroundImage"
|
||||
case closeButton = "CloseButton"
|
||||
case dots = "dots"
|
||||
case face1 = "face1"
|
||||
case face2 = "face2"
|
||||
case heand = "heand"
|
||||
case icons = "icons"
|
||||
case image = "image"
|
||||
case item0 = "item0"
|
||||
case item1 = "item1"
|
||||
case item2 = "item2"
|
||||
case item3 = "item3"
|
||||
case locationButton = "locationButton"
|
||||
case map = "map"
|
||||
case pinIcon = "pinIcon"
|
||||
case searchIcon = "searchIcon"
|
||||
case stars = "stars"
|
||||
case title = "Title"
|
||||
case backgroundImage = "BackgroundImage"
|
||||
case closeButton = "CloseButton"
|
||||
case dots
|
||||
case face1
|
||||
case face2
|
||||
case heand
|
||||
case icons
|
||||
case image
|
||||
case item0
|
||||
case item1
|
||||
case item2
|
||||
case item3
|
||||
case locationButton
|
||||
case map
|
||||
case pinIcon
|
||||
case searchIcon
|
||||
case stars
|
||||
case title = "Title"
|
||||
|
||||
var image: Image {
|
||||
let bundle = Bundle(for: BundleToken.self)
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
let image = Image(named: rawValue, in: bundle, compatibleWith: nil)
|
||||
#elseif os(OSX)
|
||||
let image = bundle.image(forResource: rawValue)
|
||||
#endif
|
||||
guard let result = image else { fatalError("Unable to load image \(rawValue).") }
|
||||
return result
|
||||
}
|
||||
var image: Image {
|
||||
let bundle = Bundle(for: BundleToken.self)
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
let image = Image(named: rawValue, in: bundle, compatibleWith: nil)
|
||||
#elseif os(OSX)
|
||||
let image = bundle.image(forResource: rawValue)
|
||||
#endif
|
||||
guard let result = image else { fatalError("Unable to load image \(rawValue).") }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:enable type_body_length
|
||||
|
||||
extension Image {
|
||||
convenience init!(asset: Asset) {
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
let bundle = Bundle(for: BundleToken.self)
|
||||
self.init(named: asset.rawValue, in: bundle, compatibleWith: nil)
|
||||
#elseif os(OSX)
|
||||
self.init(named: asset.rawValue)
|
||||
#endif
|
||||
}
|
||||
convenience init!(asset: Asset) {
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
let bundle = Bundle(for: BundleToken.self)
|
||||
self.init(named: asset.rawValue, in: bundle, compatibleWith: nil)
|
||||
#elseif os(OSX)
|
||||
self.init(named: asset.rawValue)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private final class BundleToken {}
|
||||
|
||||
+11
-11
@@ -10,25 +10,25 @@ import UIKit
|
||||
|
||||
extension UICollectionView {
|
||||
|
||||
func getReusableCellWithIdentifier<T: UICollectionViewCell>(_ indexPath: IndexPath) -> T {
|
||||
guard let cell = dequeueReusableCell(withReuseIdentifier: T.cellIdentifier, for: indexPath) as? T else {
|
||||
fatalError("Couldn't instantiate view controller with identifier \(T.cellIdentifier) ")
|
||||
func getReusableCellWithIdentifier<T: UICollectionViewCell>(_ indexPath: IndexPath) -> T {
|
||||
guard let cell = dequeueReusableCell(withReuseIdentifier: T.cellIdentifier, for: indexPath) as? T else {
|
||||
fatalError("Couldn't instantiate view controller with identifier \(T.cellIdentifier) ")
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UITableViewCell
|
||||
|
||||
protocol CollectionViewCellIdentifiable {
|
||||
static var cellIdentifier: String { get }
|
||||
static var cellIdentifier: String { get }
|
||||
}
|
||||
|
||||
extension CollectionViewCellIdentifiable where Self: UICollectionViewCell {
|
||||
static var cellIdentifier: String {
|
||||
return String(describing: self)
|
||||
}
|
||||
static var cellIdentifier: String {
|
||||
return String(describing: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension UICollectionViewCell : CollectionViewCellIdentifiable { }
|
||||
extension UICollectionViewCell: CollectionViewCellIdentifiable {}
|
||||
|
||||
+24
-24
@@ -9,38 +9,38 @@
|
||||
import UIKit
|
||||
|
||||
extension UIStoryboard {
|
||||
|
||||
enum Storyboard : String {
|
||||
case Main
|
||||
}
|
||||
|
||||
convenience init(storyboard: Storyboard, bundle: Bundle? = nil) {
|
||||
self.init(name: storyboard.rawValue, bundle: bundle)
|
||||
}
|
||||
|
||||
class func storyboard(_ storyboard: Storyboard, bundle: Bundle? = nil) -> UIStoryboard {
|
||||
return UIStoryboard(name: storyboard.rawValue, bundle: bundle)
|
||||
}
|
||||
|
||||
func instantiateViewController<T: UIViewController>() -> T {
|
||||
guard let viewController = self.instantiateViewController(withIdentifier: T.storyboardIdentifier) as? T else {
|
||||
fatalError("Couldn't instantiate view controller with identifier \(T.storyboardIdentifier) ")
|
||||
|
||||
enum Storyboard: String {
|
||||
case Main
|
||||
}
|
||||
|
||||
convenience init(storyboard: Storyboard, bundle: Bundle? = nil) {
|
||||
self.init(name: storyboard.rawValue, bundle: bundle)
|
||||
}
|
||||
|
||||
class func storyboard(_ storyboard: Storyboard, bundle: Bundle? = nil) -> UIStoryboard {
|
||||
return UIStoryboard(name: storyboard.rawValue, bundle: bundle)
|
||||
}
|
||||
|
||||
func instantiateViewController<T: UIViewController>() -> T {
|
||||
guard let viewController = self.instantiateViewController(withIdentifier: T.storyboardIdentifier) as? T else {
|
||||
fatalError("Couldn't instantiate view controller with identifier \(T.storyboardIdentifier) ")
|
||||
}
|
||||
|
||||
return viewController
|
||||
}
|
||||
|
||||
return viewController
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController : StoryboardIdentifiable { }
|
||||
extension UIViewController: StoryboardIdentifiable {}
|
||||
|
||||
// MARK: identifiable
|
||||
|
||||
protocol StoryboardIdentifiable {
|
||||
static var storyboardIdentifier: String { get }
|
||||
static var storyboardIdentifier: String { get }
|
||||
}
|
||||
|
||||
extension StoryboardIdentifiable where Self: UIViewController {
|
||||
static var storyboardIdentifier: String {
|
||||
return String(describing: self)
|
||||
}
|
||||
static var storyboardIdentifier: String {
|
||||
return String(describing: self)
|
||||
}
|
||||
}
|
||||
|
||||
+35
-33
@@ -9,53 +9,55 @@
|
||||
import UIKit
|
||||
|
||||
class DemoTableViewController: ExpandingTableViewController {
|
||||
|
||||
fileprivate var scrollOffsetY: CGFloat = 0
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
configureNavBar()
|
||||
let image1 = Asset.backgroundImage.image
|
||||
tableView.backgroundView = UIImageView(image: image1)
|
||||
if #available(iOS 11.0, *) {
|
||||
tableView.contentInsetAdjustmentBehavior = .never
|
||||
|
||||
fileprivate var scrollOffsetY: CGFloat = 0
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
configureNavBar()
|
||||
let image1 = Asset.backgroundImage.image
|
||||
tableView.backgroundView = UIImageView(image: image1)
|
||||
if #available(iOS 11.0, *) {
|
||||
tableView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension DemoTableViewController {
|
||||
|
||||
fileprivate func configureNavBar() {
|
||||
navigationItem.leftBarButtonItem?.image = navigationItem.leftBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
navigationItem.rightBarButtonItem?.image = navigationItem.rightBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
}
|
||||
|
||||
|
||||
fileprivate func configureNavBar() {
|
||||
navigationItem.leftBarButtonItem?.image = navigationItem.leftBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
navigationItem.rightBarButtonItem?.image = navigationItem.rightBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
extension DemoTableViewController {
|
||||
|
||||
@IBAction func backButtonHandler(_ sender: AnyObject) {
|
||||
// buttonAnimation
|
||||
let viewControllers: [DemoViewController?] = navigationController?.viewControllers.map { $0 as? DemoViewController } ?? []
|
||||
|
||||
for viewController in viewControllers {
|
||||
if let rightButton = viewController?.navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(false)
|
||||
}
|
||||
extension DemoTableViewController {
|
||||
|
||||
@IBAction func backButtonHandler(_: AnyObject) {
|
||||
// buttonAnimation
|
||||
let viewControllers: [DemoViewController?] = navigationController?.viewControllers.map { $0 as? DemoViewController } ?? []
|
||||
|
||||
for viewController in viewControllers {
|
||||
if let rightButton = viewController?.navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(false)
|
||||
}
|
||||
}
|
||||
popTransitionAnimation()
|
||||
}
|
||||
popTransitionAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UIScrollViewDelegate
|
||||
|
||||
extension DemoTableViewController {
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
// if scrollView.contentOffset.y < -25 {
|
||||
// // buttonAnimation
|
||||
// let viewControllers: [DemoViewController?] = navigationController?.viewControllers.map { $0 as? DemoViewController } ?? []
|
||||
//
|
||||
//
|
||||
// for viewController in viewControllers {
|
||||
// if let rightButton = viewController?.navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
// rightButton.animationSelected(false)
|
||||
@@ -63,7 +65,7 @@ extension DemoTableViewController {
|
||||
// }
|
||||
// popTransitionAnimation()
|
||||
// }
|
||||
|
||||
scrollOffsetY = scrollView.contentOffset.y
|
||||
}
|
||||
|
||||
scrollOffsetY = scrollView.contentOffset.y
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -9,15 +9,15 @@
|
||||
import UIKit
|
||||
|
||||
class DemoCollectionViewCell: BasePageCollectionCell {
|
||||
|
||||
@IBOutlet weak var backgroundImageView: UIImageView!
|
||||
@IBOutlet weak var customTitle: UILabel!
|
||||
|
||||
|
||||
@IBOutlet var backgroundImageView: UIImageView!
|
||||
@IBOutlet var customTitle: UILabel!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
customTitle.layer.shadowRadius = 2
|
||||
customTitle.layer.shadowOffset = CGSize(width: 0, height: 3)
|
||||
customTitle.layer.shadowOpacity = 0.2
|
||||
|
||||
customTitle.layer.shadowRadius = 2
|
||||
customTitle.layer.shadowOffset = CGSize(width: 0, height: 3)
|
||||
customTitle.layer.shadowOpacity = 0.2
|
||||
}
|
||||
}
|
||||
|
||||
+108
-108
@@ -9,136 +9,136 @@
|
||||
import UIKit
|
||||
|
||||
class DemoViewController: ExpandingViewController {
|
||||
|
||||
typealias ItemInfo = (imageName: String, title: String)
|
||||
fileprivate var cellsIsOpen = [Bool]()
|
||||
fileprivate let items: [ItemInfo] = [("item0", "Boston"),("item1", "New York"),("item2", "San Francisco"),("item3", "Washington")]
|
||||
|
||||
@IBOutlet weak var pageLabel: UILabel!
|
||||
|
||||
typealias ItemInfo = (imageName: String, title: String)
|
||||
fileprivate var cellsIsOpen = [Bool]()
|
||||
fileprivate let items: [ItemInfo] = [("item0", "Boston"), ("item1", "New York"), ("item2", "San Francisco"), ("item3", "Washington")]
|
||||
|
||||
@IBOutlet var pageLabel: UILabel!
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle 🌎
|
||||
|
||||
extension DemoViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
itemSize = CGSize(width: 256, height: 335)
|
||||
super.viewDidLoad()
|
||||
|
||||
registerCell()
|
||||
fillCellIsOpenArray()
|
||||
addGesture(to: collectionView!)
|
||||
configureNavBar()
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
itemSize = CGSize(width: 256, height: 335)
|
||||
super.viewDidLoad()
|
||||
|
||||
registerCell()
|
||||
fillCellIsOpenArray()
|
||||
addGesture(to: collectionView!)
|
||||
configureNavBar()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension DemoViewController {
|
||||
|
||||
fileprivate func registerCell() {
|
||||
|
||||
let nib = UINib(nibName: String(describing: DemoCollectionViewCell.self), bundle: nil)
|
||||
collectionView?.register(nib, forCellWithReuseIdentifier: String(describing: DemoCollectionViewCell.self))
|
||||
}
|
||||
|
||||
fileprivate func fillCellIsOpenArray() {
|
||||
cellsIsOpen = Array(repeating: false, count: items.count)
|
||||
}
|
||||
|
||||
fileprivate func getViewController() -> ExpandingTableViewController {
|
||||
let storyboard = UIStoryboard(storyboard: .Main)
|
||||
let toViewController: DemoTableViewController = storyboard.instantiateViewController()
|
||||
return toViewController
|
||||
}
|
||||
|
||||
fileprivate func configureNavBar() {
|
||||
navigationItem.leftBarButtonItem?.image = navigationItem.leftBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
}
|
||||
|
||||
|
||||
fileprivate func registerCell() {
|
||||
|
||||
let nib = UINib(nibName: String(describing: DemoCollectionViewCell.self), bundle: nil)
|
||||
collectionView?.register(nib, forCellWithReuseIdentifier: String(describing: DemoCollectionViewCell.self))
|
||||
}
|
||||
|
||||
fileprivate func fillCellIsOpenArray() {
|
||||
cellsIsOpen = Array(repeating: false, count: items.count)
|
||||
}
|
||||
|
||||
fileprivate func getViewController() -> ExpandingTableViewController {
|
||||
let storyboard = UIStoryboard(storyboard: .Main)
|
||||
let toViewController: DemoTableViewController = storyboard.instantiateViewController()
|
||||
return toViewController
|
||||
}
|
||||
|
||||
fileprivate func configureNavBar() {
|
||||
navigationItem.leftBarButtonItem?.image = navigationItem.leftBarButtonItem?.image!.withRenderingMode(UIImageRenderingMode.alwaysOriginal)
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: Gesture
|
||||
extension DemoViewController {
|
||||
|
||||
fileprivate func addGesture(to view: UIView) {
|
||||
let upGesture = Init(UISwipeGestureRecognizer(target: self, action: #selector(DemoViewController.swipeHandler(_:)))) {
|
||||
$0.direction = .up
|
||||
}
|
||||
|
||||
let downGesture = Init(UISwipeGestureRecognizer(target: self, action: #selector(DemoViewController.swipeHandler(_:)))) {
|
||||
$0.direction = .down
|
||||
}
|
||||
view.addGestureRecognizer(upGesture)
|
||||
view.addGestureRecognizer(downGesture)
|
||||
}
|
||||
|
||||
@objc func swipeHandler(_ sender: UISwipeGestureRecognizer) {
|
||||
let indexPath = IndexPath(row: currentIndex, section: 0)
|
||||
guard let cell = collectionView?.cellForItem(at: indexPath) as? DemoCollectionViewCell else { return }
|
||||
// double swipe Up transition
|
||||
if cell.isOpened == true && sender.direction == .up {
|
||||
pushToViewController(getViewController())
|
||||
|
||||
if let rightButton = navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(true)
|
||||
}
|
||||
fileprivate func addGesture(to view: UIView) {
|
||||
let upGesture = Init(UISwipeGestureRecognizer(target: self, action: #selector(DemoViewController.swipeHandler(_:)))) {
|
||||
$0.direction = .up
|
||||
}
|
||||
|
||||
let downGesture = Init(UISwipeGestureRecognizer(target: self, action: #selector(DemoViewController.swipeHandler(_:)))) {
|
||||
$0.direction = .down
|
||||
}
|
||||
view.addGestureRecognizer(upGesture)
|
||||
view.addGestureRecognizer(downGesture)
|
||||
}
|
||||
|
||||
@objc func swipeHandler(_ sender: UISwipeGestureRecognizer) {
|
||||
let indexPath = IndexPath(row: currentIndex, section: 0)
|
||||
guard let cell = collectionView?.cellForItem(at: indexPath) as? DemoCollectionViewCell else { return }
|
||||
// double swipe Up transition
|
||||
if cell.isOpened == true && sender.direction == .up {
|
||||
pushToViewController(getViewController())
|
||||
|
||||
if let rightButton = navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(true)
|
||||
}
|
||||
}
|
||||
|
||||
let open = sender.direction == .up ? true : false
|
||||
cell.cellIsOpen(open)
|
||||
cellsIsOpen[indexPath.row] = cell.isOpened
|
||||
}
|
||||
|
||||
let open = sender.direction == .up ? true : false
|
||||
cell.cellIsOpen(open)
|
||||
cellsIsOpen[indexPath.row] = cell.isOpened
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: UIScrollViewDelegate
|
||||
|
||||
extension DemoViewController {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
pageLabel.text = "\(currentIndex+1)/\(items.count)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewDataSource
|
||||
extension DemoViewController {
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
super.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath)
|
||||
guard let cell = cell as? DemoCollectionViewCell else { return }
|
||||
|
||||
let index = indexPath.row % items.count
|
||||
let info = items[index]
|
||||
cell.backgroundImageView?.image = UIImage(named: info.imageName)
|
||||
cell.customTitle.text = info.title
|
||||
cell.cellIsOpen(cellsIsOpen[index], animated: false)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
|
||||
guard let cell = collectionView.cellForItem(at: indexPath) as? DemoCollectionViewCell
|
||||
, currentIndex == indexPath.row else { return }
|
||||
|
||||
if cell.isOpened == false {
|
||||
cell.cellIsOpen(true)
|
||||
} else {
|
||||
pushToViewController(getViewController())
|
||||
|
||||
if let rightButton = navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(true)
|
||||
}
|
||||
func scrollViewDidScroll(_: UIScrollView) {
|
||||
pageLabel.text = "\(currentIndex + 1)/\(items.count)"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewDataSource
|
||||
|
||||
extension DemoViewController {
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
return collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: DemoCollectionViewCell.self), for: indexPath)
|
||||
}
|
||||
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
super.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath)
|
||||
guard let cell = cell as? DemoCollectionViewCell else { return }
|
||||
|
||||
let index = indexPath.row % items.count
|
||||
let info = items[index]
|
||||
cell.backgroundImageView?.image = UIImage(named: info.imageName)
|
||||
cell.customTitle.text = info.title
|
||||
cell.cellIsOpen(cellsIsOpen[index], animated: false)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
|
||||
guard let cell = collectionView.cellForItem(at: indexPath) as? DemoCollectionViewCell
|
||||
, currentIndex == indexPath.row else { return }
|
||||
|
||||
if cell.isOpened == false {
|
||||
cell.cellIsOpen(true)
|
||||
} else {
|
||||
pushToViewController(getViewController())
|
||||
|
||||
if let rightButton = navigationItem.rightBarButtonItem as? AnimatingBarButton {
|
||||
rightButton.animationSelected(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewDataSource
|
||||
|
||||
extension DemoViewController {
|
||||
|
||||
override func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
return collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: DemoCollectionViewCell.self), for: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
+49
-49
@@ -9,8 +9,8 @@
|
||||
import UIKit
|
||||
|
||||
fileprivate final class CustomView: UIView {
|
||||
var onLayoutSubviews: () -> Void = { }
|
||||
|
||||
var onLayoutSubviews: () -> Void = {}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
onLayoutSubviews()
|
||||
@@ -18,68 +18,68 @@ fileprivate final class CustomView: UIView {
|
||||
}
|
||||
|
||||
class AnimatingBarButton: UIBarButtonItem, Rotatable {
|
||||
|
||||
@IBInspectable var normalImageName: String = ""
|
||||
@IBInspectable var selectedImageName: String = ""
|
||||
|
||||
@IBInspectable var duration: Double = 1
|
||||
|
||||
let normalView = UIImageView(frame: .zero)
|
||||
let selectedView = UIImageView(frame: .zero)
|
||||
|
||||
@IBInspectable var normalImageName: String = ""
|
||||
@IBInspectable var selectedImageName: String = ""
|
||||
|
||||
@IBInspectable var duration: Double = 1
|
||||
|
||||
let normalView = UIImageView(frame: .zero)
|
||||
let selectedView = UIImageView(frame: .zero)
|
||||
}
|
||||
|
||||
// MARK: life cicle
|
||||
|
||||
extension AnimatingBarButton {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
|
||||
view.onLayoutSubviews = {[weak self] in self?.configurateImageViews() }
|
||||
customView = view
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
|
||||
view.onLayoutSubviews = { [weak self] in self?.configurateImageViews() }
|
||||
customView = view
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: public
|
||||
|
||||
extension AnimatingBarButton {
|
||||
|
||||
func animationSelected(_ selected: Bool) {
|
||||
if selected {
|
||||
rotateAnimationFrom(normalView, toItem: selectedView, duration: duration)
|
||||
} else {
|
||||
rotateAnimationFrom(selectedView, toItem: normalView, duration: duration)
|
||||
|
||||
func animationSelected(_ selected: Bool) {
|
||||
if selected {
|
||||
rotateAnimationFrom(normalView, toItem: selectedView, duration: duration)
|
||||
} else {
|
||||
rotateAnimationFrom(selectedView, toItem: normalView, duration: duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Create
|
||||
|
||||
extension AnimatingBarButton {
|
||||
|
||||
fileprivate func configurateImageViews() {
|
||||
configureImageView(normalView, imageName: normalImageName)
|
||||
configureImageView(selectedView, imageName: selectedImageName)
|
||||
|
||||
selectedView.alpha = 0
|
||||
}
|
||||
|
||||
fileprivate func configureImageView(_ imageView: UIImageView, imageName: String) {
|
||||
guard let customView = customView else { return }
|
||||
guard let image = UIImage(named: imageName)?.withRenderingMode(UIImageRenderingMode.alwaysTemplate) else { return }
|
||||
|
||||
imageView.image = image
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.tintColor = tintColor
|
||||
|
||||
imageView.layer.shadowColor = UIColor.black.cgColor
|
||||
imageView.layer.shadowRadius = 0.6
|
||||
imageView.layer.shadowOffset = CGSize(width: 0, height: 1.5)
|
||||
imageView.layer.shadowOpacity = 0.3
|
||||
|
||||
let x = (customView.bounds.size.width - image.size.width)/2 + 12
|
||||
let y = (customView.bounds.size.height - image.size.height)/2
|
||||
imageView.frame = CGRect(origin: CGPoint(x: x, y: y), size: image.size)
|
||||
customView.addSubview(imageView)
|
||||
}
|
||||
fileprivate func configurateImageViews() {
|
||||
configureImageView(normalView, imageName: normalImageName)
|
||||
configureImageView(selectedView, imageName: selectedImageName)
|
||||
|
||||
selectedView.alpha = 0
|
||||
}
|
||||
|
||||
fileprivate func configureImageView(_ imageView: UIImageView, imageName: String) {
|
||||
guard let customView = customView else { return }
|
||||
guard let image = UIImage(named: imageName)?.withRenderingMode(UIImageRenderingMode.alwaysTemplate) else { return }
|
||||
|
||||
imageView.image = image
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.tintColor = tintColor
|
||||
|
||||
imageView.layer.shadowColor = UIColor.black.cgColor
|
||||
imageView.layer.shadowRadius = 0.6
|
||||
imageView.layer.shadowOffset = CGSize(width: 0, height: 1.5)
|
||||
imageView.layer.shadowOpacity = 0.3
|
||||
|
||||
let x = (customView.bounds.size.width - image.size.width) / 2 + 12
|
||||
let y = (customView.bounds.size.height - image.size.height) / 2
|
||||
imageView.frame = CGRect(origin: CGPoint(x: x, y: y), size: image.size)
|
||||
customView.addSubview(imageView)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -10,33 +10,33 @@ import UIKit
|
||||
|
||||
protocol Rotatable {
|
||||
|
||||
func rotateAnimationFrom(_ fromItem: UIView, toItem: UIView, duration: Double)
|
||||
func rotateAnimationFrom(_ fromItem: UIView, toItem: UIView, duration: Double)
|
||||
}
|
||||
|
||||
extension Rotatable {
|
||||
|
||||
func rotateAnimationFrom(_ fromItem: UIView, toItem: UIView, duration: Double) {
|
||||
|
||||
let fromRotate = animationFrom(0, to: Double.pi, key: "transform.rotation", duration: duration)
|
||||
let fromOpacity = animationFrom(1, to: 0, key: "opacity", duration: duration)
|
||||
func rotateAnimationFrom(_ fromItem: UIView, toItem: UIView, duration: Double) {
|
||||
|
||||
let toRotate = animationFrom(-Double.pi, to: 0, key: "transform.rotation", duration: duration)
|
||||
let toOpacity = animationFrom(0, to: 1, key: "opacity", duration: duration)
|
||||
|
||||
fromItem.layer.add(fromRotate, forKey: nil)
|
||||
fromItem.layer.add(fromOpacity, forKey: nil)
|
||||
|
||||
toItem.layer.add(toRotate, forKey: nil)
|
||||
toItem.layer.add(toOpacity, forKey: nil)
|
||||
}
|
||||
|
||||
fileprivate func animationFrom(_ from: Double, to: Double, key: String, duration: Double) -> CABasicAnimation {
|
||||
return Init(CABasicAnimation(keyPath: key)) {
|
||||
$0.duration = duration
|
||||
$0.fromValue = from
|
||||
$0.toValue = to
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
let fromRotate = animationFrom(0, to: Double.pi, key: "transform.rotation", duration: duration)
|
||||
let fromOpacity = animationFrom(1, to: 0, key: "opacity", duration: duration)
|
||||
|
||||
let toRotate = animationFrom(-Double.pi, to: 0, key: "transform.rotation", duration: duration)
|
||||
let toOpacity = animationFrom(0, to: 1, key: "opacity", duration: duration)
|
||||
|
||||
fromItem.layer.add(fromRotate, forKey: nil)
|
||||
fromItem.layer.add(fromOpacity, forKey: nil)
|
||||
|
||||
toItem.layer.add(toRotate, forKey: nil)
|
||||
toItem.layer.add(toOpacity, forKey: nil)
|
||||
}
|
||||
|
||||
fileprivate func animationFrom(_ from: Double, to: Double, key: String, duration: Double) -> CABasicAnimation {
|
||||
return Init(CABasicAnimation(keyPath: key)) {
|
||||
$0.duration = duration
|
||||
$0.fromValue = from
|
||||
$0.toValue = to
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,56 +10,55 @@ import UIKit
|
||||
|
||||
/// Base class for UITableViewcontroller which have back transition method
|
||||
open class ExpandingTableViewController: UITableViewController {
|
||||
|
||||
// MARK: Vars
|
||||
/// The height of the table view header
|
||||
open var headerHeight: CGFloat = 236
|
||||
|
||||
var transitionDriver: TransitionDriver?
|
||||
|
||||
// MARK: Vars
|
||||
/// The height of the table view header, set before transition
|
||||
open var headerHeight: CGFloat = 236
|
||||
|
||||
var transitionDriver: TransitionDriver?
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
// MARK: Helpers
|
||||
|
||||
extension ExpandingTableViewController {
|
||||
|
||||
fileprivate func getScreen() -> UIImage? {
|
||||
let height = (headerHeight - tableView.contentOffset.y) < 0 ? 0 : (headerHeight - tableView.contentOffset.y)
|
||||
let backImageSize = CGSize(width: view.bounds.width, height: view.bounds.height - height + getTabBarHeight())
|
||||
let backImageOrigin = CGPoint(x: 0, y: height + tableView.contentOffset.y)
|
||||
return view.takeSnapshot(CGRect(origin: backImageOrigin, size: backImageSize))
|
||||
}
|
||||
|
||||
fileprivate func getTabBarHeight() -> CGFloat {
|
||||
guard let navigationController = self.navigationController else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let insets = automaticallyAdjustsScrollViewInsets
|
||||
let tabBarHeight = insets == true ? navigationController.navigationBar.frame.size.height : 0
|
||||
let stausBarHeight = insets == true ? UIApplication.shared.statusBarFrame.size.height : 0
|
||||
return tabBarHeight + stausBarHeight
|
||||
}
|
||||
|
||||
fileprivate func getScreen() -> UIImage? {
|
||||
let height = (headerHeight - tableView.contentOffset.y) < 0 ? 0 : (headerHeight - tableView.contentOffset.y)
|
||||
let backImageSize = CGSize(width: view.bounds.width, height: view.bounds.height - height + getTabBarHeight())
|
||||
let backImageOrigin = CGPoint(x: 0, y: height + tableView.contentOffset.y)
|
||||
return view.takeSnapshot(CGRect(origin: backImageOrigin, size: backImageSize))
|
||||
}
|
||||
|
||||
fileprivate func getTabBarHeight() -> CGFloat {
|
||||
guard let navigationController = self.navigationController else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let insets = automaticallyAdjustsScrollViewInsets
|
||||
let tabBarHeight = insets == true ? navigationController.navigationBar.frame.size.height : 0
|
||||
let stausBarHeight = insets == true ? UIApplication.shared.statusBarFrame.size.height : 0
|
||||
return tabBarHeight + stausBarHeight
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
extension ExpandingTableViewController {
|
||||
|
||||
/**
|
||||
Pops the top view controller from the navigation stack and updates the display with custom animation.
|
||||
*/
|
||||
public func popTransitionAnimation() {
|
||||
guard let transitionDriver = self.transitionDriver else {
|
||||
return
|
||||
|
||||
/**
|
||||
Pops the top view controller from the navigation stack and updates the display with custom animation.
|
||||
*/
|
||||
public func popTransitionAnimation() {
|
||||
guard let transitionDriver = self.transitionDriver else {
|
||||
return
|
||||
}
|
||||
|
||||
let backImage = getScreen()
|
||||
var offset = tableView.contentOffset.y > headerHeight ? headerHeight : tableView.contentOffset.y
|
||||
|
||||
offset += getTabBarHeight()
|
||||
|
||||
transitionDriver.popTransitionAnimationContantOffset(offset, backImage: backImage)
|
||||
_ = navigationController?.popViewController(animated: false)
|
||||
}
|
||||
|
||||
let backImage = getScreen()
|
||||
var offset = tableView.contentOffset.y > headerHeight ? headerHeight : tableView.contentOffset.y
|
||||
|
||||
offset += getTabBarHeight()
|
||||
|
||||
transitionDriver.popTransitionAnimationContantOffset(offset, backImage: backImage)
|
||||
let _ = self.navigationController?.popViewController(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
+262
-266
@@ -10,209 +10,207 @@ import UIKit
|
||||
|
||||
/// Base class for UICollectionViewCell
|
||||
open class BasePageCollectionCell: UICollectionViewCell {
|
||||
|
||||
/// DEPRECATED! Animation oposition offset when cell is open
|
||||
@IBInspectable open var yOffset: CGFloat = 40
|
||||
/// Spacing between centers of views
|
||||
@IBInspectable open var ySpacing: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Additional height of back view, when it grows
|
||||
@IBInspectable open var additionalHeight: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Additional width of back view, when it grows
|
||||
@IBInspectable open var additionalWidth: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Should front view drow shadow to bottom or not
|
||||
@IBInspectable open var dropShadow: Bool = true
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
struct Constants {
|
||||
static let backContainer = "backContainerViewKey"
|
||||
static let shadowView = "shadowViewKey"
|
||||
static let frontContainer = "frontContainerKey"
|
||||
|
||||
static let backContainerY = "backContainerYKey"
|
||||
static let frontContainerY = "frontContainerYKey"
|
||||
}
|
||||
|
||||
// MARK: Vars
|
||||
/// DEPRECATED! Animation oposition offset when cell is open
|
||||
@IBInspectable open var yOffset: CGFloat = 40
|
||||
/// Spacing between centers of views
|
||||
@IBInspectable open var ySpacing: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Additional height of back view, when it grows
|
||||
@IBInspectable open var additionalHeight: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Additional width of back view, when it grows
|
||||
@IBInspectable open var additionalWidth: CGFloat = CGFloat.greatestFiniteMagnitude
|
||||
/// Should front view drow shadow to bottom or not
|
||||
@IBInspectable open var dropShadow: Bool = true
|
||||
|
||||
/// The view used as the face of the cell must connectid from xib or storyboard.
|
||||
@IBOutlet open weak var frontContainerView: UIView!
|
||||
/// The view used as the back of the cell must connectid from xib or storyboard.
|
||||
@IBOutlet open weak var backContainerView: UIView!
|
||||
|
||||
/// constraints for backContainerView must connectid from xib or storyboard
|
||||
@IBOutlet open weak var backConstraintY: NSLayoutConstraint!
|
||||
/// constraints for frontContainerView must connectid from xib or storyboard
|
||||
@IBOutlet open weak var frontConstraintY: NSLayoutConstraint!
|
||||
|
||||
var shadowView: UIView?
|
||||
|
||||
/// A Boolean value that indicates whether the cell is opened.
|
||||
open var isOpened = false
|
||||
|
||||
// MARK: inits
|
||||
|
||||
/**
|
||||
Initializes a UICollectionViewCell from data in a given unarchiver.
|
||||
|
||||
- parameter aDecoder: An unarchiver object.
|
||||
|
||||
- returns: An initialized UICollectionViewCell object.
|
||||
*/
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
configureOutletFromDecoder(aDecoder)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes and returns a newly allocated view object with the specified frame rectangle.
|
||||
|
||||
- parameter frame: The frame rectangle for the view
|
||||
|
||||
- returns: An initialized UICollectionViewCell object.
|
||||
*/
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
commonInit()
|
||||
}
|
||||
// MARK: Constants
|
||||
|
||||
struct Constants {
|
||||
static let backContainer = "backContainerViewKey"
|
||||
static let shadowView = "shadowViewKey"
|
||||
static let frontContainer = "frontContainerKey"
|
||||
|
||||
static let backContainerY = "backContainerYKey"
|
||||
static let frontContainerY = "frontContainerYKey"
|
||||
}
|
||||
|
||||
// MARK: Vars
|
||||
|
||||
/// The view used as the face of the cell must connectid from xib or storyboard.
|
||||
@IBOutlet open var frontContainerView: UIView!
|
||||
/// The view used as the back of the cell must connectid from xib or storyboard.
|
||||
@IBOutlet open var backContainerView: UIView!
|
||||
|
||||
/// constraints for backContainerView must connectid from xib or storyboard
|
||||
@IBOutlet open var backConstraintY: NSLayoutConstraint!
|
||||
/// constraints for frontContainerView must connectid from xib or storyboard
|
||||
@IBOutlet open var frontConstraintY: NSLayoutConstraint!
|
||||
|
||||
var shadowView: UIView?
|
||||
|
||||
/// A Boolean value that indicates whether the cell is opened.
|
||||
open var isOpened = false
|
||||
|
||||
// MARK: inits
|
||||
|
||||
/**
|
||||
Initializes a UICollectionViewCell from data in a given unarchiver.
|
||||
|
||||
- parameter aDecoder: An unarchiver object.
|
||||
|
||||
- returns: An initialized UICollectionViewCell object.
|
||||
*/
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
configureOutletFromDecoder(aDecoder)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes and returns a newly allocated view object with the specified frame rectangle.
|
||||
|
||||
- parameter frame: The frame rectangle for the view
|
||||
|
||||
- returns: An initialized UICollectionViewCell object.
|
||||
*/
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
commonInit()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: life cicle
|
||||
|
||||
extension BasePageCollectionCell {
|
||||
|
||||
open override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
configurationViews()
|
||||
shadowView = createShadowViewOnView(frontContainerView)
|
||||
}
|
||||
|
||||
open override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
commonInit()
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
configurationViews()
|
||||
shadowView = createShadowViewOnView(frontContainerView)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Control
|
||||
// MARK: Control
|
||||
|
||||
extension BasePageCollectionCell {
|
||||
|
||||
/**
|
||||
Open or close cell.
|
||||
|
||||
- parameter isOpen: Contains the value true if the cell should display open state, if false should display close state.
|
||||
- parameter animated: Set to true if the change in selection state is animated.
|
||||
*/
|
||||
public func cellIsOpen(_ isOpen: Bool, animated: Bool = true) {
|
||||
if isOpen == isOpened { return }
|
||||
|
||||
if ySpacing == .greatestFiniteMagnitude {
|
||||
frontConstraintY.constant = isOpen == true ? -frontContainerView.bounds.size.height / 5 : 0
|
||||
backConstraintY.constant = isOpen == true ? frontContainerView.bounds.size.height / 5 - yOffset / 2 : 0
|
||||
} else {
|
||||
frontConstraintY.constant = isOpen == true ? -ySpacing / 2 : 0
|
||||
backConstraintY.constant = isOpen == true ? ySpacing / 2 : 0
|
||||
|
||||
/**
|
||||
Open or close cell.
|
||||
|
||||
- parameter isOpen: Contains the value true if the cell should display open state, if false should display close state.
|
||||
- parameter animated: Set to true if the change in selection state is animated.
|
||||
*/
|
||||
public func cellIsOpen(_ isOpen: Bool, animated: Bool = true) {
|
||||
if isOpen == isOpened { return }
|
||||
|
||||
if ySpacing == .greatestFiniteMagnitude {
|
||||
frontConstraintY.constant = isOpen == true ? -frontContainerView.bounds.size.height / 5 : 0
|
||||
backConstraintY.constant = isOpen == true ? frontContainerView.bounds.size.height / 5 - yOffset / 2 : 0
|
||||
} else {
|
||||
frontConstraintY.constant = isOpen == true ? -ySpacing / 2 : 0
|
||||
backConstraintY.constant = isOpen == true ? ySpacing / 2 : 0
|
||||
}
|
||||
|
||||
if let widthConstant = backContainerView.getConstraint(.width) {
|
||||
if additionalWidth == .greatestFiniteMagnitude {
|
||||
widthConstant.constant = isOpen == true ? frontContainerView.bounds.size.width + yOffset : frontContainerView.bounds.size.width
|
||||
} else {
|
||||
widthConstant.constant = isOpen == true ? frontContainerView.bounds.size.width + additionalWidth : frontContainerView.bounds.size.width
|
||||
}
|
||||
}
|
||||
|
||||
if let heightConstant = backContainerView.getConstraint(.height) {
|
||||
if additionalHeight == .greatestFiniteMagnitude {
|
||||
heightConstant.constant = isOpen == true ? frontContainerView.bounds.size.height + yOffset : frontContainerView.bounds.size.height
|
||||
} else {
|
||||
heightConstant.constant = isOpen == true ? frontContainerView.bounds.size.height + additionalHeight : frontContainerView.bounds.size.height
|
||||
}
|
||||
}
|
||||
|
||||
isOpened = isOpen
|
||||
configurationCell()
|
||||
|
||||
if animated == true {
|
||||
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.contentView.layoutIfNeeded()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
contentView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
if let widthConstant = backContainerView.getConstraint(.width) {
|
||||
if additionalWidth == .greatestFiniteMagnitude {
|
||||
widthConstant.constant = isOpen == true ? frontContainerView.bounds.size.width + yOffset : frontContainerView.bounds.size.width
|
||||
} else {
|
||||
widthConstant.constant = isOpen == true ? frontContainerView.bounds.size.width + additionalWidth : frontContainerView.bounds.size.width
|
||||
}
|
||||
}
|
||||
|
||||
if let heightConstant = backContainerView.getConstraint(.height) {
|
||||
if additionalHeight == .greatestFiniteMagnitude {
|
||||
heightConstant.constant = isOpen == true ? frontContainerView.bounds.size.height + yOffset : frontContainerView.bounds.size.height
|
||||
} else {
|
||||
heightConstant.constant = isOpen == true ? frontContainerView.bounds.size.height + additionalHeight : frontContainerView.bounds.size.height
|
||||
}
|
||||
}
|
||||
|
||||
isOpened = isOpen
|
||||
configurationCell()
|
||||
|
||||
if animated == true {
|
||||
UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.contentView.layoutIfNeeded()
|
||||
}, completion: nil)
|
||||
} else {
|
||||
self.contentView.layoutIfNeeded()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Configuration
|
||||
|
||||
extension BasePageCollectionCell {
|
||||
|
||||
fileprivate func configurationViews() {
|
||||
backContainerView.layer.masksToBounds = true
|
||||
backContainerView.layer.cornerRadius = 5
|
||||
|
||||
frontContainerView.layer.masksToBounds = true
|
||||
frontContainerView.layer.cornerRadius = 5
|
||||
|
||||
contentView.layer.masksToBounds = false
|
||||
layer.masksToBounds = false
|
||||
}
|
||||
|
||||
fileprivate func createShadowViewOnView(_ view: UIView?) -> UIView? {
|
||||
guard let view = view else {return nil}
|
||||
|
||||
let shadow = Init(UIView(frame: .zero)) {
|
||||
$0.backgroundColor = UIColor(white: 0, alpha: 0)
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.layer.masksToBounds = false;
|
||||
if dropShadow {
|
||||
$0.layer.shadowColor = UIColor.black.cgColor
|
||||
$0.layer.shadowRadius = 10
|
||||
$0.layer.shadowOpacity = 0.3
|
||||
$0.layer.shadowOffset = CGSize(width: 0, height:0)
|
||||
}
|
||||
|
||||
fileprivate func configurationViews() {
|
||||
backContainerView.layer.masksToBounds = true
|
||||
backContainerView.layer.cornerRadius = 5
|
||||
|
||||
frontContainerView.layer.masksToBounds = true
|
||||
frontContainerView.layer.cornerRadius = 5
|
||||
|
||||
contentView.layer.masksToBounds = false
|
||||
layer.masksToBounds = false
|
||||
}
|
||||
contentView.insertSubview(shadow, belowSubview: view)
|
||||
|
||||
// create constraints
|
||||
for info: (attribute: NSLayoutAttribute, scale: CGFloat) in [(NSLayoutAttribute.width, 0.8), (NSLayoutAttribute.height, 0.9)] {
|
||||
if let frontViewConstraint = view.getConstraint(info.attribute) {
|
||||
shadow >>>- {
|
||||
$0.attribute = info.attribute
|
||||
$0.constant = frontViewConstraint.constant * info.scale
|
||||
return
|
||||
|
||||
fileprivate func createShadowViewOnView(_ view: UIView?) -> UIView? {
|
||||
guard let view = view else { return nil }
|
||||
|
||||
let shadow = Init(UIView(frame: .zero)) {
|
||||
$0.backgroundColor = UIColor(white: 0, alpha: 0)
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.layer.masksToBounds = false
|
||||
if dropShadow {
|
||||
$0.layer.shadowColor = UIColor.black.cgColor
|
||||
$0.layer.shadowRadius = 10
|
||||
$0.layer.shadowOpacity = 0.3
|
||||
$0.layer.shadowOffset = CGSize(width: 0, height: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
contentView.insertSubview(shadow, belowSubview: view)
|
||||
|
||||
// create constraints
|
||||
for info: (attribute: NSLayoutAttribute, scale: CGFloat) in [(NSLayoutAttribute.width, 0.8), (NSLayoutAttribute.height, 0.9)] {
|
||||
if let frontViewConstraint = view.getConstraint(info.attribute) {
|
||||
shadow >>>- {
|
||||
$0.attribute = info.attribute
|
||||
$0.constant = frontViewConstraint.constant * info.scale
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for info: (attribute: NSLayoutAttribute, offset: CGFloat) in [(NSLayoutAttribute.centerX, 0), (NSLayoutAttribute.centerY, 30)] {
|
||||
(contentView, shadow, view) >>>- {
|
||||
$0.attribute = info.attribute
|
||||
$0.constant = info.offset
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// size shadow
|
||||
let width = shadow.getConstraint(.width)?.constant
|
||||
let height = shadow.getConstraint(.height)?.constant
|
||||
|
||||
shadow.layer.shadowPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: width!, height: height!), cornerRadius: 0).cgPath
|
||||
|
||||
return shadow
|
||||
}
|
||||
|
||||
for info: (attribute: NSLayoutAttribute, offset: CGFloat) in [(NSLayoutAttribute.centerX, 0), (NSLayoutAttribute.centerY, 30)] {
|
||||
(contentView, shadow, view) >>>- {
|
||||
$0.attribute = info.attribute
|
||||
$0.constant = info.offset
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// size shadow
|
||||
let width = shadow.getConstraint(.width)?.constant
|
||||
let height = shadow.getConstraint(.height)?.constant
|
||||
|
||||
shadow.layer.shadowPath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: width!, height: height!), cornerRadius: 0).cgPath
|
||||
|
||||
return shadow
|
||||
}
|
||||
|
||||
|
||||
func configurationCell() {
|
||||
// Prevents indefinite growing of the cell issue
|
||||
let i: CGFloat = self.isOpened ? 1 : -1
|
||||
let superHeight = superview?.frame.size.height ?? 0
|
||||
|
||||
frame.size.height += i * superHeight
|
||||
frame.origin.y -= i * superHeight / 2
|
||||
|
||||
func configurationCell() {
|
||||
// Prevents indefinite growing of the cell issue
|
||||
let i: CGFloat = isOpened ? 1 : -1
|
||||
let superHeight = superview?.frame.size.height ?? 0
|
||||
|
||||
frame.size.height += i * superHeight
|
||||
frame.origin.y -= i * superHeight / 2
|
||||
if additionalWidth == .greatestFiniteMagnitude {
|
||||
frame.origin.x -= i * yOffset / 2
|
||||
frame.size.width += i * yOffset
|
||||
@@ -220,100 +218,98 @@ extension BasePageCollectionCell {
|
||||
frame.origin.x -= i * additionalWidth / 2
|
||||
frame.size.width += i * additionalWidth
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func configureCellViewConstraintsWithSize(_ size: CGSize) {
|
||||
guard isOpened == false && frontContainerView.getConstraint(.width)?.constant != size.width else { return }
|
||||
|
||||
[frontContainerView, backContainerView].forEach {
|
||||
let constraintWidth = $0?.getConstraint(.width)
|
||||
constraintWidth?.constant = size.width
|
||||
|
||||
let constraintHeight = $0?.getConstraint(.height)
|
||||
constraintHeight?.constant = size.height
|
||||
}
|
||||
}
|
||||
|
||||
func configureCellViewConstraintsWithSize(_ size: CGSize) {
|
||||
guard isOpened == false && frontContainerView.getConstraint(.width)?.constant != size.width else { return }
|
||||
|
||||
[frontContainerView, backContainerView].forEach {
|
||||
let constraintWidth = $0?.getConstraint(.width)
|
||||
constraintWidth?.constant = size.width
|
||||
|
||||
let constraintHeight = $0?.getConstraint(.height)
|
||||
constraintHeight?.constant = size.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: NSCoding
|
||||
|
||||
extension BasePageCollectionCell {
|
||||
|
||||
fileprivate func highlightedImageFalseOnView(_ view: UIView) {
|
||||
for item in view.subviews {
|
||||
if case let imageView as UIImageView = item {
|
||||
imageView.isHighlighted = false
|
||||
}
|
||||
if item.subviews.count > 0 {
|
||||
highlightedImageFalseOnView(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func copyShadowFromView(_ fromView: UIView, toView: UIView) {
|
||||
fromView.layer.shadowPath = toView.layer.shadowPath
|
||||
fromView.layer.masksToBounds = toView.layer.masksToBounds
|
||||
fromView.layer.shadowColor = toView.layer.shadowColor
|
||||
fromView.layer.shadowRadius = toView.layer.shadowRadius
|
||||
fromView.layer.shadowOpacity = toView.layer.shadowOpacity
|
||||
fromView.layer.shadowOffset = toView.layer.shadowOffset
|
||||
}
|
||||
|
||||
|
||||
func copyCell() -> BasePageCollectionCell? {
|
||||
highlightedImageFalseOnView(contentView)
|
||||
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: self)
|
||||
guard case let copyView as BasePageCollectionCell = NSKeyedUnarchiver.unarchiveObject(with: data),
|
||||
let shadowView = self.shadowView else {
|
||||
return nil
|
||||
fileprivate func highlightedImageFalseOnView(_ view: UIView) {
|
||||
for item in view.subviews {
|
||||
if case let imageView as UIImageView = item {
|
||||
imageView.isHighlighted = false
|
||||
}
|
||||
if item.subviews.count > 0 {
|
||||
highlightedImageFalseOnView(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configure
|
||||
copyView.backContainerView.layer.masksToBounds = backContainerView.layer.masksToBounds
|
||||
copyView.backContainerView.layer.cornerRadius = backContainerView.layer.cornerRadius
|
||||
|
||||
copyView.frontContainerView.layer.masksToBounds = frontContainerView.layer.masksToBounds
|
||||
copyView.frontContainerView.layer.cornerRadius = frontContainerView.layer.cornerRadius
|
||||
|
||||
// copy shadow layer
|
||||
copyShadowFromView(copyView.shadowView!, toView: shadowView)
|
||||
|
||||
for index in 0..<copyView.frontContainerView.subviews.count {
|
||||
copyShadowFromView(copyView.frontContainerView.subviews[index], toView: frontContainerView.subviews[index])
|
||||
fileprivate func copyShadowFromView(_ fromView: UIView, toView: UIView) {
|
||||
fromView.layer.shadowPath = toView.layer.shadowPath
|
||||
fromView.layer.masksToBounds = toView.layer.masksToBounds
|
||||
fromView.layer.shadowColor = toView.layer.shadowColor
|
||||
fromView.layer.shadowRadius = toView.layer.shadowRadius
|
||||
fromView.layer.shadowOpacity = toView.layer.shadowOpacity
|
||||
fromView.layer.shadowOffset = toView.layer.shadowOffset
|
||||
}
|
||||
return copyView
|
||||
}
|
||||
|
||||
open override func encode(with coder: NSCoder) {
|
||||
super.encode(with: coder)
|
||||
coder.encode(backContainerView, forKey: Constants.backContainer)
|
||||
coder.encode(frontContainerView, forKey: Constants.frontContainer)
|
||||
coder.encode(frontConstraintY, forKey: Constants.frontContainerY)
|
||||
coder.encode(backConstraintY, forKey: Constants.backContainerY)
|
||||
coder.encode(shadowView, forKey: Constants.shadowView)
|
||||
}
|
||||
|
||||
fileprivate func configureOutletFromDecoder(_ coder: NSCoder) {
|
||||
if case let shadowView as UIView = coder.decodeObject(forKey: Constants.shadowView) {
|
||||
self.shadowView = shadowView
|
||||
|
||||
func copyCell() -> BasePageCollectionCell? {
|
||||
highlightedImageFalseOnView(contentView)
|
||||
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: self)
|
||||
guard case let copyView as BasePageCollectionCell = NSKeyedUnarchiver.unarchiveObject(with: data),
|
||||
let shadowView = self.shadowView else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// configure
|
||||
copyView.backContainerView.layer.masksToBounds = backContainerView.layer.masksToBounds
|
||||
copyView.backContainerView.layer.cornerRadius = backContainerView.layer.cornerRadius
|
||||
|
||||
copyView.frontContainerView.layer.masksToBounds = frontContainerView.layer.masksToBounds
|
||||
copyView.frontContainerView.layer.cornerRadius = frontContainerView.layer.cornerRadius
|
||||
|
||||
// copy shadow layer
|
||||
copyShadowFromView(copyView.shadowView!, toView: shadowView)
|
||||
|
||||
for index in 0 ..< copyView.frontContainerView.subviews.count {
|
||||
copyShadowFromView(copyView.frontContainerView.subviews[index], toView: frontContainerView.subviews[index])
|
||||
}
|
||||
return copyView
|
||||
}
|
||||
|
||||
if case let backView as UIView = coder.decodeObject(forKey: Constants.backContainer) {
|
||||
backContainerView = backView
|
||||
|
||||
open override func encode(with coder: NSCoder) {
|
||||
super.encode(with: coder)
|
||||
coder.encode(backContainerView, forKey: Constants.backContainer)
|
||||
coder.encode(frontContainerView, forKey: Constants.frontContainer)
|
||||
coder.encode(frontConstraintY, forKey: Constants.frontContainerY)
|
||||
coder.encode(backConstraintY, forKey: Constants.backContainerY)
|
||||
coder.encode(shadowView, forKey: Constants.shadowView)
|
||||
}
|
||||
|
||||
if case let frontView as UIView = coder.decodeObject(forKey: Constants.frontContainer) {
|
||||
frontContainerView = frontView
|
||||
|
||||
fileprivate func configureOutletFromDecoder(_ coder: NSCoder) {
|
||||
if case let shadowView as UIView = coder.decodeObject(forKey: Constants.shadowView) {
|
||||
self.shadowView = shadowView
|
||||
}
|
||||
|
||||
if case let backView as UIView = coder.decodeObject(forKey: Constants.backContainer) {
|
||||
backContainerView = backView
|
||||
}
|
||||
|
||||
if case let frontView as UIView = coder.decodeObject(forKey: Constants.frontContainer) {
|
||||
frontContainerView = frontView
|
||||
}
|
||||
|
||||
if case let constraint as NSLayoutConstraint = coder.decodeObject(forKey: Constants.frontContainerY) {
|
||||
frontConstraintY = constraint
|
||||
}
|
||||
|
||||
if case let constraint as NSLayoutConstraint = coder.decodeObject(forKey: Constants.backContainerY) {
|
||||
backConstraintY = constraint
|
||||
}
|
||||
}
|
||||
|
||||
if case let constraint as NSLayoutConstraint = coder.decodeObject(forKey: Constants.frontContainerY) {
|
||||
frontConstraintY = constraint
|
||||
}
|
||||
|
||||
if case let constraint as NSLayoutConstraint = coder.decodeObject(forKey: Constants.backContainerY) {
|
||||
backConstraintY = constraint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,141 +9,141 @@
|
||||
import UIKit
|
||||
|
||||
class PageCollectionLayout: UICollectionViewFlowLayout {
|
||||
|
||||
fileprivate var lastCollectionViewSize: CGSize = CGSize.zero
|
||||
|
||||
var scalingOffset: CGFloat = 200
|
||||
var minimumScaleFactor: CGFloat = 0.9
|
||||
var minimumAlphaFactor: CGFloat = 0.3
|
||||
var scaleItems: Bool = true
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
init(itemSize: CGSize) {
|
||||
super.init()
|
||||
commonInit(itemSize)
|
||||
}
|
||||
|
||||
fileprivate var lastCollectionViewSize: CGSize = CGSize.zero
|
||||
|
||||
var scalingOffset: CGFloat = 200
|
||||
var minimumScaleFactor: CGFloat = 0.9
|
||||
var minimumAlphaFactor: CGFloat = 0.3
|
||||
var scaleItems: Bool = true
|
||||
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
init(itemSize: CGSize) {
|
||||
super.init()
|
||||
commonInit(itemSize)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: life cicle
|
||||
|
||||
extension PageCollectionLayout {
|
||||
|
||||
fileprivate func commonInit(_ itemSize: CGSize) {
|
||||
scrollDirection = .horizontal
|
||||
minimumLineSpacing = 25
|
||||
self.itemSize = itemSize
|
||||
}
|
||||
|
||||
fileprivate func commonInit(_ itemSize: CGSize) {
|
||||
scrollDirection = .horizontal
|
||||
minimumLineSpacing = 25
|
||||
self.itemSize = itemSize
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: override
|
||||
|
||||
extension PageCollectionLayout {
|
||||
|
||||
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
|
||||
super.invalidateLayout(with: context)
|
||||
|
||||
guard let collectionView = self.collectionView else { return }
|
||||
|
||||
if collectionView.bounds.size != lastCollectionViewSize {
|
||||
self.configureInset()
|
||||
self.lastCollectionViewSize = collectionView.bounds.size
|
||||
|
||||
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
|
||||
super.invalidateLayout(with: context)
|
||||
|
||||
guard let collectionView = self.collectionView else { return }
|
||||
|
||||
if collectionView.bounds.size != lastCollectionViewSize {
|
||||
configureInset()
|
||||
lastCollectionViewSize = collectionView.bounds.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
|
||||
guard let collectionView = self.collectionView else {
|
||||
return proposedContentOffset
|
||||
|
||||
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
|
||||
guard let collectionView = self.collectionView else {
|
||||
return proposedContentOffset
|
||||
}
|
||||
|
||||
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
|
||||
guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else {
|
||||
return proposedContentOffset
|
||||
}
|
||||
|
||||
var candidateAttributes: UICollectionViewLayoutAttributes?
|
||||
let proposedContentOffsetCenterX = proposedContentOffset.x + collectionView.bounds.width / 2
|
||||
|
||||
for attributes in layoutAttributes {
|
||||
if attributes.representedElementCategory != .cell {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidateAttributes == nil {
|
||||
candidateAttributes = attributes
|
||||
continue
|
||||
}
|
||||
|
||||
if fabs(attributes.center.x - proposedContentOffsetCenterX) < fabs(candidateAttributes!.center.x - proposedContentOffsetCenterX) {
|
||||
candidateAttributes = attributes
|
||||
}
|
||||
}
|
||||
|
||||
guard let aCandidateAttributes = candidateAttributes else {
|
||||
return proposedContentOffset
|
||||
}
|
||||
|
||||
var newOffsetX = aCandidateAttributes.center.x - collectionView.bounds.size.width / 2
|
||||
let offset = newOffsetX - collectionView.contentOffset.x
|
||||
|
||||
if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) {
|
||||
let pageWidth = itemSize.width + minimumLineSpacing
|
||||
newOffsetX += velocity.x > 0 ? pageWidth : -pageWidth
|
||||
}
|
||||
|
||||
return CGPoint(x: newOffsetX, y: proposedContentOffset.y)
|
||||
}
|
||||
|
||||
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
|
||||
guard let layoutAttributes = self.layoutAttributesForElements(in: proposedRect) else {
|
||||
return proposedContentOffset
|
||||
|
||||
override func shouldInvalidateLayout(forBoundsChange _: CGRect) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var candidateAttributes: UICollectionViewLayoutAttributes?
|
||||
let proposedContentOffsetCenterX = proposedContentOffset.x + collectionView.bounds.width / 2
|
||||
|
||||
for attributes in layoutAttributes {
|
||||
if attributes.representedElementCategory != .cell {
|
||||
continue
|
||||
}
|
||||
|
||||
if candidateAttributes == nil {
|
||||
candidateAttributes = attributes
|
||||
continue
|
||||
}
|
||||
|
||||
if fabs(attributes.center.x - proposedContentOffsetCenterX) < fabs(candidateAttributes!.center.x - proposedContentOffsetCenterX) {
|
||||
candidateAttributes = attributes
|
||||
}
|
||||
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
guard let collectionView = self.collectionView,
|
||||
let superAttributes = super.layoutAttributesForElements(in: rect) else {
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
}
|
||||
if scaleItems == false {
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
}
|
||||
|
||||
let contentOffset = collectionView.contentOffset
|
||||
let size = collectionView.bounds.size
|
||||
|
||||
let visibleRect = CGRect(x: contentOffset.x, y: contentOffset.y, width: size.width, height: size.height)
|
||||
let visibleCenterX = visibleRect.midX
|
||||
|
||||
guard case let newAttributesArray as [UICollectionViewLayoutAttributes] = NSArray(array: superAttributes, copyItems: true) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
newAttributesArray.forEach {
|
||||
let distanceFromCenter = visibleCenterX - $0.center.x
|
||||
let absDistanceFromCenter = min(abs(distanceFromCenter), self.scalingOffset)
|
||||
let scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1
|
||||
$0.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
|
||||
|
||||
let alpha = absDistanceFromCenter * (self.minimumAlphaFactor - 1) / self.scalingOffset + 1
|
||||
$0.alpha = alpha
|
||||
}
|
||||
|
||||
return newAttributesArray
|
||||
}
|
||||
|
||||
guard let aCandidateAttributes = candidateAttributes else {
|
||||
return proposedContentOffset
|
||||
}
|
||||
|
||||
var newOffsetX = aCandidateAttributes.center.x - collectionView.bounds.size.width / 2
|
||||
let offset = newOffsetX - collectionView.contentOffset.x
|
||||
|
||||
if (velocity.x < 0 && offset > 0) || (velocity.x > 0 && offset < 0) {
|
||||
let pageWidth = self.itemSize.width + self.minimumLineSpacing
|
||||
newOffsetX += velocity.x > 0 ? pageWidth : -pageWidth
|
||||
}
|
||||
|
||||
return CGPoint(x: newOffsetX, y: proposedContentOffset.y)
|
||||
}
|
||||
|
||||
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
guard let collectionView = self.collectionView,
|
||||
let superAttributes = super.layoutAttributesForElements(in: rect) else {
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
}
|
||||
if scaleItems == false{
|
||||
return super.layoutAttributesForElements(in: rect)
|
||||
}
|
||||
|
||||
let contentOffset = collectionView.contentOffset
|
||||
let size = collectionView.bounds.size
|
||||
|
||||
let visibleRect = CGRect(x: contentOffset.x, y: contentOffset.y, width: size.width, height: size.height)
|
||||
let visibleCenterX = visibleRect.midX
|
||||
|
||||
guard case let newAttributesArray as [UICollectionViewLayoutAttributes] = NSArray(array: superAttributes, copyItems: true) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
newAttributesArray.forEach {
|
||||
let distanceFromCenter = visibleCenterX - $0.center.x
|
||||
let absDistanceFromCenter = min(abs(distanceFromCenter), self.scalingOffset)
|
||||
let scale = absDistanceFromCenter * (self.minimumScaleFactor - 1) / self.scalingOffset + 1
|
||||
$0.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
|
||||
|
||||
let alpha = absDistanceFromCenter * (self.minimumAlphaFactor - 1) / self.scalingOffset + 1
|
||||
$0.alpha = alpha
|
||||
}
|
||||
|
||||
return newAttributesArray
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: helpers
|
||||
|
||||
extension PageCollectionLayout {
|
||||
|
||||
fileprivate func configureInset() -> Void {
|
||||
guard let collectionView = self.collectionView else {
|
||||
return
|
||||
|
||||
fileprivate func configureInset() {
|
||||
guard let collectionView = self.collectionView else {
|
||||
return
|
||||
}
|
||||
|
||||
let inset = collectionView.bounds.size.width / 2 - itemSize.width / 2
|
||||
collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset)
|
||||
collectionView.contentOffset = CGPoint(x: -inset, y: 0)
|
||||
}
|
||||
|
||||
let inset = collectionView.bounds.size.width / 2 - itemSize.width / 2
|
||||
collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset)
|
||||
collectionView.contentOffset = CGPoint(x: -inset, y: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,42 +9,41 @@
|
||||
import UIKit
|
||||
|
||||
class PageCollectionView: UICollectionView {
|
||||
|
||||
}
|
||||
|
||||
// MARK: init
|
||||
|
||||
extension PageCollectionView {
|
||||
|
||||
class func createOnView(_ view: UIView,
|
||||
layout: UICollectionViewLayout,
|
||||
height: CGFloat,
|
||||
dataSource: UICollectionViewDataSource,
|
||||
delegate: UICollectionViewDelegate) -> PageCollectionView {
|
||||
|
||||
let collectionView = Init(PageCollectionView(frame: CGRect.zero, collectionViewLayout: layout)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.decelerationRate = UIScrollViewDecelerationRateFast
|
||||
$0.showsHorizontalScrollIndicator = false
|
||||
$0.dataSource = dataSource
|
||||
$0.delegate = delegate
|
||||
$0.backgroundColor = UIColor(white: 0, alpha: 0)
|
||||
}
|
||||
view.addSubview(collectionView)
|
||||
|
||||
// add constraints
|
||||
collectionView >>>- {
|
||||
$0.attribute = .height
|
||||
$0.constant = height
|
||||
return
|
||||
}
|
||||
[NSLayoutAttribute.left, .right, .centerY].forEach { attribute in
|
||||
(view, collectionView) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
|
||||
class func createOnView(_ view: UIView,
|
||||
layout: UICollectionViewLayout,
|
||||
height: CGFloat,
|
||||
dataSource: UICollectionViewDataSource,
|
||||
delegate: UICollectionViewDelegate) -> PageCollectionView {
|
||||
|
||||
let collectionView = Init(PageCollectionView(frame: CGRect.zero, collectionViewLayout: layout)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.decelerationRate = UIScrollViewDecelerationRateFast
|
||||
$0.showsHorizontalScrollIndicator = false
|
||||
$0.dataSource = dataSource
|
||||
$0.delegate = delegate
|
||||
$0.backgroundColor = UIColor(white: 0, alpha: 0)
|
||||
}
|
||||
view.addSubview(collectionView)
|
||||
|
||||
// add constraints
|
||||
collectionView >>>- {
|
||||
$0.attribute = .height
|
||||
$0.constant = height
|
||||
return
|
||||
}
|
||||
[NSLayoutAttribute.left, .right, .centerY].forEach { attribute in
|
||||
(view, collectionView) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return collectionView
|
||||
}
|
||||
|
||||
return collectionView
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,133 +10,132 @@ import UIKit
|
||||
|
||||
/// UIViewController with UICollectionView with custom transition method
|
||||
open class ExpandingViewController: UIViewController {
|
||||
|
||||
/// The default size to use for cells.
|
||||
open var itemSize = CGSize(width: 256, height: 335)
|
||||
|
||||
/// The collection view object managed by this view controller.
|
||||
open var collectionView: UICollectionView?
|
||||
|
||||
fileprivate var transitionDriver: TransitionDriver?
|
||||
|
||||
/// Index of current cell
|
||||
open var currentIndex: Int {
|
||||
guard let collectionView = self.collectionView else { return 0 }
|
||||
|
||||
let startOffset = (collectionView.bounds.size.width - itemSize.width) / 2
|
||||
guard let collectionLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
|
||||
return 0
|
||||
|
||||
/// The default size to use for cells.
|
||||
open var itemSize = CGSize(width: 256, height: 335)
|
||||
|
||||
/// The collection view object managed by this view controller.
|
||||
open var collectionView: UICollectionView?
|
||||
|
||||
fileprivate var transitionDriver: TransitionDriver?
|
||||
|
||||
/// Index of current cell
|
||||
open var currentIndex: Int {
|
||||
guard let collectionView = self.collectionView else { return 0 }
|
||||
|
||||
let startOffset = (collectionView.bounds.size.width - itemSize.width) / 2
|
||||
guard let collectionLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
|
||||
return 0
|
||||
}
|
||||
|
||||
let minimumLineSpacing = collectionLayout.minimumLineSpacing
|
||||
let a = collectionView.contentOffset.x + startOffset + itemSize.width / 2
|
||||
let b = itemSize.width + minimumLineSpacing
|
||||
return Int(a / b)
|
||||
}
|
||||
|
||||
let minimumLineSpacing = collectionLayout.minimumLineSpacing
|
||||
let a = collectionView.contentOffset.x + startOffset + itemSize.width / 2
|
||||
let b = itemSize.width + minimumLineSpacing
|
||||
return Int(a / b)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: life cicle
|
||||
|
||||
extension ExpandingViewController {
|
||||
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
commonInit()
|
||||
}
|
||||
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
commonInit()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Transition
|
||||
|
||||
public extension ExpandingViewController {
|
||||
|
||||
/**
|
||||
Pushes a view controller onto the receiver’s stack and updates the display with custom animation.
|
||||
|
||||
- parameter viewController: The table view controller to push onto the stack.
|
||||
*/
|
||||
func pushToViewController(_ viewController: ExpandingTableViewController) {
|
||||
guard let collectionView = self.collectionView,
|
||||
let navigationController = self.navigationController else {
|
||||
return
|
||||
|
||||
/**
|
||||
Pushes a view controller onto the receiver’s stack and updates the display with custom animation.
|
||||
|
||||
- parameter viewController: The table view controller to push onto the stack.
|
||||
*/
|
||||
func pushToViewController(_ viewController: ExpandingTableViewController) {
|
||||
guard let collectionView = self.collectionView,
|
||||
let navigationController = self.navigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
viewController.transitionDriver = transitionDriver
|
||||
let insets = viewController.automaticallyAdjustsScrollViewInsets
|
||||
let tabBarHeight = insets == true ? navigationController.navigationBar.frame.size.height : 0
|
||||
let stausBarHeight = insets == true ? UIApplication.shared.statusBarFrame.size.height : 0
|
||||
let backImage = getBackImage(viewController, headerHeight: viewController.headerHeight)
|
||||
|
||||
transitionDriver?.pushTransitionAnimationIndex(currentIndex,
|
||||
collecitionView: collectionView,
|
||||
backImage: backImage,
|
||||
headerHeight: viewController.headerHeight,
|
||||
insets: tabBarHeight + stausBarHeight) { headerView in
|
||||
viewController.tableView.tableHeaderView = headerView
|
||||
self.navigationController?.pushViewController(viewController, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
viewController.transitionDriver = transitionDriver
|
||||
let insets = viewController.automaticallyAdjustsScrollViewInsets
|
||||
let tabBarHeight = insets == true ? navigationController.navigationBar.frame.size.height : 0
|
||||
let stausBarHeight = insets == true ? UIApplication.shared.statusBarFrame.size.height : 0
|
||||
let backImage = getBackImage(viewController, headerHeight: viewController.headerHeight)
|
||||
|
||||
transitionDriver?.pushTransitionAnimationIndex(currentIndex,
|
||||
collecitionView: collectionView,
|
||||
backImage: backImage,
|
||||
headerHeight: viewController.headerHeight,
|
||||
insets: tabBarHeight + stausBarHeight) { headerView in
|
||||
viewController.tableView.tableHeaderView = headerView
|
||||
self.navigationController?.pushViewController(viewController, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: create
|
||||
|
||||
extension ExpandingViewController {
|
||||
|
||||
fileprivate func commonInit() {
|
||||
|
||||
let layout = PageCollectionLayout(itemSize: itemSize)
|
||||
collectionView = PageCollectionView.createOnView(view,
|
||||
layout: layout,
|
||||
height: itemSize.height + itemSize.height / 5 * 2,
|
||||
dataSource: self,
|
||||
delegate: self)
|
||||
if #available(iOS 10.0, *) {
|
||||
collectionView?.isPrefetchingEnabled = false
|
||||
}
|
||||
transitionDriver = TransitionDriver(view: view)
|
||||
}
|
||||
|
||||
fileprivate func commonInit() {
|
||||
|
||||
let layout = PageCollectionLayout(itemSize: itemSize)
|
||||
collectionView = PageCollectionView.createOnView(view,
|
||||
layout: layout,
|
||||
height: itemSize.height + itemSize.height / 5 * 2,
|
||||
dataSource: self,
|
||||
delegate: self)
|
||||
if #available(iOS 10.0, *) {
|
||||
collectionView?.isPrefetchingEnabled = false
|
||||
}
|
||||
transitionDriver = TransitionDriver(view: view)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension ExpandingViewController {
|
||||
|
||||
fileprivate func getBackImage(_ viewController: UIViewController, headerHeight: CGFloat) -> UIImage? {
|
||||
let imageSize = CGSize(width: viewController.view.bounds.width, height: viewController.view.bounds.height - headerHeight)
|
||||
let imageFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize)
|
||||
return viewController.view.takeSnapshot(imageFrame)
|
||||
}
|
||||
|
||||
fileprivate func getBackImage(_ viewController: UIViewController, headerHeight: CGFloat) -> UIImage? {
|
||||
let imageSize = CGSize(width: viewController.view.bounds.width, height: viewController.view.bounds.height - headerHeight)
|
||||
let imageFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize)
|
||||
return viewController.view.takeSnapshot(imageFrame)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UICollectionViewDataSource
|
||||
|
||||
extension ExpandingViewController: UICollectionViewDataSource, UICollectionViewDelegate {
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
guard case let cell as BasePageCollectionCell = cell else {
|
||||
return
|
||||
|
||||
open func collectionView(_: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt _: IndexPath) {
|
||||
guard case let cell as BasePageCollectionCell = cell else {
|
||||
return
|
||||
}
|
||||
|
||||
cell.configureCellViewConstraintsWithSize(itemSize)
|
||||
}
|
||||
|
||||
open func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
|
||||
fatalError("need emplementation in subclass")
|
||||
}
|
||||
|
||||
open func collectionView(_: UICollectionView, cellForItemAt _: IndexPath) -> UICollectionViewCell {
|
||||
fatalError("need emplementation in subclass")
|
||||
}
|
||||
|
||||
cell.configureCellViewConstraintsWithSize(itemSize)
|
||||
}
|
||||
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
fatalError("need emplementation in subclass")
|
||||
}
|
||||
|
||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
fatalError("need emplementation in subclass")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UIScrollViewDelegate
|
||||
// MARK: UIScrollViewDelegate
|
||||
|
||||
extension ExpandingViewController {
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
let indexPath = IndexPath(row: currentIndex, section: 0)
|
||||
if case let currentCell as BasePageCollectionCell = collectionView?.cellForItem(at: indexPath) {
|
||||
currentCell.configurationCell()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_: UIScrollView) {
|
||||
let indexPath = IndexPath(row: currentIndex, section: 0)
|
||||
if case let currentCell as BasePageCollectionCell = collectionView?.cellForItem(at: indexPath) {
|
||||
currentCell.configurationCell()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,23 +8,22 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
protocol CornerAnimatable {
|
||||
func animationCornerRadius(_ radius: CGFloat, duration: Double)
|
||||
func animationCornerRadius(_ radius: CGFloat, duration: Double)
|
||||
}
|
||||
|
||||
extension CornerAnimatable where Self: UIView {
|
||||
|
||||
func animationCornerRadius(_ radius: CGFloat, duration: Double) {
|
||||
let animation = Init(CABasicAnimation(keyPath: "cornerRadius")) {
|
||||
$0.duration = duration
|
||||
$0.toValue = radius
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false;
|
||||
|
||||
func animationCornerRadius(_ radius: CGFloat, duration: Double) {
|
||||
let animation = Init(CABasicAnimation(keyPath: "cornerRadius")) {
|
||||
$0.duration = duration
|
||||
$0.toValue = radius
|
||||
$0.fillMode = kCAFillModeForwards
|
||||
$0.isRemovedOnCompletion = false
|
||||
}
|
||||
|
||||
layer.add(animation, forKey: nil)
|
||||
}
|
||||
|
||||
layer.add(animation, forKey: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView: CornerAnimatable {}
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
internal func Init<Type>(_ value : Type, block: (_ object: Type) -> Void) -> Type
|
||||
{
|
||||
block(value)
|
||||
return value
|
||||
internal func Init<Type>(_ value: Type, block: (_ object: Type) -> Void) -> Type {
|
||||
block(value)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
import UIKit
|
||||
|
||||
struct ConstraintInfo {
|
||||
var attribute: NSLayoutAttribute = .left
|
||||
var secondAttribute: NSLayoutAttribute = .notAnAttribute
|
||||
var constant: CGFloat = 0
|
||||
var identifier: String?
|
||||
var relation: NSLayoutRelation = .equal
|
||||
var attribute: NSLayoutAttribute = .left
|
||||
var secondAttribute: NSLayoutAttribute = .notAnAttribute
|
||||
var constant: CGFloat = 0
|
||||
var identifier: String?
|
||||
var relation: NSLayoutRelation = .equal
|
||||
}
|
||||
|
||||
precedencegroup ConstOp {
|
||||
@@ -21,82 +21,80 @@ precedencegroup ConstOp {
|
||||
higherThan: AssignmentPrecedence
|
||||
}
|
||||
|
||||
infix operator >>>- : ConstOp
|
||||
infix operator >>>-: ConstOp
|
||||
|
||||
@discardableResult
|
||||
func >>>- <T: UIView> (left: (T, T), block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
info.secondAttribute = info.secondAttribute == .notAnAttribute ? info.attribute : info.secondAttribute
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left.1,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: left.0,
|
||||
attribute: info.secondAttribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.0.addConstraint(constraint)
|
||||
return constraint
|
||||
func >>>- <T: UIView>(left: (T, T), block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
info.secondAttribute = info.secondAttribute == .notAnAttribute ? info.attribute : info.secondAttribute
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left.1,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: left.0,
|
||||
attribute: info.secondAttribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.0.addConstraint(constraint)
|
||||
return constraint
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func >>>- <T: UIView> (left: T, block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: nil,
|
||||
attribute: info.attribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.addConstraint(constraint)
|
||||
return constraint
|
||||
func >>>- <T: UIView>(left: T, block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: nil,
|
||||
attribute: info.attribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.addConstraint(constraint)
|
||||
return constraint
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func >>>- <T: UIView> (left: (T, T, T), block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
info.secondAttribute = info.secondAttribute == .notAnAttribute ? info.attribute : info.secondAttribute
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left.1,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: left.2,
|
||||
attribute: info.secondAttribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.0.addConstraint(constraint)
|
||||
return constraint
|
||||
func >>>- <T: UIView>(left: (T, T, T), block: (inout ConstraintInfo) -> Void) -> NSLayoutConstraint {
|
||||
var info = ConstraintInfo()
|
||||
block(&info)
|
||||
info.secondAttribute = info.secondAttribute == .notAnAttribute ? info.attribute : info.secondAttribute
|
||||
|
||||
let constraint = NSLayoutConstraint(item: left.1,
|
||||
attribute: info.attribute,
|
||||
relatedBy: info.relation,
|
||||
toItem: left.2,
|
||||
attribute: info.secondAttribute,
|
||||
multiplier: 1,
|
||||
constant: info.constant)
|
||||
constraint.identifier = info.identifier
|
||||
left.0.addConstraint(constraint)
|
||||
return constraint
|
||||
}
|
||||
|
||||
// MARK: UIView
|
||||
|
||||
extension UIView {
|
||||
|
||||
func addScaleToFillConstratinsOnView(_ view: UIView) {
|
||||
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
|
||||
(self, view) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
|
||||
func addScaleToFillConstratinsOnView(_ view: UIView) {
|
||||
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
|
||||
(self, view) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getConstraint(_ attributes: NSLayoutAttribute) -> NSLayoutConstraint? {
|
||||
return constraints.filter {
|
||||
if $0.firstAttribute == attributes && $0.secondItem == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}.first
|
||||
}
|
||||
|
||||
|
||||
func getConstraint(_ attributes: NSLayoutAttribute) -> NSLayoutConstraint? {
|
||||
return constraints.filter {
|
||||
if $0.firstAttribute == attributes && $0.secondItem == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}.first
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,20 @@
|
||||
import UIKit
|
||||
|
||||
extension UIView {
|
||||
func takeSnapshot(_ frame: CGRect) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0.0)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext();
|
||||
context?.translateBy(x: frame.origin.x * -1, y: frame.origin.y * -1)
|
||||
|
||||
guard let currentContext = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
func takeSnapshot(_ frame: CGRect) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(frame.size, false, 0.0)
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
context?.translateBy(x: frame.origin.x * -1, y: frame.origin.y * -1)
|
||||
|
||||
guard let currentContext = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
layer.render(in: currentContext)
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
self.layer.render(in: currentContext)
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,24 @@
|
||||
import UIKit
|
||||
|
||||
protocol SubviewsForEach {
|
||||
func subviewsForEach(_ f: (UIView) -> Void)
|
||||
func subviewsForEach(_ f: (UIView) -> Void)
|
||||
}
|
||||
|
||||
extension SubviewsForEach where Self: UIView {
|
||||
|
||||
func subviewsForEach(_ f: (UIView) -> Void) {
|
||||
forEachView(self, f: f)
|
||||
}
|
||||
|
||||
fileprivate func forEachView(_ view: UIView, f: (UIView) -> Void) {
|
||||
view.subviews.forEach {
|
||||
f($0)
|
||||
|
||||
if $0.subviews.count > 0 {
|
||||
forEachView($0, f: f)
|
||||
}
|
||||
|
||||
func subviewsForEach(_ f: (UIView) -> Void) {
|
||||
forEachView(self, f: f)
|
||||
}
|
||||
|
||||
fileprivate func forEachView(_ view: UIView, f: (UIView) -> Void) {
|
||||
view.subviews.forEach {
|
||||
f($0)
|
||||
|
||||
if $0.subviews.count > 0 {
|
||||
forEachView($0, f: f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView: SubviewsForEach {}
|
||||
|
||||
@@ -9,258 +9,259 @@
|
||||
import UIKit
|
||||
|
||||
class TransitionDriver {
|
||||
|
||||
// MARK: Constants
|
||||
struct Constants {
|
||||
static let HideKey = 101
|
||||
}
|
||||
|
||||
// MARK: Vars
|
||||
|
||||
fileprivate let view: UIView
|
||||
fileprivate let duration: Double = 0.4
|
||||
|
||||
// for push animation
|
||||
fileprivate var copyCell: BasePageCollectionCell?
|
||||
fileprivate var currentCell: BasePageCollectionCell?
|
||||
fileprivate var backImageView: UIImageView?
|
||||
|
||||
fileprivate var leftCell: UICollectionViewCell?
|
||||
fileprivate var rightCell: UICollectionViewCell?
|
||||
fileprivate var step: CGFloat = 0
|
||||
|
||||
fileprivate var frontViewFrame = CGRect.zero
|
||||
fileprivate var backViewFrame = CGRect.zero
|
||||
|
||||
init(view: UIView) {
|
||||
self.view = view
|
||||
}
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
struct Constants {
|
||||
static let HideKey = 101
|
||||
}
|
||||
|
||||
// MARK: Vars
|
||||
|
||||
fileprivate let view: UIView
|
||||
fileprivate let duration: Double = 0.4
|
||||
|
||||
// for push animation
|
||||
fileprivate var copyCell: BasePageCollectionCell?
|
||||
fileprivate var currentCell: BasePageCollectionCell?
|
||||
fileprivate var backImageView: UIImageView?
|
||||
|
||||
fileprivate var leftCell: UICollectionViewCell?
|
||||
fileprivate var rightCell: UICollectionViewCell?
|
||||
fileprivate var step: CGFloat = 0
|
||||
|
||||
fileprivate var frontViewFrame = CGRect.zero
|
||||
fileprivate var backViewFrame = CGRect.zero
|
||||
|
||||
init(view: UIView) {
|
||||
self.view = view
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: control
|
||||
|
||||
extension TransitionDriver {
|
||||
|
||||
func pushTransitionAnimationIndex(_ currentIndex: Int,
|
||||
collecitionView: UICollectionView,
|
||||
backImage: UIImage?,
|
||||
headerHeight: CGFloat,
|
||||
insets: CGFloat,
|
||||
completion: @escaping (UIView) -> Void) {
|
||||
|
||||
guard case let cell as BasePageCollectionCell = collecitionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)),
|
||||
let copyView = cell.copyCell() else { return }
|
||||
copyCell = copyView
|
||||
|
||||
// move cells
|
||||
moveCellsCurrentIndex(currentIndex, collectionView: collecitionView)
|
||||
|
||||
currentCell = cell
|
||||
cell.isHidden = true
|
||||
|
||||
configurateCell(copyView, backImage: backImage)
|
||||
backImageView = addImageToView(copyView.backContainerView, image: backImage)
|
||||
|
||||
openBackViewConfigureConstraints(copyView, height: headerHeight, insets: insets)
|
||||
openFrontViewConfigureConstraints(copyView, height: headerHeight, insets: insets)
|
||||
|
||||
// corner animation
|
||||
copyView.backContainerView.animationCornerRadius(0, duration: duration)
|
||||
copyView.frontContainerView.animationCornerRadius(0, duration: duration)
|
||||
copyView.center = view.center
|
||||
|
||||
// constraints animation
|
||||
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.view.layoutIfNeeded()
|
||||
self.backImageView?.alpha = 1
|
||||
self.copyCell?.shadowView?.alpha = 0
|
||||
copyView.frontContainerView.subviewsForEach { if $0.tag == Constants.HideKey { $0.alpha = 0 } }
|
||||
}, completion: { success in
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: copyView.frontContainerView)
|
||||
guard case let headerView as UIView = NSKeyedUnarchiver.unarchiveObject(with: data) else {
|
||||
fatalError("must copy")
|
||||
}
|
||||
completion(headerView)
|
||||
})
|
||||
}
|
||||
|
||||
func popTransitionAnimationContantOffset(_ offset: CGFloat, backImage: UIImage?) {
|
||||
guard let copyCell = self.copyCell else {
|
||||
return
|
||||
|
||||
func pushTransitionAnimationIndex(_ currentIndex: Int,
|
||||
collecitionView: UICollectionView,
|
||||
backImage: UIImage?,
|
||||
headerHeight: CGFloat,
|
||||
insets: CGFloat,
|
||||
completion: @escaping (UIView) -> Void) {
|
||||
|
||||
guard case let cell as BasePageCollectionCell = collecitionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)),
|
||||
let copyView = cell.copyCell() else { return }
|
||||
copyCell = copyView
|
||||
|
||||
// move cells
|
||||
moveCellsCurrentIndex(currentIndex, collectionView: collecitionView)
|
||||
|
||||
currentCell = cell
|
||||
cell.isHidden = true
|
||||
|
||||
configurateCell(copyView, backImage: backImage)
|
||||
backImageView = addImageToView(copyView.backContainerView, image: backImage)
|
||||
|
||||
openBackViewConfigureConstraints(copyView, height: headerHeight, insets: insets)
|
||||
openFrontViewConfigureConstraints(copyView, height: headerHeight, insets: insets)
|
||||
|
||||
// corner animation
|
||||
copyView.backContainerView.animationCornerRadius(0, duration: duration)
|
||||
copyView.frontContainerView.animationCornerRadius(0, duration: duration)
|
||||
copyView.center = view.center
|
||||
|
||||
// constraints animation
|
||||
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.view.layoutIfNeeded()
|
||||
self.backImageView?.alpha = 1
|
||||
self.copyCell?.shadowView?.alpha = 0
|
||||
copyView.frontContainerView.subviewsForEach { if $0.tag == Constants.HideKey { $0.alpha = 0 } }
|
||||
}, completion: { _ in
|
||||
let data = NSKeyedArchiver.archivedData(withRootObject: copyView.frontContainerView)
|
||||
guard case let headerView as UIView = NSKeyedUnarchiver.unarchiveObject(with: data) else {
|
||||
fatalError("must copy")
|
||||
}
|
||||
completion(headerView)
|
||||
})
|
||||
}
|
||||
|
||||
func popTransitionAnimationContantOffset(_ offset: CGFloat, backImage: UIImage?) {
|
||||
guard let copyCell = self.copyCell else {
|
||||
return
|
||||
}
|
||||
|
||||
backImageView?.image = backImage
|
||||
// configuration start position
|
||||
configureCellBeforeClose(copyCell, offset: offset)
|
||||
|
||||
closeBackViewConfigurationConstraints(copyCell)
|
||||
closeFrontViewConfigurationConstraints(copyCell)
|
||||
|
||||
// corner animation
|
||||
copyCell.backContainerView.animationCornerRadius(copyCell.backContainerView.layer.cornerRadius, duration: duration)
|
||||
copyCell.frontContainerView.animationCornerRadius(copyCell.frontContainerView.layer.cornerRadius, duration: duration)
|
||||
|
||||
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.rightCell?.center.x -= self.step
|
||||
self.leftCell?.center.x += self.step
|
||||
|
||||
self.view.layoutIfNeeded()
|
||||
self.backImageView?.alpha = 0
|
||||
copyCell.shadowView?.alpha = 1
|
||||
|
||||
copyCell.frontContainerView.subviewsForEach { if $0.tag == Constants.HideKey { $0.alpha = 1 } }
|
||||
}, completion: { _ in
|
||||
self.currentCell?.isHidden = false
|
||||
self.removeCurrentCell()
|
||||
})
|
||||
}
|
||||
|
||||
backImageView?.image = backImage
|
||||
// configuration start position
|
||||
configureCellBeforeClose(copyCell, offset: offset)
|
||||
|
||||
closeBackViewConfigurationConstraints(copyCell)
|
||||
closeFrontViewConfigurationConstraints(copyCell)
|
||||
|
||||
// corner animation
|
||||
copyCell.backContainerView.animationCornerRadius(copyCell.backContainerView.layer.cornerRadius, duration: duration)
|
||||
copyCell.frontContainerView.animationCornerRadius(copyCell.frontContainerView.layer.cornerRadius, duration: duration)
|
||||
|
||||
UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions(), animations: {
|
||||
self.rightCell?.center.x -= self.step
|
||||
self.leftCell?.center.x += self.step
|
||||
|
||||
self.view.layoutIfNeeded()
|
||||
self.backImageView?.alpha = 0
|
||||
copyCell.shadowView?.alpha = 1
|
||||
|
||||
copyCell.frontContainerView.subviewsForEach { if $0.tag == Constants.HideKey { $0.alpha = 1 } }
|
||||
}, completion: { success in
|
||||
self.currentCell?.isHidden = false
|
||||
self.removeCurrentCell()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
extension TransitionDriver {
|
||||
|
||||
fileprivate func removeCurrentCell() {
|
||||
if case let currentCell as UIView = self.copyCell {
|
||||
currentCell.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func configurateCell(_ cell: BasePageCollectionCell, backImage: UIImage?) {
|
||||
cell.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(cell)
|
||||
|
||||
// add constraints
|
||||
[(NSLayoutAttribute.width, cell.bounds.size.width), (NSLayoutAttribute.height, cell.bounds.size.height)].forEach { info in
|
||||
cell >>>- {
|
||||
$0.attribute = info.0
|
||||
$0.constant = info.1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
[NSLayoutAttribute.centerX, .centerY].forEach { attribute in
|
||||
(view, cell) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
cell.layoutIfNeeded()
|
||||
}
|
||||
|
||||
fileprivate func addImageToView(_ view: UIView, image: UIImage?) -> UIImageView? {
|
||||
guard let image = image else { return nil }
|
||||
|
||||
let imageView = Init(UIImageView(image: image)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alpha = 0
|
||||
}
|
||||
view.addSubview(imageView)
|
||||
|
||||
// add constraints
|
||||
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
|
||||
(view, imageView) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
imageView.layoutIfNeeded()
|
||||
|
||||
return imageView
|
||||
}
|
||||
|
||||
fileprivate func moveCellsCurrentIndex(_ currentIndex: Int, collectionView: UICollectionView) {
|
||||
self.leftCell = nil
|
||||
self.rightCell = nil
|
||||
|
||||
if let leftCell = collectionView.cellForItem(at: IndexPath(row: currentIndex - 1, section: 0)) {
|
||||
let step = leftCell.frame.size.width + (leftCell.frame.origin.x - collectionView.contentOffset.x)
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
leftCell.center.x -= step
|
||||
})
|
||||
self.leftCell = leftCell
|
||||
self.step = step
|
||||
fileprivate func removeCurrentCell() {
|
||||
if case let currentCell as UIView = copyCell {
|
||||
currentCell.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let rightCell = collectionView.cellForItem(at: IndexPath(row: currentIndex + 1, section: 0)) {
|
||||
let step = collectionView.frame.size.width - (rightCell.frame.origin.x - collectionView.contentOffset.x)
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
rightCell.center.x += step
|
||||
})
|
||||
self.rightCell = rightCell
|
||||
self.step = step
|
||||
|
||||
fileprivate func configurateCell(_ cell: BasePageCollectionCell, backImage _: UIImage?) {
|
||||
cell.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(cell)
|
||||
|
||||
// add constraints
|
||||
[(NSLayoutAttribute.width, cell.bounds.size.width), (NSLayoutAttribute.height, cell.bounds.size.height)].forEach { info in
|
||||
cell >>>- {
|
||||
$0.attribute = info.0
|
||||
$0.constant = info.1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
[NSLayoutAttribute.centerX, .centerY].forEach { attribute in
|
||||
(view, cell) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
cell.layoutIfNeeded()
|
||||
}
|
||||
|
||||
fileprivate func addImageToView(_ view: UIView, image: UIImage?) -> UIImageView? {
|
||||
guard let image = image else { return nil }
|
||||
|
||||
let imageView = Init(UIImageView(image: image)) {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alpha = 0
|
||||
}
|
||||
view.addSubview(imageView)
|
||||
|
||||
// add constraints
|
||||
[NSLayoutAttribute.left, .right, .top, .bottom].forEach { attribute in
|
||||
(view, imageView) >>>- {
|
||||
$0.attribute = attribute
|
||||
return
|
||||
}
|
||||
}
|
||||
imageView.layoutIfNeeded()
|
||||
|
||||
return imageView
|
||||
}
|
||||
|
||||
fileprivate func moveCellsCurrentIndex(_ currentIndex: Int, collectionView: UICollectionView) {
|
||||
leftCell = nil
|
||||
rightCell = nil
|
||||
|
||||
if let leftCell = collectionView.cellForItem(at: IndexPath(row: currentIndex - 1, section: 0)) {
|
||||
let step = leftCell.frame.size.width + (leftCell.frame.origin.x - collectionView.contentOffset.x)
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
leftCell.center.x -= step
|
||||
})
|
||||
self.leftCell = leftCell
|
||||
self.step = step
|
||||
}
|
||||
|
||||
if let rightCell = collectionView.cellForItem(at: IndexPath(row: currentIndex + 1, section: 0)) {
|
||||
let step = collectionView.frame.size.width - (rightCell.frame.origin.x - collectionView.contentOffset.x)
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
rightCell.center.x += step
|
||||
})
|
||||
self.rightCell = rightCell
|
||||
self.step = step
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: animations
|
||||
|
||||
extension TransitionDriver {
|
||||
|
||||
fileprivate func openFrontViewConfigureConstraints(_ cell: BasePageCollectionCell, height: CGFloat, insets: CGFloat) {
|
||||
|
||||
if let heightConstraint = cell.frontContainerView.getConstraint(.height) {
|
||||
frontViewFrame.size.height = heightConstraint.constant
|
||||
heightConstraint.constant = height
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.frontContainerView.getConstraint(.width) {
|
||||
frontViewFrame.size.width = widthConstraint.constant
|
||||
widthConstraint.constant = view.bounds.size.width
|
||||
}
|
||||
|
||||
frontViewFrame.origin.y = cell.frontConstraintY.constant
|
||||
cell.frontConstraintY.constant = -view.bounds.size.height / 2 + height / 2 + insets
|
||||
}
|
||||
|
||||
fileprivate func openBackViewConfigureConstraints(_ cell: BasePageCollectionCell, height: CGFloat, insets: CGFloat) {
|
||||
|
||||
if let heightConstraint = cell.backContainerView.getConstraint(.height) {
|
||||
backViewFrame.size.height = heightConstraint.constant
|
||||
heightConstraint.constant = view.bounds.size.height - height
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.backContainerView.getConstraint(.width) {
|
||||
backViewFrame.size.width = widthConstraint.constant
|
||||
widthConstraint.constant = view.bounds.size.width
|
||||
}
|
||||
|
||||
backViewFrame.origin.y = cell.backConstraintY.constant
|
||||
cell.backConstraintY.constant = view.bounds.size.height / 2 - (view.bounds.size.height - 236) / 2 + insets
|
||||
}
|
||||
|
||||
fileprivate func closeBackViewConfigurationConstraints(_ cell: BasePageCollectionCell?) {
|
||||
guard let cell = cell else { return }
|
||||
|
||||
let heightConstraint = cell.backContainerView.getConstraint(.height)
|
||||
heightConstraint?.constant = backViewFrame.size.height
|
||||
|
||||
let widthConstraint = cell.backContainerView.getConstraint(.width)
|
||||
widthConstraint?.constant = backViewFrame.size.width
|
||||
fileprivate func openFrontViewConfigureConstraints(_ cell: BasePageCollectionCell, height: CGFloat, insets: CGFloat) {
|
||||
|
||||
cell.backConstraintY.constant = backViewFrame.origin.y
|
||||
}
|
||||
|
||||
fileprivate func closeFrontViewConfigurationConstraints(_ cell: BasePageCollectionCell?) {
|
||||
guard let cell = cell else { return }
|
||||
|
||||
if let heightConstraint = cell.frontContainerView.getConstraint(.height) {
|
||||
heightConstraint.constant = frontViewFrame.size.height
|
||||
if let heightConstraint = cell.frontContainerView.getConstraint(.height) {
|
||||
frontViewFrame.size.height = heightConstraint.constant
|
||||
heightConstraint.constant = height
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.frontContainerView.getConstraint(.width) {
|
||||
frontViewFrame.size.width = widthConstraint.constant
|
||||
widthConstraint.constant = view.bounds.size.width
|
||||
}
|
||||
|
||||
frontViewFrame.origin.y = cell.frontConstraintY.constant
|
||||
cell.frontConstraintY.constant = -view.bounds.size.height / 2 + height / 2 + insets
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.frontContainerView.getConstraint(.width) {
|
||||
widthConstraint.constant = frontViewFrame.size.width
|
||||
|
||||
fileprivate func openBackViewConfigureConstraints(_ cell: BasePageCollectionCell, height: CGFloat, insets: CGFloat) {
|
||||
|
||||
if let heightConstraint = cell.backContainerView.getConstraint(.height) {
|
||||
backViewFrame.size.height = heightConstraint.constant
|
||||
heightConstraint.constant = view.bounds.size.height - height
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.backContainerView.getConstraint(.width) {
|
||||
backViewFrame.size.width = widthConstraint.constant
|
||||
widthConstraint.constant = view.bounds.size.width
|
||||
}
|
||||
|
||||
backViewFrame.origin.y = cell.backConstraintY.constant
|
||||
cell.backConstraintY.constant = view.bounds.size.height / 2 - (view.bounds.size.height - height) / 2 + insets
|
||||
}
|
||||
|
||||
cell.frontConstraintY.constant = frontViewFrame.origin.y
|
||||
}
|
||||
|
||||
fileprivate func configureCellBeforeClose(_ cell: BasePageCollectionCell, offset: CGFloat) {
|
||||
cell.frontConstraintY.constant -= offset
|
||||
cell.backConstraintY.constant -= offset / 2.0
|
||||
if let heightConstraint = cell.backContainerView.getConstraint(.height) {
|
||||
heightConstraint.constant += offset
|
||||
|
||||
fileprivate func closeBackViewConfigurationConstraints(_ cell: BasePageCollectionCell?) {
|
||||
guard let cell = cell else { return }
|
||||
|
||||
let heightConstraint = cell.backContainerView.getConstraint(.height)
|
||||
heightConstraint?.constant = backViewFrame.size.height
|
||||
|
||||
let widthConstraint = cell.backContainerView.getConstraint(.width)
|
||||
widthConstraint?.constant = backViewFrame.size.width
|
||||
|
||||
cell.backConstraintY.constant = backViewFrame.origin.y
|
||||
}
|
||||
|
||||
fileprivate func closeFrontViewConfigurationConstraints(_ cell: BasePageCollectionCell?) {
|
||||
guard let cell = cell else { return }
|
||||
|
||||
if let heightConstraint = cell.frontContainerView.getConstraint(.height) {
|
||||
heightConstraint.constant = frontViewFrame.size.height
|
||||
}
|
||||
|
||||
if let widthConstraint = cell.frontContainerView.getConstraint(.width) {
|
||||
widthConstraint.constant = frontViewFrame.size.width
|
||||
}
|
||||
|
||||
cell.frontConstraintY.constant = frontViewFrame.origin.y
|
||||
}
|
||||
|
||||
fileprivate func configureCellBeforeClose(_ cell: BasePageCollectionCell, offset: CGFloat) {
|
||||
cell.frontConstraintY.constant -= offset
|
||||
cell.backConstraintY.constant -= offset / 2.0
|
||||
if let heightConstraint = cell.backContainerView.getConstraint(.height) {
|
||||
heightConstraint.constant += offset
|
||||
}
|
||||
cell.contentView.layoutIfNeeded()
|
||||
}
|
||||
cell.contentView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'expanding-collection'
|
||||
s.version = '1.1.2'
|
||||
s.version = '1.1.3'
|
||||
s.summary = 'Transition animtion from CollectionView to TableView'
|
||||
s.license = 'MIT'
|
||||
s.homepage = 'https://github.com/Ramotion/expanding-collection'
|
||||
|
||||
Reference in New Issue
Block a user