├── .DS_Store ├── .gitignore ├── JMMaterialTableView.podspec ├── JMMaterialTableView ├── JMMaterialLayout.swift └── JMMaterialTableView.swift ├── JMMaterialTableViewDemo ├── .DS_Store ├── JMMaterialTableView.xcodeproj │ └── project.pbxproj ├── JMMaterialTableView │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── JMMaterialCell.swift │ ├── JMMaterialTableView │ │ ├── JMMaterialLayout.swift │ │ └── JMMaterialTableView.swift │ ├── TableViewDataSource.swift │ └── ViewController.swift ├── JMMaterialTableViewTests │ ├── Info.plist │ └── JMMaterialTableViewTests.swift └── JMMaterialTableViewUITests │ ├── Info.plist │ └── JMMaterialTableViewUITests.swift ├── LICENSE ├── README.md ├── demoScreen1.gif └── demoScreen2.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ijinmao/JMMaterialTableView/c7d26e5e9a32a31706325bea02b32236c0b7a167/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | -------------------------------------------------------------------------------- /JMMaterialTableView.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint JMMaterialTableView.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "JMMaterialTableView" 19 | s.version = "0.1.1" 20 | s.summary = "JMMaterialTableView is a tableView inspired by Primer iOS app in Swift." 21 | 22 | s.description = "The UX of Primer iOS app is amazing, and Google's Primer team wrote an great article(https://medium.com/google-design/designing-a-ux-for-learning-ebed4fa0a798#.2ee2djini) about how they approached UX. JMMaterialTableView is inspired by them." 23 | 24 | s.homepage = "https://github.com/ijinmao/JMMaterialTableView" 25 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 26 | 27 | 28 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 29 | # 30 | # Licensing your code is important. See http://choosealicense.com for more info. 31 | # CocoaPods will detect a license file if there is a named LICENSE* 32 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 33 | # 34 | 35 | s.license = "MIT" 36 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 37 | 38 | 39 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 40 | # 41 | # Specify the authors of the library, with email addresses. Email addresses 42 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 43 | # accepts just a name if you'd rather not provide an email address. 44 | # 45 | # Specify a social_media_url where others can refer to, for example a twitter 46 | # profile URL. 47 | # 48 | 49 | s.author = { "ijinmao" => "340052204@qq.com" } 50 | # Or just: s.author = "" 51 | # s.authors = { "" => "" } 52 | # s.social_media_url = "http://twitter.com/" 53 | 54 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 55 | # 56 | # If this Pod runs only on iOS or OS X, then specify the platform and 57 | # the deployment target. You can optionally include the target after the platform. 58 | # 59 | 60 | s.platform = :ios, '8.0' 61 | 62 | # When using multiple platforms 63 | # s.ios.deployment_target = "5.0" 64 | # s.osx.deployment_target = "10.7" 65 | 66 | 67 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 68 | # 69 | # Specify the location from where the source should be retrieved. 70 | # Supports git, hg, bzr, svn and HTTP. 71 | # 72 | 73 | s.source = { :git => "https://github.com/ijinmao/JMMaterialTableView.git", :tag => "0.1.1" } 74 | 75 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 76 | # 77 | # CocoaPods is smart about how it includes source code. For source files 78 | # giving a folder will include any h, m, mm, c & cpp files. For header 79 | # files it will include any header in the folder. 80 | # Not including the public_header_files will make all headers public. 81 | # 82 | 83 | s.source_files = "JMMaterialTableView", "JMMaterialTableView/*.swift" 84 | s.exclude_files = "JMMaterialTableView/Exclude" 85 | 86 | # s.public_header_files = "Classes/**/*.h" 87 | 88 | 89 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 90 | # 91 | # A list of resources included with the Pod. These are copied into the 92 | # target bundle with a build phase script. Anything else will be cleaned. 93 | # You can preserve files from being cleaned, please don't preserve 94 | # non-essential files like tests, examples and documentation. 95 | # 96 | 97 | # s.resource = "icon.png" 98 | # s.resources = "Resources/*.png" 99 | 100 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 101 | 102 | 103 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 104 | # 105 | # Link your library with frameworks, or libraries. Libraries do not include 106 | # the lib prefix of their name. 107 | # 108 | 109 | # s.framework = "SomeFramework" 110 | # s.frameworks = "SomeFramework", "AnotherFramework" 111 | 112 | # s.library = "iconv" 113 | # s.libraries = "iconv", "xml2" 114 | 115 | 116 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 117 | # 118 | # If your library depends on compiler flags you can set them in the xcconfig hash 119 | # where they will only apply to your library. If you depend on other Podspecs 120 | # you can include multiple dependencies to ensure it works. 121 | 122 | # s.requires_arc = true 123 | 124 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 125 | # s.dependency "JSONKit", "~> 1.4" 126 | 127 | end 128 | -------------------------------------------------------------------------------- /JMMaterialTableView/JMMaterialLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JMMaterialLayout.swift 3 | // JMMaterialTableView 4 | // 5 | // Created by dingnan on 15/10/20. 6 | // Copyright © 2015年 ijinmao. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol JMMaterialLayoutDelegate { 13 | func updateItemsAttributes(allItemsAttributes: [UICollectionViewLayoutAttributes]?) 14 | } 15 | 16 | class JMMaterialLayout: UICollectionViewFlowLayout { 17 | 18 | var enableTransformation: Bool = true 19 | 20 | var kAttributesTransform: CGFloat = 0.03 21 | 22 | var delegate: JMMaterialLayoutDelegate? 23 | 24 | override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 25 | if let allItems = super.layoutAttributesForElementsInRect(rect) { 26 | var headerAttributes: UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes() 27 | var currentAttributes: UICollectionViewLayoutAttributes? = nil 28 | var lastAttributes: UICollectionViewLayoutAttributes = UICollectionViewLayoutAttributes() 29 | for attributes: UICollectionViewLayoutAttributes in allItems { 30 | let indexPath = attributes.indexPath 31 | if attributes.representedElementKind == UICollectionElementKindSectionHeader { 32 | headerAttributes = attributes 33 | } else { 34 | if let firstItemAttributes = currentAttributes { 35 | if indexPath.row > firstItemAttributes.indexPath.row && collectionView!.contentOffset.y >= 0 { 36 | currentAttributes = attributes 37 | updateItemAttributes(firstItemAttributes, headerAttributes: headerAttributes) 38 | } 39 | }else { 40 | currentAttributes = attributes 41 | } 42 | if indexPath.row > 0 && collectionView!.contentOffset.y < 0 { 43 | currentAttributes = attributes 44 | updateItemAttributesAtTopPosition(currentAttributes!, lastAttributes: lastAttributes, lastItemIndex: allItems.last!.indexPath.row) 45 | } 46 | lastAttributes = attributes 47 | } 48 | } 49 | delegate?.updateItemsAttributes(allItems) 50 | return allItems 51 | } 52 | return [] 53 | } 54 | 55 | //transform top items attributes 56 | private func updateItemAttributes(attributes: UICollectionViewLayoutAttributes, headerAttributes: UICollectionViewLayoutAttributes) { 57 | let itemOriginalFrame = attributes.frame 58 | let itemMaxY: CGFloat = CGRectGetMaxY(attributes.frame) - CGRectGetHeight(headerAttributes.bounds) 59 | let itemMinY: CGFloat = (self.collectionView?.contentOffset.y)! + (self.collectionView?.contentInset.top)! 60 | let largerYPosition: CGFloat = max(itemMinY, attributes.frame.origin.y) 61 | let finalPosition: CGFloat = min(itemMaxY, largerYPosition) 62 | var itemOrigin = attributes.frame.origin 63 | let deltaY: CGFloat = (finalPosition - itemOrigin.y) / CGRectGetHeight(attributes.frame) 64 | itemOrigin.y = finalPosition 65 | 66 | let transformCoef: CGFloat = (1 - deltaY * kAttributesTransform) 67 | if enableTransformation { 68 | attributes.transform = CGAffineTransformMakeScale(transformCoef, transformCoef) 69 | } 70 | attributes.frame = CGRectMake(itemOriginalFrame.origin.x, itemOrigin.y, itemOriginalFrame.size.width, itemOriginalFrame.size.height) 71 | attributes.zIndex = attributes.indexPath.row; 72 | 73 | } 74 | 75 | //transform items when collectionView did scroll to top 76 | private func updateItemAttributesAtTopPosition(attributes: UICollectionViewLayoutAttributes, lastAttributes: UICollectionViewLayoutAttributes, lastItemIndex: Int) { 77 | var collectionViewDeltaY = abs((collectionView?.contentOffset.y)! / collectionView!.frame.size.height) 78 | //along y axis, reduce the cover area 79 | let coef: CGFloat = 0.08 80 | collectionViewDeltaY = collectionViewDeltaY - CGFloat(attributes.indexPath.row) * coef * collectionViewDeltaY 81 | let itemOriginY = lastAttributes.frame.origin.y + lastAttributes.frame.size.height * (1 - collectionViewDeltaY) 82 | attributes.frame = CGRectMake(attributes.frame.origin.x, itemOriginY, attributes.frame.size.width, attributes.frame.size.height) 83 | attributes.zIndex = attributes.indexPath.row 84 | } 85 | 86 | override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { 87 | return true 88 | } 89 | } -------------------------------------------------------------------------------- /JMMaterialTableView/JMMaterialTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JMMaterialTableView.swift 3 | // JMMaterialTableView 4 | // 5 | // Created by dingnan on 15/10/20. 6 | // Copyright © 2015年 ijinmao. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class JMMaterialTableView: UICollectionView, UIScrollViewDelegate, UICollectionViewDelegateFlowLayout, JMMaterialLayoutDelegate { 13 | // ------ configurable properties 14 | 15 | var shadowOffset: CGFloat = -3.0 16 | var shadowRadius: CGFloat = 2.0 17 | var shadowOpacity: Float = 0.15 18 | var shadowColor: CGColorRef = UIColor.darkGrayColor().CGColor 19 | var transformCoef: CGFloat = 0.04 { 20 | didSet { 21 | materialLayout?.kAttributesTransform = transformCoef 22 | } 23 | } 24 | var enableTransformation: Bool = true { 25 | didSet { 26 | materialLayout?.enableTransformation = enableTransformation 27 | } 28 | } 29 | var enableAutoScroll: Bool = true 30 | var enableCellShadow: Bool = true 31 | var scrollDecelerationRate: CGFloat = 0.3 32 | var shadowAnimationDuration: CFTimeInterval = 0.3 33 | 34 | var cellSize: CGSize = CGSize.zero 35 | 36 | var materialLayout: JMMaterialLayout! 37 | 38 | //-------- 39 | 40 | private var cellMinSpacing: CGFloat = 0 41 | 42 | private var isScrollEndDecelerating: Bool = true 43 | 44 | private var lastScrollViewOffsetY: CGFloat = 0 45 | 46 | private var scrollWillEndOffsetY: CGFloat = 0 47 | 48 | override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { 49 | super.init(frame: frame, collectionViewLayout: layout) 50 | self.delegate = self 51 | self.decelerationRate = scrollDecelerationRate 52 | materialLayout = layout as! JMMaterialLayout 53 | materialLayout?.delegate = self 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | super.init(coder: aDecoder) 58 | } 59 | 60 | // MARK: UICollectionViewDelegateFlowLayout 61 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { 62 | return cellSize 63 | } 64 | 65 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat { 66 | return cellMinSpacing 67 | } 68 | 69 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets { 70 | return UIEdgeInsetsZero 71 | } 72 | 73 | // MARK: JMMaterialLayoutDelegate 74 | func updateItemsAttributes(allItemsAttributes: [UICollectionViewLayoutAttributes]?) { 75 | //the index of the first cell of not transformed 76 | var notTransformCellIndexPath: NSIndexPath? = nil 77 | //get indexs of shadowed cell and normal cells 78 | var shadowIndexPaths = [NSIndexPath]() 79 | var normalIndexPaths = [NSIndexPath]() 80 | var lastItemAttributes: UICollectionViewLayoutAttributes? = nil 81 | if let allItems = allItemsAttributes { 82 | for attributes in allItems { 83 | let indexPath = attributes.indexPath 84 | if indexPath.row > 0 { 85 | if lastItemAttributes == nil { 86 | shadowIndexPaths.append(attributes.indexPath) 87 | }else if let lastItemAttr = lastItemAttributes { 88 | if lastItemAttr.frame.origin.y + lastItemAttr.frame.size.height > attributes.frame.origin.y { 89 | shadowIndexPaths.append(attributes.indexPath) 90 | } else { 91 | normalIndexPaths.append(attributes.indexPath) 92 | } 93 | } 94 | } else { 95 | normalIndexPaths.append(attributes.indexPath) 96 | } 97 | lastItemAttributes = attributes 98 | //query the first cell of not transformed 99 | if attributes.frame.origin.x == 0 && notTransformCellIndexPath == nil { 100 | notTransformCellIndexPath = attributes.indexPath 101 | } 102 | } 103 | //hide the cells before the first cell of not transformed 104 | if notTransformCellIndexPath != nil { 105 | if let firstAttributs = allItemsAttributes?.first { 106 | for var i=firstAttributs.indexPath.row; i