├── .gitignore ├── .travis.yml ├── Examples ├── .gitignore ├── AppleTVDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - Large.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon - Small.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── RDHCollectionViewGridLayout.xcconfig ├── RDHCollectionViewGridLayout │ └── Info.plist ├── RDHCollectionViewGridLayoutDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── AppleTVDemo.xcscheme │ │ ├── RDHCollectionViewGridLayout.xcscheme │ │ └── RDHCollectionViewGridLayoutDemo.xcscheme ├── RDHCollectionViewGridLayoutDemo │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── Default-568h@2x-1.png │ │ │ ├── Default-568h@2x.png │ │ │ ├── Default.png │ │ │ ├── Default@2x-1.png │ │ │ └── Default@2x.png │ ├── RDHAppDelegate.h │ ├── RDHAppDelegate.m │ ├── RDHCollectionViewGridLayoutDemo-Info.plist │ ├── RDHCollectionViewGridLayoutDemo-Prefix.pch │ ├── RDHDemoCell.h │ ├── RDHDemoCell.m │ ├── RDHDemoViewController.h │ ├── RDHDemoViewController.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── Tests │ ├── Info.plist │ ├── RDHEmptyGridLayoutTests.swift │ ├── RDHGridLayoutTests.m │ ├── RDHStoryboardTests.swift │ ├── TestDataSources.swift │ ├── TestStoryboard.storyboard │ ├── Tests-Bridging-Header.h │ ├── Tests-Prefix.pch │ ├── XCTestCase+Extensions.h │ └── XCTestCase+Extensions.m ├── cartfile └── images │ ├── 5-line, 10-item.png │ ├── horizontal.png │ └── vertical.png ├── LICENSE ├── RDHCollectionViewGridLayout.podspec ├── RDHCollectionViewGridLayout ├── RDHCollectionViewGridLayout.h └── RDHCollectionViewGridLayout.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | script: 4 | # Build framework 5 | - xcodebuild -project Examples/RDHCollectionViewGridLayoutDemo.xcodeproj -scheme RDHCollectionViewGridLayout CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= clean build 6 | - xcodebuild -project Examples/RDHCollectionViewGridLayoutDemo.xcodeproj -scheme RDHCollectionViewGridLayoutDemo -destination "platform=iOS Simulator,name=iPhone 6" clean build test 7 | #- xcodebuild -project Examples/RDHCollectionViewGridLayoutDemo.xcodeproj -scheme AppleTVDemo -destination "platform=tvOS Simulator,name=Apple TV 1080p" clean build test 8 | -------------------------------------------------------------------------------- /Examples/.gitignore: -------------------------------------------------------------------------------- 1 | Carthage 2 | Cartfile.resolved 3 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppleTVDemo 4 | // 5 | // Created by Richard Hodgkins on 24/02/2016. 6 | // Copyright © 2016 Rich H. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /Examples/AppleTVDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Examples/AppleTVDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AppleTVDemo 4 | // 5 | // Created by Richard Hodgkins on 24/02/2016. 6 | // Copyright © 2016 Rich H. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import RDHCollectionViewGridLayout 12 | 13 | final class ViewController: UIViewController { 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | // Do any additional setup after loading the view, typically from a nib. 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayout.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // RDHCollectionViewGridLayout.xcconfig 3 | // RDHCollectionViewGridLayout 4 | // 5 | // Created by Richard Hodgkins on 24/02/2016. 6 | // Copyright © 2016 Rich H. All rights reserved. 7 | // 8 | 9 | // Custom 10 | 11 | PRODUCT_NAME = RDHCollectionViewGridLayout 12 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.rhodgkins.$(PRODUCT_NAME:rfc1034identifier) 13 | CVGL_VERSION = 1.2.5 14 | 15 | // Universal Framework 16 | APPLICATION_EXTENSION_API_ONLY = YES 17 | COMBINE_HIDPI_IMAGES = YES 18 | 19 | SUPPORTED_PLATFORMS = iphoneos iphonesimulator appletvos appletvsimulator 20 | 21 | VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s 22 | VALID_ARCHS[sdk=appletv*] = arm64 23 | 24 | LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks 25 | LD_RUNPATH_SEARCH_PATHS[sdk=appletv*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks 26 | 27 | INFOPLIST_FILE = $(PRODUCT_NAME)/Info.plist 28 | 29 | // Platform-specific 30 | 31 | // iOS 32 | CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer 33 | IPHONEOS_DEPLOYMENT_TARGET[sdk=iphone*] = 8.0 34 | TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 35 | 36 | // tvOS 37 | CODE_SIGN_IDENTITY[sdk=appletvos*] = iPhone Developer 38 | TVOS_DEPLOYMENT_TARGET[sdk=appletv*] = 9.0 39 | TARGETED_DEVICE_FAMILY[sdk=appletv*] = 3 40 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayout/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 | $(CVGL_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 442B4B261C7E694400C5B5FC /* RDHCollectionViewGridLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */; }; 11 | 442B4B271C7E694400C5B5FC /* RDHCollectionViewGridLayout.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 442B4B2B1C7E694700C5B5FC /* RDHCollectionViewGridLayout.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */; }; 13 | 442B4B2C1C7E694700C5B5FC /* RDHCollectionViewGridLayout.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 442B4B301C7E699900C5B5FC /* RDHCollectionViewGridLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 4437B7D31954CB5A005CCBBC /* RDHCollectionViewGridLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | 442B4B311C7E699E00C5B5FC /* RDHCollectionViewGridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 4437B7D41954CB5A005CCBBC /* RDHCollectionViewGridLayout.m */; }; 16 | 442BF3FF17896E1D003FAB93 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 442BF3FD17896E1D003FAB93 /* InfoPlist.strings */; }; 17 | 442BF40117896E1D003FAB93 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 442BF40017896E1D003FAB93 /* main.m */; }; 18 | 442BF40517896E1D003FAB93 /* RDHAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 442BF40417896E1D003FAB93 /* RDHAppDelegate.m */; }; 19 | 442BF41317896E5D003FAB93 /* RDHDemoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 442BF41217896E5D003FAB93 /* RDHDemoCell.m */; }; 20 | 442BF41617896E84003FAB93 /* RDHDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 442BF41517896E84003FAB93 /* RDHDemoViewController.m */; }; 21 | 4437B7DC1954CC83005CCBBC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4437B7DB1954CC83005CCBBC /* Images.xcassets */; }; 22 | 4437B80F1956DF41005CCBBC /* RDHGridLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4437B80E1956DF41005CCBBC /* RDHGridLayoutTests.m */; }; 23 | 444926661AB6C17A002DBB57 /* RDHStoryboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444926651AB6C17A002DBB57 /* RDHStoryboardTests.swift */; }; 24 | 4449266A1AB6C24D002DBB57 /* TestDataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444926691AB6C24D002DBB57 /* TestDataSources.swift */; }; 25 | 4449266C1AB6C94C002DBB57 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4449266B1AB6C94C002DBB57 /* TestStoryboard.storyboard */; }; 26 | 445AEA2E1D8EE6A7009FFBFF /* RDHEmptyGridLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445AEA2D1D8EE6A7009FFBFF /* RDHEmptyGridLayoutTests.swift */; }; 27 | 44BA60941D8EFC3600444CD8 /* XCTestCase+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 44BA60931D8EFC3600444CD8 /* XCTestCase+Extensions.m */; }; 28 | 44F92B381C7E65E900A0AD0A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44F92B371C7E65E900A0AD0A /* AppDelegate.swift */; }; 29 | 44F92B3A1C7E65E900A0AD0A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44F92B391C7E65E900A0AD0A /* ViewController.swift */; }; 30 | 44F92B3D1C7E65E900A0AD0A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44F92B3B1C7E65E900A0AD0A /* Main.storyboard */; }; 31 | 44F92B3F1C7E65EA00A0AD0A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 44F92B3E1C7E65EA00A0AD0A /* Assets.xcassets */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 442B4B281C7E694400C5B5FC /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 442BF3E917896E1D003FAB93 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 442B4B191C7E685800C5B5FC; 40 | remoteInfo = RDHCollectionViewGridLayout; 41 | }; 42 | 442B4B2D1C7E694700C5B5FC /* PBXContainerItemProxy */ = { 43 | isa = PBXContainerItemProxy; 44 | containerPortal = 442BF3E917896E1D003FAB93 /* Project object */; 45 | proxyType = 1; 46 | remoteGlobalIDString = 442B4B191C7E685800C5B5FC; 47 | remoteInfo = RDHCollectionViewGridLayout; 48 | }; 49 | 4437B8091956DF0B005CCBBC /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = 442BF3E917896E1D003FAB93 /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = 442BF3F017896E1D003FAB93; 54 | remoteInfo = RDHCollectionViewGridLayoutDemo; 55 | }; 56 | /* End PBXContainerItemProxy section */ 57 | 58 | /* Begin PBXCopyFilesBuildPhase section */ 59 | 442B4B2A1C7E694400C5B5FC /* Embed Frameworks */ = { 60 | isa = PBXCopyFilesBuildPhase; 61 | buildActionMask = 2147483647; 62 | dstPath = ""; 63 | dstSubfolderSpec = 10; 64 | files = ( 65 | 442B4B271C7E694400C5B5FC /* RDHCollectionViewGridLayout.framework in Embed Frameworks */, 66 | ); 67 | name = "Embed Frameworks"; 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | 44F92B481C7E662900A0AD0A /* Embed Frameworks */ = { 71 | isa = PBXCopyFilesBuildPhase; 72 | buildActionMask = 2147483647; 73 | dstPath = ""; 74 | dstSubfolderSpec = 10; 75 | files = ( 76 | 442B4B2C1C7E694700C5B5FC /* RDHCollectionViewGridLayout.framework in Embed Frameworks */, 77 | ); 78 | name = "Embed Frameworks"; 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXCopyFilesBuildPhase section */ 82 | 83 | /* Begin PBXFileReference section */ 84 | 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RDHCollectionViewGridLayout.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 85 | 442BF3F117896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RDHCollectionViewGridLayoutDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 86 | 442BF3FC17896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RDHCollectionViewGridLayoutDemo-Info.plist"; sourceTree = ""; }; 87 | 442BF3FE17896E1D003FAB93 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 88 | 442BF40017896E1D003FAB93 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 89 | 442BF40217896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RDHCollectionViewGridLayoutDemo-Prefix.pch"; sourceTree = ""; }; 90 | 442BF40317896E1D003FAB93 /* RDHAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RDHAppDelegate.h; sourceTree = ""; }; 91 | 442BF40417896E1D003FAB93 /* RDHAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RDHAppDelegate.m; sourceTree = ""; }; 92 | 442BF41117896E5D003FAB93 /* RDHDemoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RDHDemoCell.h; sourceTree = ""; }; 93 | 442BF41217896E5D003FAB93 /* RDHDemoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RDHDemoCell.m; sourceTree = ""; }; 94 | 442BF41417896E84003FAB93 /* RDHDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RDHDemoViewController.h; sourceTree = ""; }; 95 | 442BF41517896E84003FAB93 /* RDHDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RDHDemoViewController.m; sourceTree = ""; }; 96 | 4437B7CF1954CB54005CCBBC /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 97 | 4437B7D01954CB54005CCBBC /* RDHCollectionViewGridLayout.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; name = RDHCollectionViewGridLayout.podspec; path = ../RDHCollectionViewGridLayout.podspec; sourceTree = ""; }; 98 | 4437B7D11954CB54005CCBBC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; name = README.md; path = ../README.md; sourceTree = ""; }; 99 | 4437B7D31954CB5A005CCBBC /* RDHCollectionViewGridLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RDHCollectionViewGridLayout.h; path = ../../RDHCollectionViewGridLayout/RDHCollectionViewGridLayout.h; sourceTree = ""; }; 100 | 4437B7D41954CB5A005CCBBC /* RDHCollectionViewGridLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RDHCollectionViewGridLayout.m; path = ../../RDHCollectionViewGridLayout/RDHCollectionViewGridLayout.m; sourceTree = ""; }; 101 | 4437B7D71954CB6F005CCBBC /* 5-line, 10-item.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "5-line, 10-item.png"; sourceTree = ""; }; 102 | 4437B7D81954CB6F005CCBBC /* horizontal.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = horizontal.png; sourceTree = ""; }; 103 | 4437B7D91954CB6F005CCBBC /* vertical.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vertical.png; sourceTree = ""; }; 104 | 4437B7DA1954CB94005CCBBC /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; name = .travis.yml; path = ../.travis.yml; sourceTree = ""; }; 105 | 4437B7DB1954CC83005CCBBC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 106 | 4437B7FB1956DF0B005CCBBC /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 107 | 4437B80E1956DF41005CCBBC /* RDHGridLayoutTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RDHGridLayoutTests.m; sourceTree = ""; }; 108 | 4437B8101956DF56005CCBBC /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 109 | 444926641AB6C179002DBB57 /* Tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Bridging-Header.h"; sourceTree = ""; }; 110 | 444926651AB6C17A002DBB57 /* RDHStoryboardTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RDHStoryboardTests.swift; sourceTree = ""; }; 111 | 444926691AB6C24D002DBB57 /* TestDataSources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDataSources.swift; sourceTree = ""; }; 112 | 4449266B1AB6C94C002DBB57 /* TestStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = TestStoryboard.storyboard; sourceTree = ""; }; 113 | 445AEA2D1D8EE6A7009FFBFF /* RDHEmptyGridLayoutTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RDHEmptyGridLayoutTests.swift; sourceTree = ""; }; 114 | 446C8D781BA76D7C00FFB0E3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 115 | 449FA2371B54490100EAD562 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 116 | 44BA60921D8EFC3600444CD8 /* XCTestCase+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+Extensions.h"; sourceTree = ""; }; 117 | 44BA60931D8EFC3600444CD8 /* XCTestCase+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+Extensions.m"; sourceTree = ""; }; 118 | 44F92B301C7E4A6000A0AD0A /* RDHCollectionViewGridLayout.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RDHCollectionViewGridLayout.xcconfig; sourceTree = ""; }; 119 | 44F92B351C7E65E900A0AD0A /* AppleTVDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleTVDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 120 | 44F92B371C7E65E900A0AD0A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 121 | 44F92B391C7E65E900A0AD0A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 122 | 44F92B3C1C7E65E900A0AD0A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 123 | 44F92B3E1C7E65EA00A0AD0A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 124 | 44F92B401C7E65EA00A0AD0A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 125 | /* End PBXFileReference section */ 126 | 127 | /* Begin PBXFrameworksBuildPhase section */ 128 | 442B4B161C7E685800C5B5FC /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | 442BF3EE17896E1D003FAB93 /* Frameworks */ = { 136 | isa = PBXFrameworksBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | 442B4B261C7E694400C5B5FC /* RDHCollectionViewGridLayout.framework in Frameworks */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | 4437B7F81956DF0B005CCBBC /* Frameworks */ = { 144 | isa = PBXFrameworksBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | 44F92B321C7E65E900A0AD0A /* Frameworks */ = { 151 | isa = PBXFrameworksBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 442B4B2B1C7E694700C5B5FC /* RDHCollectionViewGridLayout.framework in Frameworks */, 155 | ); 156 | runOnlyForDeploymentPostprocessing = 0; 157 | }; 158 | /* End PBXFrameworksBuildPhase section */ 159 | 160 | /* Begin PBXGroup section */ 161 | 442BF3E817896E1D003FAB93 = { 162 | isa = PBXGroup; 163 | children = ( 164 | 4437B7DA1954CB94005CCBBC /* .travis.yml */, 165 | 4437B7CF1954CB54005CCBBC /* LICENSE */, 166 | 4437B7D01954CB54005CCBBC /* RDHCollectionViewGridLayout.podspec */, 167 | 44F92B301C7E4A6000A0AD0A /* RDHCollectionViewGridLayout.xcconfig */, 168 | 4437B7D11954CB54005CCBBC /* README.md */, 169 | 4437B7D61954CB6F005CCBBC /* images */, 170 | 449FA2351B54490100EAD562 /* RDHCollectionViewGridLayout */, 171 | 442BF3FA17896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo */, 172 | 4437B8001956DF0B005CCBBC /* Tests */, 173 | 44F92B361C7E65E900A0AD0A /* AppleTVDemo */, 174 | 442BF3F217896E1D003FAB93 /* Products */, 175 | ); 176 | sourceTree = ""; 177 | }; 178 | 442BF3F217896E1D003FAB93 /* Products */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 442BF3F117896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo.app */, 182 | 4437B7FB1956DF0B005CCBBC /* Tests.xctest */, 183 | 44F92B351C7E65E900A0AD0A /* AppleTVDemo.app */, 184 | 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */, 185 | ); 186 | name = Products; 187 | sourceTree = ""; 188 | }; 189 | 442BF3FA17896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 442BF40317896E1D003FAB93 /* RDHAppDelegate.h */, 193 | 442BF40417896E1D003FAB93 /* RDHAppDelegate.m */, 194 | 442BF41417896E84003FAB93 /* RDHDemoViewController.h */, 195 | 442BF41517896E84003FAB93 /* RDHDemoViewController.m */, 196 | 442BF41117896E5D003FAB93 /* RDHDemoCell.h */, 197 | 442BF41217896E5D003FAB93 /* RDHDemoCell.m */, 198 | 442BF3FB17896E1D003FAB93 /* Supporting Files */, 199 | ); 200 | path = RDHCollectionViewGridLayoutDemo; 201 | sourceTree = ""; 202 | }; 203 | 442BF3FB17896E1D003FAB93 /* Supporting Files */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 442BF3FC17896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo-Info.plist */, 207 | 442BF3FD17896E1D003FAB93 /* InfoPlist.strings */, 208 | 442BF40017896E1D003FAB93 /* main.m */, 209 | 442BF40217896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo-Prefix.pch */, 210 | 4437B7DB1954CC83005CCBBC /* Images.xcassets */, 211 | ); 212 | name = "Supporting Files"; 213 | sourceTree = ""; 214 | }; 215 | 4437B7D61954CB6F005CCBBC /* images */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | 4437B7D71954CB6F005CCBBC /* 5-line, 10-item.png */, 219 | 4437B7D81954CB6F005CCBBC /* horizontal.png */, 220 | 4437B7D91954CB6F005CCBBC /* vertical.png */, 221 | ); 222 | path = images; 223 | sourceTree = ""; 224 | }; 225 | 4437B8001956DF0B005CCBBC /* Tests */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | 4449266B1AB6C94C002DBB57 /* TestStoryboard.storyboard */, 229 | 444926691AB6C24D002DBB57 /* TestDataSources.swift */, 230 | 4437B8011956DF0B005CCBBC /* Supporting Files */, 231 | 4437B80E1956DF41005CCBBC /* RDHGridLayoutTests.m */, 232 | 445AEA2D1D8EE6A7009FFBFF /* RDHEmptyGridLayoutTests.swift */, 233 | 444926651AB6C17A002DBB57 /* RDHStoryboardTests.swift */, 234 | ); 235 | path = Tests; 236 | sourceTree = ""; 237 | }; 238 | 4437B8011956DF0B005CCBBC /* Supporting Files */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 446C8D781BA76D7C00FFB0E3 /* Info.plist */, 242 | 4437B8101956DF56005CCBBC /* Tests-Prefix.pch */, 243 | 444926641AB6C179002DBB57 /* Tests-Bridging-Header.h */, 244 | 44BA60921D8EFC3600444CD8 /* XCTestCase+Extensions.h */, 245 | 44BA60931D8EFC3600444CD8 /* XCTestCase+Extensions.m */, 246 | ); 247 | name = "Supporting Files"; 248 | sourceTree = ""; 249 | }; 250 | 449FA2351B54490100EAD562 /* RDHCollectionViewGridLayout */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | 4437B7D31954CB5A005CCBBC /* RDHCollectionViewGridLayout.h */, 254 | 4437B7D41954CB5A005CCBBC /* RDHCollectionViewGridLayout.m */, 255 | 449FA2361B54490100EAD562 /* Supporting Files */, 256 | ); 257 | path = RDHCollectionViewGridLayout; 258 | sourceTree = ""; 259 | }; 260 | 449FA2361B54490100EAD562 /* Supporting Files */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | 449FA2371B54490100EAD562 /* Info.plist */, 264 | ); 265 | name = "Supporting Files"; 266 | sourceTree = ""; 267 | }; 268 | 44F92B361C7E65E900A0AD0A /* AppleTVDemo */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 44F92B371C7E65E900A0AD0A /* AppDelegate.swift */, 272 | 44F92B391C7E65E900A0AD0A /* ViewController.swift */, 273 | 44F92B3B1C7E65E900A0AD0A /* Main.storyboard */, 274 | 44F92B3E1C7E65EA00A0AD0A /* Assets.xcassets */, 275 | 44F92B401C7E65EA00A0AD0A /* Info.plist */, 276 | ); 277 | path = AppleTVDemo; 278 | sourceTree = ""; 279 | }; 280 | /* End PBXGroup section */ 281 | 282 | /* Begin PBXHeadersBuildPhase section */ 283 | 442B4B171C7E685800C5B5FC /* Headers */ = { 284 | isa = PBXHeadersBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 442B4B301C7E699900C5B5FC /* RDHCollectionViewGridLayout.h in Headers */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXHeadersBuildPhase section */ 292 | 293 | /* Begin PBXNativeTarget section */ 294 | 442B4B191C7E685800C5B5FC /* RDHCollectionViewGridLayout */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = 442B4B231C7E685800C5B5FC /* Build configuration list for PBXNativeTarget "RDHCollectionViewGridLayout" */; 297 | buildPhases = ( 298 | 442B4B151C7E685800C5B5FC /* Sources */, 299 | 442B4B161C7E685800C5B5FC /* Frameworks */, 300 | 442B4B171C7E685800C5B5FC /* Headers */, 301 | 442B4B181C7E685800C5B5FC /* Resources */, 302 | ); 303 | buildRules = ( 304 | ); 305 | dependencies = ( 306 | ); 307 | name = RDHCollectionViewGridLayout; 308 | productName = RDHCollectionViewGridLayout; 309 | productReference = 442B4B1A1C7E685800C5B5FC /* RDHCollectionViewGridLayout.framework */; 310 | productType = "com.apple.product-type.framework"; 311 | }; 312 | 442BF3F017896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo */ = { 313 | isa = PBXNativeTarget; 314 | buildConfigurationList = 442BF40E17896E1D003FAB93 /* Build configuration list for PBXNativeTarget "RDHCollectionViewGridLayoutDemo" */; 315 | buildPhases = ( 316 | 442BF3ED17896E1D003FAB93 /* Sources */, 317 | 442BF3EE17896E1D003FAB93 /* Frameworks */, 318 | 442BF3EF17896E1D003FAB93 /* Resources */, 319 | 442B4B2A1C7E694400C5B5FC /* Embed Frameworks */, 320 | ); 321 | buildRules = ( 322 | ); 323 | dependencies = ( 324 | 442B4B291C7E694400C5B5FC /* PBXTargetDependency */, 325 | ); 326 | name = RDHCollectionViewGridLayoutDemo; 327 | productName = RDHCollectionViewGridLayoutDemo; 328 | productReference = 442BF3F117896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo.app */; 329 | productType = "com.apple.product-type.application"; 330 | }; 331 | 4437B7FA1956DF0B005CCBBC /* Tests */ = { 332 | isa = PBXNativeTarget; 333 | buildConfigurationList = 4437B80D1956DF0B005CCBBC /* Build configuration list for PBXNativeTarget "Tests" */; 334 | buildPhases = ( 335 | 4437B7F71956DF0B005CCBBC /* Sources */, 336 | 4437B7F81956DF0B005CCBBC /* Frameworks */, 337 | 4437B7F91956DF0B005CCBBC /* Resources */, 338 | ); 339 | buildRules = ( 340 | ); 341 | dependencies = ( 342 | 4437B80A1956DF0B005CCBBC /* PBXTargetDependency */, 343 | ); 344 | name = Tests; 345 | productName = Tests; 346 | productReference = 4437B7FB1956DF0B005CCBBC /* Tests.xctest */; 347 | productType = "com.apple.product-type.bundle.unit-test"; 348 | }; 349 | 44F92B341C7E65E900A0AD0A /* AppleTVDemo */ = { 350 | isa = PBXNativeTarget; 351 | buildConfigurationList = 44F92B411C7E65EA00A0AD0A /* Build configuration list for PBXNativeTarget "AppleTVDemo" */; 352 | buildPhases = ( 353 | 44F92B311C7E65E900A0AD0A /* Sources */, 354 | 44F92B321C7E65E900A0AD0A /* Frameworks */, 355 | 44F92B331C7E65E900A0AD0A /* Resources */, 356 | 44F92B481C7E662900A0AD0A /* Embed Frameworks */, 357 | ); 358 | buildRules = ( 359 | ); 360 | dependencies = ( 361 | 442B4B2E1C7E694700C5B5FC /* PBXTargetDependency */, 362 | ); 363 | name = AppleTVDemo; 364 | productName = AppleTVDemo; 365 | productReference = 44F92B351C7E65E900A0AD0A /* AppleTVDemo.app */; 366 | productType = "com.apple.product-type.application"; 367 | }; 368 | /* End PBXNativeTarget section */ 369 | 370 | /* Begin PBXProject section */ 371 | 442BF3E917896E1D003FAB93 /* Project object */ = { 372 | isa = PBXProject; 373 | attributes = { 374 | CLASSPREFIX = RDH; 375 | LastSwiftUpdateCheck = 0720; 376 | LastUpgradeCheck = 0800; 377 | ORGANIZATIONNAME = "Rich H"; 378 | TargetAttributes = { 379 | 442B4B191C7E685800C5B5FC = { 380 | CreatedOnToolsVersion = 7.2.1; 381 | }; 382 | 442BF3F017896E1D003FAB93 = { 383 | ProvisioningStyle = Manual; 384 | }; 385 | 4437B7FA1956DF0B005CCBBC = { 386 | LastSwiftMigration = 0800; 387 | TestTargetID = 442BF3F017896E1D003FAB93; 388 | }; 389 | 44F92B341C7E65E900A0AD0A = { 390 | CreatedOnToolsVersion = 7.2.1; 391 | ProvisioningStyle = Manual; 392 | }; 393 | }; 394 | }; 395 | buildConfigurationList = 442BF3EC17896E1D003FAB93 /* Build configuration list for PBXProject "RDHCollectionViewGridLayoutDemo" */; 396 | compatibilityVersion = "Xcode 3.2"; 397 | developmentRegion = English; 398 | hasScannedForEncodings = 0; 399 | knownRegions = ( 400 | en, 401 | Base, 402 | ); 403 | mainGroup = 442BF3E817896E1D003FAB93; 404 | productRefGroup = 442BF3F217896E1D003FAB93 /* Products */; 405 | projectDirPath = ""; 406 | projectRoot = ""; 407 | targets = ( 408 | 442B4B191C7E685800C5B5FC /* RDHCollectionViewGridLayout */, 409 | 442BF3F017896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo */, 410 | 44F92B341C7E65E900A0AD0A /* AppleTVDemo */, 411 | 4437B7FA1956DF0B005CCBBC /* Tests */, 412 | ); 413 | }; 414 | /* End PBXProject section */ 415 | 416 | /* Begin PBXResourcesBuildPhase section */ 417 | 442B4B181C7E685800C5B5FC /* Resources */ = { 418 | isa = PBXResourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | ); 422 | runOnlyForDeploymentPostprocessing = 0; 423 | }; 424 | 442BF3EF17896E1D003FAB93 /* Resources */ = { 425 | isa = PBXResourcesBuildPhase; 426 | buildActionMask = 2147483647; 427 | files = ( 428 | 442BF3FF17896E1D003FAB93 /* InfoPlist.strings in Resources */, 429 | 4437B7DC1954CC83005CCBBC /* Images.xcassets in Resources */, 430 | ); 431 | runOnlyForDeploymentPostprocessing = 0; 432 | }; 433 | 4437B7F91956DF0B005CCBBC /* Resources */ = { 434 | isa = PBXResourcesBuildPhase; 435 | buildActionMask = 2147483647; 436 | files = ( 437 | 4449266C1AB6C94C002DBB57 /* TestStoryboard.storyboard in Resources */, 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | 44F92B331C7E65E900A0AD0A /* Resources */ = { 442 | isa = PBXResourcesBuildPhase; 443 | buildActionMask = 2147483647; 444 | files = ( 445 | 44F92B3F1C7E65EA00A0AD0A /* Assets.xcassets in Resources */, 446 | 44F92B3D1C7E65E900A0AD0A /* Main.storyboard in Resources */, 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | /* End PBXResourcesBuildPhase section */ 451 | 452 | /* Begin PBXSourcesBuildPhase section */ 453 | 442B4B151C7E685800C5B5FC /* Sources */ = { 454 | isa = PBXSourcesBuildPhase; 455 | buildActionMask = 2147483647; 456 | files = ( 457 | 442B4B311C7E699E00C5B5FC /* RDHCollectionViewGridLayout.m in Sources */, 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | }; 461 | 442BF3ED17896E1D003FAB93 /* Sources */ = { 462 | isa = PBXSourcesBuildPhase; 463 | buildActionMask = 2147483647; 464 | files = ( 465 | 442BF40117896E1D003FAB93 /* main.m in Sources */, 466 | 442BF40517896E1D003FAB93 /* RDHAppDelegate.m in Sources */, 467 | 442BF41617896E84003FAB93 /* RDHDemoViewController.m in Sources */, 468 | 442BF41317896E5D003FAB93 /* RDHDemoCell.m in Sources */, 469 | ); 470 | runOnlyForDeploymentPostprocessing = 0; 471 | }; 472 | 4437B7F71956DF0B005CCBBC /* Sources */ = { 473 | isa = PBXSourcesBuildPhase; 474 | buildActionMask = 2147483647; 475 | files = ( 476 | 445AEA2E1D8EE6A7009FFBFF /* RDHEmptyGridLayoutTests.swift in Sources */, 477 | 4437B80F1956DF41005CCBBC /* RDHGridLayoutTests.m in Sources */, 478 | 444926661AB6C17A002DBB57 /* RDHStoryboardTests.swift in Sources */, 479 | 44BA60941D8EFC3600444CD8 /* XCTestCase+Extensions.m in Sources */, 480 | 4449266A1AB6C24D002DBB57 /* TestDataSources.swift in Sources */, 481 | ); 482 | runOnlyForDeploymentPostprocessing = 0; 483 | }; 484 | 44F92B311C7E65E900A0AD0A /* Sources */ = { 485 | isa = PBXSourcesBuildPhase; 486 | buildActionMask = 2147483647; 487 | files = ( 488 | 44F92B3A1C7E65E900A0AD0A /* ViewController.swift in Sources */, 489 | 44F92B381C7E65E900A0AD0A /* AppDelegate.swift in Sources */, 490 | ); 491 | runOnlyForDeploymentPostprocessing = 0; 492 | }; 493 | /* End PBXSourcesBuildPhase section */ 494 | 495 | /* Begin PBXTargetDependency section */ 496 | 442B4B291C7E694400C5B5FC /* PBXTargetDependency */ = { 497 | isa = PBXTargetDependency; 498 | target = 442B4B191C7E685800C5B5FC /* RDHCollectionViewGridLayout */; 499 | targetProxy = 442B4B281C7E694400C5B5FC /* PBXContainerItemProxy */; 500 | }; 501 | 442B4B2E1C7E694700C5B5FC /* PBXTargetDependency */ = { 502 | isa = PBXTargetDependency; 503 | target = 442B4B191C7E685800C5B5FC /* RDHCollectionViewGridLayout */; 504 | targetProxy = 442B4B2D1C7E694700C5B5FC /* PBXContainerItemProxy */; 505 | }; 506 | 4437B80A1956DF0B005CCBBC /* PBXTargetDependency */ = { 507 | isa = PBXTargetDependency; 508 | target = 442BF3F017896E1D003FAB93 /* RDHCollectionViewGridLayoutDemo */; 509 | targetProxy = 4437B8091956DF0B005CCBBC /* PBXContainerItemProxy */; 510 | }; 511 | /* End PBXTargetDependency section */ 512 | 513 | /* Begin PBXVariantGroup section */ 514 | 442BF3FD17896E1D003FAB93 /* InfoPlist.strings */ = { 515 | isa = PBXVariantGroup; 516 | children = ( 517 | 442BF3FE17896E1D003FAB93 /* en */, 518 | ); 519 | name = InfoPlist.strings; 520 | sourceTree = ""; 521 | }; 522 | 44F92B3B1C7E65E900A0AD0A /* Main.storyboard */ = { 523 | isa = PBXVariantGroup; 524 | children = ( 525 | 44F92B3C1C7E65E900A0AD0A /* Base */, 526 | ); 527 | name = Main.storyboard; 528 | sourceTree = ""; 529 | }; 530 | /* End PBXVariantGroup section */ 531 | 532 | /* Begin XCBuildConfiguration section */ 533 | 442B4B241C7E685800C5B5FC /* Debug */ = { 534 | isa = XCBuildConfiguration; 535 | baseConfigurationReference = 44F92B301C7E4A6000A0AD0A /* RDHCollectionViewGridLayout.xcconfig */; 536 | buildSettings = { 537 | CLANG_ENABLE_MODULES = YES; 538 | CLANG_WARN_BOOL_CONVERSION = YES; 539 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 540 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 541 | CLANG_WARN_UNREACHABLE_CODE = YES; 542 | CODE_SIGN_IDENTITY = ""; 543 | CURRENT_PROJECT_VERSION = 1; 544 | DEFINES_MODULE = YES; 545 | DYLIB_COMPATIBILITY_VERSION = 1; 546 | DYLIB_CURRENT_VERSION = 1; 547 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 548 | ENABLE_STRICT_OBJC_MSGSEND = YES; 549 | GCC_NO_COMMON_BLOCKS = YES; 550 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 551 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 552 | GCC_WARN_UNDECLARED_SELECTOR = YES; 553 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 554 | GCC_WARN_UNUSED_FUNCTION = YES; 555 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 556 | MTL_ENABLE_DEBUG_INFO = YES; 557 | SDKROOT = ""; 558 | SKIP_INSTALL = YES; 559 | VERSIONING_SYSTEM = "apple-generic"; 560 | VERSION_INFO_PREFIX = ""; 561 | }; 562 | name = Debug; 563 | }; 564 | 442B4B251C7E685800C5B5FC /* Release */ = { 565 | isa = XCBuildConfiguration; 566 | baseConfigurationReference = 44F92B301C7E4A6000A0AD0A /* RDHCollectionViewGridLayout.xcconfig */; 567 | buildSettings = { 568 | CLANG_ENABLE_MODULES = YES; 569 | CLANG_WARN_BOOL_CONVERSION = YES; 570 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 571 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 572 | CLANG_WARN_UNREACHABLE_CODE = YES; 573 | CODE_SIGN_IDENTITY = ""; 574 | COPY_PHASE_STRIP = NO; 575 | CURRENT_PROJECT_VERSION = 1; 576 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 577 | DEFINES_MODULE = YES; 578 | DYLIB_COMPATIBILITY_VERSION = 1; 579 | DYLIB_CURRENT_VERSION = 1; 580 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 581 | ENABLE_NS_ASSERTIONS = NO; 582 | ENABLE_STRICT_OBJC_MSGSEND = YES; 583 | GCC_NO_COMMON_BLOCKS = YES; 584 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 585 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 586 | GCC_WARN_UNDECLARED_SELECTOR = YES; 587 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 588 | GCC_WARN_UNUSED_FUNCTION = YES; 589 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 590 | MTL_ENABLE_DEBUG_INFO = NO; 591 | SDKROOT = ""; 592 | SKIP_INSTALL = YES; 593 | VERSIONING_SYSTEM = "apple-generic"; 594 | VERSION_INFO_PREFIX = ""; 595 | }; 596 | name = Release; 597 | }; 598 | 442BF40C17896E1D003FAB93 /* Debug */ = { 599 | isa = XCBuildConfiguration; 600 | buildSettings = { 601 | ALWAYS_SEARCH_USER_PATHS = NO; 602 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 603 | CLANG_CXX_LIBRARY = "libc++"; 604 | CLANG_ENABLE_OBJC_ARC = YES; 605 | CLANG_WARN_BOOL_CONVERSION = YES; 606 | CLANG_WARN_CONSTANT_CONVERSION = YES; 607 | CLANG_WARN_EMPTY_BODY = YES; 608 | CLANG_WARN_ENUM_CONVERSION = YES; 609 | CLANG_WARN_INFINITE_RECURSION = YES; 610 | CLANG_WARN_INT_CONVERSION = YES; 611 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 612 | CLANG_WARN_UNREACHABLE_CODE = YES; 613 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 614 | COPY_PHASE_STRIP = NO; 615 | DEBUG_INFORMATION_FORMAT = dwarf; 616 | DEFINES_MODULE = YES; 617 | ENABLE_STRICT_OBJC_MSGSEND = YES; 618 | ENABLE_TESTABILITY = YES; 619 | GCC_C_LANGUAGE_STANDARD = gnu99; 620 | GCC_DYNAMIC_NO_PIC = NO; 621 | GCC_NO_COMMON_BLOCKS = YES; 622 | GCC_OPTIMIZATION_LEVEL = 0; 623 | GCC_PREPROCESSOR_DEFINITIONS = ( 624 | "DEBUG=1", 625 | "$(inherited)", 626 | ); 627 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 628 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 629 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 630 | GCC_WARN_UNDECLARED_SELECTOR = YES; 631 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 632 | GCC_WARN_UNUSED_FUNCTION = YES; 633 | GCC_WARN_UNUSED_VARIABLE = YES; 634 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 635 | ONLY_ACTIVE_ARCH = YES; 636 | SDKROOT = iphoneos; 637 | SWIFT_VERSION = 3.0; 638 | TARGETED_DEVICE_FAMILY = "1,2"; 639 | }; 640 | name = Debug; 641 | }; 642 | 442BF40D17896E1D003FAB93 /* Release */ = { 643 | isa = XCBuildConfiguration; 644 | buildSettings = { 645 | ALWAYS_SEARCH_USER_PATHS = NO; 646 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 647 | CLANG_CXX_LIBRARY = "libc++"; 648 | CLANG_ENABLE_OBJC_ARC = YES; 649 | CLANG_WARN_BOOL_CONVERSION = YES; 650 | CLANG_WARN_CONSTANT_CONVERSION = YES; 651 | CLANG_WARN_EMPTY_BODY = YES; 652 | CLANG_WARN_ENUM_CONVERSION = YES; 653 | CLANG_WARN_INFINITE_RECURSION = YES; 654 | CLANG_WARN_INT_CONVERSION = YES; 655 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 656 | CLANG_WARN_UNREACHABLE_CODE = YES; 657 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 658 | COPY_PHASE_STRIP = YES; 659 | DEFINES_MODULE = YES; 660 | ENABLE_STRICT_OBJC_MSGSEND = YES; 661 | GCC_C_LANGUAGE_STANDARD = gnu99; 662 | GCC_NO_COMMON_BLOCKS = YES; 663 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 664 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 665 | GCC_WARN_UNDECLARED_SELECTOR = YES; 666 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 667 | GCC_WARN_UNUSED_FUNCTION = YES; 668 | GCC_WARN_UNUSED_VARIABLE = YES; 669 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 670 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 671 | PROVISIONING_PROFILE = ""; 672 | SDKROOT = iphoneos; 673 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 674 | SWIFT_VERSION = 3.0; 675 | TARGETED_DEVICE_FAMILY = "1,2"; 676 | VALIDATE_PRODUCT = YES; 677 | }; 678 | name = Release; 679 | }; 680 | 442BF40F17896E1D003FAB93 /* Debug */ = { 681 | isa = XCBuildConfiguration; 682 | buildSettings = { 683 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 684 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 685 | CLANG_ENABLE_MODULES = YES; 686 | CODE_SIGN_IDENTITY = "iPhone Developer"; 687 | DEVELOPMENT_TEAM = ""; 688 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 689 | GCC_PREFIX_HEADER = "RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Prefix.pch"; 690 | INFOPLIST_FILE = "RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Info.plist"; 691 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 692 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 693 | PRODUCT_BUNDLE_IDENTIFIER = "uk.co.rhodgkins.${PRODUCT_NAME:rfc1034identifier}"; 694 | PRODUCT_NAME = "$(TARGET_NAME)"; 695 | WRAPPER_EXTENSION = app; 696 | }; 697 | name = Debug; 698 | }; 699 | 442BF41017896E1D003FAB93 /* Release */ = { 700 | isa = XCBuildConfiguration; 701 | buildSettings = { 702 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 703 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 704 | CLANG_ENABLE_MODULES = YES; 705 | CODE_SIGN_IDENTITY = "iPhone Distribution"; 706 | DEVELOPMENT_TEAM = ""; 707 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 708 | GCC_PREFIX_HEADER = "RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Prefix.pch"; 709 | INFOPLIST_FILE = "RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Info.plist"; 710 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 711 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 712 | PRODUCT_BUNDLE_IDENTIFIER = "uk.co.rhodgkins.${PRODUCT_NAME:rfc1034identifier}"; 713 | PRODUCT_NAME = "$(TARGET_NAME)"; 714 | WRAPPER_EXTENSION = app; 715 | }; 716 | name = Release; 717 | }; 718 | 4437B80B1956DF0B005CCBBC /* Debug */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RDHCollectionViewGridLayoutDemo.app/RDHCollectionViewGridLayoutDemo"; 722 | CLANG_ENABLE_MODULES = YES; 723 | CLANG_WARN_BOOL_CONVERSION = YES; 724 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 725 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 726 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 727 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 728 | GCC_PREPROCESSOR_DEFINITIONS = ( 729 | "DEBUG=1", 730 | "$(inherited)", 731 | ); 732 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 733 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 734 | GCC_WARN_UNDECLARED_SELECTOR = YES; 735 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 736 | GCC_WARN_UNUSED_FUNCTION = YES; 737 | INFOPLIST_FILE = Tests/Info.plist; 738 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 739 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 740 | PRODUCT_NAME = "$(TARGET_NAME)"; 741 | SWIFT_OBJC_BRIDGING_HEADER = "Tests/Tests-Bridging-Header.h"; 742 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 743 | TEST_HOST = "$(BUNDLE_LOADER)"; 744 | WRAPPER_EXTENSION = xctest; 745 | }; 746 | name = Debug; 747 | }; 748 | 4437B80C1956DF0B005CCBBC /* Release */ = { 749 | isa = XCBuildConfiguration; 750 | buildSettings = { 751 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RDHCollectionViewGridLayoutDemo.app/RDHCollectionViewGridLayoutDemo"; 752 | CLANG_ENABLE_MODULES = YES; 753 | CLANG_WARN_BOOL_CONVERSION = YES; 754 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 755 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 756 | ENABLE_NS_ASSERTIONS = NO; 757 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 758 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 759 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 760 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 761 | GCC_WARN_UNDECLARED_SELECTOR = YES; 762 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 763 | GCC_WARN_UNUSED_FUNCTION = YES; 764 | INFOPLIST_FILE = Tests/Info.plist; 765 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 766 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 767 | PRODUCT_NAME = "$(TARGET_NAME)"; 768 | SWIFT_OBJC_BRIDGING_HEADER = "Tests/Tests-Bridging-Header.h"; 769 | TEST_HOST = "$(BUNDLE_LOADER)"; 770 | WRAPPER_EXTENSION = xctest; 771 | }; 772 | name = Release; 773 | }; 774 | 44F92B421C7E65EA00A0AD0A /* Debug */ = { 775 | isa = XCBuildConfiguration; 776 | buildSettings = { 777 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 778 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 779 | CLANG_ENABLE_MODULES = YES; 780 | CLANG_WARN_BOOL_CONVERSION = YES; 781 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 782 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 783 | CLANG_WARN_UNREACHABLE_CODE = YES; 784 | CODE_SIGN_IDENTITY = "iPhone Developer"; 785 | DEVELOPMENT_TEAM = ""; 786 | ENABLE_STRICT_OBJC_MSGSEND = YES; 787 | GCC_NO_COMMON_BLOCKS = YES; 788 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 789 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 790 | GCC_WARN_UNDECLARED_SELECTOR = YES; 791 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 792 | GCC_WARN_UNUSED_FUNCTION = YES; 793 | INFOPLIST_FILE = AppleTVDemo/Info.plist; 794 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 795 | MTL_ENABLE_DEBUG_INFO = YES; 796 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.rhodgkins.RDHCollectionViewGridLayoutDemo.AppleTVDemo; 797 | PRODUCT_NAME = "$(TARGET_NAME)"; 798 | SDKROOT = appletvos; 799 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 800 | TARGETED_DEVICE_FAMILY = 3; 801 | TVOS_DEPLOYMENT_TARGET = 9.0; 802 | }; 803 | name = Debug; 804 | }; 805 | 44F92B431C7E65EA00A0AD0A /* Release */ = { 806 | isa = XCBuildConfiguration; 807 | buildSettings = { 808 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 809 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 810 | CLANG_ENABLE_MODULES = YES; 811 | CLANG_WARN_BOOL_CONVERSION = YES; 812 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 813 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 814 | CLANG_WARN_UNREACHABLE_CODE = YES; 815 | CODE_SIGN_IDENTITY = "iPhone Distribution"; 816 | COPY_PHASE_STRIP = NO; 817 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 818 | DEVELOPMENT_TEAM = ""; 819 | ENABLE_NS_ASSERTIONS = NO; 820 | ENABLE_STRICT_OBJC_MSGSEND = YES; 821 | GCC_NO_COMMON_BLOCKS = YES; 822 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 823 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 824 | GCC_WARN_UNDECLARED_SELECTOR = YES; 825 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 826 | GCC_WARN_UNUSED_FUNCTION = YES; 827 | INFOPLIST_FILE = AppleTVDemo/Info.plist; 828 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 829 | MTL_ENABLE_DEBUG_INFO = NO; 830 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.rhodgkins.RDHCollectionViewGridLayoutDemo.AppleTVDemo; 831 | PRODUCT_NAME = "$(TARGET_NAME)"; 832 | SDKROOT = appletvos; 833 | TARGETED_DEVICE_FAMILY = 3; 834 | TVOS_DEPLOYMENT_TARGET = 9.0; 835 | }; 836 | name = Release; 837 | }; 838 | /* End XCBuildConfiguration section */ 839 | 840 | /* Begin XCConfigurationList section */ 841 | 442B4B231C7E685800C5B5FC /* Build configuration list for PBXNativeTarget "RDHCollectionViewGridLayout" */ = { 842 | isa = XCConfigurationList; 843 | buildConfigurations = ( 844 | 442B4B241C7E685800C5B5FC /* Debug */, 845 | 442B4B251C7E685800C5B5FC /* Release */, 846 | ); 847 | defaultConfigurationIsVisible = 0; 848 | defaultConfigurationName = Release; 849 | }; 850 | 442BF3EC17896E1D003FAB93 /* Build configuration list for PBXProject "RDHCollectionViewGridLayoutDemo" */ = { 851 | isa = XCConfigurationList; 852 | buildConfigurations = ( 853 | 442BF40C17896E1D003FAB93 /* Debug */, 854 | 442BF40D17896E1D003FAB93 /* Release */, 855 | ); 856 | defaultConfigurationIsVisible = 0; 857 | defaultConfigurationName = Release; 858 | }; 859 | 442BF40E17896E1D003FAB93 /* Build configuration list for PBXNativeTarget "RDHCollectionViewGridLayoutDemo" */ = { 860 | isa = XCConfigurationList; 861 | buildConfigurations = ( 862 | 442BF40F17896E1D003FAB93 /* Debug */, 863 | 442BF41017896E1D003FAB93 /* Release */, 864 | ); 865 | defaultConfigurationIsVisible = 0; 866 | defaultConfigurationName = Release; 867 | }; 868 | 4437B80D1956DF0B005CCBBC /* Build configuration list for PBXNativeTarget "Tests" */ = { 869 | isa = XCConfigurationList; 870 | buildConfigurations = ( 871 | 4437B80B1956DF0B005CCBBC /* Debug */, 872 | 4437B80C1956DF0B005CCBBC /* Release */, 873 | ); 874 | defaultConfigurationIsVisible = 0; 875 | defaultConfigurationName = Release; 876 | }; 877 | 44F92B411C7E65EA00A0AD0A /* Build configuration list for PBXNativeTarget "AppleTVDemo" */ = { 878 | isa = XCConfigurationList; 879 | buildConfigurations = ( 880 | 44F92B421C7E65EA00A0AD0A /* Debug */, 881 | 44F92B431C7E65EA00A0AD0A /* Release */, 882 | ); 883 | defaultConfigurationIsVisible = 0; 884 | defaultConfigurationName = Release; 885 | }; 886 | /* End XCConfigurationList section */ 887 | }; 888 | rootObject = 442BF3E917896E1D003FAB93 /* Project object */; 889 | } 890 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo.xcodeproj/xcshareddata/xcschemes/AppleTVDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo.xcodeproj/xcshareddata/xcschemes/RDHCollectionViewGridLayout.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 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo.xcodeproj/xcshareddata/xcschemes/RDHCollectionViewGridLayoutDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "57x57", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "57x57", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | }, 38 | "properties" : { 39 | "pre-rendered" : true 40 | } 41 | } -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "filename" : "Default@2x-1.png", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "extent" : "full-screen", 13 | "idiom" : "iphone", 14 | "subtype" : "retina4", 15 | "filename" : "Default-568h@2x-1.png", 16 | "minimum-system-version" : "7.0", 17 | "orientation" : "portrait", 18 | "scale" : "2x" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "iphone", 23 | "extent" : "full-screen", 24 | "filename" : "Default.png", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "orientation" : "portrait", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "filename" : "Default@2x.png", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "orientation" : "portrait", 36 | "idiom" : "iphone", 37 | "extent" : "full-screen", 38 | "filename" : "Default-568h@2x.png", 39 | "subtype" : "retina4", 40 | "scale" : "2x" 41 | } 42 | ], 43 | "info" : { 44 | "version" : 1, 45 | "author" : "xcode" 46 | } 47 | } -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x-1.png -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default.png -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default@2x-1.png -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/RDHCollectionViewGridLayoutDemo/Images.xcassets/LaunchImage.launchimage/Default@2x.png -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RDHAppDelegate.h 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 07/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RDHDemoViewController; 12 | 13 | @interface RDHAppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (nonatomic, weak) RDHDemoViewController *demoViewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RDHAppDelegate.m 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 07/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import "RDHAppDelegate.h" 10 | 11 | #import "RDHDemoViewController.h" 12 | 13 | @implementation RDHAppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | RDHDemoViewController *vc = [RDHDemoViewController new]; 18 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 19 | // Override point for customization after application launch. 20 | self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc]; 21 | self.demoViewController = vc; 22 | self.window.backgroundColor = [UIColor whiteColor]; 23 | [self.window makeKeyAndVisible]; 24 | return YES; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHCollectionViewGridLayoutDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'RDHCollectionViewGridLayoutDemo' target in the 'RDHCollectionViewGridLayoutDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_6_0 8 | # warning "This project uses features only available in iOS SDK 6.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #endif 14 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHDemoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // RDHDemoCell.h 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RDHDemoCell : UICollectionViewCell 12 | 13 | +(NSString *)reuseIdentifier; 14 | 15 | -(void)setText:(NSString *)text; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHDemoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // RDHDemoCell.m 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import "RDHDemoCell.h" 10 | 11 | static CGFloat const RDH_CELL_PADDING = 0; 12 | 13 | @interface RDHDemoCell () 14 | 15 | @property (nonatomic, weak, readonly) UILabel *label; 16 | 17 | @end 18 | 19 | @implementation RDHDemoCell 20 | 21 | +(NSString *)reuseIdentifier 22 | { 23 | return NSStringFromClass(self); 24 | } 25 | 26 | - (instancetype)initWithFrame:(CGRect)frame 27 | { 28 | self = [super initWithFrame:frame]; 29 | if (self) { 30 | // Initialization code 31 | 32 | UILabel *label = [UILabel new]; 33 | label.translatesAutoresizingMaskIntoConstraints = NO; 34 | label.backgroundColor = [UIColor clearColor]; 35 | label.font = [UIFont boldSystemFontOfSize:14]; 36 | label.textAlignment = NSTextAlignmentCenter; 37 | label.textColor = [UIColor whiteColor]; 38 | label.numberOfLines = 3; 39 | [self.contentView addSubview:label]; 40 | _label = label; 41 | 42 | // Pin to superview edge with RDH_CELL_PADDING inset 43 | [self pinView:label toContentViewAttribute:NSLayoutAttributeLeft withPadding:RDH_CELL_PADDING]; 44 | [self pinView:label toContentViewAttribute:NSLayoutAttributeTop withPadding:RDH_CELL_PADDING]; 45 | [self pinView:label toContentViewAttribute:NSLayoutAttributeRight withPadding:RDH_CELL_PADDING]; 46 | [self pinView:label toContentViewAttribute:NSLayoutAttributeBottom withPadding:RDH_CELL_PADDING]; 47 | 48 | self.backgroundView = [UIView new]; 49 | self.backgroundView.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.2]; 50 | } 51 | return self; 52 | } 53 | 54 | -(void)pinView:(UIView *)view toContentViewAttribute:(NSLayoutAttribute)attribute withPadding:(CGFloat)padding 55 | { 56 | [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:attribute relatedBy:NSLayoutRelationEqual toItem:view attribute:attribute multiplier:1 constant:padding]]; 57 | } 58 | 59 | -(void)setText:(NSString *)text 60 | { 61 | self.label.text = text; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RDHDemoViewController.h 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RDHDemoViewController : UICollectionViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/RDHCollectionViewGridLayoutDemo/RDHDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RDHDemoViewController.m 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import "RDHDemoViewController.h" 10 | 11 | @import RDHCollectionViewGridLayout; 12 | 13 | #import "RDHDemoCell.h" 14 | 15 | #define RDH_RANDOM_DATA 1 16 | 17 | #define FIXED_LAYOUT 0 18 | 19 | @interface RDHDemoViewController () 20 | 21 | @property (nonatomic, readonly) RDHCollectionViewGridLayout *currentCollectionViewLayout; 22 | 23 | @property (nonatomic, copy, readonly) NSDictionary *testData; 24 | 25 | @end 26 | 27 | @implementation RDHDemoViewController 28 | 29 | @dynamic collectionViewLayout; 30 | 31 | +(RDHCollectionViewGridLayout *)newGridLayout 32 | { 33 | RDHCollectionViewGridLayout *layout = [RDHCollectionViewGridLayout new]; 34 | layout.lineItemCount = RDH_RANDOM_DATA ? ((arc4random() % 5) + 1) : 4; 35 | layout.lineSpacing = RDH_RANDOM_DATA ? (arc4random() % 16) : 5; 36 | layout.itemSpacing = RDH_RANDOM_DATA ? (arc4random() % 16) : 10; 37 | if (RDH_RANDOM_DATA) { 38 | switch (arc4random() % 3) { 39 | case 0: // Line size 40 | layout.lineSize = 160; 41 | break; 42 | case 1: // Line multiplier 43 | layout.lineMultiplier = 2; 44 | break; 45 | case 2: // Line extension 46 | layout.lineExtension = 100; 47 | break; 48 | } 49 | } else { 50 | layout.lineSize = 0; 51 | } 52 | layout.scrollDirection = UICollectionViewScrollDirectionVertical; 53 | layout.sectionsStartOnNewLine = YES; 54 | 55 | #if FIXED_LAYOUT 56 | 57 | layout.itemSpacing = 0; 58 | layout.lineSpacing = 0; 59 | layout.lineSize = 0; 60 | layout.lineItemCount = 3; 61 | layout.scrollDirection = UICollectionViewScrollDirectionVertical; 62 | layout.sectionsStartOnNewLine = NO; 63 | 64 | #endif 65 | 66 | return layout; 67 | } 68 | 69 | - (instancetype)init 70 | { 71 | self = [self initWithCollectionViewLayout:[[self class] newGridLayout]]; 72 | if (self) { 73 | // Custom initialization 74 | 75 | NSUInteger sectionCount = RDH_RANDOM_DATA ? (arc4random() % 20) + 10 : 10; 76 | NSMutableDictionary *testData = [NSMutableDictionary dictionaryWithCapacity:sectionCount]; 77 | for (NSUInteger i=0; i 10 | 11 | #import "RDHAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([RDHAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Tests/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 | -------------------------------------------------------------------------------- /Examples/Tests/RDHEmptyGridLayoutTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RDHEmptyGridLayoutTests.swift 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 22/06/2014. 6 | // Copyright (c) 2014 Rich H. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | final class RDHEmptyGridLayoutTests : XCTestCase { 13 | 14 | private var collectionView: UICollectionView! 15 | private var layout: RDHCollectionViewGridLayout! 16 | private let dataSource = EmptyTestDataSource() 17 | private var window: UIWindow! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | 22 | window = UIWindow(frame: CGRect(x: 0, y: 0, width: 640, height: 640)) 23 | window.makeKeyAndVisible() 24 | layout = RDHCollectionViewGridLayout() 25 | let controller = UICollectionViewController(collectionViewLayout: layout) 26 | controller.edgesForExtendedLayout = [] 27 | controller.automaticallyAdjustsScrollViewInsets = false 28 | collectionView = controller.collectionView 29 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: TestDataSource.CellID) 30 | collectionView.dataSource = dataSource 31 | collectionView.delegate = nil 32 | window.rootViewController = controller 33 | } 34 | 35 | func testParameters() { 36 | layout.scrollDirection = .horizontal 37 | XCTAssertEqual(layout.scrollDirection, .horizontal, "Scroll direction should be horiztonal") 38 | 39 | layout.scrollDirection = .horizontal 40 | XCTAssertFalse(layout.value(forKeyPath: "verticalScrolling") as! Bool) 41 | layout.scrollDirection = .vertical 42 | XCTAssertTrue(layout.value(forKeyPath: "verticalScrolling") as! Bool) 43 | 44 | layout.setValue(true, forKey: "verticalScrolling") 45 | XCTAssertEqual(layout.scrollDirection, .vertical, "Scroll direction should be vertical") 46 | layout.setValue(false, forKey: "verticalScrolling") 47 | XCTAssertEqual(layout.scrollDirection, .horizontal, "Scroll direction should be horiztonal") 48 | 49 | layout.lineSize = 123 50 | XCTAssertEqual(layout.lineSize, 123, "Line size is incorrect") 51 | XCTAssertEqual(layout.lineMultiplier, 1, "Line multiplier should be default") 52 | XCTAssertEqual(layout.lineExtension, 0, "Line extension should be default") 53 | 54 | layout.lineMultiplier = 2 55 | XCTAssertEqual(layout.lineSize, 0, "Line size should be default") 56 | XCTAssertEqual(layout.lineMultiplier, 2, "Line multiplier is incorrect") 57 | XCTAssertEqual(layout.lineExtension, 0, "Line extension should be default") 58 | 59 | layout.lineExtension = 200 60 | XCTAssertEqual(layout.lineSize, 0, "Line size should be default") 61 | XCTAssertEqual(layout.lineMultiplier, 1, "Line multiplier should be default") 62 | XCTAssertEqual(layout.lineExtension, 200, "Line extension is incorrect") 63 | 64 | layout.lineExtension = 0 65 | XCTAssertEqual(layout.lineSize, 0, "Line size should be default") 66 | XCTAssertEqual(layout.lineMultiplier, 1, "Line multiplier should be default") 67 | XCTAssertEqual(layout.lineExtension, 0, "Line extension should be default") 68 | 69 | layout.lineDimension = 123 70 | XCTAssertEqual(layout.lineSize, 123, "Line size is incorrect") 71 | XCTAssertEqual(layout.lineMultiplier, 1, "Line multiplier should be default") 72 | XCTAssertEqual(layout.lineExtension, 0, "Line extension should be default") 73 | 74 | layout.lineItemCount = 7 75 | XCTAssertEqual(layout.lineItemCount, 7, "Line item count should be 7") 76 | 77 | layout.itemSpacing = 23 78 | XCTAssertEqual(layout.itemSpacing, 23, "Line spacing should be 23") 79 | 80 | layout.sectionsStartOnNewLine = true 81 | XCTAssertTrue(layout.sectionsStartOnNewLine, "Sections should start on new lines") 82 | } 83 | 84 | func updateWindowFrame() { 85 | 86 | var frame = CGRect.zero 87 | if (layout.scrollDirection == .vertical) { 88 | frame.size.width = 640 89 | frame.size.height = 2000 90 | } else { 91 | frame.size.width = 2000 92 | frame.size.height = 640 93 | } 94 | window.frame = frame 95 | window.layoutIfNeeded() 96 | collectionView.frame = frame 97 | collectionView.layoutIfNeeded() 98 | } 99 | 100 | func testSizeLocationsWithVerticalScrollViewSectionsStartOnNewLineFalse() { 101 | 102 | layout.itemSpacing = 0 103 | layout.lineSpacing = 0 104 | layout.lineSize = 120 105 | layout.lineItemCount = 2 106 | layout.scrollDirection = .vertical 107 | layout.sectionsStartOnNewLine = false 108 | updateWindowFrame() 109 | 110 | checkFrames() 111 | } 112 | 113 | func testSizeLocationsWithHorizontalScrollViewSectionsStartOnNewLineFalse() { 114 | 115 | layout.itemSpacing = 0 116 | layout.lineSpacing = 0 117 | layout.lineSize = 120 118 | layout.lineItemCount = 2 119 | layout.scrollDirection = .horizontal 120 | layout.sectionsStartOnNewLine = false 121 | updateWindowFrame() 122 | 123 | checkFrames() 124 | } 125 | 126 | func testSizeLocationsWithVerticalScrollViewSectionsStartOnNewLineTrue() { 127 | 128 | layout.itemSpacing = 0 129 | layout.lineSpacing = 0 130 | layout.lineSize = 120 131 | layout.lineItemCount = 2 132 | layout.scrollDirection = .vertical 133 | layout.sectionsStartOnNewLine = true 134 | updateWindowFrame() 135 | 136 | checkFrames() 137 | } 138 | 139 | func testSizeLocationsWithHorizontalScrollViewSectionsStartOnNewLineTrue() { 140 | 141 | layout.itemSpacing = 0 142 | layout.lineSpacing = 0 143 | layout.lineSize = 120 144 | layout.lineItemCount = 2 145 | layout.scrollDirection = .horizontal 146 | layout.sectionsStartOnNewLine = true 147 | updateWindowFrame() 148 | 149 | checkFrames() 150 | } 151 | 152 | func testMultiplierDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineFalse() { 153 | 154 | layout.itemSpacing = 0 155 | layout.lineSpacing = 0 156 | layout.lineMultiplier = 0.5 157 | layout.lineItemCount = 2 158 | layout.scrollDirection = .vertical 159 | layout.sectionsStartOnNewLine = false 160 | updateWindowFrame() 161 | 162 | checkFrames() 163 | } 164 | 165 | func testMultiplierDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineFalse() { 166 | 167 | layout.itemSpacing = 0 168 | layout.lineSpacing = 0 169 | layout.lineMultiplier = 0.5 170 | layout.lineItemCount = 2 171 | layout.scrollDirection = .horizontal 172 | layout.sectionsStartOnNewLine = false 173 | updateWindowFrame() 174 | 175 | checkFrames() 176 | } 177 | 178 | func testMultiplierDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineTrue() { 179 | 180 | layout.itemSpacing = 0 181 | layout.lineSpacing = 0 182 | layout.lineMultiplier = 0.5 183 | layout.lineItemCount = 2 184 | layout.scrollDirection = .vertical 185 | layout.sectionsStartOnNewLine = true 186 | updateWindowFrame() 187 | 188 | checkFrames() 189 | } 190 | 191 | func testMultiplierDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineTrue() { 192 | 193 | layout.itemSpacing = 0 194 | layout.lineSpacing = 0 195 | layout.lineMultiplier = 0.5 196 | layout.lineItemCount = 2 197 | layout.scrollDirection = .horizontal 198 | layout.sectionsStartOnNewLine = true 199 | updateWindowFrame() 200 | 201 | checkFrames() 202 | } 203 | 204 | func testExtensionDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineFalse() { 205 | 206 | layout.itemSpacing = 0 207 | layout.lineSpacing = 0 208 | layout.lineExtension = 50 209 | layout.lineItemCount = 2 210 | layout.scrollDirection = .vertical 211 | layout.sectionsStartOnNewLine = false 212 | updateWindowFrame() 213 | 214 | checkFrames() 215 | } 216 | 217 | func testExtensionDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineFalse() { 218 | 219 | layout.itemSpacing = 0 220 | layout.lineSpacing = 0 221 | layout.lineExtension = 50 222 | layout.lineItemCount = 2 223 | layout.scrollDirection = .horizontal 224 | layout.sectionsStartOnNewLine = false 225 | updateWindowFrame() 226 | 227 | checkFrames() 228 | } 229 | 230 | func testExtensionDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineTrue() { 231 | 232 | layout.itemSpacing = 0 233 | layout.lineSpacing = 0 234 | layout.lineExtension = 50 235 | layout.lineItemCount = 2 236 | layout.scrollDirection = .vertical 237 | layout.sectionsStartOnNewLine = true 238 | updateWindowFrame() 239 | 240 | checkFrames() 241 | } 242 | 243 | func testExtensionWithHorizontalScrollViewSectionsStartOnNewLineTrue() { 244 | 245 | layout.itemSpacing = 0 246 | layout.lineSpacing = 0 247 | layout.lineExtension = 50 248 | layout.lineItemCount = 2 249 | layout.scrollDirection = .horizontal 250 | layout.sectionsStartOnNewLine = true 251 | updateWindowFrame() 252 | 253 | checkFrames() 254 | } 255 | 256 | func testAutomaticDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineFalse() { 257 | 258 | layout.itemSpacing = 0 259 | layout.lineSpacing = 0 260 | layout.lineSize = 0 261 | layout.lineItemCount = 2 262 | layout.scrollDirection = .vertical 263 | layout.sectionsStartOnNewLine = false 264 | updateWindowFrame() 265 | 266 | checkFrames() 267 | } 268 | 269 | func testAutomaticDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineFalse() { 270 | 271 | layout.itemSpacing = 0 272 | layout.lineSpacing = 0 273 | layout.lineSize = 0 274 | layout.lineItemCount = 2 275 | layout.scrollDirection = .horizontal 276 | layout.sectionsStartOnNewLine = false 277 | updateWindowFrame() 278 | 279 | checkFrames() 280 | } 281 | 282 | func testAutomaticDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineTrue() { 283 | 284 | layout.itemSpacing = 0 285 | layout.lineSpacing = 0 286 | layout.lineSize = 0 287 | layout.lineItemCount = 2 288 | layout.scrollDirection = .vertical 289 | layout.sectionsStartOnNewLine = true 290 | updateWindowFrame() 291 | 292 | checkFrames() 293 | } 294 | 295 | func testAutomaticWithHorizontalScrollViewSectionsStartOnNewLineTrue() { 296 | 297 | layout.itemSpacing = 0 298 | layout.lineSpacing = 0 299 | layout.lineSize = 0 300 | layout.lineItemCount = 2 301 | layout.scrollDirection = .horizontal 302 | layout.sectionsStartOnNewLine = true 303 | updateWindowFrame() 304 | 305 | checkFrames() 306 | } 307 | 308 | func testDirtyPixelCalculationsVerticalScrolling() { 309 | 310 | layout.itemSpacing = 0 311 | layout.lineSpacing = 0 312 | layout.lineSize = 0 313 | layout.lineItemCount = 3 314 | layout.scrollDirection = .vertical 315 | layout.sectionsStartOnNewLine = false 316 | updateWindowFrame() 317 | 318 | checkFrames() 319 | } 320 | 321 | func testDirtyPixelCalculationsHorizontalScrolling() { 322 | 323 | layout.itemSpacing = 0 324 | layout.lineSpacing = 0 325 | layout.lineSize = 0 326 | layout.lineItemCount = 3 327 | layout.scrollDirection = .horizontal 328 | layout.sectionsStartOnNewLine = false 329 | updateWindowFrame() 330 | 331 | checkFrames() 332 | } 333 | 334 | func testItemSpacingWithVerticalScrolling() { 335 | 336 | layout.itemSpacing = 2 337 | layout.lineSpacing = 0 338 | layout.lineSize = 0 339 | layout.lineItemCount = 3 340 | layout.scrollDirection = .vertical 341 | layout.sectionsStartOnNewLine = false 342 | updateWindowFrame() 343 | 344 | checkFrames() 345 | } 346 | 347 | func testItemSpacingWithHorizontalScrolling() { 348 | 349 | layout.itemSpacing = 2 350 | layout.lineSpacing = 0 351 | layout.lineSize = 0 352 | layout.lineItemCount = 3 353 | layout.scrollDirection = .horizontal 354 | layout.sectionsStartOnNewLine = false 355 | updateWindowFrame() 356 | 357 | checkFrames() 358 | } 359 | 360 | func testLineSpacingVerticalScrolling() { 361 | 362 | layout.itemSpacing = 0 363 | layout.lineSpacing = 10 364 | layout.lineSize = 0 365 | layout.lineItemCount = 2 366 | layout.scrollDirection = .vertical 367 | layout.sectionsStartOnNewLine = false 368 | updateWindowFrame() 369 | 370 | checkFrames() 371 | } 372 | 373 | func testLineSpacingHorizontalScrolling() { 374 | 375 | layout.itemSpacing = 0 376 | layout.lineSpacing = 10 377 | layout.lineSize = 0 378 | layout.lineItemCount = 2 379 | layout.scrollDirection = .horizontal 380 | layout.sectionsStartOnNewLine = false 381 | updateWindowFrame() 382 | 383 | checkFrames() 384 | } 385 | 386 | func testItemAndLineSpacingVerticalScrolling() { 387 | 388 | layout.itemSpacing = 2 389 | layout.lineSpacing = 10 390 | layout.lineSize = 0 391 | layout.lineItemCount = 3 392 | layout.scrollDirection = .vertical 393 | layout.sectionsStartOnNewLine = false 394 | updateWindowFrame() 395 | 396 | checkFrames() 397 | } 398 | 399 | func testItemAndLineSpacingHorizontalScrolling() { 400 | 401 | layout.itemSpacing = 2 402 | layout.lineSpacing = 10 403 | layout.lineSize = 0 404 | layout.lineItemCount = 3 405 | layout.scrollDirection = .horizontal 406 | layout.sectionsStartOnNewLine = false 407 | updateWindowFrame() 408 | 409 | checkFrames() 410 | } 411 | 412 | func checkFrames() { 413 | 414 | let exp = expectation(description: "Frame checking") 415 | 416 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.25) { 417 | var frame = CGRect.zero 418 | var size = CGSize.zero 419 | if (self.layout.scrollDirection == .vertical) { 420 | frame.size.width = 640 421 | frame.size.height = 2000 422 | size.width = frame.size.width 423 | } else { 424 | frame.size.width = 2000 425 | frame.size.height = 640 426 | size.height = frame.size.height 427 | } 428 | XCTAssertEqual(self.window.frame, frame, "Incorrect window frame (\(self.testCaseName))") 429 | XCTAssertEqual(self.collectionView.contentSize, size, "Incorrect content size (\(self.testCaseName))") 430 | 431 | exp.fulfill() 432 | } 433 | 434 | waitForExpectations(timeout: 5, handler: nil) 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /Examples/Tests/RDHGridLayoutTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RDHGridLayoutTests.m 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 22/06/2014. 6 | // Copyright (c) 2014 Rich H. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import RDHCollectionViewGridLayout; 12 | 13 | #import "Tests-Swift.h" 14 | 15 | @interface RDHGridLayoutTests : XCTestCase 16 | 17 | @property (nonatomic, strong) UICollectionView *collectionView; 18 | @property (nonatomic, strong) RDHCollectionViewGridLayout *layout; 19 | @property (nonatomic, strong) TestDataSource *dataSource; 20 | @property (nonatomic, strong) UIWindow *window; 21 | 22 | @end 23 | 24 | @implementation RDHGridLayoutTests 25 | 26 | -(void)setUp 27 | { 28 | [super setUp]; 29 | 30 | self.dataSource = [TestDataSource new]; 31 | 32 | self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 640, 640)]; 33 | [self.window makeKeyAndVisible]; 34 | self.layout = [RDHCollectionViewGridLayout new]; 35 | UICollectionViewController *controller = [[UICollectionViewController alloc] initWithCollectionViewLayout:self.layout]; 36 | controller.edgesForExtendedLayout = UIRectEdgeNone; 37 | controller.automaticallyAdjustsScrollViewInsets = NO; 38 | self.collectionView = controller.collectionView; 39 | [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:[TestDataSource CellID]]; 40 | self.collectionView.dataSource = self.dataSource; 41 | self.collectionView.delegate = nil; 42 | self.window.rootViewController = controller; 43 | } 44 | 45 | -(void)testParameters 46 | { 47 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 48 | XCTAssertEqual(self.layout.scrollDirection, UICollectionViewScrollDirectionHorizontal, @"Scroll direction should be horiztonal"); 49 | 50 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 51 | XCTAssertFalse([[self.layout valueForKeyPath:@"verticalScrolling"] boolValue]); 52 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 53 | XCTAssertTrue([[self.layout valueForKeyPath:@"verticalScrolling"] boolValue]); 54 | 55 | [self.layout setValue:@YES forKey:@"verticalScrolling"]; 56 | XCTAssertEqual(self.layout.scrollDirection, UICollectionViewScrollDirectionVertical, @"Scroll direction should be vertical"); 57 | [self.layout setValue:@NO forKey:@"verticalScrolling"]; 58 | XCTAssertEqual(self.layout.scrollDirection, UICollectionViewScrollDirectionHorizontal, @"Scroll direction should be horiztonal"); 59 | 60 | self.layout.lineSize = 123; 61 | XCTAssertEqual(self.layout.lineSize, 123, @"Line size is incorrect"); 62 | XCTAssertEqual(self.layout.lineMultiplier, 1, @"Line multiplier should be default"); 63 | XCTAssertEqual(self.layout.lineExtension, 0, @"Line extension should be default"); 64 | 65 | self.layout.lineMultiplier = 2; 66 | XCTAssertEqual(self.layout.lineSize, 0, @"Line size should be default"); 67 | XCTAssertEqual(self.layout.lineMultiplier, 2, @"Line multiplier is incorrect"); 68 | XCTAssertEqual(self.layout.lineExtension, 0, @"Line extension should be default"); 69 | 70 | self.layout.lineExtension = 200; 71 | XCTAssertEqual(self.layout.lineSize, 0, @"Line size should be default"); 72 | XCTAssertEqual(self.layout.lineMultiplier, 1, @"Line multiplier should be default"); 73 | XCTAssertEqual(self.layout.lineExtension, 200, @"Line extension is incorrect"); 74 | 75 | self.layout.lineExtension = 0; 76 | XCTAssertEqual(self.layout.lineSize, 0, @"Line size should be default"); 77 | XCTAssertEqual(self.layout.lineMultiplier, 1, @"Line multiplier should be default"); 78 | XCTAssertEqual(self.layout.lineExtension, 0, @"Line extension should be default"); 79 | 80 | #pragma clang diagnostic push 81 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 82 | self.layout.lineDimension = 123; 83 | #pragma clang diagnostic pop 84 | XCTAssertEqual(self.layout.lineSize, 123, @"Line size is incorrect"); 85 | XCTAssertEqual(self.layout.lineMultiplier, 1, @"Line multiplier should be default"); 86 | XCTAssertEqual(self.layout.lineExtension, 0, @"Line extension should be default"); 87 | 88 | self.layout.lineItemCount = 7; 89 | XCTAssertEqual(self.layout.lineItemCount, 7, @"Line item count should be 7"); 90 | 91 | self.layout.itemSpacing = 23; 92 | XCTAssertEqual(self.layout.itemSpacing, 23, @"Line spacing should be 23"); 93 | 94 | self.layout.sectionsStartOnNewLine = YES; 95 | XCTAssertTrue(self.layout.sectionsStartOnNewLine, @"Sections should start on new lines"); 96 | } 97 | 98 | -(void)testInvalidParameters 99 | { 100 | XCTAssertThrowsSpecificNamed(self.layout.scrollDirection = 5, NSException, NSInternalInconsistencyException, @"Setting an unknown scroll direction should throw an exception"); 101 | 102 | XCTAssertThrowsSpecificNamed(self.layout.lineSize = -10, NSException, NSInternalInconsistencyException, @"Setting a negative line size should throw an exception"); 103 | XCTAssertThrowsSpecificNamed(self.layout.lineMultiplier = -10, NSException, NSInternalInconsistencyException, @"Setting a negative line multiplier should throw an exception"); 104 | XCTAssertThrowsSpecificNamed(self.layout.lineMultiplier = 0, NSException, NSInternalInconsistencyException, @"Setting a zero line multiplier should throw an exception"); 105 | XCTAssertThrowsSpecificNamed(self.layout.lineExtension = -10, NSException, NSInternalInconsistencyException, @"Setting a negative line extension should throw an exception"); 106 | #pragma clang diagnostic push 107 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 108 | XCTAssertThrowsSpecificNamed(self.layout.lineDimension = -10, NSException, NSInternalInconsistencyException, @"Setting a negative line dimension should throw an exception"); 109 | #pragma clang diagnostic pop 110 | 111 | XCTAssertThrowsSpecificNamed(self.layout.lineItemCount = 0, NSException, NSInternalInconsistencyException, @"Setting a zero line item count should throw an exception"); 112 | } 113 | 114 | -(void)updateWindowFrame 115 | { 116 | CGRect frame = CGRectZero; 117 | if (self.layout.scrollDirection == UICollectionViewScrollDirectionVertical) { 118 | frame.size.width = 640; 119 | frame.size.height = 2000; 120 | } else { 121 | frame.size.width = 2000; 122 | frame.size.height = 640; 123 | } 124 | self.window.frame = frame; 125 | [self.window layoutIfNeeded]; 126 | self.collectionView.frame = frame; 127 | [self.collectionView layoutIfNeeded]; 128 | } 129 | 130 | -(void)testSizeLocationsWithVerticalScrollViewSectionsStartOnNewLineNO 131 | { 132 | NSDictionary *frames = @{ 133 | IPF(0, 0, 0, 000, 320, 120), IPF(0, 1, 320, 000, 320, 120), 134 | IPF(0, 2, 0, 120, 320, 120), IPF(1, 0, 320, 120, 320, 120), 135 | IPF(1, 1, 0, 240, 320, 120), IPF(1, 2, 320, 240, 320, 120), 136 | IPF(2, 0, 0, 360, 320, 120), IPF(2, 1, 320, 360, 320, 120), 137 | IPF(2, 2, 0, 480, 320, 120) 138 | }; 139 | 140 | self.layout.itemSpacing = 0; 141 | self.layout.lineSpacing = 0; 142 | self.layout.lineSize = 120; 143 | self.layout.lineItemCount = 2; 144 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 145 | self.layout.sectionsStartOnNewLine = NO; 146 | [self updateWindowFrame]; 147 | 148 | [self checkFrames:frames withContentSize:CGSizeMake(640, 600)]; 149 | } 150 | 151 | -(void)testSizeLocationsWithHorizontalScrollViewSectionsStartOnNewLineNO 152 | { 153 | NSDictionary *frames = @{ 154 | 155 | IPF(0, 0, 000, 000, 120, 320), IPF(0, 2, 120, 000, 120, 320), IPF(1, 1, 240, 000, 120, 320), IPF(2, 0, 360, 000, 120, 320), IPF(2, 2, 480, 000, 120, 320), 156 | 157 | IPF(0, 1, 000, 320, 120, 320), IPF(1, 0, 120, 320, 120, 320), IPF(1, 2, 240, 320, 120, 320), IPF(2, 1, 360, 320, 120, 320) 158 | }; 159 | 160 | self.layout.itemSpacing = 0; 161 | self.layout.lineSpacing = 0; 162 | self.layout.lineSize = 120; 163 | self.layout.lineItemCount = 2; 164 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 165 | self.layout.sectionsStartOnNewLine = NO; 166 | [self updateWindowFrame]; 167 | 168 | [self checkFrames:frames withContentSize:CGSizeMake(600, 640)]; 169 | } 170 | 171 | -(void)testSizeLocationsWithVerticalScrollViewSectionsStartOnNewLineYES 172 | { 173 | NSDictionary *frames = @{ 174 | IPF(0, 0, 0, 000, 320, 120), IPF(0, 1, 320, 000, 320, 120), 175 | IPF(0, 2, 0, 120, 320, 120), 176 | IPF(1, 0, 0, 240, 320, 120), IPF(1, 1, 320, 240, 320, 120), 177 | IPF(1, 2, 0, 360, 320, 120), 178 | IPF(2, 0, 0, 480, 320, 120), IPF(2, 1, 320, 480, 320, 120), 179 | IPF(2, 2, 0, 600, 320, 120) 180 | }; 181 | 182 | self.layout.itemSpacing = 0; 183 | self.layout.lineSpacing = 0; 184 | self.layout.lineSize = 120; 185 | self.layout.lineItemCount = 2; 186 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 187 | self.layout.sectionsStartOnNewLine = YES; 188 | [self updateWindowFrame]; 189 | 190 | [self checkFrames:frames withContentSize:CGSizeMake(640, 720)]; 191 | } 192 | 193 | -(void)testSizeLocationsWithHorizontalScrollViewSectionsStartOnNewLineYES 194 | { 195 | NSDictionary *frames = @{ 196 | 197 | IPF(0, 0, 000, 000, 120, 320), IPF(0, 2, 120, 000, 120, 320), IPF(1, 0, 240, 000, 120, 320), IPF(1, 2, 360, 000, 120, 320), IPF(2, 0, 480, 000, 120, 320), IPF(2, 2, 600, 000, 120, 320), 198 | 199 | IPF(0, 1, 000, 320, 120, 320), IPF(1, 1, 240, 320, 120, 320), IPF(2, 1, 480, 320, 120, 320) 200 | }; 201 | 202 | self.layout.itemSpacing = 0; 203 | self.layout.lineSpacing = 0; 204 | self.layout.lineSize = 120; 205 | self.layout.lineItemCount = 2; 206 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 207 | self.layout.sectionsStartOnNewLine = YES; 208 | [self updateWindowFrame]; 209 | 210 | [self checkFrames:frames withContentSize:CGSizeMake(720, 640)]; 211 | } 212 | 213 | -(void)testMultiplierDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineNO 214 | { 215 | NSDictionary *frames = @{ 216 | IPF(0, 0, 0, 000, 320, 160), IPF(0, 1, 320, 000, 320, 160), 217 | IPF(0, 2, 0, 160, 320, 160), IPF(1, 0, 320, 160, 320, 160), 218 | IPF(1, 1, 0, 320, 320, 160), IPF(1, 2, 320, 320, 320, 160), 219 | IPF(2, 0, 0, 480, 320, 160), IPF(2, 1, 320, 480, 320, 160), 220 | IPF(2, 2, 0, 640, 320, 160) 221 | }; 222 | 223 | self.layout.itemSpacing = 0; 224 | self.layout.lineSpacing = 0; 225 | self.layout.lineMultiplier = 0.5; 226 | self.layout.lineItemCount = 2; 227 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 228 | self.layout.sectionsStartOnNewLine = NO; 229 | [self updateWindowFrame]; 230 | 231 | [self checkFrames:frames withContentSize:CGSizeMake(640, 800)]; 232 | } 233 | 234 | -(void)testMultiplierDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineNO 235 | { 236 | NSDictionary *frames = @{ 237 | 238 | IPF(0, 0, 000, 000, 160, 320), IPF(0, 2, 160, 000, 160, 320), IPF(1, 1, 320, 000, 160, 320), IPF(2, 0, 480, 000, 160, 320), IPF(2, 2, 640, 000, 160, 320), 239 | 240 | IPF(0, 1, 000, 320, 160, 320), IPF(1, 0, 160, 320, 160, 320), IPF(1, 2, 320, 320, 160, 320), IPF(2, 1, 480, 320, 160, 320) 241 | }; 242 | 243 | self.layout.itemSpacing = 0; 244 | self.layout.lineSpacing = 0; 245 | self.layout.lineMultiplier = 0.5; 246 | self.layout.lineItemCount = 2; 247 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 248 | self.layout.sectionsStartOnNewLine = NO; 249 | [self updateWindowFrame]; 250 | 251 | [self checkFrames:frames withContentSize:CGSizeMake(800, 640)]; 252 | } 253 | 254 | -(void)testMultiplierDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineYES 255 | { 256 | NSDictionary *frames = @{ 257 | IPF(0, 0, 0, 000, 320, 160), IPF(0, 1, 320, 000, 320, 160), 258 | IPF(0, 2, 0, 160, 320, 160), 259 | IPF(1, 0, 0, 320, 320, 160), IPF(1, 1, 320, 320, 320, 160), 260 | IPF(1, 2, 0, 480, 320, 160), 261 | IPF(2, 0, 0, 640, 320, 160), IPF(2, 1, 320, 640, 320, 160), 262 | IPF(2, 2, 0, 800, 320, 160) 263 | }; 264 | 265 | self.layout.itemSpacing = 0; 266 | self.layout.lineSpacing = 0; 267 | self.layout.lineMultiplier = 0.5; 268 | self.layout.lineItemCount = 2; 269 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 270 | self.layout.sectionsStartOnNewLine = YES; 271 | [self updateWindowFrame]; 272 | 273 | [self checkFrames:frames withContentSize:CGSizeMake(640, 960)]; 274 | } 275 | 276 | -(void)testMultiplierDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineYES 277 | { 278 | NSDictionary *frames = @{ 279 | 280 | IPF(0, 0, 000, 000, 160, 320), IPF(0, 2, 160, 000, 160, 320), IPF(1, 0, 320, 000, 160, 320), IPF(1, 2, 480, 000, 160, 320), IPF(2, 0, 640, 000, 160, 320), IPF(2, 2, 800, 000, 160, 320), 281 | 282 | IPF(0, 1, 000, 320, 160, 320), IPF(1, 1, 320, 320, 160, 320), IPF(2, 1, 640, 320, 160, 320) 283 | }; 284 | 285 | self.layout.itemSpacing = 0; 286 | self.layout.lineSpacing = 0; 287 | self.layout.lineMultiplier = 0.5; 288 | self.layout.lineItemCount = 2; 289 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 290 | self.layout.sectionsStartOnNewLine = YES; 291 | [self updateWindowFrame]; 292 | 293 | [self checkFrames:frames withContentSize:CGSizeMake(960, 640)]; 294 | } 295 | 296 | -(void)testExtensionDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineNO 297 | { 298 | NSDictionary *frames = @{ 299 | IPF(0, 0, 0, 000, 320, 370), IPF(0, 1, 320, 000, 320, 370), 300 | IPF(0, 2, 0, 370, 320, 370), IPF(1, 0, 320, 370, 320, 370), 301 | IPF(1, 1, 0, 740, 320, 370), IPF(1, 2, 320, 740, 320, 370), 302 | IPF(2, 0, 0, 1110, 320, 370), IPF(2, 1, 320, 1110, 320, 370), 303 | IPF(2, 2, 0, 1480, 320, 370) 304 | }; 305 | 306 | self.layout.itemSpacing = 0; 307 | self.layout.lineSpacing = 0; 308 | self.layout.lineExtension = 50; 309 | self.layout.lineItemCount = 2; 310 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 311 | self.layout.sectionsStartOnNewLine = NO; 312 | [self updateWindowFrame]; 313 | 314 | [self checkFrames:frames withContentSize:CGSizeMake(640, 1850)]; 315 | } 316 | 317 | -(void)testExtensionDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineNO 318 | { 319 | NSDictionary *frames = @{ 320 | 321 | IPF(0, 0, 0000, 000, 370, 320), IPF(0, 2, 370, 000, 370, 320), IPF(1, 1, 740, 000, 370, 320), IPF(2, 0, 1110, 000, 370, 320), IPF(2, 2, 1480, 000, 370, 320), 322 | 323 | IPF(0, 1, 0000, 320, 370, 320), IPF(1, 0, 370, 320, 370, 320), IPF(1, 2, 740, 320, 370, 320), IPF(2, 1, 1110, 320, 370, 320) 324 | }; 325 | 326 | self.layout.itemSpacing = 0; 327 | self.layout.lineSpacing = 0; 328 | self.layout.lineExtension = 50; 329 | self.layout.lineItemCount = 2; 330 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 331 | self.layout.sectionsStartOnNewLine = NO; 332 | [self updateWindowFrame]; 333 | 334 | [self checkFrames:frames withContentSize:CGSizeMake(1850, 640)]; 335 | } 336 | 337 | -(void)testExtensionDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineYES 338 | { 339 | NSDictionary *frames = @{ 340 | IPF(0, 0, 0, 000, 320, 370), IPF(0, 1, 320, 000, 320, 370), 341 | IPF(0, 2, 0, 370, 320, 370), 342 | IPF(1, 0, 0, 740, 320, 370), IPF(1, 1, 320, 740, 320, 370), 343 | IPF(1, 2, 0, 1110, 320, 370), 344 | IPF(2, 0, 0, 1480, 320, 370), IPF(2, 1, 320, 1480, 320, 370), 345 | IPF(2, 2, 0, 1850, 320, 370) 346 | }; 347 | 348 | self.layout.itemSpacing = 0; 349 | self.layout.lineSpacing = 0; 350 | self.layout.lineExtension = 50; 351 | self.layout.lineItemCount = 2; 352 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 353 | self.layout.sectionsStartOnNewLine = YES; 354 | [self updateWindowFrame]; 355 | 356 | [self checkFrames:frames withContentSize:CGSizeMake(640, 2220)]; 357 | } 358 | 359 | -(void)testExtensionWithHorizontalScrollViewSectionsStartOnNewLineYES 360 | { 361 | NSDictionary *frames = @{ 362 | 363 | IPF(0, 0, 000, 000, 370, 320), IPF(0, 2, 370, 000, 370, 320), IPF(1, 0, 740, 000, 370, 320), IPF(1, 2, 1110, 000, 370, 320), IPF(2, 0, 1480, 000, 370, 320), IPF(2, 2, 1850, 000, 370, 320), 364 | 365 | IPF(0, 1, 000, 320, 370, 320), IPF(1, 1, 740, 320, 370, 320), IPF(2, 1, 1480, 320, 370, 320) 366 | }; 367 | 368 | self.layout.itemSpacing = 0; 369 | self.layout.lineSpacing = 0; 370 | self.layout.lineExtension = 50; 371 | self.layout.lineItemCount = 2; 372 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 373 | self.layout.sectionsStartOnNewLine = YES; 374 | [self updateWindowFrame]; 375 | 376 | [self checkFrames:frames withContentSize:CGSizeMake(2220, 640)]; 377 | } 378 | 379 | -(void)testAutomaticDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineNO 380 | { 381 | NSDictionary *frames = @{ 382 | IPF(0, 0, 0, 000, 320, 320), IPF(0, 1, 320, 000, 320, 320), 383 | IPF(0, 2, 0, 320, 320, 320), IPF(1, 0, 320, 320, 320, 320), 384 | IPF(1, 1, 0, 640, 320, 320), IPF(1, 2, 320, 640, 320, 320), 385 | IPF(2, 0, 0, 960, 320, 320), IPF(2, 1, 320, 960, 320, 320), 386 | IPF(2, 2, 0, 1280, 320, 320) 387 | }; 388 | 389 | self.layout.itemSpacing = 0; 390 | self.layout.lineSpacing = 0; 391 | self.layout.lineSize = 0; 392 | self.layout.lineItemCount = 2; 393 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 394 | self.layout.sectionsStartOnNewLine = NO; 395 | [self updateWindowFrame]; 396 | 397 | [self checkFrames:frames withContentSize:CGSizeMake(640, 1600)]; 398 | } 399 | 400 | -(void)testAutomaticDimensionLocationsWithHorizontalScrollViewSectionsStartOnNewLineNO 401 | { 402 | NSDictionary *frames = @{ 403 | 404 | IPF(0, 0, 000, 000, 320, 320), IPF(0, 2, 320, 000, 320, 320), IPF(1, 1, 640, 000, 320, 320), IPF(2, 0, 960, 000, 320, 320), IPF(2, 2, 1280, 000, 320, 320), 405 | 406 | IPF(0, 1, 000, 320, 320, 320), IPF(1, 0, 320, 320, 320, 320), IPF(1, 2, 640, 320, 320, 320), IPF(2, 1, 960, 320, 320, 320) 407 | }; 408 | 409 | self.layout.itemSpacing = 0; 410 | self.layout.lineSpacing = 0; 411 | self.layout.lineSize = 0; 412 | self.layout.lineItemCount = 2; 413 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 414 | self.layout.sectionsStartOnNewLine = NO; 415 | [self updateWindowFrame]; 416 | 417 | [self checkFrames:frames withContentSize:CGSizeMake(1600, 640)]; 418 | } 419 | 420 | -(void)testAutomaticDimensionLocationsWithVerticalScrollViewSectionsStartOnNewLineYES 421 | { 422 | NSDictionary *frames = @{ 423 | IPF(0, 0, 0, 000, 320, 320), IPF(0, 1, 320, 000, 320, 320), 424 | IPF(0, 2, 0, 320, 320, 320), 425 | IPF(1, 0, 0, 640, 320, 320), IPF(1, 1, 320, 640, 320, 320), 426 | IPF(1, 2, 0, 960, 320, 320), 427 | IPF(2, 0, 0, 1280, 320, 320), IPF(2, 1, 320, 1280, 320, 320), 428 | IPF(2, 2, 0, 1600, 320, 320) 429 | }; 430 | 431 | self.layout.itemSpacing = 0; 432 | self.layout.lineSpacing = 0; 433 | self.layout.lineSize = 0; 434 | self.layout.lineItemCount = 2; 435 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 436 | self.layout.sectionsStartOnNewLine = YES; 437 | [self updateWindowFrame]; 438 | 439 | [self checkFrames:frames withContentSize:CGSizeMake(640, 1920)]; 440 | } 441 | 442 | -(void)testAutomaticWithHorizontalScrollViewSectionsStartOnNewLineYES 443 | { 444 | NSDictionary *frames = @{ 445 | 446 | IPF(0, 0, 000, 000, 320, 320), IPF(0, 2, 320, 000, 320, 320), IPF(1, 0, 640, 000, 320, 320), IPF(1, 2, 960, 000, 320, 320), IPF(2, 0, 1280, 000, 320, 320), IPF(2, 2, 1600, 000, 320, 320), 447 | 448 | IPF(0, 1, 000, 320, 320, 320), IPF(1, 1, 640, 320, 320, 320), IPF(2, 1, 1280, 320, 320, 320) 449 | }; 450 | 451 | self.layout.itemSpacing = 0; 452 | self.layout.lineSpacing = 0; 453 | self.layout.lineSize = 0; 454 | self.layout.lineItemCount = 2; 455 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 456 | self.layout.sectionsStartOnNewLine = YES; 457 | [self updateWindowFrame]; 458 | 459 | [self checkFrames:frames withContentSize:CGSizeMake(1920, 640)]; 460 | } 461 | 462 | -(void)testDirtyPixelCalculations 463 | { 464 | NSDictionary *frames = @{ 465 | IPF(0, 0, 0, 000, 214, 213), IPF(0, 1, 214, 000, 213, 213), IPF(0, 2, 427, 000, 213, 213), 466 | IPF(1, 0, 0, 213, 214, 213), IPF(1, 1, 214, 213, 213, 213), IPF(1, 2, 427, 213, 213, 213), 467 | IPF(2, 0, 0, 426, 214, 213), IPF(2, 1, 214, 426, 213, 213), IPF(2, 2, 427, 426, 213, 213) 468 | }; 469 | 470 | self.layout.itemSpacing = 0; 471 | self.layout.lineSpacing = 0; 472 | self.layout.lineSize = 0; 473 | self.layout.lineItemCount = 3; 474 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 475 | self.layout.sectionsStartOnNewLine = NO; 476 | [self updateWindowFrame]; 477 | 478 | [self checkFrames:frames withContentSize:CGSizeMake(640, 639)]; 479 | } 480 | 481 | -(void)testItemSpacing 482 | { 483 | NSDictionary *frames = @{ 484 | IPF(0, 0, 0, 000, 212, 212), IPF(0, 1, 214, 000, 212, 212), IPF(0, 2, 428, 000, 212, 212), 485 | IPF(1, 0, 0, 212, 212, 212), IPF(1, 1, 214, 212, 212, 212), IPF(1, 2, 428, 212, 212, 212), 486 | IPF(2, 0, 0, 424, 212, 212), IPF(2, 1, 214, 424, 212, 212), IPF(2, 2, 428, 424, 212, 212) 487 | }; 488 | 489 | self.layout.itemSpacing = 2; 490 | self.layout.lineSpacing = 0; 491 | self.layout.lineSize = 0; 492 | self.layout.lineItemCount = 3; 493 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 494 | self.layout.sectionsStartOnNewLine = NO; 495 | [self updateWindowFrame]; 496 | 497 | [self checkFrames:frames withContentSize:CGSizeMake(640, 636)]; 498 | } 499 | 500 | -(void)testLineSpacing 501 | { 502 | NSDictionary *frames = @{ 503 | IPF(0, 0, 0, 000, 320, 320), IPF(0, 1, 320, 000, 320, 320), 504 | IPF(0, 2, 0, 330, 320, 320), IPF(1, 0, 320, 330, 320, 320), 505 | IPF(1, 1, 0, 660, 320, 320), IPF(1, 2, 320, 660, 320, 320), 506 | IPF(2, 0, 0, 990, 320, 320), IPF(2, 1, 320, 990, 320, 320), 507 | IPF(2, 2, 0, 1320, 320, 320) 508 | }; 509 | 510 | self.layout.itemSpacing = 0; 511 | self.layout.lineSpacing = 10; 512 | self.layout.lineSize = 0; 513 | self.layout.lineItemCount = 2; 514 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 515 | self.layout.sectionsStartOnNewLine = NO; 516 | [self updateWindowFrame]; 517 | 518 | [self checkFrames:frames withContentSize:CGSizeMake(640, 1640)]; 519 | } 520 | 521 | -(void)testItemAndLineSpacingVerticalScrolling 522 | { 523 | NSDictionary *frames = @{ 524 | IPF(0, 0, 0, 000, 212, 212), IPF(0, 1, 214, 000, 212, 212), IPF(0, 2, 428, 000, 212, 212), 525 | IPF(1, 0, 0, 222, 212, 212), IPF(1, 1, 214, 222, 212, 212), IPF(1, 2, 428, 222, 212, 212), 526 | IPF(2, 0, 0, 444, 212, 212), IPF(2, 1, 214, 444, 212, 212), IPF(2, 2, 428, 444, 212, 212) 527 | }; 528 | 529 | self.layout.itemSpacing = 2; 530 | self.layout.lineSpacing = 10; 531 | self.layout.lineSize = 0; 532 | self.layout.lineItemCount = 3; 533 | self.layout.scrollDirection = UICollectionViewScrollDirectionVertical; 534 | self.layout.sectionsStartOnNewLine = NO; 535 | [self updateWindowFrame]; 536 | 537 | [self checkFrames:frames withContentSize:CGSizeMake(640, 656)]; 538 | } 539 | 540 | -(void)testItemAndLineSpacingHorizontalScrolling 541 | { 542 | NSDictionary *frames = @{ 543 | IPF(0, 0, 0, 000, 212, 212), IPF(1, 0, 222, 000, 212, 212), IPF(2, 0, 444, 000, 212, 212), 544 | IPF(0, 1, 0, 214, 212, 212), IPF(1, 1, 222, 214, 212, 212), IPF(2, 1, 444, 214, 212, 212), 545 | IPF(0, 2, 0, 428, 212, 212), IPF(1, 2, 222, 428, 212, 212), IPF(2, 2, 444, 428, 212, 212) 546 | }; 547 | 548 | self.layout.itemSpacing = 2; 549 | self.layout.lineSpacing = 10; 550 | self.layout.lineSize = 0; 551 | self.layout.lineItemCount = 3; 552 | self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 553 | self.layout.sectionsStartOnNewLine = NO; 554 | [self updateWindowFrame]; 555 | 556 | [self checkFrames:frames withContentSize:CGSizeMake(656, 640)]; 557 | } 558 | 559 | -(void)checkFrames:(NSDictionary *)frames withContentSize:(CGSize)contentSize 560 | { 561 | NSAssert(frames.count == 9, @"Incorrect number of frames (%@)", self.testCaseName); 562 | 563 | XCTestExpectation *expectation = [self expectationWithDescription:@"Frame checking"]; 564 | 565 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 566 | 567 | CGRect frame = CGRectZero; 568 | if (self.layout.scrollDirection == UICollectionViewScrollDirectionVertical) { 569 | frame.size.width = 640; 570 | frame.size.height = 2000; 571 | } else { 572 | frame.size.width = 2000; 573 | frame.size.height = 640; 574 | } 575 | XCTAssertEqualRect(self.window.frame, frame, @"Incorrect window frame (%@", self.testCaseName); 576 | XCTAssertEqualSize(self.collectionView.contentSize, contentSize, @"Incorrect content size (%@)", self.testCaseName); 577 | 578 | [frames enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, NSValue *vRect, BOOL *stop) { 579 | 580 | NSString *indexPathDesc = [NSString stringWithFormat:@"NSIndexPath(%ld,%ld)", (long) indexPath.section, (long) indexPath.row]; 581 | 582 | CGRect expectedFrame = [vRect CGRectValue]; 583 | CGRect actualFrame = [self.collectionView cellForItemAtIndexPath:indexPath].frame; 584 | 585 | XCTAssertEqualRect(actualFrame, expectedFrame, @"Frame of %@ is incorrect (%@)", indexPathDesc, self.testCaseName); 586 | }]; 587 | 588 | [expectation fulfill]; 589 | 590 | }); 591 | 592 | [self waitForExpectationsWithTimeout:5 handler:nil]; 593 | } 594 | 595 | @end 596 | 597 | -------------------------------------------------------------------------------- /Examples/Tests/RDHStoryboardTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RDHStoryboardTests.swift 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 16/03/2015. 6 | // Copyright (c) 2015 Rich H. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | 13 | class RDHStoryboardTests: XCTestCase { 14 | 15 | var collectionView: UICollectionView! 16 | var dataSource: TestDataSource! 17 | var layout: RDHCollectionViewGridLayout! 18 | var window: UIWindow! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | dataSource = TestDataSource() 24 | 25 | let collectionVC = UIStoryboard(name: "TestStoryboard", bundle: Bundle(for: type(of: self))).instantiateInitialViewController() as! UICollectionViewController 26 | 27 | window = UIWindow(frame:CGRect(x: 0, y: 0, width: 640, height: 640)) 28 | collectionView = collectionVC.collectionView 29 | layout = collectionView.collectionViewLayout as! RDHCollectionViewGridLayout 30 | collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: TestDataSource.CellID) 31 | collectionView.dataSource = dataSource 32 | collectionView.delegate = nil 33 | 34 | window.rootViewController = collectionVC 35 | window.makeKeyAndVisible() 36 | } 37 | 38 | fileprivate func updateWindowFrame() { 39 | var frame = CGRect.zero 40 | if layout.scrollDirection == .vertical { 41 | frame.size.width = 640 42 | frame.size.height = 2000 43 | } else { 44 | frame.size.width = 2000 45 | frame.size.height = 640 46 | } 47 | window.frame = frame 48 | window.layoutIfNeeded() 49 | collectionView.frame = frame 50 | collectionView.layoutIfNeeded() 51 | } 52 | 53 | func testDefaults() { 54 | 55 | updateWindowFrame() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Examples/Tests/TestDataSources.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestDataSources.swift 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 16/03/2015. 6 | // Copyright (c) 2015 Rich H. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit.UICollectionView 11 | 12 | @objc final class TestDataSource: NSObject, UICollectionViewDataSource { 13 | 14 | class var CellID: String { return "CellID" } 15 | 16 | func numberOfSections(in collectionView: UICollectionView) -> Int { 17 | return 3 18 | } 19 | 20 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 21 | return 3 22 | } 23 | 24 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 25 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: self).CellID, for:indexPath) 26 | cell.backgroundColor = UIColor.red 27 | return cell 28 | } 29 | } 30 | 31 | @objc final class EmptyTestDataSource: NSObject, UICollectionViewDataSource { 32 | 33 | class var CellID: String { return "CellID" } 34 | 35 | func numberOfSections(in collectionView: UICollectionView) -> Int { 36 | return 0 37 | } 38 | 39 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 40 | return 0 41 | } 42 | 43 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 44 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: self).CellID, for:indexPath) 45 | cell.backgroundColor = UIColor.red 46 | return cell 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Examples/Tests/TestStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Examples/Tests/Tests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | @import RDHCollectionViewGridLayout; 6 | 7 | #import "XCTestCase+Extensions.h" 8 | -------------------------------------------------------------------------------- /Examples/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | @import UIKit; 8 | @import Foundation; 9 | @import CoreGraphics; 10 | 11 | #import "XCTestCase+Extensions.h" 12 | 13 | #define CGRectObject(x, y, width, height) [NSValue valueWithCGRect:CGRectMake(x, y, width, height)] 14 | 15 | #define IPF(section, item, x, y, width, height) [NSIndexPath indexPathForItem:item inSection:section] : CGRectObject(x, y, width, height) 16 | 17 | #define XCTAssertEqualRect(expression1, expression2, ...) \ 18 | _XCTAssertEqualRect(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) 19 | 20 | #define _XCTAssertEqualRect(test, expression1, expressionStr1, expression2, expressionStr2, ...) \ 21 | ({ \ 22 | @try { \ 23 | if (!CGRectEqualToRect(expression1, expression2)) { \ 24 | NSValue *expressionBox1 = [NSValue valueWithCGRect:expression1]; \ 25 | NSValue *expressionBox2 = [NSValue valueWithCGRect:expression2]; \ 26 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 0, expressionStr1, expressionStr2, _XCTDescriptionForValue(expressionBox1), _XCTDescriptionForValue(expressionBox2)), __VA_ARGS__); \ 27 | } \ 28 | } \ 29 | @catch (_XCTestCaseInterruptionException *interruption) { [interruption raise]; } \ 30 | @catch (NSException *exception) { \ 31 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 1, expressionStr1, expressionStr2, [exception reason]), __VA_ARGS__); \ 32 | } \ 33 | @catch (...) { \ 34 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 2, expressionStr1, expressionStr2), __VA_ARGS__); \ 35 | } \ 36 | }) 37 | 38 | #define XCTAssertEqualSize(expression1, expression2, ...) \ 39 | _XCTAssertEqualSize(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) 40 | 41 | #define _XCTAssertEqualSize(test, expression1, expressionStr1, expression2, expressionStr2, ...) \ 42 | ({ \ 43 | @try { \ 44 | if (!CGSizeEqualToSize(expression1, expression2)) { \ 45 | NSValue *expressionBox1 = [NSValue valueWithCGSize:expression1]; \ 46 | NSValue *expressionBox2 = [NSValue valueWithCGSize:expression2]; \ 47 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 0, expressionStr1, expressionStr2, _XCTDescriptionForValue(expressionBox1), _XCTDescriptionForValue(expressionBox2)), __VA_ARGS__); \ 48 | } \ 49 | } \ 50 | @catch (_XCTestCaseInterruptionException *interruption) { [interruption raise]; } \ 51 | @catch (NSException *exception) { \ 52 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 1, expressionStr1, expressionStr2, [exception reason]), __VA_ARGS__); \ 53 | } \ 54 | @catch (...) { \ 55 | _XCTRegisterFailure(test, _XCTFailureDescription(_XCTAssertion_Equal, 2, expressionStr1, expressionStr2), __VA_ARGS__); \ 56 | } \ 57 | }) 58 | -------------------------------------------------------------------------------- /Examples/Tests/XCTestCase+Extensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+Extensions.h 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 18/09/2016. 6 | // Copyright © 2016 Rich H. All rights reserved. 7 | // 8 | 9 | @import XCTest; 10 | 11 | @interface XCTestCase (TestCaseName) 12 | 13 | @property (nonatomic, nonnull, readonly) NSString *testCaseName; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Examples/Tests/XCTestCase+Extensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+Extensions.m 3 | // RDHCollectionViewGridLayoutDemo 4 | // 5 | // Created by Richard Hodgkins on 18/09/2016. 6 | // Copyright © 2016 Rich H. All rights reserved. 7 | // 8 | 9 | #import "XCTestCase+Extensions.h" 10 | 11 | @implementation XCTestCase (TestCaseName) 12 | 13 | -(NSString *_Nonnull)testCaseName 14 | { 15 | NSString *className = [NSStringFromClass([self class]) stringByReplacingOccurrencesOfString:@"Tests." withString:@""]; 16 | return [[self.name stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"-[%@ ", className] withString:@""] stringByReplacingOccurrencesOfString:@"]" withString:@""]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/cartfile: -------------------------------------------------------------------------------- 1 | github "rhodgkins/RDHCollectionViewGridLayout" "master" 2 | -------------------------------------------------------------------------------- /Examples/images/5-line, 10-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/images/5-line, 10-item.png -------------------------------------------------------------------------------- /Examples/images/horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/images/horizontal.png -------------------------------------------------------------------------------- /Examples/images/vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhodgkins/RDHCollectionViewGridLayout/cb12530775efc6f4f9827be831a166b192b30d2a/Examples/images/vertical.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This code is distributed under the terms and conditions of the MIT license. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /RDHCollectionViewGridLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'RDHCollectionViewGridLayout' 3 | s.version = '1.2.5' 4 | s.license = 'MIT' 5 | 6 | s.summary = 'Grid layout for UICollectionView.' 7 | s.homepage = 'https://github.com/rhodgkins/RDHCollectionViewGridLayout' 8 | s.author = 'Rich Hodgkins' 9 | s.source = { :git => 'https://github.com/rhodgkins/RDHCollectionViewGridLayout.git', :tag => s.version.to_s } 10 | s.social_media_url = 'https://twitter.com/rhodgkins' 11 | 12 | s.frameworks = 'Foundation', 'UIKit', 'CoreGraphics' 13 | s.requires_arc = true 14 | 15 | s.ios.deployment_target = '6.0' 16 | s.tvos.deployment_target = '9.0' 17 | s.source_files = 'RDHCollectionViewGridLayout/' 18 | end 19 | -------------------------------------------------------------------------------- /RDHCollectionViewGridLayout/RDHCollectionViewGridLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // RDHCollectionViewGridLayout.h 3 | // RDHCollectionViewGridLayout 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | @import CoreGraphics; 11 | 12 | /// Project version number for RDHCollectionViewGridLayout. 13 | FOUNDATION_EXPORT double RDHCollectionViewGridLayoutVersionNumber; 14 | 15 | /// Project version string for RDHCollectionViewGridLayout. 16 | FOUNDATION_EXPORT const unsigned char RDHCollectionViewGridLayoutVersionString[]; 17 | 18 | @interface RDHCollectionViewGridLayout : UICollectionViewLayout 19 | 20 | /** 21 | * A vertical direction will constrain the layout by rows (lineItemCount per row), a horizontal direction by columns (lineItemCount per column). 22 | * 23 | * The default value of this property is UICollectionViewScrollDirectionVertical. 24 | * 25 | * @warning A non-enum value will throw an exception. 26 | */ 27 | @property (nonatomic, assign) IBInspectable UICollectionViewScrollDirection scrollDirection; 28 | 29 | /** 30 | * Defines the size of the unconstrained dimension of the items. Simply, for vertical layouts this is the height of the items, and for horizontal layouts this is the width of the items. 31 | * Setting this value to 0 will make layout set the dimension to the average of the other dimenion on the same line. This is due to the layout taking account of dirty pixels, adding these extra pixels in the first X items on the line. 32 | * For example, if using a vertical scrollDirection and a lineItemCount of 5 when the collectionView has a width of 104, the first 4 items on every line will have a width of 21, and the last 20 (21 + 21 + 21 + 21 + 20 = 104), so the height of the lines would be 21 (20.8 rounded). 33 | * 34 | * A value of 0 is the same as setting the `lineExtension` property to 0 `lineMultiplier` property to 1. Setting this value will ignore the `lineExtension` and `lineMultiplier` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 35 | * 36 | * The default value of this property is 0. 37 | * 38 | * @see lineMultiplier 39 | * @see lineExtension 40 | * 41 | * @warning A negative value will throw an exception. 42 | */ 43 | @property (nonatomic, assign) IBInspectable CGFloat lineSize; 44 | 45 | /** 46 | * Defines a multipler of the unconstrained dimension of the items in relation to the strained dimension. Simply, for vertical layouts this value is multiplied by the width and used as the height of the items, and for horizontal layouts this value is multiplied by the height and used as the width of the items. The final dimension is rounded to a whole integer. 47 | * 48 | * A value of 1 is the same as setting the `lineSize` or `lineExtension` properties to 0.Setting this value will ignore the `lineSize` and `lineExtension` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 49 | * 50 | * The default value of this property is 1. 51 | * 52 | * @see lineSize 53 | * @see lineExtension 54 | * 55 | * @warning A none positive value will throw an exception. 56 | */ 57 | @property (nonatomic, assign) IBInspectable CGFloat lineMultiplier; 58 | 59 | /** 60 | * Defines a multipler of the unconstrained dimension of the items in relation to the strained dimension. Simply, for vertical layouts this value is added to the width and used as the height of the items, and for horizontal layouts this value is added to the height and used as the width of the items. 61 | * 62 | * A value of 0 is the same as setting the `lineSize` property to 0 `lineMultiplier` property to 1. Setting this value will ignore the `lineSize` and `lineMultiplier` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 63 | * 64 | * The default value of this property is 0. 65 | * 66 | * @see lineSize 67 | * @see lineMultiplier 68 | * 69 | * @warning A negative value will throw an exception. 70 | */ 71 | @property (nonatomic, assign) IBInspectable CGFloat lineExtension; 72 | 73 | /** 74 | * Defines the maximum number of items allowed per line. Simply, for vertical layouts this is the number of columns, and for horizontal layouts this is the number of rows. The layout accounts for adding the extra pixels to the first X items on the line. Best case is that the useable width is exactly divisible by lineItemCount, worse case is that `(useable width) mod lineItemCount = (lineItemCount - 1)` and that the first `(lineItemCount - 1)` items are 1 pixel bigger. 75 | * 76 | * The default value of this property is 4. 77 | * 78 | * @warning A 0 value will throw an exception. 79 | */ 80 | @property (nonatomic, assign) IBInspectable NSUInteger lineItemCount; 81 | 82 | /** 83 | * Defines the spacing of items on the same line of the layout. Simply, for vertical layouts this is the column spacing, and for horizontal layouts this is the row spacing. 84 | * 85 | * The default value of this property is 0. 86 | */ 87 | @property (nonatomic, assign) IBInspectable CGFloat itemSpacing; 88 | 89 | /** 90 | * Defines the line spacing of the layout. Simply, for vertical layouts this is the row spacing, and for horizontal layouts this is the column spacing. 91 | * 92 | * The default value of this property is 0. 93 | */ 94 | @property (nonatomic, assign) IBInspectable CGFloat lineSpacing; 95 | 96 | /** 97 | * To force sections to start on a new line set this property to YES. Otherwise the section will follow on on from the previous one. 98 | * 99 | * The default value of this property is NO. 100 | */ 101 | @property (nonatomic, assign) IBInspectable BOOL sectionsStartOnNewLine; 102 | 103 | @end 104 | 105 | @interface RDHCollectionViewGridLayout (RDHCollectionViewGridLayout_Deprecated) 106 | 107 | /** 108 | * **Deprecated and replaced with `lineSize` as other line dimensions have been added. (`lineSize` has exactly the same functionality as this property) ** 109 | * 110 | * Defines the size of the unconstrained dimension of the items. Simply, for vertical layouts this is the height of the items, and for horizontal layouts this is the width of the items. 111 | * Setting this value to 0 will make layout set the dimension to the average of the other dimenion on the same line. This is due to the layout taking account of dirty pixels, adding these extra pixels in the first X items on the line. 112 | * For example, if using a vertical scrollDirection and a lineItemCount of 5 when the collectionView has a width of 104, the first 4 items on every line will have a width of 21, and the last 20 (21 + 21 + 21 + 21 + 20 = 104), so the height of the lines would be 21 (20.8 rounded). 113 | * 114 | * A value of 0 is the same as setting the `lineExtension` property to 0 `lineMultiplier` property to 1. Setting this value will ignore the `lineExtension` and `lineMultiplier` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 115 | * 116 | * The default value of this property is 0. 117 | * 118 | * @see lineSize 119 | * @see lineMultiplier 120 | * @see lineExtension 121 | * 122 | * @warning A negative value will throw an exception. 123 | */ 124 | @property (nonatomic, assign) IBInspectable CGFloat lineDimension DEPRECATED_MSG_ATTRIBUTE("use lineSize instead."); 125 | 126 | @end -------------------------------------------------------------------------------- /RDHCollectionViewGridLayout/RDHCollectionViewGridLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // RDHCollectionViewGridLayout.m 3 | // RDHCollectionViewGridLayout 4 | // 5 | // Created by Richard Hodgkins on 06/07/2013. 6 | // Copyright (c) 2013 Rich H. All rights reserved. 7 | // 8 | 9 | #import "RDHCollectionViewGridLayout.h" 10 | 11 | typedef NS_ENUM(NSUInteger, RDHLineDimensionType) { 12 | RDHLineDimensionTypeSize, 13 | RDHLineDimensionTypeMultiplier, 14 | RDHLineDimensionTypeExtension 15 | }; 16 | 17 | static RDHLineDimensionType const RDHLineDimensionTypeDefault = RDHLineDimensionTypeSize; 18 | 19 | static CGFloat const RDHLineSizeDefault = 0; 20 | static CGFloat const RDHLineMutliplierDefault = 1; 21 | static CGFloat const RDHLineExtensionDefault = 0; 22 | 23 | @interface RDHCollectionViewGridLayout () 24 | 25 | @property (nonatomic, copy, nullable) NSArray * firstLineFrames; 26 | @property (nonatomic, copy, nonnull, readonly) NSMutableDictionary * itemAttributes; 27 | 28 | /// This property is used to store the lineDimension when it is set to 0 (depends on the average item size) and the base item size. 29 | @property (nonatomic, assign) CGSize calculatedItemSize; 30 | 31 | /// This property is re-calculated when invalidating the layout 32 | @property (nonatomic, assign) NSUInteger numberOfLines; 33 | 34 | @property (nonatomic, assign) RDHLineDimensionType lineDimensionType; 35 | 36 | @end 37 | 38 | @implementation RDHCollectionViewGridLayout 39 | 40 | -(instancetype)init 41 | { 42 | self = [super init]; 43 | if (self) { 44 | [self commonInit]; 45 | } 46 | return self; 47 | } 48 | 49 | -(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder 50 | { 51 | self = [super initWithCoder:aDecoder]; 52 | if (self) { 53 | [self commonInit]; 54 | } 55 | return self; 56 | } 57 | 58 | -(void)commonInit 59 | { 60 | [self setInitialDefaults]; 61 | 62 | _firstLineFrames = nil; 63 | _itemAttributes = [NSMutableDictionary dictionary]; 64 | _numberOfLines = 0; 65 | } 66 | 67 | -(void)setInitialDefaults 68 | { 69 | // Default properties 70 | _scrollDirection = UICollectionViewScrollDirectionVertical; 71 | _lineDimensionType = RDHLineDimensionTypeDefault; 72 | _lineSize = RDHLineSizeDefault; 73 | _lineMultiplier = RDHLineMutliplierDefault; 74 | _lineExtension = RDHLineExtensionDefault; 75 | _lineItemCount = 4; 76 | _itemSpacing = 0; 77 | _lineSpacing = 0; 78 | _sectionsStartOnNewLine = NO; 79 | } 80 | 81 | -(void)invalidateLayout 82 | { 83 | [super invalidateLayout]; 84 | 85 | self.firstLineFrames = nil; 86 | [self.itemAttributes removeAllObjects]; 87 | self.numberOfLines = 0; 88 | self.calculatedItemSize = CGSizeZero; 89 | } 90 | 91 | -(void)prepareLayout 92 | { 93 | [super prepareLayout]; 94 | 95 | self.numberOfLines = [self calculateNumberOfLines]; 96 | 97 | self.calculatedItemSize = [self calculateItemSize]; 98 | 99 | NSInteger const sectionCount = [self.collectionView numberOfSections]; 100 | for (NSInteger section=0; section 0) { 122 | size.width += (self.numberOfLines - 1) * self.lineSpacing; 123 | } 124 | size.height = [self constrainedCollectionViewDimension]; 125 | break; 126 | 127 | case UICollectionViewScrollDirectionVertical: 128 | size.width = [self constrainedCollectionViewDimension]; 129 | size.height = self.numberOfLines * self.calculatedItemSize.height; 130 | // Add spacings 131 | if (self.numberOfLines > 0) { 132 | size.height += (self.numberOfLines - 1) * self.lineSpacing; 133 | } 134 | break; 135 | } 136 | 137 | return size; 138 | } 139 | 140 | -(nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(nonnull NSIndexPath *)indexPath 141 | { 142 | UICollectionViewLayoutAttributes *layoutAttrs = self.itemAttributes[indexPath]; 143 | 144 | if (!layoutAttrs) { 145 | layoutAttrs = [self calculateLayoutAttributesForItemAtIndexPath:indexPath]; 146 | self.itemAttributes[indexPath] = layoutAttrs; 147 | } 148 | 149 | return layoutAttrs; 150 | } 151 | 152 | -(NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 153 | { 154 | NSMutableArray<__kindof UICollectionViewLayoutAttributes *> *layoutAttributes = [NSMutableArray arrayWithCapacity:[self.itemAttributes count]]; 155 | 156 | [self.itemAttributes enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *const indexPath, UICollectionViewLayoutAttributes *attr, BOOL *stop) { 157 | 158 | if (CGRectIntersectsRect(rect, attr.frame)) { 159 | [layoutAttributes addObject:attr]; 160 | } 161 | }]; 162 | 163 | return layoutAttributes; 164 | } 165 | 166 | -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 167 | { 168 | return !CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size); 169 | } 170 | 171 | #pragma mark - Lazily loaded properties 172 | 173 | /// Precalculate the frames for the first line as they can be reused for every line 174 | -(NSArray *)firstLineFrames 175 | { 176 | if (!_firstLineFrames) { 177 | 178 | CGFloat collectionConstrainedDimension = [self constrainedCollectionViewDimension]; 179 | // Subtract the spacing between items on a line 180 | collectionConstrainedDimension -= (self.itemSpacing * (self.lineItemCount - 1)); 181 | 182 | CGFloat constrainedItemDimension; 183 | switch (self.scrollDirection) { 184 | case UICollectionViewScrollDirectionVertical: 185 | constrainedItemDimension = self.calculatedItemSize.width; 186 | break; 187 | 188 | case UICollectionViewScrollDirectionHorizontal: 189 | constrainedItemDimension = self.calculatedItemSize.height; 190 | break; 191 | } 192 | 193 | // This value will always be less than the lineItemCount - this is the number of dirty pixels 194 | CGFloat remainingDimension = collectionConstrainedDimension - (constrainedItemDimension * self.lineItemCount); 195 | 196 | CGRect frame = CGRectZero; 197 | frame.size = self.calculatedItemSize; 198 | 199 | NSMutableArray *frames = [NSMutableArray arrayWithCapacity:self.lineItemCount]; 200 | 201 | for (NSUInteger i=0; i 0) { 207 | switch (self.scrollDirection) { 208 | case UICollectionViewScrollDirectionVertical: 209 | itemFrame.size.width++; 210 | break; 211 | 212 | case UICollectionViewScrollDirectionHorizontal: 213 | itemFrame.size.height++; 214 | break; 215 | } 216 | } 217 | 218 | [frames addObject:[NSValue valueWithCGRect:itemFrame]]; 219 | 220 | // Move to the next item 221 | switch (self.scrollDirection) { 222 | case UICollectionViewScrollDirectionVertical: 223 | frame.origin.x = itemFrame.origin.x + itemFrame.size.width + self.itemSpacing; 224 | break; 225 | 226 | case UICollectionViewScrollDirectionHorizontal: 227 | frame.origin.y = itemFrame.origin.y + itemFrame.size.height + self.itemSpacing; 228 | break; 229 | } 230 | } 231 | 232 | _firstLineFrames = [frames copy]; 233 | } 234 | 235 | return _firstLineFrames; 236 | } 237 | 238 | #pragma mark - Calculation methods 239 | 240 | -(NSUInteger)calculateNumberOfLines 241 | { 242 | NSInteger numberOfLines; 243 | if (self.sectionsStartOnNewLine) { 244 | 245 | numberOfLines = 0; 246 | 247 | NSInteger const sectionCount = [self.collectionView numberOfSections]; 248 | for (NSInteger section=0; section= 0, @"Negative lineSize is meaningless"); 437 | 438 | // Reset other line dimensions 439 | _lineMultiplier = RDHLineMutliplierDefault; 440 | _lineExtension = RDHLineExtensionDefault; 441 | self.lineDimensionType = RDHLineDimensionTypeSize; 442 | 443 | if (_lineSize != lineSize) { 444 | _lineSize = lineSize; 445 | 446 | [self invalidateLayout]; 447 | } 448 | } 449 | 450 | -(void)setLineMultiplier:(CGFloat)lineMultiplier 451 | { 452 | NSAssert(lineMultiplier > 0, @"None positive lineMultiplier is meaningless"); 453 | 454 | // Reset other line dimensions 455 | _lineSize = RDHLineSizeDefault; 456 | _lineExtension = RDHLineExtensionDefault; 457 | self.lineDimensionType = RDHLineDimensionTypeMultiplier; 458 | 459 | if (_lineMultiplier != lineMultiplier) { 460 | _lineMultiplier = lineMultiplier; 461 | 462 | [self invalidateLayout]; 463 | } 464 | } 465 | 466 | -(void)setLineExtension:(CGFloat)lineExtension 467 | { 468 | NSAssert(lineExtension >= 0, @"Negative lineExtension is meaningless"); 469 | 470 | // Reset other line dimensions 471 | _lineSize = RDHLineSizeDefault; 472 | _lineMultiplier = RDHLineMutliplierDefault; 473 | self.lineDimensionType = RDHLineDimensionTypeExtension; 474 | 475 | if (_lineExtension != lineExtension) { 476 | _lineExtension = lineExtension; 477 | 478 | [self invalidateLayout]; 479 | } 480 | } 481 | 482 | -(void)setLineItemCount:(NSUInteger)lineItemCount 483 | { 484 | NSAssert(lineItemCount > 0, @"Zero line item count is meaningless"); 485 | if (_lineItemCount != lineItemCount) { 486 | _lineItemCount = lineItemCount; 487 | 488 | [self invalidateLayout]; 489 | } 490 | } 491 | 492 | -(void)setItemSpacing:(CGFloat)itemSpacing 493 | { 494 | if (_itemSpacing != itemSpacing) { 495 | _itemSpacing = itemSpacing; 496 | 497 | [self invalidateLayout]; 498 | } 499 | } 500 | 501 | -(void)setLineSpacing:(CGFloat)lineSpacing 502 | { 503 | if (_lineSpacing != lineSpacing) { 504 | _lineSpacing = lineSpacing; 505 | 506 | [self invalidateLayout]; 507 | } 508 | } 509 | 510 | -(void)setSectionsStartOnNewLine:(BOOL)sectionsStartOnNewLine 511 | { 512 | if (_sectionsStartOnNewLine != sectionsStartOnNewLine) { 513 | _sectionsStartOnNewLine = sectionsStartOnNewLine; 514 | 515 | [self invalidateLayout]; 516 | } 517 | } 518 | 519 | -(NSString *)description 520 | { 521 | NSString *lineDimension; 522 | 523 | if ((self.lineSize == RDHLineSizeDefault) 524 | && (self.lineMultiplier == RDHLineMutliplierDefault) 525 | && (self.lineExtension == RDHLineExtensionDefault)) { 526 | 527 | lineDimension = @"(Auto)"; 528 | 529 | } else { 530 | 531 | switch (self.lineDimensionType) { 532 | case RDHLineDimensionTypeSize: 533 | lineDimension = [NSString stringWithFormat:@"(Size, %.3lf)", self.lineSize]; 534 | break; 535 | case RDHLineDimensionTypeMultiplier: 536 | lineDimension = [NSString stringWithFormat:@"(Multiplier, %.3lf)", self.lineMultiplier]; 537 | break; 538 | case RDHLineDimensionTypeExtension: 539 | lineDimension = [NSString stringWithFormat:@"(Extension, %.3lf)", self.lineExtension]; 540 | break; 541 | } 542 | } 543 | 544 | return [NSString stringWithFormat:@"<%@: %p; scrollDirection = %@; lineDimension = %@; lineItemCount = %llu; itemSpacing = %.3lf; lineSpacing = %.3lf; sectionsStartOnNewLine = %@>", NSStringFromClass([self class]), self, (self.scrollDirection == UICollectionViewScrollDirectionVertical ? @"Vertical" : @"Horizontal"), lineDimension, (unsigned long long) self.lineItemCount, self.itemSpacing, self.lineSpacing, (self.sectionsStartOnNewLine ? @"YES" : @"NO")]; 545 | } 546 | 547 | @end 548 | 549 | @interface RDHCollectionViewGridLayout (InspectableScrolling) 550 | 551 | @property (nonatomic, assign) IBInspectable BOOL verticalScrolling; 552 | 553 | @end 554 | 555 | @implementation RDHCollectionViewGridLayout (InspectableScrolling) 556 | 557 | -(BOOL)verticalScrolling 558 | { 559 | return self.scrollDirection == UICollectionViewScrollDirectionVertical; 560 | } 561 | 562 | -(void)setVerticalScrolling:(BOOL)verticalScrolling 563 | { 564 | self.scrollDirection = verticalScrolling ? UICollectionViewScrollDirectionVertical : UICollectionViewScrollDirectionHorizontal; 565 | } 566 | 567 | @end 568 | 569 | @implementation RDHCollectionViewGridLayout (RDHCollectionViewGridLayout_Deprecated) 570 | 571 | -(CGFloat)lineDimension 572 | { 573 | return self.lineSize; 574 | } 575 | 576 | -(void)setLineDimension:(CGFloat)lineDimension 577 | { 578 | self.lineSize = lineDimension; 579 | } 580 | 581 | @end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RDHCollectionViewGridLayout 2 | =========================== 3 | [![Build Status](https://travis-ci.org/rhodgkins/RDHCollectionViewGridLayout.svg?branch=master)](https://travis-ci.org/rhodgkins/RDHCollectionViewGridLayout) 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![Pod Version](http://img.shields.io/cocoapods/v/RDHCollectionViewGridLayout.svg)](http://cocoadocs.org/docsets/RDHCollectionViewGridLayout/) 6 | [![Pod Platform](http://img.shields.io/cocoapods/p/RDHCollectionViewGridLayout.svg)](http://cocoadocs.org/docsets/RDHCollectionViewGridLayout/) 7 | [![Pod License](http://img.shields.io/cocoapods/l/RDHCollectionViewGridLayout.svg)](http://opensource.org/licenses/MIT) 8 | 9 | Works and tested on iOS 6.0 to 10.0 (under Xcode 8.0), but if you find any issues please [report](https://github.com/rhodgkins/RDHCollectionViewGridLayout/issues) them! 10 | 11 | Carthage 12 | --------- 13 | 14 | As of 1.2.2 Carthage support has been added. 15 | 16 | ```ogdl 17 | github "rhodgkins/RDHCollectionViewGridLayout" ~> 1.2 18 | ``` 19 | 20 | CocoaPods 21 | --------- 22 | 23 | Grid layout for UICollectionView. 24 | ```ruby 25 | pod 'RDHCollectionViewGridLayout', '~> 1.2' 26 | ``` 27 | 28 | Summary 29 | ------- 30 | 31 | This layout provides simple options for customisation of a collection view as a grid layout. 32 | Supporting vertical and horizontal scrolling directions, the layout also takes care of left over pixels at the end of rows. 33 | For example, a `UICollectionView` for a size of 320x300 scrolling vertically with 6 columns would normally mean each cell would be 53.3333 points wide. 34 | This would mean the cells would be blurry due to it's frame sitting over non-integer pixel values. 35 | `RDHCollectionViewGridLayout` takes care of this, and distributes these dirty extra pixels over the columns. This means the first 2 columns would have a cell width of 54 points, while the rest have a size of 53 points. 36 | 37 | PSTCollectionView 38 | ----------------- 39 | 40 | From version 1.1.0 `PSTCollectionView` support is removed. 41 | 42 | If `PSTCollectionView` is being used, use the `RDHCollectionViewGridLayout/PST` pod and a version less than 1.1.0: 43 | ``` ruby 44 | pod 'RDHCollectionViewGridLayout/PST', '< 1.1' 45 | ``` 46 | 47 | 48 | Documentation 49 | ------------- 50 | 51 | [Online docs](http://cocoadocs.org/docsets/RDHCollectionViewGridLayout) 52 | 57 | Properties 58 | ---------- 59 | 60 | ``` objective-c 61 | /** 62 | * A vertical direction will constrain the layout by rows (lineItemCount per row), a horizontal direction by columns 63 | (lineItemCount per column). 64 | * 65 | * The default value of this property is UICollectionViewScrollDirectionVertical. 66 | */ 67 | @property (nonatomic, assign) UICollectionViewScrollDirection scrollDirection; 68 | 69 | /** 70 | * Defines the size of the unconstrained dimension of the items. Simply, for vertical layouts this is the height of the items, and for horizontal layouts this is the width of the items. 71 | * Setting this value to 0 will make layout set the dimension to the average of the other dimenion on the same line. This is due to the layout taking account of dirty pixels, adding these extra pixels in the first X items on the line. 72 | * For example, if using a vertical scrollDirection and a lineItemCount of 5 when the collectionView has a width of 104, the first 4 items on every line will have a width of 21, and the last 20 (21 + 21 + 21 + 21 + 20 = 104), so the height of the lines would be 21 (20.8 rounded). 73 | * 74 | * A value of 0 is the same as setting the `lineExtension` property to 0 `lineMultiplier` property to 1. Setting this value will ignore the `lineExtension` and `lineMultiplier` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 75 | * 76 | * The default value of this property is 0. 77 | * 78 | * @see lineMultiplier 79 | * @see lineExtension 80 | * 81 | * @warning A negative value will throw an exception. 82 | */ 83 | @property (nonatomic, assign) IBInspectable CGFloat lineSize; 84 | 85 | /** 86 | * Defines a multipler of the unconstrained dimension of the items in relation to the strained dimension. Simply, for vertical layouts this value is multiplied by the width and used as the height of the items, and for horizontal layouts this value is multiplied by the height and used as the width of the items. The final dimension is rounded to a whole integer. 87 | * 88 | * A value of 1 is the same as setting the `lineSize` or `lineExtension` properties to 0.Setting this value will ignore the `lineSize` and `lineExtension` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 89 | * 90 | * The default value of this property is 1. 91 | * 92 | * @see lineSize 93 | * @see lineExtension 94 | * 95 | * @warning A negative value will throw an exception. 96 | */ 97 | @property (nonatomic, assign) IBInspectable CGFloat lineMultiplier; 98 | 99 | /** 100 | * Defines a multipler of the unconstrained dimension of the items in relation to the strained dimension. Simply, for vertical layouts this value is added to the width and used as the height of the items, and for horizontal layouts this value is added to the height and used as the width of the items. 101 | * 102 | * A value of 0 is the same as setting the `lineSize` property to 0 `lineMultiplier` property to 1. Setting this value will ignore the `lineSize` and `lineMultiplier` properties and set them to their default values. When used and set in Interface Builder, leave the other properties to Default and only set this property. 103 | * 104 | * The default value of this property is 0. 105 | * 106 | * @see lineSize 107 | * @see lineMultiplier 108 | * 109 | * @warning A negative value will throw an exception. 110 | */ 111 | @property (nonatomic, assign) IBInspectable CGFloat lineExtension; 112 | 113 | /** 114 | * Defines the maximum number of items allowed per line. Simply, for vertical layouts this is the number of columns, 115 | and for horizontal layouts this is the number of rows. The layout accounts for adding the extra pixels to the first 116 | X items on the line. Best case is that the useable width is exactly divisible by lineItemCount, worse case is that 117 | (useable width) mod lineItemCount = (lineItemCount - 1) and that the first (lineItemCount - 1) items are 1 pixel 118 | bigger. 119 | * 120 | * The default value of this property is 4. 121 | */ 122 | @property (nonatomic, assign) NSUInteger lineItemCount; 123 | 124 | /** 125 | * Defines the spacing of items on the same line of the layout. Simply, for vertical layouts this is the column 126 | spacing, and for horizontal layouts this is the row spacing. 127 | * 128 | * The default value of this property is 0. 129 | */ 130 | @property (nonatomic, assign) CGFloat itemSpacing; 131 | 132 | /** 133 | * Defines the line spacing of the layout. Simply, for vertical layouts this is the row spacing, and for horizontal 134 | layouts this is the column spacing. 135 | * 136 | * The default value of this property is 0. 137 | */ 138 | @property (nonatomic, assign) CGFloat lineSpacing; 139 | 140 | /** 141 | * To force sections to start on a new line set this property to YES. Otherwise the section will follow on on from 142 | the previous one. 143 | * 144 | * The default value of this property is NO. 145 | */ 146 | @property (nonatomic, assign) BOOL sectionsStartOnNewLine; 147 | 148 | ``` 149 | 150 | Examples 151 | -------- 152 | 153 | 154 | The following example is with a `lineSpacing` of 5 and `itemSpacing` of 10. Each section starts on a new line (`sectionsStartOnNewLine` is `YES`). 155 | 156 | ![Example image 1](Examples/images/5-line, 10-item.png) 157 | 158 | 159 | This is the same as the above but the `sectionsStartOnNewLine` is `NO`. 160 | 161 | ![Vertical scrolling](Examples/images/vertical.png) 162 | 163 | 164 | Horizontal scrolling 165 | 166 | ![Horizontal scrolling](Examples/images/horizontal.png) 167 | 168 | 169 | --------------------------------------------------------------------------------