├── .swift-version ├── Cartfile.private ├── Cartfile.resolved ├── AKPFlowLayout.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── AKPFlowLayout.xcscheme └── project.pbxproj ├── carthage.sh ├── AKPFlowLayout ├── AKPFlowLayoutError.swift ├── AKPFlowLayout.h ├── Info.plist ├── AKPFlowLayoutAttributes.swift ├── AKPLayoutConfigOptions.swift └── AKPFlowLayout.swift ├── AKPFlowLayout.xcworkspace └── contents.xcworkspacedata ├── AKPFlowLayout.podspec ├── AKPFlowLayoutTests ├── Info.plist └── AKPFlowLayoutTests.swift ├── License ├── .gitignore └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Quick" 2 | github "Quick/Nimble" 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v5.0.0" 2 | github "Quick/Quick" "v0.10.0" 3 | -------------------------------------------------------------------------------- /AKPFlowLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /carthage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! command -v carthage > /dev/null; then 4 | printf 'Carthage is not installed.\n' 5 | printf 'See https://github.com/Carthage/Carthage for install instructions.\n' 6 | exit 1 7 | fi 8 | 9 | carthage update --platform iOS --use-submodules --no-use-binaries 10 | -------------------------------------------------------------------------------- /AKPFlowLayout/AKPFlowLayoutError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AKPFlowLayoutError.swift 3 | // SwiftNetworkImages 4 | // 5 | // Created by Arseniy on 11/6/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// AKPFlowLayout Errors 12 | 13 | public enum AKPFlowLayoutError: Error { 14 | case sectionHeadersPinToVisibleBoundsSettingError 15 | } 16 | -------------------------------------------------------------------------------- /AKPFlowLayout/AKPFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // AKPFlowLayout.h 3 | // AKPFlowLayout 4 | // 5 | // Created by Arseniy on 12/6/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | //! Project version number for AKPFlowLayout. 13 | FOUNDATION_EXPORT double AKPFlowLayoutVersionNumber; 14 | 15 | //! Project version string for AKPFlowLayout. 16 | FOUNDATION_EXPORT const unsigned char AKPFlowLayoutVersionString[]; 17 | 18 | -------------------------------------------------------------------------------- /AKPFlowLayout.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 10 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AKPFlowLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "AKPFlowLayout" 3 | spec.version = "0.1.2" 4 | spec.summary = "A custom UICollectionView layout with configurable global header and pinnable, stretchable section headers" 5 | spec.homepage = "https://github.com/akpw/AKPFlowLayout" 6 | spec.license = { type: 'MIT', file: 'LICENSE' } 7 | spec.authors = { "Arseniy Kuznetsov" => 'k.arseniy@gmail.com' } 8 | 9 | spec.platform = :ios, "8.0" 10 | spec.requires_arc = true 11 | spec.source = { git: "https://github.com/akpw/AKPFlowLayout.git", tag: "v#{spec.version}", submodules: true } 12 | spec.source_files = "AKPFlowLayout/**/*.{h,swift}" 13 | end 14 | -------------------------------------------------------------------------------- /AKPFlowLayoutTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /AKPFlowLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Arseniy Kuznetsov 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. 22 | -------------------------------------------------------------------------------- /AKPFlowLayout/AKPFlowLayoutAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AKPFlowLayoutAttributes.swift 3 | // AKPFlowLayout 4 | // 5 | // Created by Arseniy on 14/6/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Layout Attributes class for AKPFlowLayout 12 | 13 | open class AKPFlowLayoutAttributes: UICollectionViewLayoutAttributes { 14 | 15 | /// Set by AKPFlowLayout when managing section headers stretching 16 | /// Can be used further for e.g. reporting amount of stretch back to the collection view items 17 | open var stretchFactor: CGFloat = 0 18 | 19 | override open func copy(with zone: NSZone?) -> Any { 20 | let aCopy = super.copy(with: zone) as! AKPFlowLayoutAttributes 21 | aCopy.stretchFactor = stretchFactor 22 | return aCopy 23 | } 24 | 25 | override open func isEqual(_ object: Any?) -> Bool { 26 | if let attributes = object as? AKPFlowLayoutAttributes { 27 | if attributes.stretchFactor == stretchFactor { 28 | return super.isEqual(object) 29 | } 30 | } 31 | return false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | *.pyc 10 | 11 | # caches 12 | ######### 13 | __pycache__/ 14 | 15 | # setup / build 16 | ############### 17 | *.egg-info 18 | *.pot 19 | *.py[co] 20 | dist/ 21 | build/ 22 | 23 | # Packages # 24 | ############ 25 | # it's better to unpack these files and commit the raw source 26 | # git has its own built in compression methods 27 | *.7z 28 | *.dmg 29 | *.gz 30 | *.iso 31 | *.jar 32 | *.rar 33 | *.tar 34 | *.zip 35 | 36 | # Logs and databases # 37 | ###################### 38 | *.log 39 | *.sql 40 | *.sqlite 41 | 42 | # OS generated files # 43 | ###################### 44 | .DS_Store 45 | .DS_Store? 46 | ._* 47 | .Spotlight-V100 48 | .Trashes 49 | ehthumbs.db 50 | Thumbs.db 51 | 52 | # Xcode # 53 | ######### 54 | */build/* 55 | *.pbxuser 56 | !default.pbxuser 57 | *.mode1v3 58 | !default.mode1v3 59 | *.mode2v3 60 | !default.mode2v3 61 | *.perspectivev3 62 | !default.perspectivev3 63 | xcuserdata 64 | 65 | ############# 66 | *.moved-aside 67 | DerivedData 68 | .idea/ 69 | *.iml 70 | *.hmap 71 | *.xccheckout 72 | *.xcuserstate 73 | *.xcscmblueprint 74 | *.ipa 75 | 76 | #CocoaPods 77 | Pods 78 | Podfile.lock 79 | .xcworkspace 80 | 81 | # Swift Package Manager 82 | .build/ 83 | 84 | # Carthage 85 | Carthage/Build 86 | Carthage/Checkouts 87 | .gitmodules 88 | 89 | # Other source repository archive directories # 90 | ############################################### 91 | .hg 92 | .svn 93 | CVS 94 | -------------------------------------------------------------------------------- /AKPFlowLayout/AKPLayoutConfigOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AKPLayoutConfigOptions.swift 3 | // SwiftNetworkImages 4 | // 5 | // Created by Arseniy on 9/6/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// AKPFlowLayout configuration options 12 | 13 | public struct AKPLayoutConfigOptions: OptionSet { 14 | public static let firstSectionIsGlobalHeader = 15 | AKPLayoutConfigOptions(rawValue: 1 << 0) 16 | public static let firstSectionStretchable = 17 | AKPLayoutConfigOptions(rawValue: 1 << 1) 18 | public static let sectionsPinToGlobalHeaderOrVisibleBounds = 19 | AKPLayoutConfigOptions(rawValue: 1 << 2) 20 | public let rawValue: Int 21 | public init(rawValue: Int) { 22 | self.rawValue = rawValue 23 | } 24 | } 25 | 26 | extension AKPLayoutConfigOptions: CustomStringConvertible { 27 | public var descriptions: [String] { 28 | let optionsDescriptions = ["First Section Is Global Header", 29 | "First Section Stretchable", 30 | "Sections Pin To Global Header Or Visible Bounds"] 31 | var memberDescriptions = [String]() 32 | for (shift, description) in optionsDescriptions.enumerated() 33 | where contains( AKPLayoutConfigOptions( rawValue: 1 << shift) ) { 34 | memberDescriptions.append(description) 35 | } 36 | return memberDescriptions 37 | } 38 | 39 | public var description: String { 40 | return descriptions.description 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AKPFlowLayoutTests/AKPFlowLayoutTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AKPFlowLayoutTests.swift 3 | // AKPFlowLayoutTests 4 | // 5 | // Created by Arseniy on 12/6/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | import AKPFlowLayout 12 | 13 | class AKPFlowLayoutTests: QuickSpec { 14 | override func spec() { 15 | describe("AKPFlowLayout") { 16 | let akpFlowLayout: AKPFlowLayout = { 17 | $0.firsSectionMaximumStretchHeight = 200 18 | return $0 19 | }( AKPFlowLayout() ) 20 | 21 | it("uses AKPFlowLayoutAttributes when creating layout attributes") { 22 | print(AKPFlowLayout.layoutAttributesClass) 23 | expect(AKPFlowLayout.layoutAttributesClass == AKPFlowLayoutAttributes.self).to(beTrue()) 24 | } 25 | 26 | it("has all layout config options on by default") { 27 | expect(akpFlowLayout.layoutOptions.contains(.firstSectionIsGlobalHeader)).to(beTrue()) 28 | expect(akpFlowLayout.layoutOptions.contains(.firstSectionStretchable)).to(beTrue()) 29 | expect(akpFlowLayout.layoutOptions.contains(.sectionsPinToGlobalHeaderOrVisibleBounds)).to(beTrue()) 30 | } 31 | 32 | it("when running on iSO9, it disables built-in sectionHeadersPinToVisibleBounds") { 33 | if #available(iOS 9.0, *) { 34 | akpFlowLayout.sectionHeadersPinToVisibleBounds = false 35 | expect(akpFlowLayout.sectionHeadersPinToVisibleBounds).to(beFalse()) 36 | 37 | akpFlowLayout.sectionHeadersPinToVisibleBounds = true 38 | expect(akpFlowLayout.sectionHeadersPinToVisibleBounds).to(beFalse()) 39 | } 40 | } 41 | 42 | it("has given firsSectionMaximumStretchHeight value") { 43 | expect(akpFlowLayout.firsSectionMaximumStretchHeight == 200).to(beTrue()) 44 | } 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AKPFlowLayout 2 | ============ 3 | 4 | ![Language](https://img.shields.io/badge/language-Swift3-orange.svg) 5 | ![License](https://img.shields.io/badge/License-MIT%20License-blue.svg) 6 | 7 | 8 | AKPFlowLayout is a custom Collection View layout with configurable global header and pinnable, stretchable sections. 9 | 10 | 11 | ## Blogs 12 | * [Custom UICollectionView: Global Headers](https://akpw.github.io//articles/2016/06/16/CollectionView-I.html) 13 | 14 | 15 | ## Sample App 16 | * [SwiftNetworkImages](https://github.com/akpw/SwiftNetworkImages) 17 | 18 | ## Dev Docs 19 | [Initial docs][docsLink], generated with [jazzy](https://github.com/realm/jazzy) and hosted by [GitHub Pages](https://pages.github.com). 20 | 21 | ## Features 22 | 23 | * A custom `UICollectionViewFlowLayout`-based layout with support for: 24 | - Global header 25 | - Sticky section headers 26 | - Pinnable, stretchable sections 27 | 28 | * Fully configurable 29 | 30 | * Built for performace using custom invalidation context 31 | 32 | * Written in Swift 3 and Xcode 8 33 | 34 | ## Requirements 35 | * iOS 8+ 36 | * Xcode 8 37 | * Swift 3 38 | 39 | ## Installation 40 | 41 | #### [CocoaPods](http://cocoapods.org) (recommended) 42 | 43 | ````sh 44 | use_frameworks! 45 | pod 'AKPFlowLayout' 46 | ```` 47 | 48 | #### [Carthage](https://github.com/Carthage/Carthage) 49 | 1. Add AKPFlowLayout to your [`Cartfile`](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile): 50 | ``` 51 | github "akpw/AKPFlowLayout" 52 | ``` 53 | 2. Follow the [Carthage instructions on adding frameworks](https://github.com/Carthage/Carthage/blob/master/README.md#adding-frameworks-to-an-application) for further reference 54 | 55 | 56 | ## Building the project 57 | 58 | 1) Clone the repository 59 | 60 | ```bash 61 | $ git clone https://github.com/akpw/AKPFlowLayout 62 | ``` 63 | 64 | 2) Run carthage.sh 65 | 66 | ```bash 67 | $ cd AKPFlowLayout 68 | $ ./carthage.sh 69 | ``` 70 | 71 | 3) Open the workspace in Xcode 72 | 73 | ```bash 74 | $ open "AKPFlowLayout.xcworkspace" 75 | ``` 76 | 77 | 4) Compile and test in Xcode 78 | 79 | 80 | 81 | 82 | [docsLink]:https://akpw.github.io//AKPFlowLayout/index.html 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /AKPFlowLayout.xcodeproj/xcshareddata/xcschemes/AKPFlowLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /AKPFlowLayout/AKPFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AKPFlowLayout.swift 3 | // SwiftNetworkImages 4 | // 5 | // Created by Arseniy on 18/5/16. 6 | // Copyright © 2016 Arseniy Kuznetsov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 12 | Global / Sticky / Stretchy Headers using UICollectionViewFlowLayout. 13 | Works for iOS8 and above. 14 | */ 15 | 16 | public final class AKPFlowLayout: UICollectionViewFlowLayout { 17 | /// Layout configuration options 18 | public var layoutOptions: AKPLayoutConfigOptions = [.firstSectionIsGlobalHeader, 19 | .firstSectionStretchable, 20 | .sectionsPinToGlobalHeaderOrVisibleBounds] 21 | /// For stretchy headers, allowis limiting amount of stretch 22 | public var firsSectionMaximumStretchHeight = CGFloat.greatestFiniteMagnitude 23 | 24 | // MARK: - Initialization 25 | override public init() { 26 | super.init() 27 | // For iOS9, needs to ensure the impl does not interfere with `sectionHeadersPinToVisibleBounds` 28 | // Seems to be no reasonable way yet to use Swift property observers with conditional compilation, 29 | // so falling back to KVO 30 | if #available(iOS 9.0, *) { 31 | addObserver(self, forKeyPath: "sectionHeadersPinToVisibleBounds", 32 | options: .new, context: &AKPFlowLayoutKVOContext) 33 | } 34 | } 35 | required public init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | } 38 | deinit { 39 | if #available(iOS 9.0, *) { 40 | removeObserver(self, forKeyPath: "sectionHeadersPinToVisibleBounds", context: &AKPFlowLayoutKVOContext) 41 | } 42 | } 43 | 44 | // MARK: - 📐Custom Layout 45 | /// - returns: AKPFlowLayoutAttributes class for handling layout attributes 46 | override public class var layoutAttributesClass : AnyClass { 47 | return AKPFlowLayoutAttributes.self 48 | } 49 | 50 | /// Returns layout attributes for specified rectangle, with added custom headers 51 | override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 52 | guard shouldDoCustomLayout else { return super.layoutAttributesForElements(in: rect) } 53 | 54 | guard var layoutAttributes = super.layoutAttributesForElements(in: rect) as? [AKPFlowLayoutAttributes], 55 | // calculate custom headers that should be confined in the rect 56 | let customSectionHeadersIdxs = customSectionHeadersIdxs(rect) else { return nil } 57 | 58 | // add the custom headers to the regular UICollectionViewFlowLayout layoutAttributes 59 | for idx in customSectionHeadersIdxs { 60 | let indexPath = IndexPath(item: 0, section: idx) 61 | if let attributes = super.layoutAttributesForSupplementaryView( 62 | ofKind: UICollectionElementKindSectionHeader, 63 | at: indexPath) as? AKPFlowLayoutAttributes { 64 | layoutAttributes.append(attributes) 65 | } 66 | } 67 | // for section headers, need to adjust their attributes 68 | for attributes in layoutAttributes where 69 | attributes.representedElementKind == UICollectionElementKindSectionHeader { 70 | (attributes.frame, attributes.zIndex) = adjustLayoutAttributes(forSectionAttributes: attributes) 71 | } 72 | return layoutAttributes 73 | } 74 | 75 | /// Adjusts layout attributes for the custom section headers 76 | override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, 77 | at indexPath: IndexPath) 78 | -> UICollectionViewLayoutAttributes? { 79 | guard shouldDoCustomLayout else { 80 | return super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) } 81 | 82 | guard let sectionHeaderAttributes = super.layoutAttributesForSupplementaryView( 83 | ofKind: elementKind, 84 | at: indexPath) 85 | as? AKPFlowLayoutAttributes else { return nil } 86 | // Adjust section attributes 87 | (sectionHeaderAttributes.frame, sectionHeaderAttributes.zIndex) = 88 | adjustLayoutAttributes(forSectionAttributes: sectionHeaderAttributes) 89 | return sectionHeaderAttributes 90 | } 91 | 92 | // MARK: - 🎳Invalidation 93 | /// - returns: `true`, unless running on iOS9 with `sectionHeadersPinToVisibleBounds` set to `true` 94 | override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 95 | guard shouldDoCustomLayout else { return super.shouldInvalidateLayout(forBoundsChange: newBounds) } 96 | return true 97 | } 98 | 99 | /// Custom invalidation 100 | override public func invalidationContext(forBoundsChange newBounds: CGRect) 101 | -> UICollectionViewLayoutInvalidationContext { 102 | guard shouldDoCustomLayout, 103 | let invalidationContext = super.invalidationContext(forBoundsChange: newBounds) 104 | as? UICollectionViewFlowLayoutInvalidationContext, 105 | let oldBounds = collectionView?.bounds 106 | else { return super.invalidationContext(forBoundsChange: newBounds) } 107 | // Size changes? 108 | if oldBounds.size != newBounds.size { 109 | // re-query the collection view delegate for metrics such as size information etc. 110 | invalidationContext.invalidateFlowLayoutDelegateMetrics = true 111 | } 112 | 113 | // Origin changes? 114 | if oldBounds.origin != newBounds.origin { 115 | // find and invalidate sections that would fall into the new bounds 116 | guard let sectionIdxPaths = sectionsHeadersIDxs(forRect: newBounds) else {return invalidationContext} 117 | 118 | // then invalidate 119 | let invalidatedIdxPaths = sectionIdxPaths.map { IndexPath(item: 0, section: $0) } 120 | invalidationContext.invalidateSupplementaryElements( 121 | ofKind: UICollectionElementKindSectionHeader, at: invalidatedIdxPaths ) 122 | } 123 | return invalidationContext 124 | } 125 | fileprivate var previousStretchFactor = CGFloat(0) 126 | } 127 | 128 | // MARK: - 🕶Private Helpers 129 | extension AKPFlowLayout { 130 | fileprivate var shouldDoCustomLayout: Bool { 131 | var requestForCustomLayout = layoutOptions.contains(.firstSectionIsGlobalHeader) || 132 | layoutOptions.contains(.firstSectionStretchable) || 133 | layoutOptions.contains(.sectionsPinToGlobalHeaderOrVisibleBounds) 134 | // iOS9 supports sticky headers natively, so we should not 135 | // interfere with the the built-in functionality 136 | if #available(iOS 9.0, *) { 137 | requestForCustomLayout = requestForCustomLayout && !sectionHeadersPinToVisibleBounds 138 | } 139 | return requestForCustomLayout 140 | } 141 | 142 | fileprivate func zIndexForSection(_ section: Int) -> Int { 143 | return section > 0 ? 128 : 256 144 | } 145 | 146 | // Given a rect, calculates indexes of all confined section headers 147 | // _including_ the custom headers 148 | fileprivate func sectionsHeadersIDxs(forRect rect: CGRect) -> Set? { 149 | guard let layoutAttributes = super.layoutAttributesForElements(in: rect) 150 | as? [AKPFlowLayoutAttributes] else {return nil} 151 | let sectionsShouldPin = layoutOptions.contains(.sectionsPinToGlobalHeaderOrVisibleBounds) 152 | 153 | var headersIdxs = Set() 154 | for attributes in layoutAttributes 155 | where attributes.visibleSectionHeader(sectionsShouldPin) { 156 | headersIdxs.insert((attributes.indexPath as NSIndexPath).section) 157 | } 158 | if layoutOptions.contains(.firstSectionIsGlobalHeader) { 159 | headersIdxs.insert(0) 160 | } 161 | return headersIdxs 162 | } 163 | 164 | // Given a rect, calculates the indexes of confined custom section headers 165 | // _excluding_ the regular headers handled by UICollectionViewFlowLayout 166 | fileprivate func customSectionHeadersIdxs(_ rect: CGRect) -> Set? { 167 | guard let layoutAttributes = super.layoutAttributesForElements(in: rect), 168 | var sectionIdxs = sectionsHeadersIDxs(forRect: rect) else {return nil} 169 | 170 | // remove the sections that should already be taken care of by UICollectionViewFlowLayout 171 | for attributes in layoutAttributes 172 | where attributes.representedElementKind == UICollectionElementKindSectionHeader { 173 | sectionIdxs.remove((attributes.indexPath as NSIndexPath).section) 174 | } 175 | return sectionIdxs 176 | } 177 | 178 | // Adjusts layout attributes of section headers 179 | fileprivate func adjustLayoutAttributes(forSectionAttributes 180 | sectionHeadersLayoutAttributes: AKPFlowLayoutAttributes) 181 | -> (CGRect, Int) { 182 | guard let collectionView = collectionView else { return (CGRect.zero, 0) } 183 | let section = (sectionHeadersLayoutAttributes.indexPath as NSIndexPath).section 184 | var sectionFrame = sectionHeadersLayoutAttributes.frame 185 | 186 | // 1. Establish the section boundaries: 187 | let (minY, maxY) = boundaryMetrics(forSectionAttributes: sectionHeadersLayoutAttributes) 188 | 189 | // 2. Determine the height and insets of the first section, 190 | // in case it's stretchable or serves as a global header 191 | let (firstSectionHeight, firstSectionInsets) = firstSectionMetrics() 192 | 193 | // 3. If within the above boundaries, the section should follow content offset 194 | // (adjusting a few more things along the way) 195 | var offset = collectionView.contentOffset.y + collectionView.contentInset.top 196 | if (section > 0) { 197 | // The global section 198 | if layoutOptions.contains(.sectionsPinToGlobalHeaderOrVisibleBounds) { 199 | if layoutOptions.contains(.firstSectionIsGlobalHeader) { 200 | // A global header adjustment 201 | offset += firstSectionHeight + firstSectionInsets.top 202 | } 203 | sectionFrame.origin.y = min(max(offset, minY), maxY) 204 | } 205 | } else { 206 | if layoutOptions.contains(.firstSectionStretchable) && offset < 0 { 207 | // Stretchy header 208 | if firstSectionHeight - offset < firsSectionMaximumStretchHeight { 209 | sectionFrame.size.height = firstSectionHeight - offset 210 | sectionHeadersLayoutAttributes.stretchFactor = fabs(offset) 211 | previousStretchFactor = sectionHeadersLayoutAttributes.stretchFactor 212 | } else { 213 | // need to limit the stretch 214 | sectionFrame.size.height = firsSectionMaximumStretchHeight 215 | sectionHeadersLayoutAttributes.stretchFactor = previousStretchFactor 216 | } 217 | sectionFrame.origin.y += offset + firstSectionInsets.top 218 | } else if layoutOptions.contains(.firstSectionIsGlobalHeader) { 219 | // Sticky header position needs to be relative to the global header 220 | sectionFrame.origin.y += offset + firstSectionInsets.top 221 | } else { 222 | sectionFrame.origin.y = min(max(offset, minY), maxY) 223 | } 224 | } 225 | return (sectionFrame, zIndexForSection(section)) 226 | } 227 | 228 | fileprivate func boundaryMetrics( 229 | forSectionAttributes sectionHeadersLayoutAttributes: UICollectionViewLayoutAttributes) 230 | -> (CGFloat, CGFloat) { 231 | // get attributes for first and last items in section 232 | guard let collectionView = collectionView else { return (0, 0) } 233 | let section = (sectionHeadersLayoutAttributes.indexPath as NSIndexPath).section 234 | 235 | // Trying to use layoutAttributesForItemAtIndexPath for empty section would 236 | // cause EXC_ARITHMETIC in simulator (division by zero items) 237 | let lastInSectionIdx = collectionView.numberOfItems(inSection: section) - 1 238 | if lastInSectionIdx < 0 { return (0, 0) } 239 | 240 | guard let attributesForFirstItemInSection = layoutAttributesForItem( 241 | at: IndexPath(item: 0, section: section)), 242 | let attributesForLastItemInSection = layoutAttributesForItem( 243 | at: IndexPath(item: lastInSectionIdx, section: section)) 244 | else {return (0, 0)} 245 | let sectionFrame = sectionHeadersLayoutAttributes.frame 246 | 247 | // Section Boundaries: 248 | // The section should not be higher than the top of its first cell 249 | let minY = attributesForFirstItemInSection.frame.minY - sectionFrame.height 250 | // The section should not be lower than the bottom of its last cell 251 | let maxY = attributesForLastItemInSection.frame.maxY - sectionFrame.height 252 | return (minY, maxY) 253 | } 254 | 255 | fileprivate func firstSectionMetrics() -> (height: CGFloat, insets: UIEdgeInsets) { 256 | guard let collectionView = collectionView else { return (0, UIEdgeInsets.zero) } 257 | // height of the first section 258 | var firstSectionHeight = headerReferenceSize.height 259 | if let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout 260 | , firstSectionHeight == 0 { 261 | firstSectionHeight = delegate.collectionView!(collectionView, 262 | layout: self, 263 | referenceSizeForHeaderInSection: 0).height 264 | } 265 | // insets of the first section 266 | var theSectionInset = sectionInset 267 | if let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout 268 | , theSectionInset == UIEdgeInsets.zero { 269 | theSectionInset = delegate.collectionView!(collectionView, 270 | layout: self, 271 | insetForSectionAt: 0) 272 | } 273 | return (firstSectionHeight, theSectionInset) 274 | } 275 | } 276 | 277 | // MARK: - KVO check for `sectionHeadersPinToVisibleBounds` 278 | extension AKPFlowLayout { 279 | /// KVO check for `sectionHeadersPinToVisibleBounds`. 280 | /// For iOS9, needs to ensure the impl does not interfere with `sectionHeadersPinToVisibleBounds` 281 | override public func observeValue(forKeyPath keyPath: String?, of object: Any?, 282 | change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 283 | if context == &AKPFlowLayoutKVOContext { 284 | if let newValue = change?[NSKeyValueChangeKey.newKey], 285 | let boolValue = newValue as? Bool , boolValue { 286 | print("AKPFlowLayout supports sticky headers by default, therefore " + 287 | "the built-in functionality via sectionHeadersPinToVisibleBounds has been disabled") 288 | if #available(iOS 9.0, *) { sectionHeadersPinToVisibleBounds = false } 289 | } 290 | } else { 291 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 292 | } 293 | } 294 | } 295 | 296 | 297 | private extension AKPFlowLayoutAttributes { 298 | // Determines if element is a section, or is a cell in a section with custom header 299 | func visibleSectionHeader(_ sectionsShouldPin: Bool) -> Bool { 300 | let isHeader = representedElementKind == UICollectionElementKindSectionHeader 301 | let isCellInPinnedSection = sectionsShouldPin && ( representedElementCategory == .cell ) 302 | return isCellInPinnedSection || isHeader 303 | } 304 | } 305 | 306 | private var AKPFlowLayoutKVOContext = 0 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /AKPFlowLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 577338171DB8AD7D0083B350 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 577338151DB8AD7D0083B350 /* Nimble.framework */; }; 11 | 577338181DB8AD7D0083B350 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 577338161DB8AD7D0083B350 /* Quick.framework */; }; 12 | 57C203C81D103318004AB164 /* AKPFlowLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57C203C71D103318004AB164 /* AKPFlowLayoutAttributes.swift */; }; 13 | 57FA3EBA1D0D877E00EFD324 /* AKPFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 57FA3EB91D0D877E00EFD324 /* AKPFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | 57FA3EC11D0D877E00EFD324 /* AKPFlowLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57FA3EB61D0D877E00EFD324 /* AKPFlowLayout.framework */; }; 15 | 57FA3EC61D0D877E00EFD324 /* AKPFlowLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FA3EC51D0D877E00EFD324 /* AKPFlowLayoutTests.swift */; }; 16 | 57FA3F0D1D0D8FC300EFD324 /* AKPFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FA3F0A1D0D8FC300EFD324 /* AKPFlowLayout.swift */; }; 17 | 57FA3F0E1D0D8FC300EFD324 /* AKPFlowLayoutError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FA3F0B1D0D8FC300EFD324 /* AKPFlowLayoutError.swift */; }; 18 | 57FA3F0F1D0D8FC300EFD324 /* AKPLayoutConfigOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FA3F0C1D0D8FC300EFD324 /* AKPLayoutConfigOptions.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 57FA3EC21D0D877E00EFD324 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 57FA3EAD1D0D877E00EFD324 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 57FA3EB51D0D877E00EFD324; 27 | remoteInfo = AKPFlowLayout; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 577338151DB8AD7D0083B350 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = "Carthage/Checkouts/Nimble/build/Debug-iphoneos/Nimble.framework"; sourceTree = ""; }; 33 | 577338161DB8AD7D0083B350 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = "Carthage/Checkouts/Quick/build/Debug-iphoneos/Quick.framework"; sourceTree = ""; }; 34 | 57C203C71D103318004AB164 /* AKPFlowLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPFlowLayoutAttributes.swift; sourceTree = ""; }; 35 | 57FA3EB61D0D877E00EFD324 /* AKPFlowLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AKPFlowLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 57FA3EB91D0D877E00EFD324 /* AKPFlowLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AKPFlowLayout.h; sourceTree = ""; }; 37 | 57FA3EBB1D0D877E00EFD324 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 57FA3EC01D0D877E00EFD324 /* AKPFlowLayoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AKPFlowLayoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 57FA3EC51D0D877E00EFD324 /* AKPFlowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AKPFlowLayoutTests.swift; sourceTree = ""; }; 40 | 57FA3EC71D0D877E00EFD324 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 57FA3F0A1D0D8FC300EFD324 /* AKPFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPFlowLayout.swift; sourceTree = ""; }; 42 | 57FA3F0B1D0D8FC300EFD324 /* AKPFlowLayoutError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPFlowLayoutError.swift; sourceTree = ""; }; 43 | 57FA3F0C1D0D8FC300EFD324 /* AKPLayoutConfigOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AKPLayoutConfigOptions.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 57FA3EB21D0D877E00EFD324 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | 57FA3EBD1D0D877E00EFD324 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 577338171DB8AD7D0083B350 /* Nimble.framework in Frameworks */, 59 | 577338181DB8AD7D0083B350 /* Quick.framework in Frameworks */, 60 | 57FA3EC11D0D877E00EFD324 /* AKPFlowLayout.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 57FA3EAC1D0D877E00EFD324 = { 68 | isa = PBXGroup; 69 | children = ( 70 | 57FA3EB81D0D877E00EFD324 /* AKPFlowLayout */, 71 | 57FA3EC41D0D877E00EFD324 /* AKPFlowLayoutTests */, 72 | 57FA3F091D0D8C0100EFD324 /* Frameworks */, 73 | 57FA3EB71D0D877E00EFD324 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | 57FA3EB71D0D877E00EFD324 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 57FA3EB61D0D877E00EFD324 /* AKPFlowLayout.framework */, 81 | 57FA3EC01D0D877E00EFD324 /* AKPFlowLayoutTests.xctest */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 57FA3EB81D0D877E00EFD324 /* AKPFlowLayout */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 57FA3EB91D0D877E00EFD324 /* AKPFlowLayout.h */, 90 | 57FA3F0A1D0D8FC300EFD324 /* AKPFlowLayout.swift */, 91 | 57FA3F0C1D0D8FC300EFD324 /* AKPLayoutConfigOptions.swift */, 92 | 57C203C71D103318004AB164 /* AKPFlowLayoutAttributes.swift */, 93 | 57FA3F0B1D0D8FC300EFD324 /* AKPFlowLayoutError.swift */, 94 | 57FA3EBB1D0D877E00EFD324 /* Info.plist */, 95 | ); 96 | path = AKPFlowLayout; 97 | sourceTree = ""; 98 | }; 99 | 57FA3EC41D0D877E00EFD324 /* AKPFlowLayoutTests */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 57FA3EC51D0D877E00EFD324 /* AKPFlowLayoutTests.swift */, 103 | 57FA3EC71D0D877E00EFD324 /* Info.plist */, 104 | ); 105 | path = AKPFlowLayoutTests; 106 | sourceTree = ""; 107 | }; 108 | 57FA3F091D0D8C0100EFD324 /* Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 577338151DB8AD7D0083B350 /* Nimble.framework */, 112 | 577338161DB8AD7D0083B350 /* Quick.framework */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXHeadersBuildPhase section */ 120 | 57FA3EB31D0D877E00EFD324 /* Headers */ = { 121 | isa = PBXHeadersBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | 57FA3EBA1D0D877E00EFD324 /* AKPFlowLayout.h in Headers */, 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | /* End PBXHeadersBuildPhase section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 57FA3EB51D0D877E00EFD324 /* AKPFlowLayout */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 57FA3ECA1D0D877E00EFD324 /* Build configuration list for PBXNativeTarget "AKPFlowLayout" */; 134 | buildPhases = ( 135 | 57FA3EB11D0D877E00EFD324 /* Sources */, 136 | 57FA3EB21D0D877E00EFD324 /* Frameworks */, 137 | 57FA3EB31D0D877E00EFD324 /* Headers */, 138 | 57FA3EB41D0D877E00EFD324 /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = AKPFlowLayout; 145 | productName = AKPFlowLayout; 146 | productReference = 57FA3EB61D0D877E00EFD324 /* AKPFlowLayout.framework */; 147 | productType = "com.apple.product-type.framework"; 148 | }; 149 | 57FA3EBF1D0D877E00EFD324 /* AKPFlowLayoutTests */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 57FA3ECD1D0D877E00EFD324 /* Build configuration list for PBXNativeTarget "AKPFlowLayoutTests" */; 152 | buildPhases = ( 153 | 57FA3EBC1D0D877E00EFD324 /* Sources */, 154 | 57FA3EBD1D0D877E00EFD324 /* Frameworks */, 155 | 57FA3EBE1D0D877E00EFD324 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | 57FA3EC31D0D877E00EFD324 /* PBXTargetDependency */, 161 | ); 162 | name = AKPFlowLayoutTests; 163 | productName = AKPFlowLayoutTests; 164 | productReference = 57FA3EC01D0D877E00EFD324 /* AKPFlowLayoutTests.xctest */; 165 | productType = "com.apple.product-type.bundle.unit-test"; 166 | }; 167 | /* End PBXNativeTarget section */ 168 | 169 | /* Begin PBXProject section */ 170 | 57FA3EAD1D0D877E00EFD324 /* Project object */ = { 171 | isa = PBXProject; 172 | attributes = { 173 | LastSwiftUpdateCheck = 0730; 174 | LastUpgradeCheck = 0800; 175 | ORGANIZATIONNAME = "Arseniy Kuznetsov"; 176 | TargetAttributes = { 177 | 57FA3EB51D0D877E00EFD324 = { 178 | CreatedOnToolsVersion = 7.3.1; 179 | DevelopmentTeam = 73TM6WSTY5; 180 | LastSwiftMigration = 0800; 181 | }; 182 | 57FA3EBF1D0D877E00EFD324 = { 183 | CreatedOnToolsVersion = 7.3.1; 184 | DevelopmentTeam = 73TM6WSTY5; 185 | LastSwiftMigration = 0800; 186 | }; 187 | }; 188 | }; 189 | buildConfigurationList = 57FA3EB01D0D877E00EFD324 /* Build configuration list for PBXProject "AKPFlowLayout" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | ); 196 | mainGroup = 57FA3EAC1D0D877E00EFD324; 197 | productRefGroup = 57FA3EB71D0D877E00EFD324 /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 57FA3EB51D0D877E00EFD324 /* AKPFlowLayout */, 202 | 57FA3EBF1D0D877E00EFD324 /* AKPFlowLayoutTests */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | 57FA3EB41D0D877E00EFD324 /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | 57FA3EBE1D0D877E00EFD324 /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 57FA3EB11D0D877E00EFD324 /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 57FA3F0E1D0D8FC300EFD324 /* AKPFlowLayoutError.swift in Sources */, 230 | 57C203C81D103318004AB164 /* AKPFlowLayoutAttributes.swift in Sources */, 231 | 57FA3F0D1D0D8FC300EFD324 /* AKPFlowLayout.swift in Sources */, 232 | 57FA3F0F1D0D8FC300EFD324 /* AKPLayoutConfigOptions.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 57FA3EBC1D0D877E00EFD324 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 57FA3EC61D0D877E00EFD324 /* AKPFlowLayoutTests.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXSourcesBuildPhase section */ 245 | 246 | /* Begin PBXTargetDependency section */ 247 | 57FA3EC31D0D877E00EFD324 /* PBXTargetDependency */ = { 248 | isa = PBXTargetDependency; 249 | target = 57FA3EB51D0D877E00EFD324 /* AKPFlowLayout */; 250 | targetProxy = 57FA3EC21D0D877E00EFD324 /* PBXContainerItemProxy */; 251 | }; 252 | /* End PBXTargetDependency section */ 253 | 254 | /* Begin XCBuildConfiguration section */ 255 | 57FA3EC81D0D877E00EFD324 /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 272 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 273 | CLANG_WARN_UNREACHABLE_CODE = YES; 274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 276 | COPY_PHASE_STRIP = NO; 277 | CURRENT_PROJECT_VERSION = 1; 278 | DEBUG_INFORMATION_FORMAT = dwarf; 279 | ENABLE_STRICT_OBJC_MSGSEND = YES; 280 | ENABLE_TESTABILITY = YES; 281 | GCC_C_LANGUAGE_STANDARD = gnu99; 282 | GCC_DYNAMIC_NO_PIC = NO; 283 | GCC_NO_COMMON_BLOCKS = YES; 284 | GCC_OPTIMIZATION_LEVEL = 0; 285 | GCC_PREPROCESSOR_DEFINITIONS = ( 286 | "DEBUG=1", 287 | "$(inherited)", 288 | ); 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 296 | MTL_ENABLE_DEBUG_INFO = YES; 297 | ONLY_ACTIVE_ARCH = YES; 298 | SDKROOT = iphoneos; 299 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | VERSION_INFO_PREFIX = ""; 303 | }; 304 | name = Debug; 305 | }; 306 | 57FA3EC91D0D877E00EFD324 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INFINITE_RECURSION = YES; 321 | CLANG_WARN_INT_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNREACHABLE_CODE = YES; 325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 326 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 327 | COPY_PHASE_STRIP = NO; 328 | CURRENT_PROJECT_VERSION = 1; 329 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 330 | ENABLE_NS_ASSERTIONS = NO; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | GCC_C_LANGUAGE_STANDARD = gnu99; 333 | GCC_NO_COMMON_BLOCKS = YES; 334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 336 | GCC_WARN_UNDECLARED_SELECTOR = YES; 337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 338 | GCC_WARN_UNUSED_FUNCTION = YES; 339 | GCC_WARN_UNUSED_VARIABLE = YES; 340 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 341 | MTL_ENABLE_DEBUG_INFO = NO; 342 | SDKROOT = iphoneos; 343 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 344 | TARGETED_DEVICE_FAMILY = "1,2"; 345 | VALIDATE_PRODUCT = YES; 346 | VERSIONING_SYSTEM = "apple-generic"; 347 | VERSION_INFO_PREFIX = ""; 348 | }; 349 | name = Release; 350 | }; 351 | 57FA3ECB1D0D877E00EFD324 /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | CLANG_ENABLE_MODULES = YES; 355 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 356 | DEFINES_MODULE = YES; 357 | DEVELOPMENT_TEAM = 73TM6WSTY5; 358 | DYLIB_COMPATIBILITY_VERSION = 1; 359 | DYLIB_CURRENT_VERSION = 1; 360 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 361 | INFOPLIST_FILE = AKPFlowLayout/Info.plist; 362 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 363 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 364 | OTHER_SWIFT_FLAGS = ""; 365 | PRODUCT_BUNDLE_IDENTIFIER = com.akpw.AKPFlowLayout; 366 | PRODUCT_NAME = "$(TARGET_NAME)"; 367 | SKIP_INSTALL = YES; 368 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 369 | SWIFT_VERSION = 3.0; 370 | }; 371 | name = Debug; 372 | }; 373 | 57FA3ECC1D0D877E00EFD324 /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | CLANG_ENABLE_MODULES = YES; 377 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 378 | DEFINES_MODULE = YES; 379 | DEVELOPMENT_TEAM = 73TM6WSTY5; 380 | DYLIB_COMPATIBILITY_VERSION = 1; 381 | DYLIB_CURRENT_VERSION = 1; 382 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 383 | INFOPLIST_FILE = AKPFlowLayout/Info.plist; 384 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 386 | OTHER_SWIFT_FLAGS = ""; 387 | PRODUCT_BUNDLE_IDENTIFIER = com.akpw.AKPFlowLayout; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SKIP_INSTALL = YES; 390 | SWIFT_VERSION = 3.0; 391 | }; 392 | name = Release; 393 | }; 394 | 57FA3ECE1D0D877E00EFD324 /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | DEVELOPMENT_TEAM = 73TM6WSTY5; 398 | FRAMEWORK_SEARCH_PATHS = ( 399 | "$(inherited)", 400 | "$(PROJECT_DIR)/Carthage/Checkouts/Nimble/build/Debug-iphoneos", 401 | "$(PROJECT_DIR)/Carthage/Checkouts/Quick/build/Debug-iphoneos", 402 | ); 403 | INFOPLIST_FILE = AKPFlowLayoutTests/Info.plist; 404 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.akpw.AKPFlowLayoutTests; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 3.0; 408 | }; 409 | name = Debug; 410 | }; 411 | 57FA3ECF1D0D877E00EFD324 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | DEVELOPMENT_TEAM = 73TM6WSTY5; 415 | FRAMEWORK_SEARCH_PATHS = ( 416 | "$(inherited)", 417 | "$(PROJECT_DIR)/Carthage/Checkouts/Nimble/build/Debug-iphoneos", 418 | "$(PROJECT_DIR)/Carthage/Checkouts/Quick/build/Debug-iphoneos", 419 | ); 420 | INFOPLIST_FILE = AKPFlowLayoutTests/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.akpw.AKPFlowLayoutTests; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_VERSION = 3.0; 425 | }; 426 | name = Release; 427 | }; 428 | /* End XCBuildConfiguration section */ 429 | 430 | /* Begin XCConfigurationList section */ 431 | 57FA3EB01D0D877E00EFD324 /* Build configuration list for PBXProject "AKPFlowLayout" */ = { 432 | isa = XCConfigurationList; 433 | buildConfigurations = ( 434 | 57FA3EC81D0D877E00EFD324 /* Debug */, 435 | 57FA3EC91D0D877E00EFD324 /* Release */, 436 | ); 437 | defaultConfigurationIsVisible = 0; 438 | defaultConfigurationName = Release; 439 | }; 440 | 57FA3ECA1D0D877E00EFD324 /* Build configuration list for PBXNativeTarget "AKPFlowLayout" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | 57FA3ECB1D0D877E00EFD324 /* Debug */, 444 | 57FA3ECC1D0D877E00EFD324 /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | 57FA3ECD1D0D877E00EFD324 /* Build configuration list for PBXNativeTarget "AKPFlowLayoutTests" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 57FA3ECE1D0D877E00EFD324 /* Debug */, 453 | 57FA3ECF1D0D877E00EFD324 /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | /* End XCConfigurationList section */ 459 | }; 460 | rootObject = 57FA3EAD1D0D877E00EFD324 /* Project object */; 461 | } 462 | --------------------------------------------------------------------------------