├── .gitignore
├── .travis.yml
├── G3GridView.podspec
├── GridView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── GridView.xcscheme
│ └── GridViewTests.xcscheme
├── GridView
├── AnimatedLayer.swift
├── AroundInsets.swift
├── ArrayExtension.swift
├── CGFloatExtension.swift
├── CGRectExtension.swift
├── CGSizeExtension.swift
├── Countable.swift
├── GridView+DelegateProxy.m
├── GridView.h
├── GridView.swift
├── GridViewCell.swift
├── GridViewScrollPosition.swift
├── Horizontal.swift
├── IndexPathExtension.swift
├── Info.plist
├── Location.swift
├── NSObjectProtocolExtension.swift
├── NeedsLayout.swift
├── ReuseQueue.swift
├── Scale.swift
├── UIEdgeInsetsExtension.swift
├── UIScrollViewExtension.swift
├── Vertical.swift
├── ViewBundle.swift
├── ViewMatrix.swift
├── ViewReference.swift
└── ViewVisibleInfo.swift
├── GridViewExample
├── GridViewExample.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── GridViewExample
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── ChannelListGridViewCell.swift
│ ├── ChannelListGridViewCell.xib
│ ├── DateTimeGridViewCell.swift
│ ├── DateTimeGridViewCell.xib
│ ├── Info.plist
│ ├── PageGridViewCell.swift
│ ├── PageGridViewCell.xib
│ ├── PageViewController.swift
│ ├── Slot.swift
│ ├── TimeTableGridViewCell.swift
│ ├── TimeTableGridViewCell.xib
│ └── TimeTableViewController.swift
├── GridViewTests
├── AroundInsetsTests.swift
├── ArrayExtensionTests.swift
├── CGFloatExtensionTests.swift
├── CGRectExtensionTests.swift
├── CGSizeExtensionTests.swift
├── CountableTests.swift
├── GridViewCellTests.swift
├── GridViewScrollPositionTests.swift
├── GridViewTests.swift
├── HorizontalTests.swift
├── IndexPathTests.swift
├── Info.plist
├── LocationTests.swift
├── Mock
│ ├── MockCell.swift
│ ├── MockCell.xib
│ ├── MockLocation.swift
│ └── MockReusable.swift
├── NeedsLayoutTests.swift
├── ReuseQueueTests.swift
├── ScaleTests.swift
├── UIEdgeInsetsExtensionTests.swift
├── UIScrollViewExtensionTests.swift
├── VerticalTests.swift
├── ViewBundleTests.swift
├── ViewMatrixTests.swift
├── ViewReferenceTests.swift
└── ViewVisibleInfoTests.swift
├── LICENSE
├── Package.swift
└── 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 | # Carthage
29 | #
30 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
31 | # Carthage/Checkouts
32 |
33 | Carthage/Build
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | os: osx
3 | osx_image: xcode10.3
4 | before_install:
5 | - gem install xcpretty
6 | before_script:
7 | - set -o pipefail
8 | script:
9 | - xcodebuild test -project ./GridView.xcodeproj -scheme GridViewTests -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=12.4,name=iPhone 8' | xcpretty -c
10 | notifications:
11 | email: false
12 |
--------------------------------------------------------------------------------
/G3GridView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "G3GridView"
3 | s.summary = "Reusable GridView with excellent performance and customization that can be time table, spreadsheet, paging and more."
4 | s.homepage = "https://github.com/KyoheiG3/GridView"
5 | s.version = "0.8.0"
6 | s.license = { :type => "MIT", :file => "LICENSE" }
7 | s.author = { "Kyohei Ito" => "je.suis.kyohei@gmail.com" }
8 | s.swift_version = '5.0'
9 | s.ios.deployment_target = '9.0'
10 | s.source = { :git => "https://github.com/KyoheiG3/GridView.git", :tag => s.version.to_s }
11 | s.source_files = "GridView/**/*.{h,m,swift}"
12 | s.module_name = "GridView"
13 | s.requires_arc = true
14 | s.frameworks = "UIKit"
15 | end
16 |
--------------------------------------------------------------------------------
/GridView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GridView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GridView.xcodeproj/xcshareddata/xcschemes/GridView.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/GridView.xcodeproj/xcshareddata/xcschemes/GridViewTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
49 |
50 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/GridView/AnimatedLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimatedLayer.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/08.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol AnimatedLayerDelegate: CALayerDelegate {
12 | func animatedLayer(_ layer: AnimatedLayer, statusDidChange status: AnimatedLayer.Status)
13 | }
14 |
15 | extension AnimatedLayerDelegate {
16 | func animatedLayer(_ layer: AnimatedLayer, statusDidChange status: AnimatedLayer.Status) {
17 | // Do nothing
18 | }
19 | }
20 |
21 | class AnimatedLayer: CALayer {
22 | enum Status {
23 | case cancelled, finished
24 | }
25 |
26 | private static let animationkey = "progress"
27 | fileprivate struct Animation {
28 | let key = AnimatedLayer.animationkey
29 | let from: CGFloat = 0
30 | let to: CGFloat = 1
31 | }
32 |
33 | override class func needsDisplay(forKey key: String) -> Bool {
34 | if key == animationkey {
35 | return true
36 | }
37 | return super.needsDisplay(forKey: key)
38 | }
39 |
40 | private var isAnimating = false
41 | private var isAnimatingFinish: Bool {
42 | if isAnimating, let layer = presentation() {
43 | return layer.progress == animation.to
44 | } else {
45 | return false
46 | }
47 | }
48 | fileprivate var isProgressing: Bool {
49 | return isAnimating == true && isAnimatingFinish == false
50 | }
51 | fileprivate let animation = Animation()
52 |
53 | override func action(forKey event: String) -> CAAction? {
54 | if event == animation.key {
55 | isAnimating = false
56 | if let action = super.action(forKey: "opacity") as? CABasicAnimation {
57 | isAnimating = true
58 |
59 | action.keyPath = event
60 | action.fromValue = animation.from
61 | action.toValue = animation.to
62 | return action
63 | }
64 | }
65 |
66 | return super.action(forKey: event)
67 | }
68 |
69 | override func display() {
70 | super.display()
71 |
72 | if isProgressing == false {
73 | setCurrentProgress(animation.from, status: .finished)
74 | }
75 | }
76 | }
77 |
78 | extension AnimatedLayer {
79 | @NSManaged private(set) fileprivate var progress: CGFloat
80 | private var animatedDelegate: AnimatedLayerDelegate? {
81 | return delegate as? AnimatedLayerDelegate
82 | }
83 |
84 | fileprivate func setCurrentProgress(_ progress: CGFloat, status: Status? = nil) {
85 | if let status = status {
86 | animatedDelegate?.animatedLayer(self, statusDidChange: status)
87 | }
88 |
89 | if delegate != nil {
90 | self.progress = progress
91 | }
92 | }
93 | }
94 |
95 | extension AnimatedLayer {
96 | func animate() {
97 | if isProgressing {
98 | UIView.performWithoutAnimation {
99 | setCurrentProgress(animation.from, status: .cancelled)
100 | }
101 | }
102 |
103 | setCurrentProgress(animation.to)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/GridView/AroundInsets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AroundInsets.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/25.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct AroundInsets {
12 | struct Inset {
13 | static let zero = Inset(width: 0)
14 |
15 | let width: CGFloat
16 |
17 | init(width: CGFloat) {
18 | self.width = width
19 | }
20 | }
21 |
22 | static let zero = AroundInsets(parentSize: .zero, frame: .zero)
23 | var left: Inset
24 | var right: Inset
25 |
26 | init(parentSize: CGSize, frame: CGRect) {
27 | func around(_ x: CGFloat) -> CGFloat {
28 | return ceil(x / frame.width) * frame.width
29 | }
30 |
31 | if frame.width > 0 {
32 | left = Inset(width: around(frame.minX))
33 | right = Inset(width: around(parentSize.width - frame.maxX))
34 | } else {
35 | left = .zero
36 | right = .zero
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/GridView/ArrayExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/03.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Hashable, Element: Comparable {
12 | func union(_ elements: [Element]) -> [Element] {
13 | return self + elements.subtracting(self)
14 | }
15 |
16 | func subtracting(_ elements: [Element]) -> [Element] {
17 | return [Element](Set(self).subtracting(elements)).sorted()
18 | }
19 |
20 | func intersection(_ elements: [Element]) -> [Element] {
21 | return [Element](Set(self).intersection(elements)).sorted()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/GridView/CGFloatExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloatExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/02.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 |
11 | extension CGFloat {
12 | fileprivate static var scale = UIScreen.main.scale
13 | var integral: CGFloat {
14 | return CoreGraphics.round(self * CGFloat.scale) / CGFloat.scale
15 | }
16 |
17 | func rounded(p: CGFloat) -> CGFloat {
18 | let scale = pow(10, p)
19 | return CoreGraphics.round(self * scale) / scale
20 | }
21 |
22 | func floored(p: CGFloat) -> CGFloat {
23 | let scale = pow(10, p)
24 | return CoreGraphics.floor(self * scale) / scale
25 | }
26 | }
27 |
28 | #if DEBUG
29 | extension CGFloat {
30 | static var debugScale: CGFloat {
31 | get { return scale }
32 | set { scale = newValue }
33 | }
34 | }
35 | #endif
36 |
--------------------------------------------------------------------------------
/GridView/CGRectExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGRectExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/26.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 |
11 | extension CGRect {
12 | init(horizontal: Horizontal) {
13 | self.init(x: horizontal.x, y: 0, width: horizontal.width, height: 0)
14 | }
15 | init(vertical: Vertical) {
16 | self.init(x: 0, y: vertical.y, width: 0, height: vertical.height)
17 | }
18 | init(horizontal: Horizontal, vertical: Vertical) {
19 | self.init(x: horizontal.x, y: vertical.y, width: horizontal.width, height: vertical.height)
20 | }
21 |
22 | var vertical: Vertical {
23 | get {
24 | return Vertical(y: origin.y, height: height)
25 | }
26 | set {
27 | origin.y = newValue.y
28 | size.height = newValue.height
29 | }
30 | }
31 |
32 | var horizontal: Horizontal {
33 | get {
34 | return Horizontal(x: origin.x, width: width)
35 | }
36 | set {
37 | origin.x = newValue.x
38 | size.width = newValue.width
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/GridView/CGSizeExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSizeExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/29.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 |
11 | extension CGSize {
12 | static func +(lhs: CGSize, rhs: CGSize) -> CGSize {
13 | return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
14 | }
15 |
16 | static func -(lhs: CGSize, rhs: CGSize) -> CGSize {
17 | return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/GridView/Countable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Countable.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/23.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | enum Threshold {
10 | case `in`, above, below
11 | }
12 |
13 | protocol Countable {
14 | var count: Int { get }
15 |
16 | func `repeat`(_ value: Int) -> Int
17 | func threshold(with value: Int) -> Threshold
18 | }
19 |
20 | extension Dictionary: Countable {}
21 | extension Array: Countable {}
22 |
23 | extension Countable {
24 | func `repeat`(_ x: Int) -> Int {
25 | guard count != 0 && (x < 0 || x >= count) else {
26 | return x
27 | }
28 |
29 | var value = x
30 | while value < 0 {
31 | value += count
32 | }
33 | return value % count
34 | }
35 |
36 | func threshold(with value: Int) -> Threshold {
37 | switch value {
38 | case (let s) where s < 0:
39 | return .below
40 | case (let s) where s >= count:
41 | return .above
42 | default:
43 | return .in
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/GridView/GridView+DelegateProxy.m:
--------------------------------------------------------------------------------
1 | //
2 | // GridView+DelegateProxy.m
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/20.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @implementation GridView (DelegateProxy)
13 |
14 | - (void)forwardInvocation:(NSInvocation *)invocation {
15 | [invocation invokeWithTarget: self.originDelegate];
16 | }
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/GridView/GridView.h:
--------------------------------------------------------------------------------
1 | //
2 | // GridView.h
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/27.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for GridView.
12 | FOUNDATION_EXPORT double GridViewVersionNumber;
13 |
14 | //! Project version string for GridView.
15 | FOUNDATION_EXPORT const unsigned char GridViewVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/GridView/GridViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridViewCell.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/10/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class GridViewCell: UIView, Reusable {
12 | open internal(set) var indexPath = IndexPath(row: 0, column: 0)
13 | open var isSelected: Bool = false {
14 | didSet {
15 | setSelected(isSelected)
16 | }
17 | }
18 |
19 | open var isHighlighted: Bool = false {
20 | didSet {
21 | setHighlighted(isHighlighted)
22 | }
23 | }
24 |
25 | open func prepareForReuse() {
26 | }
27 |
28 | open func setSelected(_ selected: Bool) {
29 | }
30 |
31 | open func setHighlighted(_ highlighted: Bool) {
32 | }
33 |
34 | public required init?(coder aDecoder: NSCoder) {
35 | super.init(coder: aDecoder)
36 | autoresizingMask = .init(rawValue: 0)
37 | }
38 |
39 | public required override init(frame: CGRect) {
40 | super.init(frame: frame)
41 | autoresizingMask = .init(rawValue: 0)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/GridView/GridViewScrollPosition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridViewScrollPosition.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | public struct GridViewScrollPosition: OptionSet {
10 | public private(set) var rawValue: UInt
11 |
12 | public init(rawValue: UInt) {
13 | self.rawValue = rawValue
14 | }
15 |
16 | // The vertical positions are mutually exclusive to each other, but are bitwise or-able with the horizontal scroll positions.
17 | // Combining positions from the same grouping (horizontal or vertical) will result in an NSInvalidArgumentException.
18 | public static var top = GridViewScrollPosition(rawValue: 1 << 0)
19 | public static var centeredVertically = GridViewScrollPosition(rawValue: 1 << 1)
20 | public static var bottom = GridViewScrollPosition(rawValue: 1 << 2)
21 |
22 | // Likewise, the horizontal positions are mutually exclusive to each other.
23 | public static var left = GridViewScrollPosition(rawValue: 1 << 3)
24 | public static var centeredHorizontally = GridViewScrollPosition(rawValue: 1 << 4)
25 | public static var right = GridViewScrollPosition(rawValue: 1 << 5)
26 |
27 | public static var topFit = GridViewScrollPosition(rawValue: 1 << 6)
28 | public static var bottomFit = GridViewScrollPosition(rawValue: 1 << 7)
29 | public static var leftFit = GridViewScrollPosition(rawValue: 1 << 8)
30 | public static var rightFit = GridViewScrollPosition(rawValue: 1 << 9)
31 | }
32 |
--------------------------------------------------------------------------------
/GridView/Horizontal.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Horizontal.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 |
11 | struct Horizontal: Equatable {
12 | static let zero = Horizontal(x: 0, width: 0)
13 |
14 | static func ==(lhs: Horizontal, rhs: Horizontal) -> Bool {
15 | return lhs.x == rhs.x && lhs.width == rhs.width
16 | }
17 |
18 | static func *(lhs: Horizontal, rhs: CGFloat) -> Horizontal {
19 | return Horizontal(x: lhs.x * rhs, width: lhs.width * rhs)
20 | }
21 |
22 | var x: CGFloat {
23 | didSet {
24 | maxX = maxX - oldValue + x
25 | }
26 | }
27 | var width: CGFloat {
28 | didSet {
29 | maxX = maxX - oldValue + width
30 | }
31 | }
32 | private(set) var maxX: CGFloat
33 | var integral: Horizontal {
34 | return Horizontal(origin: self)
35 | }
36 |
37 | init(x: CGFloat, width: CGFloat) {
38 | self.x = x
39 | self.width = width
40 | self.maxX = x + width
41 | }
42 |
43 | private init(origin: Horizontal) {
44 | self.x = origin.x.integral
45 | self.width = origin.width.integral
46 | self.maxX = (origin.x + origin.width).integral
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/GridView/IndexPathExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexPathExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/21.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension IndexPath {
12 | public init(row: Int, column: Int) {
13 | self.init(row: row, section: column)
14 | }
15 |
16 | public var column: Int {
17 | get { return section }
18 | set { section = newValue }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GridView/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/GridView/Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Location.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol Location: Equatable {
12 | var x: CGFloat { get }
13 | var y: CGFloat { get }
14 |
15 | init(x: CGFloat, y: CGFloat)
16 |
17 | func min() -> CGFloat
18 | func max() -> CGFloat
19 | }
20 |
21 | extension Location {
22 | public static func ==(lhs: Self, rhs: Self) -> Bool {
23 | return lhs.x == rhs.x && lhs.y == rhs.y
24 | }
25 |
26 | public static func +(lhs: Self, rhs: Self) -> Self {
27 | return Self(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
28 | }
29 |
30 | public static func -(lhs: Self, rhs: Self) -> Self {
31 | return Self(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
32 | }
33 |
34 | public static func *(lhs: Self, rhs: Self) -> Self {
35 | return Self(x: lhs.x * rhs.x, y: lhs.y * rhs.y)
36 | }
37 |
38 | public static func /(lhs: Self, rhs: Self) -> Self {
39 | return Self(x: lhs.x / rhs.x, y: lhs.y / rhs.y)
40 | }
41 |
42 | public static func +(lhs: Self, rhs: CGFloat) -> Self {
43 | return Self(x: lhs.x + rhs, y: lhs.y + rhs)
44 | }
45 |
46 | public static func -(lhs: Self, rhs: CGFloat) -> Self {
47 | return Self(x: lhs.x - rhs, y: lhs.y - rhs)
48 | }
49 |
50 | func min() -> CGFloat {
51 | return Swift.min(x, y)
52 | }
53 |
54 | func max() -> CGFloat {
55 | return Swift.max(x, y)
56 | }
57 | }
58 |
59 | extension CGPoint: Location {}
60 |
61 | func max(_ lhs: T, _ rhs: T...) -> T {
62 | let rx = rhs.max(by: { $0.x < $1.x })?.x ?? lhs.x
63 | let ry = rhs.max(by: { $0.y < $1.y })?.y ?? lhs.y
64 | return T(x: max(lhs.x, rx), y: max(lhs.y, ry))
65 | }
66 |
67 | func min(_ lhs: T, _ rhs: T...) -> T {
68 | let rx = rhs.min(by: { $0.x < $1.x })?.x ?? lhs.x
69 | let ry = rhs.min(by: { $0.y < $1.y })?.y ?? lhs.y
70 | return T(x: min(lhs.x, rx), y: min(lhs.y, ry))
71 | }
72 |
--------------------------------------------------------------------------------
/GridView/NSObjectProtocolExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObjectProtocolExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/29.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSObjectProtocol {
12 | static var className: String {
13 | return String(describing: self)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/GridView/NeedsLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NeedsLayout.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | enum NeedsLayout: CustomDebugStringConvertible {
10 | case none, reload, layout(LayoutType)
11 | var debugDescription: String {
12 | switch self {
13 | case .none: return ".none"
14 | case .reload: return ".reload"
15 | case .layout(let type): return ".layout(\(type.debugDescription))"
16 | }
17 | }
18 |
19 | enum LayoutType: CustomDebugStringConvertible {
20 | case all(ViewMatrix), horizontally(ViewMatrix), rotating(ViewMatrix), scaling(ViewMatrix), pinching(ViewMatrix)
21 | var isScaling: Bool {
22 | switch self {
23 | case .scaling, .pinching:
24 | return true
25 | default:
26 | return false
27 | }
28 | }
29 |
30 | var debugDescription: String {
31 | switch self {
32 | case .all: return ".all"
33 | case .horizontally: return ".horizontally"
34 | case .rotating: return ".rotating"
35 | case .scaling: return ".scaling"
36 | case .pinching: return ".pinching"
37 | }
38 | }
39 | }
40 | }
41 |
42 | extension NeedsLayout: Equatable {
43 | static func ==(lhs: NeedsLayout, rhs: NeedsLayout) -> Bool {
44 | switch (lhs, rhs) {
45 | case (none, none), (reload, reload), (layout, layout):
46 | return true
47 | default:
48 | return false
49 | }
50 | }
51 | }
52 |
53 | extension NeedsLayout: Comparable {
54 | /// .none < .layout < .reload
55 | static func <(lhs: NeedsLayout, rhs: NeedsLayout) -> Bool {
56 | switch rhs {
57 | case .reload:
58 | switch lhs {
59 | case .reload:
60 | return false
61 | default:
62 | return true
63 | }
64 | case .layout:
65 | switch lhs {
66 | case .reload, .layout:
67 | return false
68 | default:
69 | return true
70 | }
71 | case .none:
72 | return false
73 | }
74 | }
75 | }
76 |
77 | extension NeedsLayout.LayoutType {
78 | var matrix: ViewMatrix {
79 | switch self {
80 | case .all(let m), .horizontally(let m), .rotating(let m), .scaling(let m), .pinching(let m):
81 | return m
82 | }
83 | }
84 | }
85 |
86 | extension NeedsLayout.LayoutType: Equatable {
87 | static func ==(lhs: NeedsLayout.LayoutType, rhs: NeedsLayout.LayoutType) -> Bool {
88 | switch (lhs, rhs) {
89 | case (all, all), (horizontally, horizontally), (rotating, rotating), (scaling, scaling), (pinching, pinching):
90 | return true
91 | default:
92 | return false
93 | }
94 | }
95 | }
96 |
97 | extension NeedsLayout.LayoutType: Comparable {
98 | /// .pinching < .rotating < .horizontally < .all
99 | static func <(lhs: NeedsLayout.LayoutType, rhs: NeedsLayout.LayoutType) -> Bool {
100 | switch rhs {
101 | case .all:
102 | switch lhs {
103 | case .all:
104 | return false
105 | default:
106 | return true
107 | }
108 | case .horizontally:
109 | switch lhs {
110 | case .all, .horizontally:
111 | return false
112 | default:
113 | return true
114 | }
115 | case .rotating:
116 | switch lhs {
117 | case .all, .horizontally, .rotating:
118 | return false
119 | default:
120 | return true
121 | }
122 | case .scaling:
123 | switch lhs {
124 | case .all, .horizontally, .rotating, scaling:
125 | return false
126 | default:
127 | return true
128 | }
129 | case .pinching:
130 | return false
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/GridView/ReuseQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReuseQueue.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/03.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | public protocol Reusable: class {
10 | var canReuse: Bool { get }
11 | func prepareForReuse()
12 | }
13 |
14 | extension Reusable {
15 | public func prepareForReuse() {
16 | // Do nothing
17 | }
18 | }
19 |
20 | extension Reusable where Self: UIView {
21 | public var canReuse: Bool {
22 | return superview == nil
23 | }
24 | }
25 |
26 | struct ReuseQueue {
27 | private var queue: [String: [E]] = [:]
28 |
29 | func dequeue(with identifier: String) -> E? {
30 | guard let elements = queue[identifier] else { return nil }
31 |
32 | for element in elements where element.canReuse {
33 | return element
34 | }
35 | return nil
36 | }
37 |
38 | mutating func append(_ element: E, for identifier: String) {
39 | if queue[identifier] == nil {
40 | queue[identifier] = []
41 | }
42 |
43 | queue[identifier]?.append(element)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/GridView/Scale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Scale.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct Scale: Location {
12 | public static let zero = Scale(x: 0, y: 0)
13 |
14 | public var x: CGFloat
15 | public var y: CGFloat
16 |
17 | public init(x: CGFloat, y: CGFloat) {
18 | self.x = x
19 | self.y = y
20 | }
21 | }
22 |
23 | public extension Scale {
24 | static let `default` = Scale(x: 1, y: 1)
25 | }
26 |
--------------------------------------------------------------------------------
/GridView/UIEdgeInsetsExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIEdgeInsetsExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/28.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | extension UIEdgeInsets {
10 | static func +(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets {
11 | return UIEdgeInsets(top: lhs.top + rhs.top, left: lhs.left + rhs.left, bottom: lhs.bottom + rhs.bottom, right: lhs.right + rhs.right)
12 | }
13 |
14 | static func -(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> UIEdgeInsets {
15 | return UIEdgeInsets(top: lhs.top - rhs.top, left: lhs.left - rhs.left, bottom: lhs.bottom - rhs.bottom, right: lhs.right - rhs.right)
16 | }
17 |
18 | var horizontal: CGFloat {
19 | return left + right
20 | }
21 |
22 | var vertical: CGFloat {
23 | return top + bottom
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/GridView/UIScrollViewExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollViewExtension.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/10.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIScrollView {
12 | var validityContentOffset: CGPoint {
13 | return CGPoint(x: contentOffset.x - frame.minX, y: contentOffset.y - frame.minY)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/GridView/Vertical.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Vertical.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/28.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 |
11 | struct Vertical: Equatable {
12 | static let zero = Vertical(y: 0, height: 0)
13 |
14 | static func ==(lhs: Vertical, rhs: Vertical) -> Bool {
15 | return lhs.y == rhs.y && lhs.height == rhs.height
16 | }
17 |
18 | static func *(lhs: Vertical, rhs: CGFloat) -> Vertical {
19 | return Vertical(y: lhs.y * rhs, height: lhs.height * rhs)
20 | }
21 |
22 | var y: CGFloat {
23 | didSet {
24 | maxY = maxY - oldValue + y
25 | }
26 | }
27 | var height: CGFloat {
28 | didSet {
29 | maxY = maxY - oldValue + height
30 | }
31 | }
32 | private(set) var maxY: CGFloat
33 | var integral: Vertical {
34 | return Vertical(origin: self)
35 | }
36 |
37 | init(y: CGFloat, height: CGFloat) {
38 | self.y = y
39 | self.height = height
40 | self.maxY = y + height
41 | }
42 |
43 | private init(origin: Vertical) {
44 | self.y = origin.y.integral
45 | self.height = origin.height.integral
46 | self.maxY = (origin.y + origin.height).integral
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/GridView/ViewBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewBundle.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/04.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol Initializable {
12 | init()
13 | }
14 |
15 | extension NSObject: Initializable {}
16 |
17 | struct ViewBundle where T: NSObjectProtocol {
18 | private var nib: [String: UINib] = [:]
19 | private var `class`: [String: T.Type] = [:]
20 |
21 | mutating func register(ofNib nib: UINib?, for identifier: String) {
22 | self.nib[identifier] = nib
23 | self.class[identifier] = nil
24 | }
25 |
26 | mutating func register(ofClass cellClass: T.Type, for identifier: String) {
27 | self.class[identifier] = cellClass
28 | self.nib[identifier] = nil
29 | }
30 |
31 | private func nibInstantiate(with identifier: String) -> T? {
32 | return self.nib[identifier]?.instantiate(withOwner: nil, options: nil).first as? T
33 | }
34 |
35 | private func classInstantiate(with identifier: String) -> T? {
36 | return self.class[identifier]?.init()
37 | }
38 |
39 | func instantiate(with identifier: String) -> T {
40 | if let cell = nibInstantiate(with: identifier) ?? classInstantiate(with: identifier) {
41 | return cell
42 | } else {
43 | fatalError("could not dequeue a view of kind: \(T.className) with identifier \(identifier) - must register a nib or a class for the identifier")
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/GridView/ViewMatrix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewMatrix.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/25.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct ViewMatrix: Countable {
12 | private let isInfinitable: Bool
13 | private let horizontals: [Horizontal]?
14 | private let verticals: [[Vertical?]]
15 | private let visibleSize: CGSize?
16 | private let viewFrame: CGRect
17 | private let scale: Scale
18 | private let inset: UIEdgeInsets
19 | private let aroundInset: AroundInsets
20 | private let contentHeight: CGFloat
21 |
22 | let validityContentRect: CGRect
23 | let contentSize: CGSize
24 | let contentInset: UIEdgeInsets
25 | var count: Int {
26 | return verticals.count
27 | }
28 |
29 | func convertToActualOffset(_ offset: CGPoint) -> CGPoint {
30 | return CGPoint(x: offset.x - aroundInset.left.width, y: offset.y)
31 | }
32 |
33 | func convert(_ offset: CGPoint, from matrix: ViewMatrix) -> CGPoint {
34 | let oldContentOffset = offset + matrix.viewFrame.origin
35 | let indexPath = matrix.indexPathForRow(at: oldContentOffset)
36 |
37 | let oldRect = matrix.rectForRow(at: indexPath)
38 | let oldOffset = oldContentOffset - oldRect.origin
39 | guard oldRect.width != 0 && oldRect.height != 0 else { return .zero }
40 |
41 | let newRect = rectForRow(at: indexPath)
42 | let newOffset = CGPoint(x: newRect.width * oldOffset.x / oldRect.width, y: newRect.height * oldOffset.y / oldRect.height)
43 |
44 | let contentOffset = newRect.origin + newOffset
45 |
46 | let actualSize: CGSize = contentSize - (visibleSize ?? .zero)
47 | let maxOffset = CGPoint(x: viewFrame.origin.x + inset.right + actualSize.width, y: viewFrame.origin.y + inset.bottom + actualSize.height)
48 | let minOffset = CGPoint(x: viewFrame.origin.x - inset.left, y: viewFrame.origin.y - inset.top)
49 | return min(max(contentOffset, minOffset), maxOffset)
50 | }
51 |
52 | init() {
53 | self.init(horizontals: nil, verticals: [], viewFrame: .zero, contentHeight: 0, superviewSize: nil, scale: .zero, inset: .zero, isInfinitable: false)
54 | }
55 |
56 | init(matrix: ViewMatrix, horizontals: [Horizontal]? = nil, viewFrame: CGRect, superviewSize: CGSize?, scale: Scale, inset: UIEdgeInsets) {
57 | let height = matrix.contentHeight
58 | self.init(horizontals: horizontals ?? matrix.horizontals, verticals: matrix.verticals, viewFrame: viewFrame, contentHeight: height, superviewSize: superviewSize, scale: scale, inset: inset, isInfinitable: matrix.isInfinitable)
59 | }
60 |
61 | init(horizontals: [Horizontal]?, verticals: [[Vertical?]], viewFrame: CGRect, contentHeight: CGFloat, superviewSize: CGSize?, scale: Scale, inset: UIEdgeInsets, isInfinitable: Bool) {
62 | var contentSize: CGSize = .zero
63 | contentSize.width = (horizontals?.last?.maxX ?? viewFrame.width * CGFloat(verticals.endIndex)) * scale.x
64 | if contentHeight == 0 {
65 | contentSize.height = viewFrame.height * CGFloat(verticals.first?.endIndex ?? 0) * scale.y
66 | } else {
67 | contentSize.height = contentHeight * scale.y
68 | }
69 |
70 | self.horizontals = horizontals
71 | self.verticals = verticals
72 | self.viewFrame = viewFrame
73 | self.visibleSize = superviewSize
74 | self.scale = scale
75 | self.inset = inset
76 | self.contentHeight = contentHeight
77 | self.isInfinitable = isInfinitable
78 |
79 | let parentSize = superviewSize ?? .zero
80 | if isInfinitable {
81 | let aroundInset = AroundInsets(parentSize: parentSize, frame: viewFrame)
82 | self.aroundInset = aroundInset
83 |
84 | let allWidth = aroundInset.left.width + aroundInset.right.width + viewFrame.width
85 | self.validityContentRect = CGRect(origin: CGPoint(x: aroundInset.left.width - viewFrame.minX, y: 0), size: contentSize)
86 | self.contentSize = CGSize(width: contentSize.width + allWidth, height: contentSize.height)
87 | self.contentInset = UIEdgeInsets(top: -viewFrame.minY + inset.top, left: -aroundInset.left.width, bottom: -parentSize.height + viewFrame.maxY + inset.bottom, right: -aroundInset.right.width)
88 | } else {
89 | self.aroundInset = .zero
90 | self.validityContentRect = CGRect(origin: .zero, size: contentSize)
91 | self.contentSize = contentSize
92 | self.contentInset = UIEdgeInsets(top: -viewFrame.minY, left: -viewFrame.minX, bottom: -parentSize.height + viewFrame.maxY, right: -parentSize.width + viewFrame.maxX) + inset
93 | }
94 | }
95 |
96 | fileprivate func verticalsForColumn(_ column: Int) -> [Vertical?] {
97 | guard verticals.indices ~= column else {
98 | return []
99 | }
100 | return verticals[column]
101 | }
102 |
103 | fileprivate func vertical(of verticals: [Vertical?], at index: Int) -> Vertical {
104 | guard verticals.indices ~= index else {
105 | return .zero
106 | }
107 | guard let vertical = verticals[index] else {
108 | let viewHeight = viewFrame.height
109 | return Vertical(y: viewHeight * CGFloat(index), height: viewHeight)
110 | }
111 | return vertical
112 | }
113 |
114 | fileprivate func verticalForRow(at indexPath: IndexPath) -> Vertical {
115 | return vertical(of: verticalsForColumn(indexPath.column), at: indexPath.row) * scale.y
116 | }
117 |
118 | fileprivate func offsetXForColumn(_ column: Int) -> CGFloat {
119 | guard let horizontals = horizontals else {
120 | return 0
121 | }
122 |
123 | switch horizontals.threshold(with: column) {
124 | case .below:
125 | return -validityContentRect.width
126 | case .above:
127 | return validityContentRect.width
128 | case .in:
129 | return 0
130 | }
131 | }
132 |
133 | fileprivate func horizontalForColumn(_ column: Int) -> Horizontal {
134 | var horizontal: Horizontal
135 | if let horizontals = horizontals {
136 | let absColumn = self.repeat(column)
137 | horizontal = horizontals[absColumn] * scale.x
138 | horizontal.x += offsetXForColumn(column)
139 | } else {
140 | let viewWidth = viewFrame.width * scale.x
141 | horizontal = Horizontal(x: viewWidth * CGFloat(column), width: viewWidth)
142 | }
143 |
144 | return horizontal
145 | }
146 |
147 | func rectForRow(at indexPath: IndexPath, threshold: Threshold = .in) -> CGRect {
148 | let vertical = verticalForRow(at: indexPath)
149 | var rect = CGRect(vertical: vertical)
150 | rect.horizontal = horizontalForColumn(indexPath.column)
151 | rect.origin.x += aroundInset.left.width
152 |
153 | switch threshold {
154 | case .below:
155 | rect.origin.x -= validityContentRect.width
156 | case .above:
157 | rect.origin.x += validityContentRect.width
158 | case .in:
159 | break
160 | }
161 |
162 | return rect
163 | }
164 |
165 | func indexPathForRow(at point: CGPoint) -> IndexPath {
166 | let absPoint = CGPoint(x: point.x - aroundInset.left.width, y: point.y)
167 | let absColumn = self.repeat(column(at: absPoint))
168 | let row = indexForRow(at: absPoint, in: absColumn)
169 | return IndexPath(row: row, column: absColumn)
170 | }
171 |
172 | fileprivate func column(at point: CGPoint) -> Int {
173 | guard let horizontals = horizontals else {
174 | let viewWidth = viewFrame.width * scale.x
175 | guard viewWidth != 0 else {
176 | return 0
177 | }
178 | return Int(floor(point.x / viewWidth))
179 | }
180 |
181 | var point = point
182 | var column = 0
183 | repeat {
184 | if point.x < 0 {
185 | column += horizontals.endIndex
186 | point.x += validityContentRect.width
187 | } else if point.x >= validityContentRect.width {
188 | column -= horizontals.endIndex
189 | point.x -= validityContentRect.width
190 | }
191 | } while point.x < 0 || point.x >= validityContentRect.width
192 |
193 | for index in horizontals.indices {
194 | let horizontal = (horizontals[index] * scale.x).integral
195 | if horizontal.x <= point.x && horizontal.maxX > point.x {
196 | return index - column
197 | }
198 | }
199 |
200 | fatalError("Column did not get at location \(point).")
201 | }
202 |
203 | fileprivate func indexForRow(at point: CGPoint, in column: Int) -> Int {
204 | let step = 100
205 | let verticals = verticalsForColumn(column)
206 |
207 | for index in stride(from: verticals.startIndex, to: verticals.endIndex, by: step) {
208 | let next = index + step
209 | guard verticals.endIndex <= next || vertical(of: verticals, at: next).maxY * scale.y > point.y else {
210 | continue
211 | }
212 |
213 | for offset in (index.. point.y else {
215 | continue
216 | }
217 |
218 | return offset
219 | }
220 | }
221 |
222 | return 0
223 | }
224 |
225 | func indexesForVisibleColumn(at point: CGPoint) -> [Int] {
226 | var columns: [Int] = []
227 | guard let visibleSize = visibleSize else {
228 | return columns
229 | }
230 |
231 | let visibleRect = CGRect(origin: CGPoint(x: point.x - aroundInset.left.width, y: 0), size: visibleSize)
232 | let index = column(at: visibleRect.origin)
233 |
234 | var frame = CGRect(origin: .zero, size: viewFrame.size)
235 | for offset in (0.. [Int] {
250 | var rows: [Int] = []
251 | guard let visibleSize = visibleSize else {
252 | return rows
253 | }
254 |
255 | let visibleRect = CGRect(origin: CGPoint(x: 0, y: point.y), size: visibleSize)
256 | let absColumn: Int
257 | if isInfinitable {
258 | absColumn = self.repeat(column)
259 | } else {
260 | absColumn = column
261 | }
262 |
263 | let index = indexForRow(at: visibleRect.origin, in: absColumn)
264 | let verticals = verticalsForColumn(absColumn)
265 |
266 | var rect: CGRect = .zero
267 | rect.size.width = horizontalForColumn(column).width
268 | for row in (index.. [Vertical?] {
285 | return verticalsForColumn(column)
286 | }
287 | func debugVerticalForRow(at indexPath: IndexPath) -> Vertical {
288 | return verticalForRow(at: indexPath)
289 | }
290 | func debugOffsetXForColumn(_ column: Int) -> CGFloat {
291 | return offsetXForColumn(column)
292 | }
293 | func debugHorizontalForColumn(_ column: Int) -> Horizontal {
294 | return horizontalForColumn(column)
295 | }
296 | func debugColumn(at point: CGPoint) -> Int {
297 | return column(at: point)
298 | }
299 | func debugIndexForRow(at point: CGPoint, in column: Int) -> Int {
300 | return indexForRow(at: point, in: column)
301 | }
302 | }
303 | #endif
304 |
--------------------------------------------------------------------------------
/GridView/ViewReference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewReference.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/03.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol View: class {
12 | func removeFromSuperview()
13 | }
14 |
15 | extension UIView: View {}
16 |
17 | class ViewReference {
18 |
19 | deinit {
20 | view?.removeFromSuperview()
21 | }
22 |
23 | private(set) weak var view: T?
24 |
25 | init(_ view: T) {
26 | self.view = view
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/GridView/ViewVisibleInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewVisibleInfo.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/11/11.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ViewVisibleInfo {
12 | private var column: [Int] = []
13 | private var row: [Int: [Int]] = [:]
14 | private var object: [IndexPath: ViewReference] = [:]
15 | private var selectedIndexPath: Set = []
16 |
17 | mutating func replaceColumn(_ column: [Int]) {
18 | self.column = column
19 | }
20 |
21 | mutating func replaceRows(_ rows: (Int) -> [Int]) {
22 | for column in self.column {
23 | self.row[column] = rows(column)
24 | }
25 | }
26 |
27 | mutating func replaceObject(with info: ViewVisibleInfo) {
28 | self.object = info.object
29 | }
30 |
31 | mutating func replaceSelectedIndexPath(with info: ViewVisibleInfo) {
32 | self.selectedIndexPath = info.selectedIndexPath
33 | }
34 |
35 | func columns() -> [Int] {
36 | return column
37 | }
38 |
39 | func rows() -> [Int: [Int]] {
40 | return row
41 | }
42 |
43 | func rows(in column: Int) -> [Int] {
44 | return row[column] ?? []
45 | }
46 |
47 | func visibleObject() -> [IndexPath: ViewReference] {
48 | return object
49 | }
50 |
51 | func isSelected(_ indexPath: IndexPath) -> Bool {
52 | return selectedIndexPath.contains(indexPath)
53 | }
54 |
55 | func object(at indexPath: IndexPath) -> T? {
56 | return object[indexPath]?.view
57 | }
58 |
59 | func indexPathsForSelected() -> [IndexPath] {
60 | return selectedIndexPath.sorted()
61 | }
62 |
63 | mutating func selected(at indexPath: IndexPath) -> T? {
64 | selectedIndexPath.insert(indexPath)
65 | return object[indexPath]?.view
66 | }
67 |
68 | mutating func deselected(at indexPath: IndexPath) -> T? {
69 | selectedIndexPath.remove(indexPath)
70 | return object[indexPath]?.view
71 | }
72 |
73 | mutating func append(_ newObject: T, at indexPath: IndexPath) {
74 | object[indexPath] = ViewReference(newObject)
75 | }
76 |
77 | mutating func removedObject(at indexPath: IndexPath) -> T? {
78 | let oldObject = object[indexPath]
79 | object[indexPath] = nil
80 | return oldObject?.view
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C70D53051EC6977700525575 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70D53041EC6977700525575 /* PageViewController.swift */; };
11 | C70D53071EC699FE00525575 /* PageGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70D53061EC699FE00525575 /* PageGridViewCell.swift */; };
12 | C70D53091EC69A1B00525575 /* PageGridViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C70D53081EC69A1B00525575 /* PageGridViewCell.xib */; };
13 | C77287631DEABECC005154DE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77287621DEABECC005154DE /* AppDelegate.swift */; };
14 | C77287651DEABECC005154DE /* TimeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77287641DEABECC005154DE /* TimeTableViewController.swift */; };
15 | C77287681DEABECC005154DE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C77287661DEABECC005154DE /* Main.storyboard */; };
16 | C772876A1DEABECC005154DE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77287691DEABECC005154DE /* Assets.xcassets */; };
17 | C772876D1DEABECC005154DE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C772876B1DEABECC005154DE /* LaunchScreen.storyboard */; };
18 | C772877D1DEABF3C005154DE /* GridView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C772877A1DEABF36005154DE /* GridView.framework */; };
19 | C772877E1DEABF3C005154DE /* GridView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C772877A1DEABF36005154DE /* GridView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | C7E9F9A51EC52C1D00D029AF /* DateTimeGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E9F9A41EC52C1D00D029AF /* DateTimeGridViewCell.swift */; };
21 | C7E9F9A81EC52CCA00D029AF /* DateTimeGridViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7E9F9A71EC52CCA00D029AF /* DateTimeGridViewCell.xib */; };
22 | C7E9F9AA1EC52E7C00D029AF /* ChannelListGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E9F9A91EC52E7C00D029AF /* ChannelListGridViewCell.swift */; };
23 | C7E9F9AC1EC52EA900D029AF /* ChannelListGridViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7E9F9AB1EC52EA900D029AF /* ChannelListGridViewCell.xib */; };
24 | C7E9F9AE1EC5325D00D029AF /* TimeTableGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E9F9AD1EC5325D00D029AF /* TimeTableGridViewCell.swift */; };
25 | C7E9F9B01EC5329100D029AF /* TimeTableGridViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C7E9F9AF1EC5329100D029AF /* TimeTableGridViewCell.xib */; };
26 | C7E9F9B21EC5383600D029AF /* Slot.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E9F9B11EC5383600D029AF /* Slot.swift */; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXContainerItemProxy section */
30 | C77287791DEABF36005154DE /* PBXContainerItemProxy */ = {
31 | isa = PBXContainerItemProxy;
32 | containerPortal = C77287741DEABF36005154DE /* GridView.xcodeproj */;
33 | proxyType = 2;
34 | remoteGlobalIDString = C77286A01DEAAC26005154DE;
35 | remoteInfo = GridView;
36 | };
37 | C772877B1DEABF36005154DE /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = C77287741DEABF36005154DE /* GridView.xcodeproj */;
40 | proxyType = 2;
41 | remoteGlobalIDString = C77286A91DEAAC26005154DE;
42 | remoteInfo = GridViewTests;
43 | };
44 | C772877F1DEABF3C005154DE /* PBXContainerItemProxy */ = {
45 | isa = PBXContainerItemProxy;
46 | containerPortal = C77287741DEABF36005154DE /* GridView.xcodeproj */;
47 | proxyType = 1;
48 | remoteGlobalIDString = C772869F1DEAAC26005154DE;
49 | remoteInfo = GridView;
50 | };
51 | /* End PBXContainerItemProxy section */
52 |
53 | /* Begin PBXCopyFilesBuildPhase section */
54 | C77287811DEABF3C005154DE /* Embed Frameworks */ = {
55 | isa = PBXCopyFilesBuildPhase;
56 | buildActionMask = 2147483647;
57 | dstPath = "";
58 | dstSubfolderSpec = 10;
59 | files = (
60 | C772877E1DEABF3C005154DE /* GridView.framework in Embed Frameworks */,
61 | );
62 | name = "Embed Frameworks";
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | /* End PBXCopyFilesBuildPhase section */
66 |
67 | /* Begin PBXFileReference section */
68 | C70D53041EC6977700525575 /* PageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; };
69 | C70D53061EC699FE00525575 /* PageGridViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageGridViewCell.swift; sourceTree = ""; };
70 | C70D53081EC69A1B00525575 /* PageGridViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PageGridViewCell.xib; sourceTree = ""; };
71 | C772875F1DEABECC005154DE /* GridViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GridViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
72 | C77287621DEABECC005154DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
73 | C77287641DEABECC005154DE /* TimeTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTableViewController.swift; sourceTree = ""; };
74 | C77287671DEABECC005154DE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
75 | C77287691DEABECC005154DE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
76 | C772876C1DEABECC005154DE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
77 | C772876E1DEABECC005154DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
78 | C77287741DEABF36005154DE /* GridView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GridView.xcodeproj; path = ../GridView.xcodeproj; sourceTree = ""; };
79 | C7E9F9A41EC52C1D00D029AF /* DateTimeGridViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateTimeGridViewCell.swift; sourceTree = ""; };
80 | C7E9F9A71EC52CCA00D029AF /* DateTimeGridViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DateTimeGridViewCell.xib; sourceTree = ""; };
81 | C7E9F9A91EC52E7C00D029AF /* ChannelListGridViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelListGridViewCell.swift; sourceTree = ""; };
82 | C7E9F9AB1EC52EA900D029AF /* ChannelListGridViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChannelListGridViewCell.xib; sourceTree = ""; };
83 | C7E9F9AD1EC5325D00D029AF /* TimeTableGridViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeTableGridViewCell.swift; sourceTree = ""; };
84 | C7E9F9AF1EC5329100D029AF /* TimeTableGridViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TimeTableGridViewCell.xib; sourceTree = ""; };
85 | C7E9F9B11EC5383600D029AF /* Slot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slot.swift; sourceTree = ""; };
86 | /* End PBXFileReference section */
87 |
88 | /* Begin PBXFrameworksBuildPhase section */
89 | C772875C1DEABECC005154DE /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | C772877D1DEABF3C005154DE /* GridView.framework in Frameworks */,
94 | );
95 | runOnlyForDeploymentPostprocessing = 0;
96 | };
97 | /* End PBXFrameworksBuildPhase section */
98 |
99 | /* Begin PBXGroup section */
100 | C77287561DEABECC005154DE = {
101 | isa = PBXGroup;
102 | children = (
103 | C77287741DEABF36005154DE /* GridView.xcodeproj */,
104 | C77287611DEABECC005154DE /* GridViewExample */,
105 | C77287601DEABECC005154DE /* Products */,
106 | );
107 | sourceTree = "";
108 | };
109 | C77287601DEABECC005154DE /* Products */ = {
110 | isa = PBXGroup;
111 | children = (
112 | C772875F1DEABECC005154DE /* GridViewExample.app */,
113 | );
114 | name = Products;
115 | sourceTree = "";
116 | };
117 | C77287611DEABECC005154DE /* GridViewExample */ = {
118 | isa = PBXGroup;
119 | children = (
120 | C77287621DEABECC005154DE /* AppDelegate.swift */,
121 | C77287661DEABECC005154DE /* Main.storyboard */,
122 | C77287691DEABECC005154DE /* Assets.xcassets */,
123 | C772876B1DEABECC005154DE /* LaunchScreen.storyboard */,
124 | C772876E1DEABECC005154DE /* Info.plist */,
125 | C7E9F9A91EC52E7C00D029AF /* ChannelListGridViewCell.swift */,
126 | C7E9F9AB1EC52EA900D029AF /* ChannelListGridViewCell.xib */,
127 | C7E9F9A41EC52C1D00D029AF /* DateTimeGridViewCell.swift */,
128 | C7E9F9A71EC52CCA00D029AF /* DateTimeGridViewCell.xib */,
129 | C70D53061EC699FE00525575 /* PageGridViewCell.swift */,
130 | C70D53081EC69A1B00525575 /* PageGridViewCell.xib */,
131 | C70D53041EC6977700525575 /* PageViewController.swift */,
132 | C7E9F9AD1EC5325D00D029AF /* TimeTableGridViewCell.swift */,
133 | C7E9F9AF1EC5329100D029AF /* TimeTableGridViewCell.xib */,
134 | C77287641DEABECC005154DE /* TimeTableViewController.swift */,
135 | C7E9F9B11EC5383600D029AF /* Slot.swift */,
136 | );
137 | path = GridViewExample;
138 | sourceTree = "";
139 | };
140 | C77287751DEABF36005154DE /* Products */ = {
141 | isa = PBXGroup;
142 | children = (
143 | C772877A1DEABF36005154DE /* GridView.framework */,
144 | C772877C1DEABF36005154DE /* GridViewTests.xctest */,
145 | );
146 | name = Products;
147 | sourceTree = "";
148 | };
149 | /* End PBXGroup section */
150 |
151 | /* Begin PBXNativeTarget section */
152 | C772875E1DEABECC005154DE /* GridViewExample */ = {
153 | isa = PBXNativeTarget;
154 | buildConfigurationList = C77287711DEABECC005154DE /* Build configuration list for PBXNativeTarget "GridViewExample" */;
155 | buildPhases = (
156 | C772875B1DEABECC005154DE /* Sources */,
157 | C772875C1DEABECC005154DE /* Frameworks */,
158 | C772875D1DEABECC005154DE /* Resources */,
159 | C77287811DEABF3C005154DE /* Embed Frameworks */,
160 | );
161 | buildRules = (
162 | );
163 | dependencies = (
164 | C77287801DEABF3C005154DE /* PBXTargetDependency */,
165 | );
166 | name = GridViewExample;
167 | productName = GridViewExample;
168 | productReference = C772875F1DEABECC005154DE /* GridViewExample.app */;
169 | productType = "com.apple.product-type.application";
170 | };
171 | /* End PBXNativeTarget section */
172 |
173 | /* Begin PBXProject section */
174 | C77287571DEABECC005154DE /* Project object */ = {
175 | isa = PBXProject;
176 | attributes = {
177 | LastSwiftUpdateCheck = 0810;
178 | LastUpgradeCheck = 1020;
179 | ORGANIZATIONNAME = "Kyohei Ito";
180 | TargetAttributes = {
181 | C772875E1DEABECC005154DE = {
182 | CreatedOnToolsVersion = 8.1;
183 | DevelopmentTeam = MX36A76L68;
184 | LastSwiftMigration = 1020;
185 | ProvisioningStyle = Automatic;
186 | };
187 | };
188 | };
189 | buildConfigurationList = C772875A1DEABECC005154DE /* Build configuration list for PBXProject "GridViewExample" */;
190 | compatibilityVersion = "Xcode 3.2";
191 | developmentRegion = en;
192 | hasScannedForEncodings = 0;
193 | knownRegions = (
194 | en,
195 | Base,
196 | );
197 | mainGroup = C77287561DEABECC005154DE;
198 | productRefGroup = C77287601DEABECC005154DE /* Products */;
199 | projectDirPath = "";
200 | projectReferences = (
201 | {
202 | ProductGroup = C77287751DEABF36005154DE /* Products */;
203 | ProjectRef = C77287741DEABF36005154DE /* GridView.xcodeproj */;
204 | },
205 | );
206 | projectRoot = "";
207 | targets = (
208 | C772875E1DEABECC005154DE /* GridViewExample */,
209 | );
210 | };
211 | /* End PBXProject section */
212 |
213 | /* Begin PBXReferenceProxy section */
214 | C772877A1DEABF36005154DE /* GridView.framework */ = {
215 | isa = PBXReferenceProxy;
216 | fileType = wrapper.framework;
217 | path = GridView.framework;
218 | remoteRef = C77287791DEABF36005154DE /* PBXContainerItemProxy */;
219 | sourceTree = BUILT_PRODUCTS_DIR;
220 | };
221 | C772877C1DEABF36005154DE /* GridViewTests.xctest */ = {
222 | isa = PBXReferenceProxy;
223 | fileType = wrapper.cfbundle;
224 | path = GridViewTests.xctest;
225 | remoteRef = C772877B1DEABF36005154DE /* PBXContainerItemProxy */;
226 | sourceTree = BUILT_PRODUCTS_DIR;
227 | };
228 | /* End PBXReferenceProxy section */
229 |
230 | /* Begin PBXResourcesBuildPhase section */
231 | C772875D1DEABECC005154DE /* Resources */ = {
232 | isa = PBXResourcesBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | C7E9F9AC1EC52EA900D029AF /* ChannelListGridViewCell.xib in Resources */,
236 | C7E9F9B01EC5329100D029AF /* TimeTableGridViewCell.xib in Resources */,
237 | C772876D1DEABECC005154DE /* LaunchScreen.storyboard in Resources */,
238 | C772876A1DEABECC005154DE /* Assets.xcassets in Resources */,
239 | C7E9F9A81EC52CCA00D029AF /* DateTimeGridViewCell.xib in Resources */,
240 | C77287681DEABECC005154DE /* Main.storyboard in Resources */,
241 | C70D53091EC69A1B00525575 /* PageGridViewCell.xib in Resources */,
242 | );
243 | runOnlyForDeploymentPostprocessing = 0;
244 | };
245 | /* End PBXResourcesBuildPhase section */
246 |
247 | /* Begin PBXSourcesBuildPhase section */
248 | C772875B1DEABECC005154DE /* Sources */ = {
249 | isa = PBXSourcesBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | C77287651DEABECC005154DE /* TimeTableViewController.swift in Sources */,
253 | C70D53051EC6977700525575 /* PageViewController.swift in Sources */,
254 | C77287631DEABECC005154DE /* AppDelegate.swift in Sources */,
255 | C7E9F9B21EC5383600D029AF /* Slot.swift in Sources */,
256 | C7E9F9A51EC52C1D00D029AF /* DateTimeGridViewCell.swift in Sources */,
257 | C7E9F9AE1EC5325D00D029AF /* TimeTableGridViewCell.swift in Sources */,
258 | C7E9F9AA1EC52E7C00D029AF /* ChannelListGridViewCell.swift in Sources */,
259 | C70D53071EC699FE00525575 /* PageGridViewCell.swift in Sources */,
260 | );
261 | runOnlyForDeploymentPostprocessing = 0;
262 | };
263 | /* End PBXSourcesBuildPhase section */
264 |
265 | /* Begin PBXTargetDependency section */
266 | C77287801DEABF3C005154DE /* PBXTargetDependency */ = {
267 | isa = PBXTargetDependency;
268 | name = GridView;
269 | targetProxy = C772877F1DEABF3C005154DE /* PBXContainerItemProxy */;
270 | };
271 | /* End PBXTargetDependency section */
272 |
273 | /* Begin PBXVariantGroup section */
274 | C77287661DEABECC005154DE /* Main.storyboard */ = {
275 | isa = PBXVariantGroup;
276 | children = (
277 | C77287671DEABECC005154DE /* Base */,
278 | );
279 | name = Main.storyboard;
280 | sourceTree = "";
281 | };
282 | C772876B1DEABECC005154DE /* LaunchScreen.storyboard */ = {
283 | isa = PBXVariantGroup;
284 | children = (
285 | C772876C1DEABECC005154DE /* Base */,
286 | );
287 | name = LaunchScreen.storyboard;
288 | sourceTree = "";
289 | };
290 | /* End PBXVariantGroup section */
291 |
292 | /* Begin XCBuildConfiguration section */
293 | C772876F1DEABECC005154DE /* Debug */ = {
294 | isa = XCBuildConfiguration;
295 | buildSettings = {
296 | ALWAYS_SEARCH_USER_PATHS = NO;
297 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
298 | CLANG_ANALYZER_NONNULL = YES;
299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
300 | CLANG_CXX_LIBRARY = "libc++";
301 | CLANG_ENABLE_MODULES = YES;
302 | CLANG_ENABLE_OBJC_ARC = YES;
303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
304 | CLANG_WARN_BOOL_CONVERSION = YES;
305 | CLANG_WARN_COMMA = YES;
306 | CLANG_WARN_CONSTANT_CONVERSION = YES;
307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
310 | CLANG_WARN_EMPTY_BODY = YES;
311 | CLANG_WARN_ENUM_CONVERSION = YES;
312 | CLANG_WARN_INFINITE_RECURSION = YES;
313 | CLANG_WARN_INT_CONVERSION = YES;
314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
319 | CLANG_WARN_STRICT_PROTOTYPES = YES;
320 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
321 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
322 | CLANG_WARN_UNREACHABLE_CODE = YES;
323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
324 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
325 | COPY_PHASE_STRIP = NO;
326 | DEBUG_INFORMATION_FORMAT = dwarf;
327 | ENABLE_STRICT_OBJC_MSGSEND = YES;
328 | ENABLE_TESTABILITY = YES;
329 | GCC_C_LANGUAGE_STANDARD = gnu99;
330 | GCC_DYNAMIC_NO_PIC = NO;
331 | GCC_NO_COMMON_BLOCKS = YES;
332 | GCC_OPTIMIZATION_LEVEL = 0;
333 | GCC_PREPROCESSOR_DEFINITIONS = (
334 | "DEBUG=1",
335 | "$(inherited)",
336 | );
337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
339 | GCC_WARN_UNDECLARED_SELECTOR = YES;
340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
341 | GCC_WARN_UNUSED_FUNCTION = YES;
342 | GCC_WARN_UNUSED_VARIABLE = YES;
343 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
344 | MTL_ENABLE_DEBUG_INFO = YES;
345 | ONLY_ACTIVE_ARCH = YES;
346 | SDKROOT = iphoneos;
347 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
348 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
349 | };
350 | name = Debug;
351 | };
352 | C77287701DEABECC005154DE /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | buildSettings = {
355 | ALWAYS_SEARCH_USER_PATHS = NO;
356 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
357 | CLANG_ANALYZER_NONNULL = YES;
358 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
359 | CLANG_CXX_LIBRARY = "libc++";
360 | CLANG_ENABLE_MODULES = YES;
361 | CLANG_ENABLE_OBJC_ARC = YES;
362 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
363 | CLANG_WARN_BOOL_CONVERSION = YES;
364 | CLANG_WARN_COMMA = YES;
365 | CLANG_WARN_CONSTANT_CONVERSION = YES;
366 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
367 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
368 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
369 | CLANG_WARN_EMPTY_BODY = YES;
370 | CLANG_WARN_ENUM_CONVERSION = YES;
371 | CLANG_WARN_INFINITE_RECURSION = YES;
372 | CLANG_WARN_INT_CONVERSION = YES;
373 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
374 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
378 | CLANG_WARN_STRICT_PROTOTYPES = YES;
379 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
380 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
381 | CLANG_WARN_UNREACHABLE_CODE = YES;
382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
384 | COPY_PHASE_STRIP = NO;
385 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
386 | ENABLE_NS_ASSERTIONS = NO;
387 | ENABLE_STRICT_OBJC_MSGSEND = YES;
388 | GCC_C_LANGUAGE_STANDARD = gnu99;
389 | GCC_NO_COMMON_BLOCKS = YES;
390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
392 | GCC_WARN_UNDECLARED_SELECTOR = YES;
393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
394 | GCC_WARN_UNUSED_FUNCTION = YES;
395 | GCC_WARN_UNUSED_VARIABLE = YES;
396 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
397 | MTL_ENABLE_DEBUG_INFO = NO;
398 | SDKROOT = iphoneos;
399 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
400 | VALIDATE_PRODUCT = YES;
401 | };
402 | name = Release;
403 | };
404 | C77287721DEABECC005154DE /* Debug */ = {
405 | isa = XCBuildConfiguration;
406 | buildSettings = {
407 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
409 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
410 | CODE_SIGN_STYLE = Automatic;
411 | DEVELOPMENT_TEAM = MX36A76L68;
412 | INFOPLIST_FILE = GridViewExample/Info.plist;
413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
414 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.GridViewExample;
415 | PRODUCT_NAME = "$(TARGET_NAME)";
416 | PROVISIONING_PROFILE_SPECIFIER = "";
417 | SWIFT_VERSION = 5.0;
418 | };
419 | name = Debug;
420 | };
421 | C77287731DEABECC005154DE /* Release */ = {
422 | isa = XCBuildConfiguration;
423 | buildSettings = {
424 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
427 | CODE_SIGN_STYLE = Automatic;
428 | DEVELOPMENT_TEAM = MX36A76L68;
429 | INFOPLIST_FILE = GridViewExample/Info.plist;
430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
431 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.GridViewExample;
432 | PRODUCT_NAME = "$(TARGET_NAME)";
433 | PROVISIONING_PROFILE_SPECIFIER = "";
434 | SWIFT_VERSION = 5.0;
435 | };
436 | name = Release;
437 | };
438 | /* End XCBuildConfiguration section */
439 |
440 | /* Begin XCConfigurationList section */
441 | C772875A1DEABECC005154DE /* Build configuration list for PBXProject "GridViewExample" */ = {
442 | isa = XCConfigurationList;
443 | buildConfigurations = (
444 | C772876F1DEABECC005154DE /* Debug */,
445 | C77287701DEABECC005154DE /* Release */,
446 | );
447 | defaultConfigurationIsVisible = 0;
448 | defaultConfigurationName = Release;
449 | };
450 | C77287711DEABECC005154DE /* Build configuration list for PBXNativeTarget "GridViewExample" */ = {
451 | isa = XCConfigurationList;
452 | buildConfigurations = (
453 | C77287721DEABECC005154DE /* Debug */,
454 | C77287731DEABECC005154DE /* Release */,
455 | );
456 | defaultConfigurationIsVisible = 0;
457 | defaultConfigurationName = Release;
458 | };
459 | /* End XCConfigurationList section */
460 | };
461 | rootObject = C77287571DEABECC005154DE /* Project object */;
462 | }
463 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2016/10/30.
6 | // Copyright © 2016年 Kyohei Ito. 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: [UIApplication.LaunchOptionsKey: Any]?) -> 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 invalidate graphics rendering callbacks. 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 active 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 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/Base.lproj/LaunchScreen.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 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/ChannelListGridViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChannelListGridViewCell.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/11.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import GridView
10 |
11 | class ChannelListGridViewCell: GridViewCell {
12 | @IBOutlet weak var channelLabel: UILabel!
13 |
14 | static var nib: UINib {
15 | return UINib(nibName: "ChannelListGridViewCell", bundle: Bundle(for: self))
16 | }
17 |
18 | override func awakeFromNib() {
19 | super.awakeFromNib()
20 |
21 | channelLabel.font = .boldSystemFont(ofSize: 16)
22 | channelLabel.textColor = .lightGray
23 | channelLabel.textAlignment = .center
24 | }
25 |
26 | func configure(_ channelName: String) {
27 | channelLabel.text = channelName
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/ChannelListGridViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/DateTimeGridViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateTimeGridViewCell.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/11.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import GridView
11 |
12 | class DateTimeGridViewCell: GridViewCell {
13 | @IBOutlet weak var timeLabel: UILabel!
14 | @IBOutlet weak var borderView: UIView!
15 |
16 | static var nib: UINib {
17 | return UINib(nibName: "DateTimeGridViewCell", bundle: Bundle(for: self))
18 | }
19 |
20 | override func awakeFromNib() {
21 | super.awakeFromNib()
22 |
23 | timeLabel.font = .boldSystemFont(ofSize: 14)
24 | timeLabel.textColor = .white
25 | timeLabel.numberOfLines = 1
26 | timeLabel.textAlignment = .center
27 |
28 | borderView.backgroundColor = .white
29 | }
30 |
31 | func configure(_ hour: Int) {
32 | timeLabel.text = "\(hour)"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/DateTimeGridViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/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 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/PageGridViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageGridViewCell.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/12.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import GridView
11 |
12 | class PageGridViewCell: GridViewCell {
13 | @IBOutlet weak var label: UILabel!
14 |
15 | static var nib: UINib {
16 | return UINib(nibName: "PageGridViewCell", bundle: Bundle(for: self))
17 | }
18 |
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 |
22 | layer.borderColor = UIColor.gray.cgColor
23 | layer.borderWidth = 2
24 |
25 | label.font = .boldSystemFont(ofSize: 36)
26 | label.textColor = UIColor.lightGray
27 | label.textAlignment = .center
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/PageGridViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/PageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageViewController.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/12.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import GridView
11 |
12 | class PageViewController: UIViewController {
13 | @IBOutlet weak var gridView: GridView!
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | gridView.register(PageGridViewCell.nib, forCellWithReuseIdentifier: "PageGridViewCell")
19 | gridView.dataSource = self
20 | gridView.delegate = self
21 | gridView.isPagingEnabled = true
22 | gridView.isDirectionalLockEnabled = true
23 | gridView.minimumScale = Scale(x: 1/3, y: 1/3)
24 | }
25 |
26 | override func viewDidLayoutSubviews() {
27 | super.viewDidLayoutSubviews()
28 |
29 | let frame = gridView.frame
30 | gridView.contentInset = UIEdgeInsets(top: frame.minY, left: frame.minX, bottom: view.bounds.height - frame.maxY, right: view.bounds.width - frame.maxX)
31 | gridView.scrollIndicatorInsets = gridView.contentInset
32 | }
33 |
34 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
35 | super.viewWillTransition(to: size, with: coordinator)
36 |
37 | coordinator.animate(alongsideTransition: { _ in
38 | self.gridView.invalidateContentSize()
39 | self.view.layoutIfNeeded()
40 | self.adjustStateCells()
41 | })
42 | }
43 |
44 | func adjustStateCells() {
45 | gridView.visibleCells().forEach { (cell: PageGridViewCell) in
46 | let cellFrame = cell.convert(cell.bounds, to: view)
47 | let centerX = view.frame.midX - cellFrame.midX
48 | let centerY = view.frame.midY - cellFrame.midY
49 | let distanceRatio = 1 - min(1, abs(centerX / view.bounds.width) + abs(centerY / view.bounds.height))
50 |
51 | let color = 0.5 * distanceRatio
52 | cell.backgroundColor = UIColor(red: color, green: 0, blue: 0.5 - color, alpha: 1)
53 | }
54 | }
55 | }
56 |
57 | extension PageViewController: GridViewDataSource, GridViewDelegate {
58 | func numberOfColumns(in gridView: GridView) -> Int {
59 | return 30
60 | }
61 |
62 | func gridView(_ gridView: GridView, numberOfRowsInColumn column: Int) -> Int {
63 | return 30
64 | }
65 |
66 | func gridView(_ gridView: GridView, cellForRowAt indexPath: IndexPath) -> GridViewCell {
67 | let cell = gridView.dequeueReusableCell(withReuseIdentifier: "PageGridViewCell", for: indexPath)
68 | if let cell = cell as? PageGridViewCell {
69 | cell.label.text = "\(indexPath.column) - \(indexPath.row)"
70 | }
71 |
72 | return cell
73 | }
74 |
75 | func gridView(_ gridView: GridView, didSelectRowAt indexPath: IndexPath) {
76 | gridView.scrollToRow(at: indexPath, at: [.topFit, .leftFit], animated: true)
77 | }
78 |
79 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
80 | adjustStateCells()
81 | }
82 |
83 | func gridView(_ gridView: GridView, didScaleAt scale: CGFloat) {
84 | adjustStateCells()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/Slot.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slot.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/11.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Slot {
12 | let minutes: Int
13 | let startAt: Int
14 | let title: String
15 | let detail: String
16 | }
17 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/TimeTableGridViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeTableGridViewCell.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2017/05/11.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import GridView
10 |
11 | class TimeTableGridViewCell: GridViewCell {
12 | @IBOutlet weak var timeLabel: UILabel!
13 | @IBOutlet weak var titleLabel: UILabel!
14 | @IBOutlet weak var detailLabel: UILabel!
15 |
16 | static var nib: UINib {
17 | return UINib(nibName: "TimeTableGridViewCell", bundle: Bundle(for: self))
18 | }
19 |
20 | override func prepareForReuse() {
21 | super.prepareForReuse()
22 |
23 | timeLabel.text = nil
24 | titleLabel.text = nil
25 | detailLabel.text = nil
26 | }
27 |
28 | override func awakeFromNib() {
29 | super.awakeFromNib()
30 |
31 | clipsToBounds = true
32 | layer.borderColor = UIColor.gray.cgColor
33 | layer.borderWidth = 1 / UIScreen.main.scale
34 |
35 | timeLabel.font = .boldSystemFont(ofSize: 10)
36 | timeLabel.textAlignment = .center
37 |
38 | titleLabel.font = .boldSystemFont(ofSize: 10)
39 | titleLabel.textColor = UIColor.black
40 | titleLabel.numberOfLines = 4
41 | titleLabel.textAlignment = .left
42 |
43 | detailLabel.font = .systemFont(ofSize: 10)
44 | detailLabel.textColor = UIColor.darkGray
45 | detailLabel.textAlignment = .left
46 | }
47 |
48 | func configure(_ slot: Slot) {
49 | timeLabel.text = String(format: "%02d", slot.startAt % 60)
50 | titleLabel.text = slot.title
51 | detailLabel.text = slot.detail
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/TimeTableGridViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/GridViewExample/GridViewExample/TimeTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // GridViewExample
4 | //
5 | // Created by Kyohei Ito on 2016/10/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import GridView
11 |
12 | extension UIColor {
13 | fileprivate convenience init(hex: Int, alpha: CGFloat = 1) {
14 | let red = CGFloat((hex & 0xFF0000) >> 16) / 255
15 | let green = CGFloat((hex & 0x00FF00) >> 8 ) / 255
16 | let blue = CGFloat((hex & 0x0000FF) >> 0 ) / 255
17 |
18 | self.init(red: red, green: green, blue: blue, alpha: alpha)
19 | }
20 | }
21 |
22 | class TimeTableViewController: UIViewController {
23 | @IBOutlet weak var timeTableView: GridView!
24 | @IBOutlet weak var channelListView: GridView!
25 | @IBOutlet weak var dateTimeView: GridView!
26 |
27 | private let channels: [String] = ["News", "Anime", "Drama", "MTV", "Music", "Pets", "Documentary", "Soccer", "Cooking", "Gourmet", "Extreme", "Esports"]
28 |
29 | fileprivate lazy var slotList: [[Slot]] = {
30 | let detailText = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
31 | let minutesOfDay = 24 * 60
32 | let frames = [15, 15, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 75, 75, 90, 90]
33 | return self.channels.enumerated().map { index, channel in
34 | var slots: [Slot] = []
35 | var totalMinutes = 0
36 | while totalMinutes < minutesOfDay {
37 | var minutes = frames[Int(arc4random_uniform(UInt32(frames.count)))]
38 | let startAt = totalMinutes + minutes
39 | minutes -= max(startAt - minutesOfDay, 0)
40 | let slot = Slot(minutes: minutes, startAt: totalMinutes, title: "\(channel)'s slot", detail: detailText)
41 | totalMinutes = startAt
42 | slots.append(slot)
43 | }
44 | return slots
45 | }
46 | }()
47 |
48 | private lazy var channelListDataSource: ChannelListDataSource = .init(channels: self.channels)
49 | private let dateTimeDataSource = DateTimeGridViewDataSource()
50 |
51 | override func viewDidLoad() {
52 | super.viewDidLoad()
53 |
54 | view.backgroundColor = .black
55 | view.layoutIfNeeded()
56 |
57 | timeTableView.register(TimeTableGridViewCell.nib, forCellWithReuseIdentifier: "TimeTableGridViewCell")
58 | channelListView.register(ChannelListGridViewCell.nib, forCellWithReuseIdentifier: "ChannelListGridViewCell")
59 | dateTimeView.register(DateTimeGridViewCell.nib, forCellWithReuseIdentifier: "DateTimeGridViewCell")
60 |
61 | // timeTableView.layoutWithoutFillForCell = true
62 | timeTableView.superview?.clipsToBounds = true
63 | timeTableView.contentInset.top = channelListView.bounds.height
64 | timeTableView.minimumScale = Scale(x: 0.5, y: 0.5)
65 | timeTableView.maximumScale = Scale(x: 1.5, y: 1.5)
66 | timeTableView.scrollIndicatorInsets.top = timeTableView.contentInset.top
67 | timeTableView.scrollIndicatorInsets.left = dateTimeView.bounds.width
68 | timeTableView.dataSource = self
69 | timeTableView.delegate = self
70 | timeTableView.reloadData()
71 |
72 | // channelListView.layoutWithoutFillForCell = true
73 | channelListView.superview?.backgroundColor = .black
74 | channelListView.superview?.isUserInteractionEnabled = false
75 | channelListView.minimumScale.x = timeTableView.minimumScale.x
76 | channelListView.maximumScale.x = timeTableView.maximumScale.x
77 | channelListView.dataSource = channelListDataSource
78 | channelListView.delegate = channelListDataSource
79 | channelListView.reloadData()
80 |
81 | dateTimeView.superview?.clipsToBounds = true
82 | dateTimeView.superview?.backgroundColor = UIColor(hex: 0x6FB900)
83 | dateTimeView.superview?.isUserInteractionEnabled = false
84 | dateTimeView.contentInset.top = channelListView.bounds.height
85 | dateTimeView.minimumScale.y = timeTableView.minimumScale.y
86 | dateTimeView.maximumScale.y = timeTableView.maximumScale.y
87 | dateTimeView.isInfinitable = false
88 | dateTimeView.dataSource = dateTimeDataSource
89 | dateTimeView.delegate = dateTimeDataSource
90 | dateTimeView.reloadData()
91 | }
92 |
93 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
94 | super.viewWillTransition(to: size, with: coordinator)
95 |
96 | coordinator.animate(alongsideTransition: { _ in
97 | self.timeTableView.invalidateContentSize()
98 | self.channelListView.invalidateContentSize()
99 | self.view.layoutIfNeeded()
100 | })
101 | }
102 | }
103 |
104 | extension TimeTableViewController: GridViewDataSource, GridViewDelegate {
105 | func numberOfColumns(in gridView: GridView) -> Int {
106 | return slotList.count
107 | }
108 |
109 | func gridView(_ gridView: GridView, numberOfRowsInColumn column: Int) -> Int {
110 | return slotList[column].count
111 | }
112 |
113 | func gridView(_ gridView: GridView, heightForRowAt indexPath: IndexPath) -> CGFloat {
114 | return CGFloat(slotList[indexPath.column][indexPath.row].minutes * 2)
115 | }
116 |
117 | func gridView(_ gridView: GridView, cellForRowAt indexPath: IndexPath) -> GridViewCell {
118 | let cell = gridView.dequeueReusableCell(withReuseIdentifier: "TimeTableGridViewCell", for: indexPath)
119 | if let cell = cell as? TimeTableGridViewCell {
120 | cell.configure(slotList[indexPath.column][indexPath.row])
121 | }
122 |
123 | return cell
124 | }
125 |
126 | func gridView(_ gridView: GridView, didScaleAt scale: CGFloat) {
127 | channelListView.contentScale(scale)
128 | dateTimeView.contentScale(scale)
129 | }
130 |
131 | func gridView(_ gridView: GridView, didSelectRowAt indexPath: IndexPath) {
132 | gridView.deselectRow(at: indexPath)
133 | gridView.scrollToRow(at: indexPath, at: [.topFit, .centeredHorizontally], animated: true)
134 | }
135 |
136 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
137 | channelListView.contentOffset.x = scrollView.contentOffset.x
138 | dateTimeView.contentOffset.y = scrollView.contentOffset.y
139 | }
140 | }
141 |
142 | final class DateTimeGridViewDataSource: NSObject, GridViewDataSource, GridViewDelegate {
143 | func gridView(_ gridView: GridView, numberOfRowsInColumn column: Int) -> Int {
144 | return 24
145 | }
146 |
147 | func gridView(_ gridView: GridView, heightForRowAt indexPath: IndexPath) -> CGFloat {
148 | return 60 * 2
149 | }
150 |
151 | func gridView(_ gridView: GridView, cellForRowAt indexPath: IndexPath) -> GridViewCell {
152 | let cell = gridView.dequeueReusableCell(withReuseIdentifier: "DateTimeGridViewCell", for: indexPath)
153 | if let cell = cell as? DateTimeGridViewCell {
154 | cell.configure(indexPath.row)
155 | }
156 |
157 | return cell
158 | }
159 | }
160 |
161 | final class ChannelListDataSource: NSObject, GridViewDataSource, GridViewDelegate {
162 | let channels: [String]
163 |
164 | init(channels: [String]) {
165 | self.channels = channels
166 | }
167 |
168 | func numberOfColumns(in gridView: GridView) -> Int {
169 | return channels.count
170 | }
171 |
172 | func gridView(_ gridView: GridView, numberOfRowsInColumn column: Int) -> Int {
173 | return 1
174 | }
175 |
176 | func gridView(_ gridView: GridView, cellForRowAt indexPath: IndexPath) -> GridViewCell {
177 | let cell = gridView.dequeueReusableCell(withReuseIdentifier: "ChannelListGridViewCell", for: indexPath)
178 | if let cell = cell as? ChannelListGridViewCell {
179 | cell.configure(channels[indexPath.column])
180 | }
181 |
182 | return cell
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/GridViewTests/AroundInsetsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AroundInsetsTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class AroundInsetsTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let parentSize = CGSize(width: 300, height: 300)
26 | let inset1 = AroundInsets(parentSize: parentSize, frame: CGRect(origin: CGPoint(x: 135, y: 135), size: CGSize(width: 30, height: 30)))
27 |
28 | XCTAssertEqual(inset1.left.width.truncatingRemainder(dividingBy: 30), 0)
29 | XCTAssertEqual(inset1.right.width.truncatingRemainder(dividingBy: 30), 0)
30 | XCTAssertEqual(inset1.left.width / 30, 5)
31 | XCTAssertEqual(inset1.right.width / 30, 5)
32 |
33 | let inset2 = AroundInsets(parentSize: parentSize, frame: CGRect(origin: CGPoint(x: 100, y: 100), size: CGSize(width: 100, height: 100)))
34 |
35 | XCTAssertEqual(inset2.left.width.truncatingRemainder(dividingBy: 100), 0)
36 | XCTAssertEqual(inset2.right.width.truncatingRemainder(dividingBy: 100), 0)
37 | XCTAssertEqual(inset2.left.width / 100, 1)
38 | XCTAssertEqual(inset2.right.width / 100, 1)
39 | }
40 |
41 | func testZero() {
42 | XCTAssertEqual(AroundInsets.zero.left.width, 0)
43 | XCTAssertEqual(AroundInsets.zero.right.width, 0)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/GridViewTests/ArrayExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class ArrayExtensionTests: XCTestCase {
13 | let array1 = [0,1,2,3,4,5,6,7,8,9]
14 | let array2 = [0,2,4,6,8]
15 | let array3 = [1,3,5,7,9]
16 |
17 | override func setUp() {
18 | super.setUp()
19 | // Put setup code here. This method is called before the invocation of each test method in the class.
20 | }
21 |
22 | override func tearDown() {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | super.tearDown()
25 | }
26 |
27 | func testUnion() {
28 | XCTAssertEqual(array1.union(array1), [0,1,2,3,4,5,6,7,8,9])
29 | XCTAssertEqual(array1.union(array2), [0,1,2,3,4,5,6,7,8,9])
30 | XCTAssertEqual(array1.union(array3), [0,1,2,3,4,5,6,7,8,9])
31 |
32 | XCTAssertEqual(array2.union(array1), [0,2,4,6,8,1,3,5,7,9])
33 | XCTAssertEqual(array2.union(array2), [0,2,4,6,8])
34 | XCTAssertEqual(array2.union(array3), [0,2,4,6,8,1,3,5,7,9])
35 |
36 | XCTAssertEqual(array3.union(array1), [1,3,5,7,9,0,2,4,6,8])
37 | XCTAssertEqual(array3.union(array2), [1,3,5,7,9,0,2,4,6,8])
38 | XCTAssertEqual(array3.union(array3), [1,3,5,7,9])
39 | }
40 |
41 | func testSubtracting() {
42 | XCTAssertEqual(array1.subtracting(array1), [])
43 | XCTAssertEqual(array1.subtracting(array2), [1,3,5,7,9])
44 | XCTAssertEqual(array1.subtracting(array3), [0,2,4,6,8])
45 |
46 | XCTAssertEqual(array2.subtracting(array1), [])
47 | XCTAssertEqual(array2.subtracting(array2), [])
48 | XCTAssertEqual(array2.subtracting(array3), [0,2,4,6,8])
49 |
50 | XCTAssertEqual(array3.subtracting(array1), [])
51 | XCTAssertEqual(array3.subtracting(array2), [1,3,5,7,9])
52 | XCTAssertEqual(array3.subtracting(array3), [])
53 | }
54 |
55 | func testIntersection() {
56 | XCTAssertEqual(array1.intersection(array1), [0,1,2,3,4,5,6,7,8,9])
57 | XCTAssertEqual(array1.intersection(array2), [0,2,4,6,8])
58 | XCTAssertEqual(array1.intersection(array3), [1,3,5,7,9])
59 |
60 | XCTAssertEqual(array2.intersection(array1), [0,2,4,6,8])
61 | XCTAssertEqual(array2.intersection(array2), [0,2,4,6,8])
62 | XCTAssertEqual(array2.intersection(array3), [])
63 |
64 | XCTAssertEqual(array3.intersection(array1), [1,3,5,7,9])
65 | XCTAssertEqual(array3.intersection(array2), [])
66 | XCTAssertEqual(array3.intersection(array3), [1,3,5,7,9])
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/GridViewTests/CGFloatExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloatExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/02.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class CGFloatExtensionTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testIntegral() {
25 | CGFloat.debugScale = 2
26 | XCTAssertEqual(CGFloat.debugScale, 2)
27 | XCTAssertEqual(CGFloat(10.0).integral, 10.0)
28 | XCTAssertEqual(CGFloat(10.1).integral, 10.0)
29 | XCTAssertEqual(CGFloat(10.2).integral, 10.0)
30 | XCTAssertEqual(CGFloat(10.3).integral, 10.5)
31 | XCTAssertEqual(CGFloat(10.4).integral, 10.5)
32 | XCTAssertEqual(CGFloat(10.5).integral, 10.5)
33 | XCTAssertEqual(CGFloat(10.6).integral, 10.5)
34 | XCTAssertEqual(CGFloat(10.7).integral, 10.5)
35 | XCTAssertEqual(CGFloat(10.8).integral, 11)
36 | XCTAssertEqual(CGFloat(10.9).integral, 11)
37 | XCTAssertEqual(CGFloat(-10.0).integral, -10.0)
38 | XCTAssertEqual(CGFloat(-10.1).integral, -10.0)
39 | XCTAssertEqual(CGFloat(-10.2).integral, -10.0)
40 | XCTAssertEqual(CGFloat(-10.3).integral, -10.5)
41 | XCTAssertEqual(CGFloat(-10.4).integral, -10.5)
42 | XCTAssertEqual(CGFloat(-10.5).integral, -10.5)
43 | XCTAssertEqual(CGFloat(-10.6).integral, -10.5)
44 | XCTAssertEqual(CGFloat(-10.7).integral, -10.5)
45 | XCTAssertEqual(CGFloat(-10.8).integral, -11)
46 | XCTAssertEqual(CGFloat(-10.9).integral, -11)
47 |
48 | CGFloat.debugScale = 3
49 | XCTAssertEqual(CGFloat.debugScale, 3)
50 | XCTAssertEqual(CGFloat(10.0).integral.rounded(p: 5), 10.0)
51 | XCTAssertEqual(CGFloat(10.1).integral.rounded(p: 5), 10.0)
52 | XCTAssertEqual(CGFloat(10.2).integral.rounded(p: 5), 10.33333)
53 | XCTAssertEqual(CGFloat(10.3).integral.rounded(p: 5), 10.33333)
54 | XCTAssertEqual(CGFloat(10.4).integral.rounded(p: 5), 10.33333)
55 | XCTAssertEqual(CGFloat(10.5).integral.rounded(p: 5), 10.66667)
56 | XCTAssertEqual(CGFloat(10.6).integral.rounded(p: 5), 10.66667)
57 | XCTAssertEqual(CGFloat(10.7).integral.rounded(p: 5), 10.66667)
58 | XCTAssertEqual(CGFloat(10.8).integral.rounded(p: 5), 10.66667)
59 | XCTAssertEqual(CGFloat(10.9).integral.rounded(p: 5), 11)
60 | XCTAssertEqual(CGFloat(-10.0).integral.rounded(p: 5), -10.0)
61 | XCTAssertEqual(CGFloat(-10.1).integral.rounded(p: 5), -10.0)
62 | XCTAssertEqual(CGFloat(-10.2).integral.rounded(p: 5), -10.33333)
63 | XCTAssertEqual(CGFloat(-10.3).integral.rounded(p: 5), -10.33333)
64 | XCTAssertEqual(CGFloat(-10.4).integral.rounded(p: 5), -10.33333)
65 | XCTAssertEqual(CGFloat(-10.5).integral.rounded(p: 5), -10.66667)
66 | XCTAssertEqual(CGFloat(-10.6).integral.rounded(p: 5), -10.66667)
67 | XCTAssertEqual(CGFloat(-10.7).integral.rounded(p: 5), -10.66667)
68 | XCTAssertEqual(CGFloat(-10.8).integral.rounded(p: 5), -10.66667)
69 | XCTAssertEqual(CGFloat(-10.9).integral.rounded(p: 5), -11)
70 | }
71 |
72 | func testRounded() {
73 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 0), 10)
74 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 1), 10)
75 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 2), 10.01)
76 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 3), 10.012)
77 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 4), 10.0123)
78 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 5), 10.01235)
79 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 6), 10.012346)
80 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 7), 10.0123457)
81 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 8), 10.01234568)
82 | XCTAssertEqual(CGFloat(10.0123456789).rounded(p: 9), 10.012345679)
83 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 0), -10)
84 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 1), -10)
85 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 2), -10.01)
86 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 3), -10.012)
87 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 4), -10.0123)
88 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 5), -10.01235)
89 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 6), -10.012346)
90 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 7), -10.0123457)
91 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 8), -10.01234568)
92 | XCTAssertEqual(CGFloat(-10.0123456789).rounded(p: 9), -10.012345679)
93 | }
94 |
95 | func testFloored() {
96 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 0), 10)
97 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 1), 10)
98 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 2), 10.01)
99 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 3), 10.012)
100 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 4), 10.0123)
101 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 5), 10.01234)
102 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 6), 10.012345)
103 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 7), 10.0123456)
104 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 8), 10.01234567)
105 | XCTAssertEqual(CGFloat(10.0123456789).floored(p: 9), 10.012345678)
106 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 0), -11)
107 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 1), -10.1)
108 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 2), -10.02)
109 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 3), -10.013)
110 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 4), -10.0124)
111 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 5), -10.01235)
112 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 6), -10.012346)
113 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 7), -10.0123457)
114 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 8), -10.01234568)
115 | XCTAssertEqual(CGFloat(-10.0123456789).floored(p: 9), -10.012345679)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/GridViewTests/CGRectExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGRectExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class CGRectExtensionTests: XCTestCase {
13 | let horizontal = Horizontal(x: 100, width: 100)
14 | let vertical = Vertical(y: 100, height: 100)
15 |
16 | override func setUp() {
17 | super.setUp()
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 | }
20 |
21 | override func tearDown() {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | super.tearDown()
24 | }
25 |
26 | func testHorizontalInit() {
27 | let rect = CGRect(horizontal: horizontal)
28 |
29 | XCTAssertEqual(rect.origin.x, 100)
30 | XCTAssertEqual(rect.origin.y, 0)
31 | XCTAssertEqual(rect.size.width, 100)
32 | XCTAssertEqual(rect.size.height, 0)
33 | }
34 |
35 | func testVerticalInit() {
36 | let rect = CGRect(vertical: vertical)
37 |
38 | XCTAssertEqual(rect.origin.x, 0)
39 | XCTAssertEqual(rect.origin.y, 100)
40 | XCTAssertEqual(rect.size.width, 0)
41 | XCTAssertEqual(rect.size.height, 100)
42 | }
43 |
44 | func testInit() {
45 | let rect = CGRect(horizontal: horizontal, vertical: vertical)
46 |
47 | XCTAssertEqual(rect.origin.x, 100)
48 | XCTAssertEqual(rect.origin.y, 100)
49 | XCTAssertEqual(rect.size.width, 100)
50 | XCTAssertEqual(rect.size.height, 100)
51 | }
52 |
53 | func testVerticalProperty() {
54 | var rect = CGRect(x: 100, y: 100, width: 100, height: 100)
55 |
56 | XCTAssertEqual(rect.vertical.y, 100)
57 | XCTAssertEqual(rect.vertical.height, 100)
58 | XCTAssertEqual(rect.vertical.maxY, 200)
59 |
60 | rect.vertical = Vertical(y: 200, height: 200)
61 |
62 | XCTAssertEqual(rect.vertical.y, 200)
63 | XCTAssertEqual(rect.vertical.height, 200)
64 | XCTAssertEqual(rect.vertical.maxY, 400)
65 | }
66 |
67 | func testHorizontalProperty() {
68 | var rect = CGRect(x: 100, y: 100, width: 100, height: 100)
69 |
70 | XCTAssertEqual(rect.horizontal.x, 100)
71 | XCTAssertEqual(rect.horizontal.width, 100)
72 | XCTAssertEqual(rect.horizontal.maxX, 200)
73 |
74 | rect.horizontal = Horizontal(x: 200, width: 200)
75 |
76 | XCTAssertEqual(rect.horizontal.x, 200)
77 | XCTAssertEqual(rect.horizontal.width, 200)
78 | XCTAssertEqual(rect.horizontal.maxX, 400)
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/GridViewTests/CGSizeExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSizeExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/29.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class CGSizeExtensionTests: XCTestCase {
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | super.tearDown()
21 | }
22 |
23 | func testOperator() {
24 | let size1 = CGSize(width: 10, height: 20)
25 | let size2 = CGSize(width: -10, height: -20)
26 |
27 | XCTAssertEqual(size1 + size1, CGSize(width: 20, height: 40))
28 | XCTAssertEqual(size1 + size2, .zero)
29 | XCTAssertEqual(size2 + size1, .zero)
30 | XCTAssertEqual(size2 + size2, CGSize(width: -20, height: -40))
31 |
32 | XCTAssertEqual(size1 - size1, .zero)
33 | XCTAssertEqual(size1 - size2, CGSize(width: 20, height: 40))
34 | XCTAssertEqual(size2 - size1, CGSize(width: -20, height: -40))
35 | XCTAssertEqual(size2 - size2, .zero)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/GridViewTests/CountableTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CountableTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class CountableTests: XCTestCase {
13 | let array = [0,1,2,3,4,5,6,7,8,9]
14 |
15 | override func setUp() {
16 | super.setUp()
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | }
19 |
20 | override func tearDown() {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | super.tearDown()
23 | }
24 |
25 | func testRepeat() {
26 | XCTAssertEqual(array.repeat(5), 5)
27 | XCTAssertEqual(array.repeat(13), 3)
28 | XCTAssertEqual(array.repeat(20), 0)
29 | XCTAssertEqual(array.repeat(35), 5)
30 | XCTAssertEqual(array.repeat(-3), 7)
31 | XCTAssertEqual(array.repeat(-5), 5)
32 | XCTAssertEqual(array.repeat(-13), 7)
33 | XCTAssertEqual(array.repeat(-20), 0)
34 | XCTAssertEqual(array.repeat(-35), 5)
35 | }
36 |
37 | func testThreshold() {
38 | XCTAssertEqual(array.threshold(with: 5), .in)
39 | XCTAssertEqual(array.threshold(with: 13), .above)
40 | XCTAssertEqual(array.threshold(with: 20), .above)
41 | XCTAssertEqual(array.threshold(with: 35), .above)
42 | XCTAssertEqual(array.threshold(with: -5), .below)
43 | XCTAssertEqual(array.threshold(with: -13), .below)
44 | XCTAssertEqual(array.threshold(with: -20), .below)
45 | XCTAssertEqual(array.threshold(with: -35), .below)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/GridViewTests/GridViewCellTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridViewCellTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class GridViewCellTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let cell1 = MockCell.nib.instantiate(withOwner: nil, options: nil).first as? GridViewCell
26 | let cell2 = GridViewCell(frame: .zero)
27 |
28 | XCTAssertEqual(cell1?.autoresizingMask, UIView.AutoresizingMask(rawValue: 0))
29 | XCTAssertEqual(cell2.autoresizingMask, UIView.AutoresizingMask(rawValue: 0))
30 |
31 | XCTAssertNotNil(GridViewCell().prepareForReuse())
32 | XCTAssertNotNil(GridViewCell().setSelected(true))
33 | XCTAssertNotNil(GridViewCell().setSelected(false))
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/GridViewTests/GridViewScrollPositionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridViewScrollPositionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class GridViewScrollPositionTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let p1 = GridViewScrollPosition(rawValue: 1)
26 | XCTAssertEqual(p1.rawValue, 1)
27 | XCTAssertEqual(p1, .top)
28 |
29 | let p2 = GridViewScrollPosition(rawValue: 2)
30 | XCTAssertEqual(p2.rawValue, 2)
31 | XCTAssertEqual(p2, .centeredVertically)
32 |
33 | let p3 = GridViewScrollPosition(rawValue: 3)
34 | XCTAssertEqual(p3.rawValue, 3)
35 | XCTAssertEqual(p3, [.top, .centeredVertically])
36 |
37 | let p4 = GridViewScrollPosition(rawValue: 4)
38 | XCTAssertEqual(p4.rawValue, 4)
39 | XCTAssertEqual(p4, .bottom)
40 |
41 | let p5 = GridViewScrollPosition(rawValue: 5)
42 | XCTAssertEqual(p5.rawValue, 5)
43 | XCTAssertEqual(p5, [.bottom, .top])
44 | }
45 |
46 | func testContains() {
47 | let positions: [GridViewScrollPosition] = [.top, .centeredVertically, .bottom, .left, .centeredHorizontally, .right, .topFit, .bottomFit, .rightFit, .leftFit]
48 |
49 | XCTAssertTrue(positions.contains(GridViewScrollPosition.top))
50 | XCTAssertTrue(positions.contains(GridViewScrollPosition.centeredVertically))
51 | XCTAssertTrue(positions.contains(GridViewScrollPosition.bottom))
52 | XCTAssertTrue(positions.contains(GridViewScrollPosition.left))
53 | XCTAssertTrue(positions.contains(GridViewScrollPosition.centeredHorizontally))
54 | XCTAssertTrue(positions.contains(GridViewScrollPosition.right))
55 | XCTAssertTrue(positions.contains(GridViewScrollPosition.topFit))
56 | XCTAssertTrue(positions.contains(GridViewScrollPosition.bottomFit))
57 | XCTAssertTrue(positions.contains(GridViewScrollPosition.rightFit))
58 | XCTAssertTrue(positions.contains(GridViewScrollPosition.leftFit))
59 | XCTAssertFalse(positions.contains(GridViewScrollPosition(rawValue: 1 << 10)))
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/GridViewTests/GridViewTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridViewTests.swift
3 | // GridViewTests
4 | //
5 | // Created by Kyohei Ito on 2016/11/27.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class GridViewTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/GridViewTests/HorizontalTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class HorizontalTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let horizontal = Horizontal(x: 100, width: 100)
26 |
27 | XCTAssertEqual(horizontal.x, 100)
28 | XCTAssertEqual(horizontal.width, 100)
29 | XCTAssertEqual(horizontal.maxX, 200)
30 | }
31 |
32 | func testZero() {
33 | XCTAssertEqual(Horizontal.zero.x, 0)
34 | XCTAssertEqual(Horizontal.zero.width, 0)
35 | XCTAssertEqual(Horizontal.zero.maxX, 0)
36 | }
37 |
38 | func testMaxX() {
39 | var horizontal = Horizontal(x: 100, width: 100)
40 | XCTAssertEqual(horizontal.maxX, 200)
41 |
42 | horizontal.x = 200
43 | XCTAssertEqual(horizontal.maxX, 300)
44 |
45 | horizontal.width = 200
46 | XCTAssertEqual(horizontal.maxX, 400)
47 | }
48 |
49 | func testOperator() {
50 | let horizontal = Horizontal(x: 100, width: 100) * 10
51 |
52 | XCTAssertEqual(horizontal.x, 1000)
53 | XCTAssertEqual(horizontal.width, 1000)
54 | XCTAssertEqual(horizontal.maxX, 2000)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/GridViewTests/IndexPathTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexPathTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/21.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class IndexPathTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | XCTAssertEqual(IndexPath(row: 0, column: 0), IndexPath(row: 0, section: 0))
26 | XCTAssertEqual(IndexPath(row: 2, column: 2), IndexPath(row: 2, section: 2))
27 | XCTAssertEqual(IndexPath(row: 5, column: 5), IndexPath(row: 5, section: 5))
28 | XCTAssertEqual(IndexPath(row: 7, column: 7), IndexPath(row: 7, section: 7))
29 | XCTAssertEqual(IndexPath(row: 10, column: 10), IndexPath(row: 10, section: 10))
30 | }
31 |
32 | func testColumn() {
33 | var indexPath = IndexPath(row: 0, section: 0)
34 |
35 | XCTAssertEqual(indexPath.section, 0)
36 | XCTAssertEqual(indexPath.column, 0)
37 |
38 | indexPath.section = 2
39 | XCTAssertEqual(indexPath.section, 2)
40 | XCTAssertEqual(indexPath.column, 2)
41 |
42 | indexPath.column = 5
43 | XCTAssertEqual(indexPath.section, 5)
44 | XCTAssertEqual(indexPath.column, 5)
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/GridViewTests/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 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/GridViewTests/LocationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class LocationTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testOperator() {
25 | XCTAssertTrue(MockLocation(x: 10, y: 10) == MockLocation(x: 10, y: 10))
26 | XCTAssertFalse(MockLocation(x: 10, y: 10) == MockLocation(x: 20, y: 10))
27 | XCTAssertFalse(MockLocation(x: 10, y: 10) == MockLocation(x: 10, y: 20))
28 | XCTAssertFalse(MockLocation(x: 10, y: 10) == MockLocation(x: 20, y: 20))
29 |
30 | let l1 = MockLocation(x: 10, y: 10) + MockLocation(x: 10, y: 10)
31 | XCTAssertEqual(l1.x, 20)
32 | XCTAssertEqual(l1.y, 20)
33 |
34 | let l2 = MockLocation(x: 10, y: 10) - MockLocation(x: 10, y: 10)
35 | XCTAssertEqual(l2.x, 0)
36 | XCTAssertEqual(l2.y, 0)
37 |
38 | let l3 = MockLocation(x: 10, y: 10) * MockLocation(x: 10, y: 10)
39 | XCTAssertEqual(l3.x, 100)
40 | XCTAssertEqual(l3.y, 100)
41 |
42 | let l4 = MockLocation(x: 10, y: 10) / MockLocation(x: 10, y: 10)
43 | XCTAssertEqual(l4.x, 1)
44 | XCTAssertEqual(l4.y, 1)
45 |
46 | let l5 = MockLocation(x: 10, y: 10) + 10
47 | XCTAssertEqual(l5.x, 20)
48 | XCTAssertEqual(l5.y, 20)
49 |
50 | let l6 = MockLocation(x: 10, y: 10) - 10
51 | XCTAssertEqual(l6.x, 0)
52 | XCTAssertEqual(l6.y, 0)
53 | }
54 |
55 | func testMin() {
56 | XCTAssertEqual(MockLocation(x: 10, y: 10).min(), 10)
57 | XCTAssertEqual(MockLocation(x: 1, y: 10).min(), 1)
58 | XCTAssertEqual(MockLocation(x: 10, y: 1).min(), 1)
59 |
60 | XCTAssertEqual(min(MockLocation(x: 10, y: 10), MockLocation(x: 10, y: 1), MockLocation(x: 1, y: 10)), MockLocation(x: 1, y: 1))
61 | XCTAssertEqual(min(MockLocation(x: 10, y: 10), MockLocation(x: 10, y: 1), MockLocation(x: 10, y: 5)), MockLocation(x: 10, y: 1))
62 | XCTAssertEqual(min(MockLocation(x: 10, y: 10), MockLocation(x: 1, y: 10), MockLocation(x: 5, y: 10)), MockLocation(x: 1, y: 10))
63 | XCTAssertEqual(min(MockLocation(x: 1, y: 1), MockLocation(x: 5, y: 5), MockLocation(x: 10, y: 10)), MockLocation(x: 1, y: 1))
64 | XCTAssertEqual(min(MockLocation(x: 10, y: 10)), MockLocation(x: 10, y: 10))
65 | }
66 |
67 | func testMax() {
68 | XCTAssertEqual(MockLocation(x: 10, y: 10).max(), 10)
69 | XCTAssertEqual(MockLocation(x: 1, y: 10).max(), 10)
70 | XCTAssertEqual(MockLocation(x: 10, y: 1).max(), 10)
71 |
72 | XCTAssertEqual(max(MockLocation(x: 10, y: 10), MockLocation(x: 10, y: 1), MockLocation(x: 1, y: 10)), MockLocation(x: 10, y: 10))
73 | XCTAssertEqual(max(MockLocation(x: 1, y: 1), MockLocation(x: 10, y: 1), MockLocation(x: 5, y: 1)), MockLocation(x: 10, y: 1))
74 | XCTAssertEqual(max(MockLocation(x: 1, y: 1), MockLocation(x: 1, y: 10), MockLocation(x: 1, y: 5)), MockLocation(x: 1, y: 10))
75 | XCTAssertEqual(max(MockLocation(x: 10, y: 10), MockLocation(x: 5, y: 5), MockLocation(x: 1, y: 1)), MockLocation(x: 10, y: 10))
76 | XCTAssertEqual(max(MockLocation(x: 10, y: 10)), MockLocation(x: 10, y: 10))
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/GridViewTests/Mock/MockCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockCell.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | @testable import GridView
11 |
12 | class MockCell: GridViewCell {
13 | static var nib: UINib {
14 | return UINib(nibName: "MockCell", bundle: Bundle(for: self))
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/GridViewTests/Mock/MockCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/GridViewTests/Mock/MockLocation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockLocation.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | @testable import GridView
11 |
12 | struct MockLocation: Location {
13 | public var x: CGFloat
14 | public var y: CGFloat
15 |
16 | public init(x: CGFloat, y: CGFloat) {
17 | self.x = x
18 | self.y = y
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GridViewTests/Mock/MockReusable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockReusable.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | @testable import GridView
11 |
12 | class MockReusable: Reusable {
13 | var canReuse = true
14 | }
15 |
--------------------------------------------------------------------------------
/GridViewTests/NeedsLayoutTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NeedsLayoutTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class NeedsLayoutTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testNeedsLayoutEquatable() {
25 | let none = NeedsLayout.none
26 | let reload = NeedsLayout.reload
27 | let layout = NeedsLayout.layout(.all(ViewMatrix()))
28 |
29 | XCTAssertTrue(none == none)
30 | XCTAssertTrue(reload == reload)
31 | XCTAssertTrue(layout == layout)
32 |
33 | XCTAssertFalse(none == reload)
34 | XCTAssertFalse(none == layout)
35 | XCTAssertFalse(reload == none)
36 | XCTAssertFalse(reload == layout)
37 | XCTAssertFalse(layout == none)
38 | XCTAssertFalse(layout == reload)
39 | }
40 |
41 | func testNeedsLayoutComparable() {
42 | let none = NeedsLayout.none
43 | let layout = NeedsLayout.layout(.all(ViewMatrix()))
44 | let reload = NeedsLayout.reload
45 |
46 | XCTAssertFalse(none < none)
47 | XCTAssertTrue(none < reload)
48 | XCTAssertTrue(none < layout)
49 |
50 | XCTAssertFalse(layout < none)
51 | XCTAssertTrue(layout < reload)
52 | XCTAssertFalse(layout < layout)
53 |
54 | XCTAssertFalse(reload < none)
55 | XCTAssertFalse(reload < reload)
56 | XCTAssertFalse(reload < layout)
57 | }
58 |
59 | func testLayoutTypeEquatable() {
60 | let matrix = ViewMatrix()
61 | let all = NeedsLayout.LayoutType.all(matrix)
62 | let horizontally = NeedsLayout.LayoutType.horizontally(matrix)
63 | let rotating = NeedsLayout.LayoutType.rotating(matrix)
64 | let scaling = NeedsLayout.LayoutType.scaling(matrix)
65 | let pinching = NeedsLayout.LayoutType.pinching(matrix)
66 |
67 | XCTAssertTrue(all == all)
68 | XCTAssertTrue(horizontally == horizontally)
69 | XCTAssertTrue(rotating == rotating)
70 | XCTAssertTrue(scaling == scaling)
71 | XCTAssertTrue(pinching == pinching)
72 |
73 | XCTAssertFalse(all == horizontally)
74 | XCTAssertFalse(all == rotating)
75 | XCTAssertFalse(all == scaling)
76 | XCTAssertFalse(all == pinching)
77 | XCTAssertFalse(horizontally == all)
78 | XCTAssertFalse(horizontally == rotating)
79 | XCTAssertFalse(horizontally == scaling)
80 | XCTAssertFalse(horizontally == pinching)
81 | XCTAssertFalse(rotating == all)
82 | XCTAssertFalse(rotating == horizontally)
83 | XCTAssertFalse(rotating == scaling)
84 | XCTAssertFalse(rotating == pinching)
85 | XCTAssertFalse(scaling == all)
86 | XCTAssertFalse(scaling == horizontally)
87 | XCTAssertFalse(scaling == rotating)
88 | XCTAssertFalse(scaling == pinching)
89 | XCTAssertFalse(pinching == all)
90 | XCTAssertFalse(pinching == horizontally)
91 | XCTAssertFalse(pinching == scaling)
92 | XCTAssertFalse(pinching == rotating)
93 | }
94 |
95 | func testLayoutTypeComparable() {
96 | let matrix = ViewMatrix()
97 | let all = NeedsLayout.LayoutType.all(matrix)
98 | let horizontally = NeedsLayout.LayoutType.horizontally(matrix)
99 | let rotating = NeedsLayout.LayoutType.rotating(matrix)
100 | let scaling = NeedsLayout.LayoutType.scaling(matrix)
101 | let pinching = NeedsLayout.LayoutType.pinching(matrix)
102 |
103 | XCTAssertFalse(all < all)
104 | XCTAssertFalse(all < horizontally)
105 | XCTAssertFalse(all < rotating)
106 | XCTAssertFalse(all < scaling)
107 | XCTAssertFalse(all < pinching)
108 |
109 | XCTAssertTrue(horizontally < all)
110 | XCTAssertFalse(horizontally < horizontally)
111 | XCTAssertFalse(horizontally < rotating)
112 | XCTAssertFalse(horizontally < scaling)
113 | XCTAssertFalse(horizontally < pinching)
114 |
115 | XCTAssertTrue(rotating < all)
116 | XCTAssertTrue(rotating < horizontally)
117 | XCTAssertFalse(rotating < rotating)
118 | XCTAssertFalse(rotating < scaling)
119 | XCTAssertFalse(rotating < pinching)
120 |
121 | XCTAssertTrue(scaling < all)
122 | XCTAssertTrue(scaling < horizontally)
123 | XCTAssertTrue(scaling < rotating)
124 | XCTAssertFalse(scaling < scaling)
125 | XCTAssertFalse(scaling < pinching)
126 |
127 | XCTAssertTrue(pinching < all)
128 | XCTAssertTrue(pinching < horizontally)
129 | XCTAssertTrue(pinching < rotating)
130 | XCTAssertTrue(pinching < scaling)
131 | XCTAssertFalse(pinching < pinching)
132 | }
133 |
134 | func testLayoutTypeMatrix() {
135 | let matrix = ViewMatrix()
136 |
137 | XCTAssertNotNil(NeedsLayout.LayoutType.all(matrix).matrix)
138 | XCTAssertNotNil(NeedsLayout.LayoutType.horizontally(matrix).matrix)
139 | XCTAssertNotNil(NeedsLayout.LayoutType.rotating(matrix).matrix)
140 | XCTAssertNotNil(NeedsLayout.LayoutType.pinching(matrix).matrix)
141 | }
142 |
143 | func testDebugDescription() {
144 | let matrix = ViewMatrix()
145 |
146 | XCTAssertNotNil(NeedsLayout.none.debugDescription)
147 | XCTAssertNotNil(NeedsLayout.layout(.all(ViewMatrix())).debugDescription)
148 | XCTAssertNotNil(NeedsLayout.reload.debugDescription)
149 |
150 | XCTAssertNotNil(NeedsLayout.LayoutType.all(matrix).debugDescription)
151 | XCTAssertNotNil(NeedsLayout.LayoutType.horizontally(matrix).debugDescription)
152 | XCTAssertNotNil(NeedsLayout.LayoutType.rotating(matrix).debugDescription)
153 | XCTAssertNotNil(NeedsLayout.LayoutType.pinching(matrix).debugDescription)
154 | }
155 |
156 | func testIsScaling() {
157 | let matrix = ViewMatrix()
158 | let all = NeedsLayout.LayoutType.all(matrix)
159 | let horizontally = NeedsLayout.LayoutType.horizontally(matrix)
160 | let rotating = NeedsLayout.LayoutType.rotating(matrix)
161 | let scaling = NeedsLayout.LayoutType.scaling(matrix)
162 | let pinching = NeedsLayout.LayoutType.pinching(matrix)
163 |
164 | XCTAssertFalse(all.isScaling)
165 | XCTAssertFalse(horizontally.isScaling)
166 | XCTAssertFalse(rotating.isScaling)
167 | XCTAssertTrue(scaling.isScaling)
168 | XCTAssertTrue(pinching.isScaling)
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/GridViewTests/ReuseQueueTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReuseQueueTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/29.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | private class MockView: UIView, Reusable {}
13 |
14 | class ReuseQueueTests: XCTestCase {
15 |
16 | override func setUp() {
17 | super.setUp()
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 | }
20 |
21 | override func tearDown() {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | super.tearDown()
24 | }
25 |
26 | func testExtensionDefaults() {
27 | XCTAssertNotNil(MockReusable().prepareForReuse())
28 |
29 | let view = MockView()
30 | XCTAssertTrue(view.canReuse)
31 |
32 | let superview = UIView()
33 | superview.addSubview(view)
34 |
35 | XCTAssertFalse(view.canReuse)
36 | }
37 |
38 | func testAppend() {
39 | var queue = ReuseQueue()
40 |
41 | XCTAssertNil(queue.dequeue(with: "1"))
42 | XCTAssertNil(queue.dequeue(with: "2"))
43 |
44 | queue.append(MockReusable(), for: "1")
45 |
46 | XCTAssertNotNil(queue.dequeue(with: "1"))
47 | XCTAssertNil(queue.dequeue(with: "2"))
48 |
49 | queue.append(MockReusable(), for: "2")
50 |
51 | XCTAssertNotNil(queue.dequeue(with: "1"))
52 | XCTAssertNotNil(queue.dequeue(with: "2"))
53 | }
54 |
55 | func testDequeue() {
56 | var queue = ReuseQueue()
57 |
58 | XCTAssertNil(queue.dequeue(with: "1"))
59 |
60 | let reusable = MockReusable()
61 | queue.append(reusable, for: "1")
62 |
63 | XCTAssertNotNil(queue.dequeue(with: "1"))
64 | XCTAssertNotNil(queue.dequeue(with: "1"))
65 |
66 | reusable.canReuse = false
67 | XCTAssertNil(queue.dequeue(with: "1"))
68 | XCTAssertNil(queue.dequeue(with: "1"))
69 |
70 | reusable.canReuse = true
71 | XCTAssertNotNil(queue.dequeue(with: "1"))
72 |
73 | reusable.canReuse = false
74 | XCTAssertNil(queue.dequeue(with: "1"))
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GridViewTests/ScaleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScaleTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class ScaleTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let scale = Scale(x: 100, y: 100)
26 |
27 | XCTAssertEqual(scale.x, 100)
28 | XCTAssertEqual(scale.y, 100)
29 | }
30 |
31 | func testZero() {
32 | XCTAssertEqual(Scale.zero.x, 0)
33 | XCTAssertEqual(Scale.zero.y, 0)
34 | }
35 |
36 | func testDefault() {
37 | XCTAssertEqual(Scale.default.x, 1)
38 | XCTAssertEqual(Scale.default.y, 1)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/GridViewTests/UIEdgeInsetsExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIEdgeInsetsExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2017/01/29.
6 | // Copyright © 2017年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class UIEdgeInsetsExtensionTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testOperator() {
25 | let inset1 = UIEdgeInsets(top: 10, left: 20, bottom: 30, right: 40)
26 | let inset2 = UIEdgeInsets(top: -10, left: -20, bottom: -30, right: -40)
27 |
28 | XCTAssertEqual(inset1 + inset1, UIEdgeInsets(top: 20, left: 40, bottom: 60, right: 80))
29 | XCTAssertEqual(inset1 + inset2, .zero)
30 | XCTAssertEqual(inset2 + inset1, .zero)
31 | XCTAssertEqual(inset2 + inset2, UIEdgeInsets(top: -20, left: -40, bottom: -60, right: -80))
32 |
33 | XCTAssertEqual(inset1 - inset1, .zero)
34 | XCTAssertEqual(inset1 - inset2, UIEdgeInsets(top: 20, left: 40, bottom: 60, right: 80))
35 | XCTAssertEqual(inset2 - inset1, UIEdgeInsets(top: -20, left: -40, bottom: -60, right: -80))
36 | XCTAssertEqual(inset2 - inset2, .zero)
37 | }
38 |
39 | func testHorizontal() {
40 | let inset1 = UIEdgeInsets(top: 10, left: 20, bottom: 30, right: 40)
41 | let inset2 = UIEdgeInsets(top: -10, left: -20, bottom: -30, right: -40)
42 |
43 | XCTAssertEqual(inset1.horizontal, 60)
44 | XCTAssertEqual(inset2.horizontal, -60)
45 |
46 | XCTAssertEqual(inset1.vertical, 40)
47 | XCTAssertEqual(inset2.vertical, -40)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/GridViewTests/UIScrollViewExtensionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollViewExtensionTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class UIScrollViewExtensionTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testValidityContentOffset() {
25 | let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100)))
26 | let scrollView = UIScrollView(frame: CGRect(x: 10, y: 10, width: 80, height: 80))
27 | view.addSubview(scrollView)
28 |
29 | scrollView.contentSize = view.bounds.size
30 |
31 | XCTAssertEqual(scrollView.validityContentOffset.x, -10)
32 | XCTAssertEqual(scrollView.validityContentOffset.y, -10)
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/GridViewTests/VerticalTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VerticalTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class VerticalTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let vertical = Vertical(y: 100, height: 100)
26 |
27 | XCTAssertEqual(vertical.y, 100)
28 | XCTAssertEqual(vertical.height, 100)
29 | XCTAssertEqual(vertical.maxY, 200)
30 | }
31 |
32 | func testZero() {
33 | XCTAssertEqual(Vertical.zero.y, 0)
34 | XCTAssertEqual(Vertical.zero.height, 0)
35 | XCTAssertEqual(Vertical.zero.maxY, 0)
36 | }
37 |
38 | func testMaxX() {
39 | var vertical = Vertical(y: 100, height: 100)
40 | XCTAssertEqual(vertical.maxY, 200)
41 |
42 | vertical.y = 200
43 | XCTAssertEqual(vertical.maxY, 300)
44 |
45 | vertical.height = 200
46 | XCTAssertEqual(vertical.maxY, 400)
47 | }
48 |
49 | func testOperator() {
50 | let vertical = Vertical(y: 100, height: 100) * 10
51 |
52 | XCTAssertEqual(vertical.y, 1000)
53 | XCTAssertEqual(vertical.height, 1000)
54 | XCTAssertEqual(vertical.maxY, 2000)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/GridViewTests/ViewBundleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewBundleTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class ViewBundleTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testRegisterOfNib() {
25 | var bundle = ViewBundle()
26 |
27 | bundle.register(ofNib: MockCell.nib, for: "MockCellNib")
28 | XCTAssertNotNil(bundle.instantiate(with: "MockCellNib"))
29 | }
30 |
31 | func testRegisterOfClass() {
32 | var bundle = ViewBundle()
33 |
34 | bundle.register(ofClass: MockCell.self, for: "MockCellClass")
35 | XCTAssertNotNil(bundle.instantiate(with: "MockCellClass"))
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/GridViewTests/ViewReferenceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewReferenceTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class ViewReferenceTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testInit() {
25 | let view = UIView()
26 | let ref = ViewReference(view)
27 |
28 | XCTAssertNotNil(ref.view)
29 | }
30 |
31 | func testDeinit() {
32 | let view = UIView()
33 | let superview = UIView()
34 | superview.addSubview(view)
35 |
36 | XCTAssertNotNil(view.superview)
37 |
38 | var ref: ViewReference? = ViewReference(view)
39 | XCTAssertNotNil(ref?.view?.superview)
40 | XCTAssertNotNil(view.superview)
41 |
42 | ref = nil
43 | XCTAssertNil(ref?.view?.superview)
44 | XCTAssertNil(view.superview)
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/GridViewTests/ViewVisibleInfoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewVisibleInfoTests.swift
3 | // GridView
4 | //
5 | // Created by Kyohei Ito on 2016/12/30.
6 | // Copyright © 2016年 Kyohei Ito. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import GridView
11 |
12 | class ViewVisibleInfoTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testcolumns() {
25 | var info = ViewVisibleInfo()
26 |
27 | info.replaceColumn([0,1,2])
28 | XCTAssertEqual(info.columns(), [0,1,2])
29 |
30 | info.replaceColumn([3,4,5])
31 | XCTAssertEqual(info.columns(), [3,4,5])
32 | }
33 |
34 | func testRows() {
35 | var info = ViewVisibleInfo()
36 |
37 | info.replaceColumn([0,1,2])
38 | info.replaceRows { column -> [Int] in
39 | [0,1,2]
40 | }
41 | XCTAssertEqual(info.columns(), [0,1,2])
42 | XCTAssertEqual(info.rows()[0]!, [0,1,2])
43 | XCTAssertEqual(info.rows()[1]!, [0,1,2])
44 | XCTAssertEqual(info.rows()[2]!, [0,1,2])
45 | XCTAssertEqual(info.rows(in: 0), [0,1,2])
46 | XCTAssertEqual(info.rows(in: 1), [0,1,2])
47 | XCTAssertEqual(info.rows(in: 2), [0,1,2])
48 | XCTAssertEqual(info.rows(in: 3), [])
49 |
50 | info.replaceColumn([3,4,5])
51 | info.replaceRows { column -> [Int] in
52 | [3,4,5]
53 | }
54 | XCTAssertEqual(info.columns(), [3,4,5])
55 | XCTAssertEqual(info.rows()[3]!, [3,4,5])
56 | XCTAssertEqual(info.rows()[4]!, [3,4,5])
57 | XCTAssertEqual(info.rows()[5]!, [3,4,5])
58 | XCTAssertEqual(info.rows(in: 3), [3,4,5])
59 | XCTAssertEqual(info.rows(in: 4), [3,4,5])
60 | XCTAssertEqual(info.rows(in: 5), [3,4,5])
61 | XCTAssertEqual(info.rows(in: 6), [])
62 | }
63 |
64 | func testObjects() {
65 | let view1 = UIView(), view2 = UIView(), view3 = UIView()
66 | let path1 = IndexPath(row: 1, column: 0), path2 = IndexPath(row: 2, column: 0), path3 = IndexPath(row: 3, column: 0)
67 |
68 | var info1 = ViewVisibleInfo()
69 | info1.append(view1, at: path1)
70 | info1.append(view2, at: path2)
71 | info1.append(view3, at: path3)
72 |
73 | XCTAssertEqual(info1.visibleObject()[path1]?.view, view1)
74 | XCTAssertEqual(info1.visibleObject()[path2]?.view, view2)
75 | XCTAssertEqual(info1.visibleObject()[path3]?.view, view3)
76 | XCTAssertEqual(info1.object(at: path1), view1)
77 | XCTAssertEqual(info1.object(at: path2), view2)
78 | XCTAssertEqual(info1.object(at: path3), view3)
79 |
80 | let view4 = UIView(), view5 = UIView(), view6 = UIView()
81 | let path4 = IndexPath(row: 4, column: 0), path5 = IndexPath(row: 5, column: 0), path6 = IndexPath(row: 6, column: 0)
82 | var info2 = ViewVisibleInfo()
83 | info2.append(view4, at: path4)
84 | info2.append(view5, at: path5)
85 | info2.append(view6, at: path6)
86 |
87 | XCTAssertEqual(info2.visibleObject()[path4]?.view, view4)
88 | XCTAssertEqual(info2.visibleObject()[path5]?.view, view5)
89 | XCTAssertEqual(info2.visibleObject()[path6]?.view, view6)
90 | XCTAssertEqual(info2.object(at: path4), view4)
91 | XCTAssertEqual(info2.object(at: path5), view5)
92 | XCTAssertEqual(info2.object(at: path6), view6)
93 |
94 | info1.replaceObject(with: info2)
95 | XCTAssertNil(info1.visibleObject()[path1]?.view)
96 | XCTAssertNil(info1.visibleObject()[path2]?.view)
97 | XCTAssertNil(info1.visibleObject()[path3]?.view)
98 | XCTAssertNil(info1.object(at: path1))
99 | XCTAssertNil(info1.object(at: path2))
100 | XCTAssertNil(info1.object(at: path3))
101 |
102 | XCTAssertNotNil(info1.visibleObject()[path4]?.view)
103 | XCTAssertNotNil(info1.visibleObject()[path5]?.view)
104 | XCTAssertNotNil(info1.visibleObject()[path6]?.view)
105 | XCTAssertNotNil(info1.object(at: path4))
106 | XCTAssertNotNil(info1.object(at: path5))
107 | XCTAssertNotNil(info1.object(at: path6))
108 |
109 | XCTAssertNil(info1.removedObject(at: path1))
110 | XCTAssertNil(info1.removedObject(at: path2))
111 | XCTAssertNil(info1.removedObject(at: path3))
112 | XCTAssertNotNil(info1.removedObject(at: path4))
113 | XCTAssertNotNil(info1.removedObject(at: path5))
114 | XCTAssertNotNil(info1.removedObject(at: path6))
115 |
116 | XCTAssertNil(info1.object(at: path4))
117 | XCTAssertNil(info1.object(at: path5))
118 | XCTAssertNil(info1.object(at: path6))
119 | }
120 |
121 | func testSelected() {
122 | let view1 = UIView()
123 | let path1 = IndexPath(row: 1, column: 0), path2 = IndexPath(row: 2, column: 0), path3 = IndexPath(row: 3, column: 0)
124 |
125 | var info1 = ViewVisibleInfo()
126 | info1.append(view1, at: path1)
127 |
128 | XCTAssertNotNil(info1.selected(at: path1))
129 | XCTAssertNil(info1.selected(at: path2))
130 |
131 | XCTAssertTrue(info1.isSelected(path1))
132 | XCTAssertTrue(info1.isSelected(path2))
133 | XCTAssertFalse(info1.isSelected(path3))
134 |
135 | XCTAssertEqual(info1.indexPathsForSelected(), [path1,path2])
136 | XCTAssertNil(info1.selected(at: path3))
137 | XCTAssertNotNil(info1.deselected(at: path1))
138 | XCTAssertEqual(info1.indexPathsForSelected(), [path2,path3])
139 | XCTAssertNil(info1.deselected(at: path2))
140 | XCTAssertEqual(info1.indexPathsForSelected(), [path3])
141 |
142 | let view4 = UIView(), view5 = UIView(), view6 = UIView()
143 | let path4 = IndexPath(row: 4, column: 0), path5 = IndexPath(row: 5, column: 0), path6 = IndexPath(row: 6, column: 0)
144 | var info2 = ViewVisibleInfo()
145 | info2.append(view4, at: path4)
146 | info2.append(view5, at: path5)
147 | info2.append(view6, at: path6)
148 | XCTAssertNotNil(info2.selected(at: path4))
149 | XCTAssertNotNil(info2.selected(at: path5))
150 | XCTAssertNotNil(info2.selected(at: path6))
151 |
152 | XCTAssertEqual(info2.indexPathsForSelected(), [path4,path5,path6])
153 | info2.replaceSelectedIndexPath(with: info1)
154 | XCTAssertEqual(info1.indexPathsForSelected(), [path3])
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Kyohei Ito
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "GridView",
8 | platforms: [.iOS(.v9)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "GridView",
13 | targets: ["GridView"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(
23 | name: "GridView",
24 | dependencies: [], path: "GridView"),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GridView
2 |
3 | [](https://travis-ci.org/KyoheiG3/GridView)
4 | [](https://github.com/Carthage/Carthage)
5 | [](http://cocoadocs.org/docsets/G3GridView)
6 | [](http://cocoadocs.org/docsets/G3GridView)
7 | [](http://cocoadocs.org/docsets/G3GridView)
8 |
9 | GridView can tile the view while reusing it. It has an API like UIKit that works fast. Even when device rotates it smoothly relayout.
10 |
11 |
12 |
13 | #### [Appetize's Demo](https://appetize.io/app/d5qk2927a8y07armrbbdck64c4)
14 |
15 | ### You Can
16 |
17 | - Scroll like paging
18 | - Scroll infinitely
19 | - Scale the view
20 | - Call API like the `UITableView`
21 |
22 | ## Requirements
23 |
24 | - Swift 5.0
25 | - iOS 9.0 or later
26 |
27 | ## How to Install
28 |
29 | #### CocoaPods
30 |
31 | Add the following to your `Podfile`:
32 |
33 | ```Ruby
34 | pod "G3GridView"
35 | ```
36 |
37 | > :warning: **WARNING :** If you want to install from `CocoaPods`, must add `G3GridView` to Podfile because there is a `GridView` different from this `GridView`.
38 |
39 | #### Carthage
40 |
41 | Add the following to your `Cartfile`:
42 |
43 | ```Ruby
44 | github "KyoheiG3/GridView"
45 | ```
46 |
47 | ## Over View
48 |
49 | GridView can scroll in any direction while reusing Cell like `UITableView`. Also it is based `UIScrollView` and paging and scaling are possible. If necessary, it is possible to repeat the left and right scroll infinitely.
50 |
51 | GridView is one `UIScrollView`, but the range which Cell is viewed depends on Superview. Cell reuse is also done within the range which Superview is viewed, so its size is very important.
52 |
53 | On the other hand, scaling and paging depend to position and size of GridView. 'bounds' is important for paging, 'frame' is important in scaling. The same is true for offset of content.
54 |
55 | The following image is a visual explanation of the view hierarchy.
56 |
57 | 
58 |
59 | You can use it like the `UITableView` APIs. However, there is concept of `Column`. The following functions are delegate APIs of 'GridView'.
60 |
61 | ```swift
62 | func gridView(_ gridView: GridView, numberOfRowsInColumn column: Int) -> Int
63 | func gridView(_ gridView: GridView, cellForRowAt indexPath: IndexPath) -> GridViewCell
64 |
65 | @objc optional func numberOfColumns(in gridView: GridView) -> Int
66 | ```
67 |
68 | You can see that must return the count.
69 |
70 | ## Examples
71 |
72 | This project is including two examples that is timetable and paging. Those can change on Interface Builder for following:
73 |
74 | 
75 |
76 | Try the two examples.
77 |
78 | | timetable | paging |
79 | |-|-|
80 | |
|
|
81 |
82 |
83 | ## Usage
84 |
85 | ### Variables
86 |
87 | #### Infinite Loop
88 |
89 | A horizontal loop is possible.
90 |
91 | ```swift
92 | open var isInfinitable: Bool
93 | ```
94 |
95 | - Default is `true`.
96 | - Set `false` if you don't need to loop of view.
97 |
98 |
99 |
100 | ```swift
101 | gridView.isInfinitable = true
102 | ```
103 |
104 | #### Scaling
105 |
106 | Content is done relayout rather than scaling like 'UIScrollView'.
107 |
108 | ```swift
109 | open var minimumScale: Scale
110 | open var maximumScale: Scale
111 | ```
112 |
113 | - Default for x and y are 1.
114 | - Set the vertical and horizontal scales.
115 |
116 | ```swift
117 | public var currentScale: Scale { get }
118 | ```
119 |
120 | - Get current vertical and horizontal scales.
121 |
122 |
123 |
124 | ```swift
125 | gridView.minimumScale = Scale(x: 0.5, y: 0.5)
126 | gridView.maximumScale = Scale(x: 1.5, y: 1.5)
127 | ```
128 |
129 | #### Fill for Cell
130 |
131 | It is possible to decide the placement of Cell at relayout.
132 |
133 | ```swift
134 | open var layoutWithoutFillForCell: Bool
135 | ```
136 |
137 | - Default is `false`.
138 | - Set `true` if need to improved view layout performance.
139 |
140 | | false | true |
141 | |-|-|
142 | |
|
|
143 |
144 | ```swift
145 | gridView.layoutWithoutFillForCell = true
146 | ```
147 |
148 | #### Content Offset
149 |
150 | If `isInfinitable` is true, `contentOffset` depends on the content size including size to loop. It is possible to take content offset that actually visible.
151 |
152 | ```swift
153 | open var actualContentOffset: CGPoint { get }
154 | ```
155 |
156 | #### Delegate
157 |
158 | Set the delegate destination. This delegate property is `UIScrollViewDelegate` but, actually set the `GridViewDelegate`.
159 |
160 | ```
161 | weak open var dataSource: GridViewDataSource?
162 | open var delegate: UIScrollViewDelegate?
163 | ```
164 |
165 | ### Functions
166 |
167 | #### State
168 |
169 | Get the view state.
170 |
171 | ```swift
172 | public func visibleCells() -> [T]
173 | public func cellForRow(at indexPath: IndexPath) -> GridViewCell?
174 | public func rectForRow(at indexPath: IndexPath) -> CGRect
175 | public func indexPathsForSelectedRows() -> [IndexPath]
176 | public func indexPathForRow(at position: CGPoint) -> IndexPath
177 | ```
178 |
179 | #### Operation
180 |
181 | Operate the view.
182 |
183 | ```swift
184 | public func contentScale(_ scale: CGFloat)
185 | public func reloadData()
186 | public func invalidateContentSize()
187 | public func invalidateLayout(horizontally: Bool = default)
188 | public func deselectRow(at indexPath: IndexPath)
189 | override open func setContentOffset(_ contentOffset: CGPoint, animated: Bool)
190 | public func scrollToRow(at indexPath: IndexPath, at scrollPosition: GridViewScrollPosition = default, animated: Bool = default)
191 | ```
192 |
193 | ## LICENSE
194 |
195 | Under the MIT license. See [LICENSE](./LICENSE) file for details.
196 |
--------------------------------------------------------------------------------