Skip to content

Commit 1b5643d

Browse files
committed
Initial commit :)
0 parents  commit 1b5643d

16 files changed

+834
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj

.swift-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.1

.travis.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
osx_image: xcode8.3
2+
language: objective-c
3+
sudo: required
4+
env:
5+
global:
6+
- PROJECT="UICollectionViewFlexLayout.xcodeproj"
7+
- SCHEME="UICollectionViewFlexLayout"
8+
- IOS_SDK="iphonesimulator10.3"
9+
- MACOS_SDK="macosx10.12"
10+
- TVOS_SDK="appletvsimulator10.2"
11+
- WATCHOS_SDK="watchsimulator3.2"
12+
matrix:
13+
- SDK="$IOS_SDK" TEST=1 DESTINATION="platform=iOS Simulator,name=iPhone 7,OS=10.3.1"
14+
15+
install:
16+
- eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)"
17+
- swift --version
18+
19+
before_script:
20+
- set -o pipefail
21+
- swift package generate-xcodeproj
22+
23+
script:
24+
- if [ $TEST == 1 ]; then
25+
xcodebuild clean build test
26+
-project "$PROJECT"
27+
-scheme "$SCHEME"
28+
-sdk "$SDK"
29+
-destination "$DESTINATION"
30+
-configuration Debug
31+
-enableCodeCoverage YES
32+
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c;
33+
else
34+
xcodebuild clean build
35+
-project "$PROJECT"
36+
-scheme "$SCHEME"
37+
-sdk "$SDK"
38+
-destination "$DESTINATION"
39+
-configuration Debug
40+
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c;
41+
fi
42+
43+
after_success:
44+
- if [ $TEST == 1 ]; then
45+
bash <(curl -s https://codecov.io/bash) -J 'UICollectionViewFlexLayout';
46+
fi

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Suyeol Jeon (xoul.kr)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.pins

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"autoPin": true,
3+
"pins": [
4+
{
5+
"package": "Stubber",
6+
"reason": null,
7+
"repositoryURL": "https://github.com/devxoul/Stubber.git",
8+
"version": "0.2.0"
9+
}
10+
],
11+
"version": 1
12+
}

Package.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// swift-tools-version:3.1
2+
3+
import Foundation
4+
import PackageDescription
5+
6+
var dependencies: [Package.Dependency] = []
7+
8+
let isTest = ProcessInfo.processInfo.environment["TEST"] == "1"
9+
if isTest {
10+
dependencies.append(
11+
.Package(url: "https://github.com/devxoul/Stubber.git", majorVersion: 0)
12+
)
13+
}
14+
15+
let package = Package(
16+
name: "UICollectionViewFlexLayout",
17+
dependencies: dependencies
18+
)

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# UICollectionViewFlexLayout
2+
3+
![Swift](https://img.shields.io/badge/Swift-3.1-orange.svg)
4+
[![CocoaPods](http://img.shields.io/cocoapods/v/UICollectionViewFlexLayout.svg)](https://cocoapods.org/pods/UICollectionViewFlexLayout)
5+
[![Build Status](https://travis-ci.org/devxoul/UICollectionViewFlexLayout.svg?branch=master)](https://travis-ci.org/devxoul/UICollectionViewFlexLayout)
6+
[![Codecov](https://img.shields.io/codecov/c/github/devxoul/UICollectionViewFlexLayout.svg)](https://codecov.io/gh/devxoul/UICollectionViewFlexLayout)
7+
8+
UICollectionViewFlexLayout is a drop-in replacement for UICollectionViewFlowLayout. Currently in development.
9+
10+
## Features
11+
12+
* [x] Section Spacing
13+
* [x] Section Margin
14+
* [x] Section Padding
15+
* [x] Section Background
16+
* [x] Item Spacing
17+
* [x] Item Margin
18+
* [x] Item Padding
19+
* [x] Item Size
20+
21+
## Basic Concept
22+
23+
Don't let cells have margins and paddings. Cell metrics are now set outside of the cell. Just focus on contents.
24+
25+
![idea](https://user-images.githubusercontent.com/931655/28981116-59c51f24-798b-11e7-8877-b4e7f83644d1.jpg)
26+
27+
## Usage
28+
29+
### UICollectionViewDelegateFlexLayout
30+
31+
```swift
32+
protocol UICollectionViewDelegateFlexLayout {
33+
// section vertical spacing
34+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, verticalSpacingBetweenSectionAt section: Int, and nextSection: Int) -> CGFloat
35+
36+
// section margin
37+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, marginForSectionAt section: Int) -> UIEdgeInsets
38+
39+
// section padding
40+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, paddingForSectionAt section: Int) -> UIEdgeInsets
41+
42+
// item horizontal spacing
43+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, horizontalSpacingBetweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat
44+
45+
// item vertical spacing
46+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, verticalSpacingBetweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat
47+
48+
// item margin
49+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, marginForItemAt indexPath: IndexPath) -> UIEdgeInsets
50+
51+
// item padding
52+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, paddingForItemAt indexPath: IndexPath) -> UIEdgeInsets
53+
54+
// item size
55+
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
56+
}
57+
```
58+
59+
### Section Background
60+
61+
```swift
62+
// register
63+
collectionView.register(MyBackgroundView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionBackground, withReuseIdentifier: "myBackgroundView")
64+
65+
// configure
66+
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
67+
let backgroundView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionBackground, withReuseIdentifier: "myBackgroundView", for: indexPath)
68+
if indexPath.section == 0 {
69+
backgroundView.backgroundColor = .white
70+
} else {
71+
backgroundView.backgroundColor = .clear
72+
}
73+
return backgroundView
74+
}
75+
```
76+
77+
## Contributing
78+
79+
```console
80+
$ TEST=1 swift package generate-xcodeproj
81+
```
82+
83+
## License
84+
85+
UICollectionViewFlexLayout is under MIT license. See the [LICENSE](LICENSE) file for more info.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import UIKit
2+
3+
@objc public protocol UICollectionViewDelegateFlexLayout: UICollectionViewDelegate {
4+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
5+
6+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, verticalSpacingBetweenSectionAt section: Int, and nextSection: Int) -> CGFloat
7+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, marginForSectionAt section: Int) -> UIEdgeInsets
8+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, paddingForSectionAt section: Int) -> UIEdgeInsets
9+
10+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, horizontalSpacingBetweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat
11+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, verticalSpacingBetweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat
12+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, marginForItemAt indexPath: IndexPath) -> UIEdgeInsets
13+
@objc optional func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewFlexLayout, paddingForItemAt indexPath: IndexPath) -> UIEdgeInsets
14+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import UIKit
2+
3+
public let UICollectionElementKindSectionBackground = "UICollectionElementKindSectionBackground"
4+
5+
open class UICollectionViewFlexLayout: UICollectionViewLayout {
6+
private(set) var layoutAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
7+
private(set) var backgroundAttributes: [Int: UICollectionViewLayoutAttributes] = [:]
8+
private(set) var cachedContentSize: CGSize = .zero
9+
10+
override open func prepare() {
11+
guard let collectionView = self.collectionView else { return }
12+
13+
let contentWidth = collectionView.frame.width
14+
var offset: CGPoint = .zero
15+
16+
self.layoutAttributes.removeAll()
17+
for section in 0..<collectionView.numberOfSections {
18+
let sectionVerticalSpacing: CGFloat
19+
if section > 0 {
20+
sectionVerticalSpacing = self.verticalSpacing(betweenSectionAt: section - 1, and: section)
21+
} else {
22+
sectionVerticalSpacing = 0
23+
}
24+
let sectionMargin = self.margin(forSectionAt: section)
25+
let sectionPadding = self.padding(forSectionAt: section)
26+
27+
// maximum value of (height + padding bottom + margin bottom) in current row
28+
var maxItemBottom: CGFloat = 0
29+
30+
offset.x = sectionMargin.left + sectionPadding.left // start from left
31+
offset.y += sectionVerticalSpacing + sectionMargin.top + sectionPadding.top // accumulated
32+
33+
for item in 0..<collectionView.numberOfItems(inSection: section) {
34+
let indexPath = IndexPath(item: item, section: section)
35+
let itemMargin = self.margin(forItemAt: indexPath)
36+
let itemPadding = self.padding(forItemAt: indexPath)
37+
let itemSize = self.size(forItemAt: indexPath)
38+
39+
if item > 0 {
40+
offset.x += self.horizontalSpacing(betweenItemAt: IndexPath(item: item - 1, section: section), and: indexPath)
41+
}
42+
if offset.x + itemMargin.left + itemPadding.left + itemSize.width + itemPadding.right + itemMargin.right + sectionPadding.right + sectionMargin.right > contentWidth {
43+
offset.x = sectionMargin.left + sectionPadding.left // start from left
44+
offset.y += maxItemBottom // next line
45+
if item > 0 {
46+
offset.y += self.verticalSpacing(betweenItemAt: IndexPath(item: item - 1, section: section), and: indexPath)
47+
}
48+
maxItemBottom = 0
49+
}
50+
51+
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
52+
attributes.size = itemSize
53+
attributes.frame.origin.x = offset.x + itemMargin.left + itemPadding.left
54+
attributes.frame.origin.y = offset.y + itemMargin.top + itemPadding.top
55+
56+
offset.x += itemSize.width + itemPadding.right + itemMargin.right
57+
maxItemBottom = max(maxItemBottom, itemMargin.top + itemPadding.top + itemSize.height + itemPadding.bottom + itemMargin.bottom)
58+
self.layoutAttributes[indexPath] = attributes
59+
}
60+
61+
offset.y += maxItemBottom + sectionPadding.bottom + sectionMargin.bottom
62+
self.cachedContentSize = CGSize(width: contentWidth, height: offset.y)
63+
}
64+
65+
self.backgroundAttributes.removeAll()
66+
for section in 0..<collectionView.numberOfSections {
67+
let layoutAttributes = self.layoutAttributes.lazy.filter { $0.key.section == section }.map { $0.value }
68+
guard let minXAttribute = layoutAttributes.min(by: { $0.frame.minX < $1.frame.minX }) else { continue }
69+
guard let minYAttribute = layoutAttributes.min(by: { $0.frame.minY < $1.frame.minY }) else { continue }
70+
guard let maxXAttribute = layoutAttributes.max(by: { $0.frame.maxX < $1.frame.maxX }) else { continue }
71+
guard let maxYAttribute = layoutAttributes.max(by: { $0.frame.maxY < $1.frame.maxY }) else { continue }
72+
let (minX, minY) = (minXAttribute.frame.minX, minYAttribute.frame.minY)
73+
let (maxX, maxY) = (maxXAttribute.frame.maxX, maxYAttribute.frame.maxY)
74+
let (width, height) = (maxX - minX, maxY - minY)
75+
guard width > 0 && height > 0 else { continue }
76+
77+
let sectionPadding = self.padding(forSectionAt: section)
78+
let attributes = UICollectionViewLayoutAttributes(
79+
forSupplementaryViewOfKind: UICollectionElementKindSectionBackground,
80+
with: IndexPath(item: 0, section: section)
81+
)
82+
let itemPaddingLeft = self.padding(forItemAt: minXAttribute.indexPath).left
83+
let itemPaddingTop = self.padding(forItemAt: minYAttribute.indexPath).top
84+
let itemPaddingRight = self.padding(forItemAt: maxXAttribute.indexPath).right
85+
let itemPaddingBottom = self.padding(forItemAt: maxYAttribute.indexPath).bottom
86+
attributes.frame = CGRect(
87+
x: minX - sectionPadding.left - itemPaddingLeft,
88+
y: minY - sectionPadding.top - itemPaddingTop,
89+
width: width + sectionPadding.left + sectionPadding.right + itemPaddingLeft + itemPaddingRight,
90+
height: height + sectionPadding.top + sectionPadding.bottom + itemPaddingTop + itemPaddingBottom
91+
)
92+
attributes.zIndex = -1
93+
self.backgroundAttributes[section] = attributes
94+
}
95+
}
96+
97+
override open var collectionViewContentSize: CGSize {
98+
return self.cachedContentSize
99+
}
100+
101+
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
102+
return self.layoutAttributes.values.filter { $0.frame.intersects(rect) }
103+
+ self.backgroundAttributes.values.filter { $0.frame.intersects(rect) }
104+
}
105+
106+
override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
107+
return self.layoutAttributes[indexPath]
108+
}
109+
110+
override open func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
111+
guard elementKind == UICollectionElementKindSectionBackground else { return nil }
112+
guard indexPath.item == 0 else { return nil }
113+
return self.backgroundAttributes[indexPath.section]
114+
}
115+
116+
open func maximumWidth(forItemAt indexPath: IndexPath) -> CGFloat {
117+
guard let collectionView = self.collectionView else { return 0 }
118+
let sectionMargin = self.margin(forSectionAt: indexPath.section)
119+
let sectionPadding = self.padding(forSectionAt: indexPath.section)
120+
let itemMargin = self.margin(forItemAt: indexPath)
121+
let itemPadding = self.padding(forItemAt: indexPath)
122+
return collectionView.frame.width
123+
- sectionMargin.left
124+
- sectionPadding.left
125+
- itemMargin.left
126+
- itemPadding.left
127+
- itemPadding.right
128+
- itemMargin.right
129+
- sectionPadding.right
130+
- sectionMargin.right
131+
}
132+
}
133+
134+
extension UICollectionViewFlexLayout {
135+
var delegate: UICollectionViewDelegateFlexLayout? {
136+
return self.collectionView?.delegate as? UICollectionViewDelegateFlexLayout
137+
}
138+
139+
func size(forItemAt indexPath: IndexPath) -> CGSize {
140+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return .zero }
141+
return delegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) ?? .zero
142+
}
143+
144+
func verticalSpacing(betweenSectionAt section: Int, and nextSection: Int) -> CGFloat {
145+
guard section != nextSection else { return 0 }
146+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return 0 }
147+
return delegate.collectionView?(collectionView, layout: self, verticalSpacingBetweenSectionAt: section, and: nextSection) ?? 0
148+
}
149+
150+
func margin(forSectionAt section: Int) -> UIEdgeInsets {
151+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return .zero }
152+
return delegate.collectionView?(collectionView, layout: self, marginForSectionAt: section) ?? .zero
153+
}
154+
155+
func padding(forSectionAt section: Int) -> UIEdgeInsets {
156+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return .zero }
157+
return delegate.collectionView?(collectionView, layout: self, paddingForSectionAt: section) ?? .zero
158+
}
159+
160+
func horizontalSpacing(betweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat {
161+
guard indexPath != nextIndexPath else { return 0 }
162+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return 0 }
163+
return delegate.collectionView?(collectionView, layout: self, horizontalSpacingBetweenItemAt: indexPath, and: nextIndexPath) ?? 0
164+
}
165+
166+
func verticalSpacing(betweenItemAt indexPath: IndexPath, and nextIndexPath: IndexPath) -> CGFloat {
167+
guard indexPath != nextIndexPath else { return 0 }
168+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return 0 }
169+
return delegate.collectionView?(collectionView, layout: self, verticalSpacingBetweenItemAt: indexPath, and: nextIndexPath) ?? 0
170+
}
171+
172+
func margin(forItemAt indexPath: IndexPath) -> UIEdgeInsets {
173+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return .zero }
174+
return delegate.collectionView?(collectionView, layout: self, marginForItemAt: indexPath) ?? .zero
175+
}
176+
177+
func padding(forItemAt indexPath: IndexPath) -> UIEdgeInsets {
178+
guard let collectionView = self.collectionView, let delegate = self.delegate else { return .zero }
179+
return delegate.collectionView?(collectionView, layout: self, paddingForItemAt: indexPath) ?? .zero
180+
}
181+
}

0 commit comments

Comments
 (0)