├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Examples ├── ClassData │ ├── ClassData.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── ClassData.xcscheme │ └── Sources │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Cells.swift │ │ ├── Info.plist │ │ ├── ViewController.swift │ │ └── data.tsv ├── GanttChart │ ├── GanttChart.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── GanttChart.xcscheme │ └── Sources │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Cells.swift │ │ ├── Info.plist │ │ └── ViewController.swift ├── Playground.playground │ ├── DataSource.o │ ├── DebugCell.o │ ├── Pages │ │ ├── Circular Scrolling.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Navigation View Controller.xcplaygroundpage │ │ │ └── Contents.swift │ │ ├── Simple View.xcplaygroundpage │ │ │ └── Contents.swift │ │ └── View Controller.xcplaygroundpage │ │ │ └── Contents.swift │ ├── Sources │ │ ├── DataSource.swift │ │ ├── DebugCell.swift │ │ └── SpreadsheetViewController.swift │ └── contents.xcplayground ├── Schedule │ ├── Schedule.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Schedule.xcscheme │ └── Sources │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Cells.swift │ │ ├── Info.plist │ │ └── ViewController.swift └── Timetable │ ├── Sources │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Cells.swift │ ├── Info.plist │ ├── Models.swift │ ├── SlotCell.xib │ └── ViewController.swift │ └── Timetable.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ └── xcschemes │ └── Timetable.xcscheme ├── Framework ├── Sources │ ├── Address.swift │ ├── Array+BinarySearch.swift │ ├── Borders.swift │ ├── Cell.swift │ ├── CellRange.swift │ ├── CircularScrolling.swift │ ├── Gridlines.swift │ ├── IndexPath+Column.swift │ ├── Info.plist │ ├── LayoutEngine.swift │ ├── Location.swift │ ├── ReuseQueue.swift │ ├── ScrollPosition.swift │ ├── ScrollView.swift │ ├── SpreadsheetView+CirclularScrolling.swift │ ├── SpreadsheetView+Layout.swift │ ├── SpreadsheetView+Touches.swift │ ├── SpreadsheetView+UIScrollView.swift │ ├── SpreadsheetView+UIScrollViewDelegate.swift │ ├── SpreadsheetView+UISnapshotting.swift │ ├── SpreadsheetView+UIViewHierarchy.swift │ ├── SpreadsheetView.h │ ├── SpreadsheetView.swift │ ├── SpreadsheetViewDataSource.swift │ └── SpreadsheetViewDelegate.swift ├── SpreadsheetView.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── HostApp.xcscheme │ │ └── SpreadsheetView.xcscheme ├── Tests │ ├── CellRangeTests.swift │ ├── CellTests.swift │ ├── ConfigurationTests.swift │ ├── DataSourceTests.swift │ ├── HelperFunctions.swift │ ├── HelperObjects.swift │ ├── HostApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Info.plist │ ├── MergedCellTests.swift │ ├── PerformanceTests.swift │ ├── ScrollTests.swift │ ├── SelectionTests.swift │ └── ViewTests.swift └── docs │ ├── Classes.html │ ├── Classes │ └── SpreadsheetView.html │ ├── Protocols.html │ ├── Protocols │ ├── SpreadsheetViewDataSource.html │ └── SpreadsheetViewDelegate.html │ ├── Resources │ ├── Border.png │ ├── BothHeaders.gif │ ├── CircularScrolling.gif │ ├── ColumnHeader.gif │ ├── DailySchedule_landscape.png │ ├── DailySchedule_portrait.png │ ├── GanttChart.png │ ├── Grid.png │ ├── IntercellSpacing.png │ ├── Logo.png │ ├── MergedCells.png │ ├── RowHeader.gif │ └── Timetable.png │ ├── badge.svg │ ├── css │ ├── highlight.css │ └── jazzy.css │ ├── docsets │ ├── .docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ └── SpreadsheetView.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── SpreadsheetViewDataSource.html │ │ │ │ └── SpreadsheetViewDelegate.html │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ └── gh.png │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ └── jquery.min.js │ │ │ └── search.json │ │ │ └── docSet.dsidx │ └── .tgz │ ├── img │ ├── carat.png │ ├── dash.png │ └── gh.png │ ├── index.html │ ├── js │ ├── jazzy.js │ └── jquery.min.js │ ├── search.json │ └── undocumented.json ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── Resources ├── Border.png ├── BothHeaders.gif ├── CircularScrolling.gif ├── ColumnHeader.gif ├── DailySchedule_landscape.png ├── DailySchedule_portrait.png ├── GanttChart.png ├── Grid.png ├── IntercellSpacing.png ├── Logo.png ├── MergedCells.png ├── RowHeader.gif └── Timetable.png ├── SpreadsheetView.podspec └── SpreadsheetView.xcworkspace └── contents.xcworkspacedata /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Xcode version: ? 16 | - iOS version: ? 17 | - Dependency manager + version: ? 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | 16 | ## Other 17 | *.moved-aside 18 | *.xcuserstate 19 | 20 | ## Obj-C/Swift specific 21 | *.hmap 22 | *.ipa 23 | *.dSYM.zip 24 | *.dSYM 25 | 26 | ## Playgrounds 27 | timeline.xctimeline 28 | playground.xcworkspace 29 | 30 | ## Swift Package Manager 31 | .DS_Store 32 | /.build 33 | /Packages 34 | 35 | ## CocoaPods 36 | Pods/ 37 | 38 | ## Carthage 39 | Carthage/Checkouts 40 | Carthage/Build 41 | 42 | ## Bundler 43 | .bundle 44 | vendor 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | cache: 3 | directories: 4 | - build 5 | - vendor 6 | jobs: 7 | include: 8 | - stage: build 9 | osx_image: xcode9 10 | install: 11 | - bundle install --path=vendor/bundle --binstubs=vendor/bin 12 | script: 13 | - bundle exec rake build-for-testing:simulator 14 | - &test 15 | stage: 'test (xcode9)' 16 | osx_image: xcode9 17 | script: 18 | - bundle exec rake test-without-building:simulator 19 | DESTINATIONS="['name=iPhone 4s,OS=9.3']" 20 | after_success: 21 | - bash <(curl -s https://codecov.io/bash) -f 'coverage.txt' 22 | - <<: *test 23 | script: 24 | - bundle exec rake test-without-building:simulator 25 | DESTINATIONS="['name=iPhone 5,OS=9.3']" 26 | - <<: *test 27 | script: 28 | - bundle exec rake test-without-building:simulator 29 | DESTINATIONS="['name=iPhone 6s,OS=10.3.1', 'name=iPhone 7 Plus,OS=10.3.1']" 30 | - <<: *test 31 | script: 32 | - bundle exec rake test-without-building:simulator 33 | DESTINATIONS="['name=iPad Air 2,OS=10.3.1']" 34 | - <<: *test 35 | script: 36 | - bundle exec rake test-without-building:simulator 37 | DESTINATIONS="['name=iPad Pro (9.7-inch),OS=10.3.1']" 38 | - <<: *test 39 | script: 40 | - bundle exec rake test-without-building:simulator 41 | DESTINATIONS="['name=iPad Pro (12.9-inch),OS=10.3.1']" 42 | - <<: *test 43 | script: 44 | - bundle exec rake test-without-building:simulator 45 | DESTINATIONS="['name=iPhone 6s Plus,OS=11.0']" 46 | - <<: *test 47 | script: 48 | - bundle exec rake test-without-building:simulator 49 | DESTINATIONS="['name=iPhone 7,OS=11.0']" 50 | - <<: *test 51 | script: 52 | - bundle exec rake test-without-building:simulator 53 | DESTINATIONS="['name=iPad Pro (12.9-inch),OS=11.0']" 54 | - <<: *test 55 | script: 56 | - bundle exec rake test-without-building:simulator 57 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='MergedCellTests' 58 | - <<: *test 59 | script: 60 | - bundle exec rake test-without-building:simulator 61 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='ScrollTests/testTableViewScrolling' 62 | - <<: *test 63 | script: 64 | - bundle exec rake test-without-building:simulator 65 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='ScrollTests/testColumnHeaderViewScrolling' 66 | - <<: *test 67 | script: 68 | - bundle exec rake test-without-building:simulator 69 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='ScrollTests/testRowHeaderViewScrolling' 70 | - <<: *test 71 | script: 72 | - bundle exec rake test-without-building:simulator 73 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='ScrollTests/testColumnAndRowHeaderViewScrolling' 74 | - <<: *test 75 | script: 76 | - bundle exec rake test-without-building:simulator 77 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='ScrollTests/testCircularScrolling' 78 | - <<: *test 79 | script: 80 | - bundle exec rake test-without-building:simulator 81 | DESTINATIONS="['name=iPad 2,OS=9.3']" TESTCASE='SelectionTests' 82 | - stage: 'test (xcode8.3)' 83 | osx_image: xcode8.3 84 | script: 85 | - bundle exec rake test:simulator DESTINATIONS="['name=iPhone 4s,OS=9.3']" SWIFT_VERSION=3.0 86 | after_success: 87 | - bash <(curl -s https://codecov.io/bash) -f 'coverage.txt' 88 | - stage: 'test (xcode8.3)' 89 | osx_image: xcode8.3 90 | script: 91 | - bundle exec rake test:simulator DESTINATIONS="['name=iPhone 7,OS=10.3.1']" SWIFT_VERSION=3.0 92 | after_success: 93 | - bash <(curl -s https://codecov.io/bash) -f 'coverage.txt' 94 | - stage: carthage 95 | osx_image: xcode9 96 | script: 97 | - carthage build --no-skip-current 98 | - stage: carthage 99 | osx_image: xcode8.3 100 | script: 101 | - echo SWIFT_VERSION=\"3.0\" > swift3.xcconfig 102 | - XCODE_XCCONFIG_FILE=`pwd`/swift3.xcconfig carthage build --no-skip-current 103 | env: 104 | global: 105 | - LANG=en_US.UTF-8 106 | - LC_ALL=en_US.UTF-8 107 | branches: 108 | only: 109 | - master 110 | skip_cleanup: true 111 | -------------------------------------------------------------------------------- /Examples/ClassData/ClassData.xcodeproj/xcshareddata/xcschemes/ClassData.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/18/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/ClassData/Sources/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 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/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 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/Cells.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cells.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/18/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpreadsheetView 11 | 12 | class HeaderCell: Cell { 13 | let label = UILabel() 14 | let sortArrow = UILabel() 15 | 16 | override var frame: CGRect { 17 | didSet { 18 | label.frame = bounds.insetBy(dx: 4, dy: 2) 19 | } 20 | } 21 | 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | 25 | label.frame = bounds 26 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 27 | label.font = UIFont.boldSystemFont(ofSize: 14) 28 | label.textAlignment = .left 29 | label.numberOfLines = 2 30 | contentView.addSubview(label) 31 | 32 | sortArrow.text = "" 33 | sortArrow.font = UIFont.boldSystemFont(ofSize: 14) 34 | sortArrow.textAlignment = .center 35 | contentView.addSubview(sortArrow) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | super.init(coder: aDecoder) 40 | } 41 | 42 | override func layoutSubviews() { 43 | super.layoutSubviews() 44 | sortArrow.sizeToFit() 45 | sortArrow.frame.origin.x = frame.width - sortArrow.frame.width - 8 46 | sortArrow.frame.origin.y = (frame.height - sortArrow.frame.height) / 2 47 | } 48 | } 49 | 50 | class TextCell: Cell { 51 | let label = UILabel() 52 | 53 | override var frame: CGRect { 54 | didSet { 55 | label.frame = bounds.insetBy(dx: 4, dy: 2) 56 | } 57 | } 58 | 59 | override init(frame: CGRect) { 60 | super.init(frame: frame) 61 | 62 | let backgroundView = UIView() 63 | backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.2) 64 | selectedBackgroundView = backgroundView 65 | 66 | label.frame = bounds 67 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 68 | label.font = UIFont.systemFont(ofSize: 14) 69 | label.textAlignment = .left 70 | 71 | contentView.addSubview(label) 72 | } 73 | 74 | required init?(coder aDecoder: NSCoder) { 75 | super.init(coder: aDecoder) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/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 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/18/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpreadsheetView 11 | 12 | class ViewController: UIViewController, SpreadsheetViewDataSource, SpreadsheetViewDelegate { 13 | @IBOutlet weak var spreadsheetView: SpreadsheetView! 14 | var header = [String]() 15 | var data = [[String]]() 16 | 17 | enum Sorting { 18 | case ascending 19 | case descending 20 | 21 | var symbol: String { 22 | switch self { 23 | case .ascending: 24 | return "\u{25B2}" 25 | case .descending: 26 | return "\u{25BC}" 27 | } 28 | } 29 | } 30 | var sortedColumn = (column: 0, sorting: Sorting.ascending) 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | spreadsheetView.dataSource = self 36 | spreadsheetView.delegate = self 37 | 38 | spreadsheetView.register(HeaderCell.self, forCellWithReuseIdentifier: String(describing: HeaderCell.self)) 39 | spreadsheetView.register(TextCell.self, forCellWithReuseIdentifier: String(describing: TextCell.self)) 40 | 41 | let data = try! String(contentsOf: Bundle.main.url(forResource: "data", withExtension: "tsv")!, encoding: .utf8) 42 | .components(separatedBy: "\r\n") 43 | .map { $0.components(separatedBy: "\t") } 44 | header = data[0] 45 | self.data = Array(data.dropFirst()) 46 | } 47 | 48 | override func viewDidAppear(_ animated: Bool) { 49 | super.viewDidAppear(animated) 50 | spreadsheetView.flashScrollIndicators() 51 | } 52 | 53 | // MARK: DataSource 54 | 55 | func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int { 56 | return header.count 57 | } 58 | 59 | func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int { 60 | return 1 + data.count 61 | } 62 | 63 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat { 64 | return 140 65 | } 66 | 67 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat { 68 | if case 0 = row { 69 | return 60 70 | } else { 71 | return 44 72 | } 73 | } 74 | 75 | func frozenRows(in spreadsheetView: SpreadsheetView) -> Int { 76 | return 1 77 | } 78 | 79 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { 80 | if case 0 = indexPath.row { 81 | let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: HeaderCell.self), for: indexPath) as! HeaderCell 82 | cell.label.text = header[indexPath.column] 83 | 84 | if case indexPath.column = sortedColumn.column { 85 | cell.sortArrow.text = sortedColumn.sorting.symbol 86 | } else { 87 | cell.sortArrow.text = "" 88 | } 89 | cell.setNeedsLayout() 90 | 91 | return cell 92 | } else { 93 | let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: String(describing: TextCell.self), for: indexPath) as! TextCell 94 | cell.label.text = data[indexPath.row - 1][indexPath.column] 95 | return cell 96 | } 97 | } 98 | 99 | /// Delegate 100 | 101 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, didSelectItemAt indexPath: IndexPath) { 102 | if case 0 = indexPath.row { 103 | if sortedColumn.column == indexPath.column { 104 | sortedColumn.sorting = sortedColumn.sorting == .ascending ? .descending : .ascending 105 | } else { 106 | sortedColumn = (indexPath.column, .ascending) 107 | } 108 | data.sort { 109 | let ascending = $0[sortedColumn.column] < $1[sortedColumn.column] 110 | return sortedColumn.sorting == .ascending ? ascending : !ascending 111 | } 112 | spreadsheetView.reloadData() 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Examples/ClassData/Sources/data.tsv: -------------------------------------------------------------------------------- 1 | Student Name Gender Class Level Home State Major Extracurricular Activity 2 | Alexandra Female 4. Senior CA English Drama Club 3 | Andrew Male 1. Freshman SD Math Lacrosse 4 | Anna Female 1. Freshman NC English Basketball 5 | Becky Female 2. Sophomore SD Art Baseball 6 | Benjamin Male 4. Senior WI English Basketball 7 | Carl Male 3. Junior MD Art Debate 8 | Carrie Female 3. Junior NE English Track & Field 9 | Dorothy Female 4. Senior MD Math Lacrosse 10 | Dylan Male 1. Freshman MA Math Baseball 11 | Edward Male 3. Junior FL English Drama Club 12 | Ellen Female 1. Freshman WI Physics Drama Club 13 | Fiona Female 1. Freshman MA Art Debate 14 | John Male 3. Junior CA Physics Basketball 15 | Jonathan Male 2. Sophomore SC Math Debate 16 | Joseph Male 1. Freshman AK English Drama Club 17 | Josephine Female 1. Freshman NY Math Debate 18 | Karen Female 2. Sophomore NH English Basketball 19 | Kevin Male 2. Sophomore NE Physics Drama Club 20 | Lisa Female 3. Junior SC Art Lacrosse 21 | Mary Female 2. Sophomore AK Physics Track & Field 22 | Maureen Female 1. Freshman CA Physics Basketball 23 | Nick Male 4. Senior NY Art Baseball 24 | Olivia Female 4. Senior NC Physics Track & Field 25 | Pamela Female 3. Junior RI Math Baseball 26 | Patrick Male 1. Freshman NY Art Lacrosse 27 | Robert Male 1. Freshman CA English Track & Field 28 | Sean Male 1. Freshman NH Physics Track & Field 29 | Stacy Female 1. Freshman NY Math Baseball 30 | Thomas Male 2. Sophomore RI Art Lacrosse 31 | Will Male 4. Senior FL Math Debate -------------------------------------------------------------------------------- /Examples/GanttChart/GanttChart.xcodeproj/xcshareddata/xcschemes/GanttChart.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/8/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/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 | -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/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 | -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/Cells.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cells.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/8/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpreadsheetView 11 | 12 | class HeaderCell: Cell { 13 | let label = UILabel() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | backgroundColor = UIColor(white: 0.95, alpha: 1.0) 18 | 19 | label.frame = bounds 20 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 21 | label.font = UIFont.boldSystemFont(ofSize: 10) 22 | label.textAlignment = .center 23 | label.textColor = .gray 24 | 25 | contentView.addSubview(label) 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | super.init(coder: aDecoder) 30 | } 31 | } 32 | 33 | class TextCell: Cell { 34 | let label = UILabel() 35 | 36 | override init(frame: CGRect) { 37 | super.init(frame: frame) 38 | 39 | label.frame = bounds 40 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 41 | label.font = UIFont.boldSystemFont(ofSize: 10) 42 | label.textAlignment = .center 43 | 44 | contentView.addSubview(label) 45 | } 46 | 47 | required init?(coder aDecoder: NSCoder) { 48 | super.init(coder: aDecoder) 49 | } 50 | } 51 | 52 | class TaskCell: Cell { 53 | let label = UILabel() 54 | 55 | override var frame: CGRect { 56 | didSet { 57 | label.frame = bounds.insetBy(dx: 2, dy: 2) 58 | } 59 | } 60 | 61 | override init(frame: CGRect) { 62 | super.init(frame: frame) 63 | 64 | label.frame = bounds 65 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 66 | label.font = UIFont.boldSystemFont(ofSize: 10) 67 | label.textAlignment = .left 68 | label.numberOfLines = 0 69 | 70 | contentView.addSubview(label) 71 | } 72 | 73 | required init?(coder aDecoder: NSCoder) { 74 | super.init(coder: aDecoder) 75 | } 76 | } 77 | 78 | class ChartBarCell: Cell { 79 | let colorBarView = UIView() 80 | let label = UILabel() 81 | 82 | var color: UIColor = .clear { 83 | didSet { 84 | colorBarView.backgroundColor = color 85 | } 86 | } 87 | 88 | override var frame: CGRect { 89 | didSet { 90 | colorBarView.frame = bounds.insetBy(dx: 2, dy: 2) 91 | } 92 | } 93 | 94 | override init(frame: CGRect) { 95 | super.init(frame: frame) 96 | 97 | contentView.addSubview(colorBarView) 98 | 99 | label.frame = bounds 100 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 101 | label.font = UIFont.boldSystemFont(ofSize: 10) 102 | label.textAlignment = .center 103 | label.textColor = .white 104 | 105 | contentView.addSubview(label) 106 | } 107 | 108 | required init?(coder aDecoder: NSCoder) { 109 | super.init(coder: aDecoder) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Examples/GanttChart/Sources/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 | -------------------------------------------------------------------------------- /Examples/Playground.playground/DataSource.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Examples/Playground.playground/DataSource.o -------------------------------------------------------------------------------- /Examples/Playground.playground/DebugCell.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Examples/Playground.playground/DebugCell.o -------------------------------------------------------------------------------- /Examples/Playground.playground/Pages/Circular Scrolling.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import SpreadsheetView 4 | 5 | let viewController = SpreadsheetViewController() 6 | viewController.title = "Spreadsheet" 7 | let navigationController = UINavigationController(rootViewController: viewController) 8 | 9 | viewController.numberOfColumns = { _ in return 60 } 10 | viewController.numberOfRows = { _ in return 80 } 11 | viewController.widthForColumn = { _ in return 60 } 12 | viewController.heightForRow = { _ in return 40 } 13 | viewController.frozenColumns = { _ in return 1 } 14 | viewController.frozenRows = { _ in return 2 } 15 | viewController.cellForItemAt = { 16 | let cell = $0.dequeueReusableCell(withReuseIdentifier: "cell", for: $1) as! DebugCell 17 | cell.indexPath = $1 18 | return cell 19 | } 20 | 21 | viewController.spreadsheetView.circularScrolling = CircularScrolling.Configuration.both 22 | 23 | viewController.spreadsheetView.register(DebugCell.self, forCellWithReuseIdentifier: "cell") 24 | 25 | PlaygroundPage.current.liveView = navigationController 26 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Pages/Navigation View Controller.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import SpreadsheetView 4 | 5 | let viewController = SpreadsheetViewController() 6 | viewController.title = "Spreadsheet" 7 | let navigationController = UINavigationController(rootViewController: viewController) 8 | 9 | viewController.numberOfColumns = { _ in return 60 } 10 | viewController.numberOfRows = { _ in return 80 } 11 | viewController.widthForColumn = { _ in return 60 } 12 | viewController.heightForRow = { _ in return 40 } 13 | viewController.frozenColumns = { _ in return 1 } 14 | viewController.frozenRows = { _ in return 2 } 15 | viewController.cellForItemAt = { 16 | let cell = $0.dequeueReusableCell(withReuseIdentifier: "cell", for: $1) as! DebugCell 17 | cell.indexPath = $1 18 | return cell 19 | } 20 | 21 | viewController.spreadsheetView.register(DebugCell.self, forCellWithReuseIdentifier: "cell") 22 | 23 | PlaygroundPage.current.liveView = navigationController 24 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Pages/Simple View.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import SpreadsheetView 4 | 5 | let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) 6 | containerView.backgroundColor = .white 7 | 8 | let spreadsheetView = SpreadsheetView() 9 | 10 | spreadsheetView.frame = containerView.bounds 11 | containerView.addSubview(spreadsheetView) 12 | 13 | PlaygroundPage.current.liveView = containerView 14 | 15 | let bgView = UIView() 16 | bgView.frame = spreadsheetView.bounds 17 | bgView.backgroundColor = .lightGray 18 | spreadsheetView.backgroundView = bgView 19 | 20 | spreadsheetView.register(DebugCell.self, forCellWithReuseIdentifier: "cell") 21 | 22 | let dataSource = DataSource() 23 | spreadsheetView.dataSource = dataSource 24 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Pages/View Controller.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | import SpreadsheetView 4 | 5 | let viewController = SpreadsheetViewController() 6 | 7 | viewController.numberOfColumns = { _ in return 60 } 8 | viewController.numberOfRows = { _ in return 80 } 9 | viewController.widthForColumn = { _ in return 60 } 10 | viewController.heightForRow = { _ in return 40 } 11 | viewController.frozenColumns = { _ in return 1 } 12 | viewController.frozenRows = { _ in return 2 } 13 | viewController.cellForItemAt = { 14 | let cell = $0.dequeueReusableCell(withReuseIdentifier: "cell", for: $1) as! DebugCell 15 | cell.indexPath = $1 16 | return cell 17 | } 18 | 19 | viewController.spreadsheetView.register(DebugCell.self, forCellWithReuseIdentifier: "cell") 20 | 21 | PlaygroundPage.current.liveView = viewController 22 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Sources/DataSource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SpreadsheetView 3 | 4 | public class DataSource: SpreadsheetViewDataSource { 5 | public init() {} 6 | 7 | public func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int { 8 | return 40 9 | } 10 | 11 | public func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int { 12 | return 60 13 | } 14 | 15 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat { 16 | return 80 17 | } 18 | 19 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat { 20 | return 50 21 | } 22 | 23 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { 24 | let cell = spreadsheetView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! DebugCell 25 | cell.indexPath = indexPath 26 | return cell 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Sources/DebugCell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SpreadsheetView 3 | 4 | public class DebugCell: Cell { 5 | var label = UILabel() 6 | 7 | public var indexPath: IndexPath! { 8 | didSet { 9 | label.text = "R\(indexPath.row)C\(indexPath.column)" 10 | } 11 | } 12 | 13 | public override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | 16 | label.frame = bounds 17 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 18 | label.font = UIFont.systemFont(ofSize: 8) 19 | label.textAlignment = .center 20 | contentView.addSubview(label) 21 | 22 | let bgView = UIView() 23 | bgView.backgroundColor = .white 24 | backgroundView = bgView 25 | 26 | let sbgView = UIView() 27 | sbgView.backgroundColor = UIColor(red: 0, green: 0, blue: 1, alpha: 0.2) 28 | selectedBackgroundView = sbgView 29 | } 30 | 31 | public required init?(coder aDecoder: NSCoder) { 32 | super.init(coder: aDecoder) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/Playground.playground/Sources/SpreadsheetViewController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SpreadsheetView 3 | 4 | public class SpreadsheetViewController: UIViewController, SpreadsheetViewDataSource, SpreadsheetViewDelegate { 5 | public var spreadsheetView = SpreadsheetView() 6 | 7 | public var numberOfColumns: (_ spreadsheetView: SpreadsheetView) -> Int = { _ in return 0 } 8 | public var numberOfRows: (_ spreadsheetView: SpreadsheetView) -> Int = { _ in return 0 } 9 | 10 | public var widthForColumn: (_ spreadsheetView: SpreadsheetView, _ column: Int) -> CGFloat = { _ in return 0 } 11 | public var heightForRow: (_ spreadsheetView: SpreadsheetView, _ column: Int) -> CGFloat = { _ in return 0 } 12 | 13 | public var cellForItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Cell? = { _ in return nil } 14 | 15 | public var mergedCells: (_ spreadsheetView: SpreadsheetView) -> [CellRange] = { _ in return [] } 16 | 17 | public var frozenColumns: (_ spreadsheetView: SpreadsheetView) -> Int = { _ in return 0 } 18 | public var frozenRows: (_ spreadsheetView: SpreadsheetView) -> Int = { _ in return 0 } 19 | 20 | public var shouldHighlightItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Bool = { _ in return true } 21 | public var didHighlightItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Void = { _ in } 22 | public var didUnhighlightItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Void = { _ in } 23 | public var shouldSelectItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Bool = { _ in return true } 24 | public var shouldDeselectItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Bool = { _ in return true } 25 | public var didSelectItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Void = { _ in } 26 | public var didDeselectItemAt: (_ spreadsheetView: SpreadsheetView, _ indexPath: IndexPath) -> Void = { _ in } 27 | 28 | public override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | spreadsheetView.dataSource = self 32 | spreadsheetView.delegate = self 33 | 34 | spreadsheetView.frame = view.bounds 35 | spreadsheetView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 36 | 37 | let backgroundView = UIView() 38 | backgroundView.backgroundColor = UIColor(white: 0.9, alpha: 1.0) 39 | spreadsheetView.backgroundView = backgroundView 40 | 41 | view.addSubview(spreadsheetView) 42 | } 43 | 44 | // DataSource 45 | 46 | public func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int { 47 | return numberOfColumns(spreadsheetView) 48 | } 49 | 50 | public func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int { 51 | return numberOfRows(spreadsheetView) 52 | } 53 | 54 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat { 55 | return widthForColumn(spreadsheetView, column) 56 | } 57 | 58 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat { 59 | return heightForRow(spreadsheetView, row) 60 | } 61 | 62 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { 63 | return cellForItemAt(spreadsheetView, indexPath) 64 | } 65 | 66 | public func mergedCells(in spreadsheetView: SpreadsheetView) -> [CellRange] { 67 | return mergedCells(spreadsheetView) 68 | } 69 | 70 | public func frozenColumns(in spreadsheetView: SpreadsheetView) -> Int { 71 | return frozenColumns(spreadsheetView) 72 | } 73 | 74 | public func frozenRows(in spreadsheetView: SpreadsheetView) -> Int { 75 | return frozenRows(spreadsheetView) 76 | } 77 | 78 | // Delegate 79 | 80 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { 81 | return shouldHighlightItemAt(spreadsheetView, indexPath) 82 | } 83 | 84 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, didHighlightItemAt indexPath: IndexPath) { 85 | didHighlightItemAt(spreadsheetView, indexPath) 86 | } 87 | 88 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, didUnhighlightItemAt indexPath: IndexPath) { 89 | didUnhighlightItemAt(spreadsheetView, indexPath) 90 | } 91 | 92 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, shouldSelectItemAt indexPath: IndexPath) -> Bool { 93 | return shouldSelectItemAt(spreadsheetView, indexPath) 94 | } 95 | 96 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, shouldDeselectItemAt indexPath: IndexPath) -> Bool { 97 | return shouldDeselectItemAt(spreadsheetView, indexPath) 98 | } 99 | 100 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, didSelectItemAt indexPath: IndexPath) { 101 | didSelectItemAt(spreadsheetView, indexPath) 102 | } 103 | 104 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, didDeselectItemAt indexPath: IndexPath) { 105 | didDeselectItemAt(spreadsheetView, indexPath) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Examples/Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Schedule/Schedule.xcodeproj/xcshareddata/xcschemes/Schedule.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Examples/Schedule/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/7/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Schedule/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/Schedule/Sources/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Examples/Schedule/Sources/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 | -------------------------------------------------------------------------------- /Examples/Schedule/Sources/Cells.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cells.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/8/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpreadsheetView 11 | 12 | class DateCell: Cell { 13 | let label = UILabel() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | label.frame = bounds 19 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 20 | label.font = UIFont.boldSystemFont(ofSize: 10) 21 | label.textAlignment = .center 22 | 23 | contentView.addSubview(label) 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | } 29 | } 30 | 31 | class DayTitleCell: Cell { 32 | let label = UILabel() 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | 37 | label.frame = bounds 38 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 39 | label.font = UIFont.boldSystemFont(ofSize: 14) 40 | label.textAlignment = .center 41 | 42 | contentView.addSubview(label) 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | } 48 | } 49 | 50 | class TimeTitleCell: Cell { 51 | let label = UILabel() 52 | 53 | override init(frame: CGRect) { 54 | super.init(frame: frame) 55 | 56 | label.frame = bounds 57 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 58 | label.font = UIFont.boldSystemFont(ofSize: 12) 59 | label.textAlignment = .center 60 | 61 | contentView.addSubview(label) 62 | } 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | super.init(coder: aDecoder) 66 | } 67 | } 68 | 69 | class TimeCell: Cell { 70 | let label = UILabel() 71 | 72 | override init(frame: CGRect) { 73 | super.init(frame: frame) 74 | 75 | label.frame = bounds 76 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 77 | label.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: UIFontWeightMedium) 78 | label.textAlignment = .right 79 | 80 | contentView.addSubview(label) 81 | } 82 | 83 | override var frame: CGRect { 84 | didSet { 85 | label.frame = bounds.insetBy(dx: 6, dy: 0) 86 | } 87 | } 88 | 89 | required init?(coder aDecoder: NSCoder) { 90 | super.init(coder: aDecoder) 91 | } 92 | } 93 | 94 | class ScheduleCell: Cell { 95 | let label = UILabel() 96 | var color: UIColor = .clear { 97 | didSet { 98 | backgroundView?.backgroundColor = color 99 | } 100 | } 101 | 102 | override var frame: CGRect { 103 | didSet { 104 | label.frame = bounds.insetBy(dx: 4, dy: 0) 105 | } 106 | } 107 | 108 | override init(frame: CGRect) { 109 | super.init(frame: frame) 110 | 111 | backgroundView = UIView() 112 | 113 | label.frame = bounds 114 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 115 | label.font = UIFont.boldSystemFont(ofSize: 12) 116 | label.textAlignment = .left 117 | 118 | contentView.addSubview(label) 119 | } 120 | 121 | required init?(coder aDecoder: NSCoder) { 122 | super.init(coder: aDecoder) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Examples/Schedule/Sources/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 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/11/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 16 | return true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/Timetable/Sources/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 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/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 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/Cells.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cells.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/11/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpreadsheetView 11 | 12 | class HourCell: Cell { 13 | let label = UILabel() 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | label.frame = bounds 18 | 19 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 20 | label.backgroundColor = UIColor(red: 0.220, green: 0.471, blue: 0.871, alpha: 1) 21 | label.font = UIFont.systemFont(ofSize: 12) 22 | label.textColor = .white 23 | label.textAlignment = .center 24 | label.numberOfLines = 2 25 | 26 | addSubview(label) 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | } 32 | } 33 | 34 | class ChannelCell: Cell { 35 | let label = UILabel() 36 | 37 | var channel = "" { 38 | didSet { 39 | label.text = String(channel) 40 | } 41 | } 42 | 43 | override init(frame: CGRect) { 44 | super.init(frame: frame) 45 | label.frame = bounds 46 | label.autoresizingMask = [.flexibleWidth, .flexibleHeight] 47 | label.backgroundColor = .black 48 | label.font = UIFont.boldSystemFont(ofSize: 16) 49 | label.textColor = .lightGray 50 | label.textAlignment = .center 51 | label.numberOfLines = 2 52 | 53 | addSubview(label) 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | super.init(coder: aDecoder) 58 | } 59 | } 60 | 61 | class SlotCell: Cell { 62 | @IBOutlet private weak var minutesLabel: UILabel! 63 | @IBOutlet private weak var titleLabel: UILabel! 64 | @IBOutlet private weak var tableHighlightLabel: UILabel! 65 | 66 | var minutes = 0 { 67 | didSet { 68 | minutesLabel.text = String(format: "%02d", minutes) 69 | } 70 | } 71 | var title = "" { 72 | didSet { 73 | titleLabel.text = title 74 | } 75 | } 76 | var tableHighlight = "" { 77 | didSet { 78 | tableHighlightLabel.text = tableHighlight 79 | } 80 | } 81 | 82 | override init(frame: CGRect) { 83 | super.init(frame: frame) 84 | } 85 | 86 | required init?(coder aDecoder: NSCoder) { 87 | super.init(coder: aDecoder) 88 | } 89 | } 90 | 91 | class BlankCell: Cell { 92 | override init(frame: CGRect) { 93 | super.init(frame: frame) 94 | backgroundColor = UIColor(white: 0.9, alpha: 1) 95 | } 96 | 97 | required init?(coder aDecoder: NSCoder) { 98 | super.init(coder: aDecoder) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/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 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/Models.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Models.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/11/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Table { 12 | var slots = [String: [Slot]]() 13 | 14 | mutating func add(slot: Slot) { 15 | let slots = self[slot.channelId] 16 | if var slots = slots { 17 | slots.append(slot) 18 | self[slot.channelId] = slots 19 | } else { 20 | var slots = [Slot]() 21 | slots.append(slot) 22 | self[slot.channelId] = slots 23 | } 24 | } 25 | 26 | subscript(key: String) -> [Slot]? { 27 | get { 28 | return slots[key] 29 | } 30 | set(newValue) { 31 | return slots[key] = newValue 32 | } 33 | } 34 | } 35 | 36 | struct Channel { 37 | let id: String 38 | let name: String 39 | let order: Int 40 | } 41 | 42 | struct Slot { 43 | let id: String 44 | let title: String 45 | let startAt: Int 46 | let endAt: Int 47 | let tableHighlight: String 48 | let channelId: String 49 | } 50 | -------------------------------------------------------------------------------- /Examples/Timetable/Sources/SlotCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 33 | 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 | -------------------------------------------------------------------------------- /Examples/Timetable/Timetable.xcodeproj/xcshareddata/xcschemes/Timetable.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Framework/Sources/Address.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Address.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 3/16/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Address: Hashable { 12 | let row: Int 13 | let column: Int 14 | let rowIndex: Int 15 | let columnIndex: Int 16 | 17 | var hashValue: Int { 18 | return 32768 * rowIndex + columnIndex 19 | } 20 | 21 | static func ==(lhs: Address, rhs: Address) -> Bool { 22 | return lhs.rowIndex == rhs.rowIndex && lhs.columnIndex == rhs.columnIndex 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Framework/Sources/Array+BinarySearch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+BinarySearch.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/23/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Comparable { 12 | func insertionIndex(of element: Element) -> Int { 13 | var lower = 0 14 | var upper = count - 1 15 | while lower <= upper { 16 | let middle = (lower + upper) / 2 17 | if self[middle] < element { 18 | lower = middle + 1 19 | } else if element < self[middle] { 20 | upper = middle - 1 21 | } else { 22 | return middle 23 | } 24 | } 25 | return lower 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Sources/Borders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Borders.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/8/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct Borders { 12 | public var top: BorderStyle 13 | public var bottom: BorderStyle 14 | public var left: BorderStyle 15 | public var right: BorderStyle 16 | 17 | public static func all(_ style: BorderStyle) -> Borders { 18 | return Borders(top: style, bottom: style, left: style, right: style) 19 | } 20 | } 21 | 22 | public enum BorderStyle { 23 | case none 24 | case solid(width: CGFloat, color: UIColor) 25 | } 26 | 27 | extension BorderStyle: Equatable { 28 | public static func ==(lhs: BorderStyle, rhs: BorderStyle) -> Bool { 29 | switch (lhs, rhs) { 30 | case (.none, .none): 31 | return true 32 | case let (.solid(lhs), .solid(rhs)): 33 | return lhs.width == rhs.width && lhs.color == rhs.color 34 | default: 35 | return false 36 | } 37 | } 38 | } 39 | 40 | final class Border: UIView { 41 | var borders: Borders = .all(.none) 42 | 43 | override init(frame: CGRect) { 44 | super.init(frame: frame) 45 | isUserInteractionEnabled = false 46 | backgroundColor = .clear 47 | layer.zPosition = 1000 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | super.init(coder: aDecoder) 52 | } 53 | 54 | override func draw(_ rect: CGRect) { 55 | guard let context = UIGraphicsGetCurrentContext() else { 56 | return 57 | } 58 | context.setFillColor(UIColor.clear.cgColor) 59 | if case let .solid(width, color) = borders.left { 60 | context.move(to: CGPoint(x: width * 0.5, y: 0)) 61 | context.addLine(to: CGPoint(x: width * 0.5, y: bounds.height)) 62 | context.setLineWidth(width) 63 | context.setStrokeColor(color.cgColor) 64 | context.strokePath() 65 | } 66 | if case let .solid(width, color) = borders.right { 67 | context.move(to: CGPoint(x: bounds.width - width * 0.5, y: 0)) 68 | context.addLine(to: CGPoint(x: bounds.width - width * 0.5, y: bounds.height)) 69 | context.setLineWidth(width) 70 | context.setStrokeColor(color.cgColor) 71 | context.strokePath() 72 | } 73 | if case let .solid(width, color) = borders.top { 74 | context.move(to: CGPoint(x: 0, y: width * 0.5)) 75 | context.addLine(to: CGPoint(x: bounds.width, y: width * 0.5)) 76 | context.setLineWidth(width) 77 | context.setStrokeColor(color.cgColor) 78 | context.strokePath() 79 | } 80 | if case let .solid(width, color) = borders.bottom { 81 | context.move(to: CGPoint(x: 0, y: bounds.height - width * 0.5)) 82 | context.addLine(to: CGPoint(x: bounds.width, y: bounds.height - width * 0.5)) 83 | context.setLineWidth(width) 84 | context.setStrokeColor(color.cgColor) 85 | context.strokePath() 86 | } 87 | } 88 | 89 | override func layoutSubviews() { 90 | super.layoutSubviews() 91 | 92 | if case let .solid(width, _) = borders.left { 93 | frame.origin.x -= width * 0.5 94 | frame.size.width += width * 0.5 95 | } 96 | if case let .solid(width, _) = borders.right { 97 | frame.size.width += width * 0.5 98 | } 99 | if case let .solid(width, _) = borders.top { 100 | frame.origin.y -= width * 0.5 101 | frame.size.height += width * 0.5 102 | } 103 | if case let .solid(width, _) = borders.bottom { 104 | frame.size.height += width * 0.5 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Framework/Sources/Cell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cell.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 3/16/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class Cell: UIView { 12 | public let contentView = UIView() 13 | 14 | public var backgroundView: UIView? { 15 | willSet { 16 | backgroundView?.removeFromSuperview() 17 | } 18 | didSet { 19 | guard let backgroundView = backgroundView else { 20 | return 21 | } 22 | backgroundView.frame = bounds 23 | backgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 24 | insertSubview(backgroundView, at: 0) 25 | } 26 | } 27 | public var selectedBackgroundView: UIView? { 28 | willSet { 29 | selectedBackgroundView?.removeFromSuperview() 30 | } 31 | didSet { 32 | guard let selectedBackgroundView = selectedBackgroundView else { 33 | return 34 | } 35 | selectedBackgroundView.frame = bounds 36 | selectedBackgroundView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 37 | selectedBackgroundView.alpha = 0 38 | if let backgroundView = backgroundView { 39 | insertSubview(selectedBackgroundView, aboveSubview: backgroundView) 40 | } else { 41 | insertSubview(selectedBackgroundView, at: 0) 42 | } 43 | } 44 | } 45 | 46 | open var isHighlighted = false { 47 | didSet { 48 | selectedBackgroundView?.alpha = isHighlighted || isSelected ? 1 : 0 49 | } 50 | } 51 | open var isSelected = false { 52 | didSet { 53 | selectedBackgroundView?.alpha = isSelected ? 1 : 0 54 | } 55 | } 56 | 57 | public var gridlines = Gridlines(top: .default, bottom: .default, left: .default, right: .default) 58 | @available(*, deprecated: 0.6.3, renamed: "gridlines") 59 | public var grids: Gridlines { 60 | get { 61 | return gridlines 62 | } 63 | set { 64 | gridlines = grids 65 | } 66 | } 67 | public var borders = Borders(top: .none, bottom: .none, left: .none, right: .none) { 68 | didSet { 69 | hasBorder = borders.top != .none || borders.bottom != .none || borders.left != .none || borders.right != .none 70 | } 71 | } 72 | var hasBorder = false 73 | 74 | public internal(set) var reuseIdentifier: String? 75 | 76 | var indexPath: IndexPath! 77 | 78 | public override init(frame: CGRect) { 79 | super.init(frame: frame) 80 | setup() 81 | } 82 | 83 | public required init?(coder aDecoder: NSCoder) { 84 | super.init(coder: aDecoder) 85 | setup() 86 | } 87 | 88 | func setup() { 89 | backgroundColor = .white 90 | 91 | contentView.frame = bounds 92 | contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 93 | insertSubview(contentView, at: 0) 94 | } 95 | 96 | open func prepareForReuse() {} 97 | 98 | func setSelected(_ selected: Bool, animated: Bool) { 99 | if animated { 100 | UIView.animate(withDuration: CATransaction.animationDuration()) { 101 | self.isSelected = selected 102 | } 103 | } else { 104 | isSelected = selected 105 | } 106 | } 107 | } 108 | 109 | extension Cell: Comparable { 110 | public static func <(lhs: Cell, rhs: Cell) -> Bool { 111 | return lhs.indexPath < rhs.indexPath 112 | } 113 | } 114 | 115 | final class BlankCell: Cell {} 116 | -------------------------------------------------------------------------------- /Framework/Sources/CellRange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellRange.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 3/16/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class CellRange { 12 | public let from: Location 13 | public let to: Location 14 | 15 | public let columnCount: Int 16 | public let rowCount: Int 17 | 18 | var size: CGSize? 19 | 20 | public convenience init(from: (row: Int, column: Int), to: (row: Int, column: Int)) { 21 | self.init(from: Location(row: from.row, column: from.column), 22 | to: Location(row: to.row, column: to.column)) 23 | } 24 | 25 | public convenience init(from: IndexPath, to: IndexPath) { 26 | self.init(from: Location(row: from.row, column: from.column), 27 | to: Location(row: to.row, column: to.column)) 28 | } 29 | 30 | init(from: Location, to: Location) { 31 | guard from.column <= to.column && from.row <= to.row else { 32 | fatalError("the value of `from` must be less than or equal to the value of `to`") 33 | } 34 | self.from = from 35 | self.to = to 36 | columnCount = to.column - from.column + 1 37 | rowCount = to.row - from.row + 1 38 | } 39 | 40 | public func contains(_ indexPath: IndexPath) -> Bool { 41 | return from.column <= indexPath.column && to.column >= indexPath.column && 42 | from.row <= indexPath.row && to.row >= indexPath.row 43 | } 44 | 45 | public func contains(_ cellRange: CellRange) -> Bool { 46 | return from.column <= cellRange.from.column && to.column >= cellRange.to.column && 47 | from.row <= cellRange.from.row && to.row >= cellRange.to.row 48 | } 49 | } 50 | 51 | extension CellRange: Hashable { 52 | public var hashValue: Int { 53 | return from.hashValue 54 | } 55 | 56 | public static func ==(lhs: CellRange, rhs: CellRange) -> Bool { 57 | return lhs.from == rhs.from 58 | } 59 | } 60 | 61 | extension CellRange: CustomStringConvertible, CustomDebugStringConvertible { 62 | public var description: String { 63 | return "R\(from.row)C\(from.column):R\(to.row)C\(to.column)" 64 | } 65 | 66 | public var debugDescription: String { 67 | return description 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Framework/Sources/Gridlines.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gridlines.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/7/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct Gridlines { 12 | public var top: GridStyle 13 | public var bottom: GridStyle 14 | public var left: GridStyle 15 | public var right: GridStyle 16 | 17 | public static func all(_ style: GridStyle) -> Gridlines { 18 | return Gridlines(top: style, bottom: style, left: style, right: style) 19 | } 20 | } 21 | 22 | @available(*, deprecated: 0.6.3, renamed: "Gridlines") 23 | public typealias Grids = Gridlines 24 | 25 | public enum GridStyle { 26 | case `default` 27 | case none 28 | case solid(width: CGFloat, color: UIColor) 29 | } 30 | 31 | extension GridStyle: Equatable { 32 | public static func ==(lhs: GridStyle, rhs: GridStyle) -> Bool { 33 | switch (lhs, rhs) { 34 | case (.none, .none): 35 | return true 36 | case let (.solid(lhs), .solid(rhs)): 37 | return lhs.width == rhs.width && lhs.color == rhs.color 38 | default: 39 | return false 40 | } 41 | } 42 | } 43 | 44 | final class Gridline: CALayer { 45 | var color: UIColor = .clear { 46 | didSet { 47 | backgroundColor = color.cgColor 48 | } 49 | } 50 | 51 | override init() { 52 | super.init() 53 | } 54 | 55 | override init(layer: Any) { 56 | super.init(layer: layer) 57 | } 58 | 59 | required init?(coder aDecoder: NSCoder) { 60 | super.init(coder: aDecoder) 61 | } 62 | 63 | override func action(forKey event: String) -> CAAction? { 64 | return nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Framework/Sources/IndexPath+Column.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexPath+Column.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/23/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension IndexPath { 12 | public var column: Int { 13 | return section 14 | } 15 | 16 | public init(row: Int, column: Int) { 17 | self.init(row: row, section: column) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Framework/Sources/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 | 0.8.4 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Framework/Sources/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/19/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct Location: Hashable { 12 | public let row: Int 13 | public let column: Int 14 | 15 | init(row: Int, column: Int) { 16 | self.row = row 17 | self.column = column 18 | } 19 | 20 | init(indexPath: IndexPath) { 21 | self.init(row: indexPath.row, column: indexPath.column) 22 | } 23 | 24 | public var hashValue: Int { 25 | return 32768 * row + column 26 | } 27 | 28 | public static func ==(lhs: Location, rhs: Location) -> Bool { 29 | return lhs.row == rhs.row && lhs.column == rhs.column 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Framework/Sources/ReuseQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReuseQueue.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/16/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ReuseQueue where Reusable: NSObject { 12 | var reusableObjects = Set() 13 | 14 | func enqueue(_ reusableObject: Reusable) { 15 | reusableObjects.insert(reusableObject) 16 | } 17 | 18 | func dequeue() -> Reusable? { 19 | if let _ = reusableObjects.first { 20 | return reusableObjects.removeFirst() 21 | } 22 | return nil 23 | } 24 | 25 | func dequeueOrCreate() -> Reusable { 26 | if let _ = reusableObjects.first { 27 | return reusableObjects.removeFirst() 28 | } 29 | return Reusable() 30 | } 31 | } 32 | 33 | final class ReusableCollection: Sequence where Reusable: NSObject { 34 | var pairs = [Address: Reusable]() 35 | var addresses = Set
() 36 | 37 | func contains(_ member: Address) -> Bool { 38 | return addresses.contains(member) 39 | } 40 | 41 | @discardableResult 42 | func insert(_ newMember: Address) -> (inserted: Bool, memberAfterInsert: Address) { 43 | return addresses.insert(newMember) 44 | } 45 | 46 | func subtract(_ other: Set
) { 47 | addresses.subtract(other) 48 | } 49 | 50 | subscript(key: Address) -> Reusable? { 51 | get { 52 | return pairs[key] 53 | } 54 | set(newValue) { 55 | pairs[key] = newValue 56 | } 57 | } 58 | 59 | func makeIterator() -> AnyIterator { 60 | return AnyIterator(pairs.values.makeIterator()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Framework/Sources/ScrollPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollPosition.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/23/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ScrollPosition: OptionSet { 12 | // The vertical positions are mutually exclusive to each other, but are bitwise or-able with the horizontal scroll positions. 13 | // Combining positions from the same grouping (horizontal or vertical) will result in an NSInvalidArgumentException. 14 | public static var top = ScrollPosition(rawValue: 1 << 0) 15 | public static var centeredVertically = ScrollPosition(rawValue: 1 << 1) 16 | public static var bottom = ScrollPosition(rawValue: 1 << 2) 17 | 18 | // Likewise, the horizontal positions are mutually exclusive to each other. 19 | public static var left = ScrollPosition(rawValue: 1 << 3) 20 | public static var centeredHorizontally = ScrollPosition(rawValue: 1 << 4) 21 | public static var right = ScrollPosition(rawValue: 1 << 5) 22 | 23 | public let rawValue: Int 24 | 25 | public init(rawValue: Int) { 26 | self.rawValue = rawValue 27 | } 28 | } 29 | 30 | extension ScrollPosition: CustomStringConvertible, CustomDebugStringConvertible { 31 | public var description: String { 32 | var options = [String]() 33 | if contains(.top) { 34 | options.append(".top") 35 | } 36 | if contains(.centeredVertically) { 37 | options.append(".centeredVertically") 38 | } 39 | if contains(.bottom) { 40 | options.append(".bottom") 41 | } 42 | if contains(.left) { 43 | options.append(".left") 44 | } 45 | if contains(.centeredHorizontally) { 46 | options.append(".centeredHorizontally") 47 | } 48 | if contains(.right) { 49 | options.append(".right") 50 | } 51 | return options.description 52 | } 53 | 54 | public var debugDescription: String { 55 | return description 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Framework/Sources/ScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollView.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 3/16/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ScrollView: UIScrollView, UIGestureRecognizerDelegate { 12 | var columnRecords = [CGFloat]() 13 | var rowRecords = [CGFloat]() 14 | 15 | var visibleCells = ReusableCollection() 16 | var visibleVerticalGridlines = ReusableCollection() 17 | var visibleHorizontalGridlines = ReusableCollection() 18 | var visibleBorders = ReusableCollection() 19 | 20 | typealias TouchHandler = (_ touches: Set, _ event: UIEvent?) -> Void 21 | var touchesBegan: TouchHandler? 22 | var touchesEnded: TouchHandler? 23 | var touchesCancelled: TouchHandler? 24 | 25 | var layoutAttributes = LayoutAttributes(startColumn: 0, startRow: 0, numberOfColumns: 0, numberOfRows: 0, columnCount: 0, rowCount: 0, insets: .zero) 26 | var state = State() 27 | struct State { 28 | var frame = CGRect.zero 29 | var contentSize = CGSize.zero 30 | var contentOffset = CGPoint.zero 31 | } 32 | 33 | var hasDisplayedContent: Bool { 34 | return columnRecords.count > 0 || rowRecords.count > 0 35 | } 36 | 37 | func resetReusableObjects() { 38 | for cell in visibleCells { 39 | cell.removeFromSuperview() 40 | } 41 | for gridline in visibleVerticalGridlines { 42 | gridline.removeFromSuperlayer() 43 | } 44 | for gridline in visibleHorizontalGridlines { 45 | gridline.removeFromSuperlayer() 46 | } 47 | for border in visibleBorders { 48 | border.removeFromSuperview() 49 | } 50 | visibleCells = ReusableCollection() 51 | visibleVerticalGridlines = ReusableCollection() 52 | visibleHorizontalGridlines = ReusableCollection() 53 | visibleBorders = ReusableCollection() 54 | } 55 | 56 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 57 | return gestureRecognizer is UIPanGestureRecognizer 58 | } 59 | 60 | override func touchesShouldBegin(_ touches: Set, with event: UIEvent?, in view: UIView) -> Bool { 61 | return hasDisplayedContent 62 | } 63 | 64 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 65 | guard hasDisplayedContent else { 66 | return 67 | } 68 | touchesBegan?(touches, event) 69 | } 70 | 71 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 72 | guard hasDisplayedContent else { 73 | return 74 | } 75 | touchesEnded?(touches, event) 76 | } 77 | 78 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 79 | guard hasDisplayedContent else { 80 | return 81 | } 82 | touchesCancelled?(touches, event) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView+CirclularScrolling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView+CirclularScrolling.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/1/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SpreadsheetView { 12 | func scrollToHorizontalCenter() { 13 | rowHeaderView.state.contentOffset.x = centerOffset.x 14 | tableView.state.contentOffset.x = centerOffset.x 15 | } 16 | 17 | func scrollToVerticalCenter() { 18 | columnHeaderView.state.contentOffset.y = centerOffset.y 19 | tableView.state.contentOffset.y = centerOffset.y 20 | } 21 | 22 | func recenterHorizontallyIfNecessary() { 23 | let currentOffset = tableView.state.contentOffset 24 | let distance = currentOffset.x - centerOffset.x 25 | let threshold = tableView.state.contentSize.width / 4 26 | if fabs(distance) > threshold { 27 | if distance > 0 { 28 | rowHeaderView.state.contentOffset.x = distance 29 | tableView.state.contentOffset.x = distance 30 | } else { 31 | let offset = centerOffset.x + (centerOffset.x - threshold) 32 | rowHeaderView.state.contentOffset.x = offset 33 | tableView.state.contentOffset.x = offset 34 | } 35 | } 36 | } 37 | 38 | func recenterVerticallyIfNecessary() { 39 | let currentOffset = tableView.state.contentOffset 40 | let distance = currentOffset.y - centerOffset.y 41 | let threshold = tableView.state.contentSize.height / 4 42 | if fabs(distance) > threshold { 43 | if distance > 0 { 44 | columnHeaderView.state.contentOffset.y = distance 45 | tableView.state.contentOffset.y = distance 46 | } else { 47 | let offset = centerOffset.y + (centerOffset.y - threshold) 48 | columnHeaderView.state.contentOffset.y = offset 49 | tableView.state.contentOffset.y = offset 50 | } 51 | } 52 | } 53 | 54 | func determineCircularScrollScalingFactor() -> (horizontal: Int, vertical: Int) { 55 | return (determineHorizontalCircularScrollScalingFactor(), determineVerticalCircularScrollScalingFactor()) 56 | } 57 | 58 | func determineHorizontalCircularScrollScalingFactor() -> Int { 59 | guard circularScrollingOptions.direction.contains(.horizontally) else { 60 | return 1 61 | } 62 | let tableContentWidth = layoutProperties.columnWidth - layoutProperties.frozenColumnWidth 63 | let tableWidth = frame.width - layoutProperties.frozenColumnWidth 64 | var scalingFactor = 3 65 | while tableContentWidth > 0 && Int(tableContentWidth) * scalingFactor < Int(tableWidth) * 3 { 66 | scalingFactor += 3 67 | } 68 | return scalingFactor 69 | } 70 | 71 | func determineVerticalCircularScrollScalingFactor() -> Int { 72 | guard circularScrollingOptions.direction.contains(.vertically) else { 73 | return 1 74 | } 75 | let tableContentHeight = layoutProperties.rowHeight - layoutProperties.frozenRowHeight 76 | let tableHeight = frame.height - layoutProperties.frozenRowHeight 77 | var scalingFactor = 3 78 | while tableContentHeight > 0 && Int(tableContentHeight) * scalingFactor < Int(tableHeight) * 3 { 79 | scalingFactor += 3 80 | } 81 | return scalingFactor 82 | } 83 | 84 | func calculateCenterOffset() -> CGPoint { 85 | var centerOffset = CGPoint.zero 86 | if circularScrollingOptions.direction.contains(.horizontally) { 87 | for column in 0.., _ event: UIEvent?) { 13 | guard currentTouch == nil else { 14 | return 15 | } 16 | currentTouch = touches.first 17 | 18 | NSObject.cancelPreviousPerformRequests(withTarget: self) 19 | unhighlightAllItems() 20 | highlightItems(on: touches) 21 | if !allowsMultipleSelection, 22 | let touch = touches.first, let indexPath = indexPathForItem(at: touch.location(in: self)), 23 | let cell = cellForItem(at: indexPath), cell.isUserInteractionEnabled { 24 | selectedIndexPaths.forEach { 25 | cellsForItem(at: $0).forEach { $0.isSelected = false } 26 | } 27 | } 28 | } 29 | 30 | func touchesEnded(_ touches: Set, _ event: UIEvent?) { 31 | guard let touch = touches.first, touch == currentTouch else { 32 | return 33 | } 34 | 35 | let highlightedItems = highlightedIndexPaths 36 | unhighlightAllItems() 37 | if allowsMultipleSelection, 38 | let touch = touches.first, let indexPath = indexPathForItem(at: touch.location(in: self)), 39 | selectedIndexPaths.contains(indexPath) { 40 | if delegate?.spreadsheetView(self, shouldDeselectItemAt: indexPath) ?? true { 41 | deselectItem(at: indexPath) 42 | } 43 | } else { 44 | selectItems(on: touches, highlightedItems: highlightedItems) 45 | } 46 | 47 | perform(#selector(clearCurrentTouch), with: nil, afterDelay: 0) 48 | } 49 | 50 | func touchesCancelled(_ touches: Set, _ event: UIEvent?) { 51 | unhighlightAllItems() 52 | perform(#selector(restorePreviousSelection), with: touches, afterDelay: 0) 53 | perform(#selector(clearCurrentTouch), with: nil, afterDelay: 0) 54 | } 55 | 56 | func highlightItems(on touches: Set) { 57 | guard allowsSelection else { 58 | return 59 | } 60 | if let touch = touches.first { 61 | if let indexPath = indexPathForItem(at: touch.location(in: self)) { 62 | guard let cell = cellForItem(at: indexPath), cell.isUserInteractionEnabled else { 63 | return 64 | } 65 | if delegate?.spreadsheetView(self, shouldHighlightItemAt: indexPath) ?? true { 66 | highlightedIndexPaths.insert(indexPath) 67 | cellsForItem(at: indexPath).forEach { 68 | $0.isHighlighted = true 69 | } 70 | delegate?.spreadsheetView(self, didHighlightItemAt: indexPath) 71 | } 72 | } 73 | } 74 | } 75 | 76 | private func unhighlightAllItems() { 77 | highlightedIndexPaths.forEach { (indexPath) in 78 | cellsForItem(at: indexPath).forEach { 79 | $0.isHighlighted = false 80 | } 81 | delegate?.spreadsheetView(self, didUnhighlightItemAt: indexPath) 82 | } 83 | highlightedIndexPaths.removeAll() 84 | } 85 | 86 | private func selectItems(on touches: Set, highlightedItems: Set) { 87 | guard allowsSelection else { 88 | return 89 | } 90 | if let touch = touches.first { 91 | if let indexPath = indexPathForItem(at: touch.location(in: self)), highlightedItems.contains(indexPath) { 92 | selectItem(at: indexPath) 93 | } 94 | } 95 | } 96 | 97 | private func selectItem(at indexPath: IndexPath) { 98 | let cells = cellsForItem(at: indexPath) 99 | if !cells.isEmpty && delegate?.spreadsheetView(self, shouldSelectItemAt: indexPath) ?? true { 100 | if !allowsMultipleSelection { 101 | selectedIndexPaths.remove(indexPath) 102 | deselectAllItems() 103 | } 104 | cells.forEach { 105 | $0.isSelected = true 106 | } 107 | delegate?.spreadsheetView(self, didSelectItemAt: indexPath) 108 | selectedIndexPaths.insert(indexPath) 109 | } 110 | } 111 | 112 | private func deselectItem(at indexPath: IndexPath) { 113 | let cells = cellsForItem(at: indexPath) 114 | cells.forEach { 115 | $0.isSelected = false 116 | } 117 | delegate?.spreadsheetView(self, didDeselectItemAt: indexPath) 118 | selectedIndexPaths.remove(indexPath) 119 | } 120 | 121 | private func deselectAllItems() { 122 | selectedIndexPaths.forEach { deselectItem(at: $0) } 123 | } 124 | 125 | @objc func restorePreviousSelection() { 126 | selectedIndexPaths.forEach { 127 | cellsForItem(at: $0).forEach { $0.isSelected = true } 128 | } 129 | } 130 | 131 | @objc func clearCurrentTouch() { 132 | currentTouch = nil 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView+UIScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView+UIScrollView.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/1/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SpreadsheetView { 12 | public var contentOffset: CGPoint { 13 | get { 14 | return tableView.contentOffset 15 | } 16 | set { 17 | tableView.contentOffset = newValue 18 | } 19 | } 20 | 21 | public var scrollIndicatorInsets: UIEdgeInsets { 22 | get { 23 | return overlayView.scrollIndicatorInsets 24 | } 25 | set { 26 | overlayView.scrollIndicatorInsets = newValue 27 | } 28 | } 29 | 30 | public var contentSize: CGSize { 31 | get { 32 | return overlayView.contentSize 33 | } 34 | } 35 | 36 | public var contentInset: UIEdgeInsets { 37 | get { 38 | return rootView.contentInset 39 | } 40 | set { 41 | rootView.contentInset = newValue 42 | overlayView.contentInset = newValue 43 | } 44 | } 45 | 46 | #if swift(>=3.2) 47 | @available(iOS 11.0, *) 48 | public var adjustedContentInset: UIEdgeInsets { 49 | get { 50 | return rootView.adjustedContentInset 51 | } 52 | } 53 | #endif 54 | 55 | public func flashScrollIndicators() { 56 | overlayView.flashScrollIndicators() 57 | } 58 | 59 | public func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { 60 | tableView.setContentOffset(contentOffset, animated: animated) 61 | } 62 | 63 | public func scrollRectToVisible(_ rect: CGRect, animated: Bool) { 64 | tableView.scrollRectToVisible(rect, animated: animated) 65 | } 66 | 67 | func _notifyDidScroll() { 68 | resetScrollViewFrame() 69 | } 70 | 71 | public override func isKind(of aClass: AnyClass) -> Bool { 72 | if #available(iOS 11.0, *) { 73 | return super.isKind(of: aClass) 74 | } else { 75 | return rootView.isKind(of: aClass) 76 | } 77 | } 78 | 79 | public override func forwardingTarget(for aSelector: Selector!) -> Any? { 80 | if #available(iOS 11.0, *) { 81 | return super.forwardingTarget(for: aSelector) 82 | } else { 83 | if overlayView.responds(to: aSelector) { 84 | return overlayView 85 | } else { 86 | return super.forwardingTarget(for: aSelector) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView+UIScrollViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView+UIScrollViewDelegate.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/1/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SpreadsheetView: UIScrollViewDelegate { 12 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 13 | rowHeaderView.delegate = nil 14 | columnHeaderView.delegate = nil 15 | tableView.delegate = nil 16 | defer { 17 | rowHeaderView.delegate = self 18 | columnHeaderView.delegate = self 19 | tableView.delegate = self 20 | } 21 | 22 | if tableView.contentOffset.x < 0 && !stickyColumnHeader { 23 | let offset = tableView.contentOffset.x * -1 24 | cornerView.frame.origin.x = offset 25 | columnHeaderView.frame.origin.x = offset 26 | } else { 27 | cornerView.frame.origin.x = 0 28 | columnHeaderView.frame.origin.x = 0 29 | } 30 | if tableView.contentOffset.y < 0 && !stickyRowHeader { 31 | let offset = tableView.contentOffset.y * -1 32 | cornerView.frame.origin.y = offset 33 | rowHeaderView.frame.origin.y = offset 34 | } else { 35 | cornerView.frame.origin.y = 0 36 | rowHeaderView.frame.origin.y = 0 37 | } 38 | 39 | rowHeaderView.contentOffset.x = tableView.contentOffset.x 40 | columnHeaderView.contentOffset.y = tableView.contentOffset.y 41 | 42 | setNeedsLayout() 43 | } 44 | 45 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 46 | guard let indexPath = pendingSelectionIndexPath else { 47 | return 48 | } 49 | cellsForItem(at: indexPath).forEach { $0.setSelected(true, animated: true) } 50 | delegate?.spreadsheetView(self, didSelectItemAt: indexPath) 51 | pendingSelectionIndexPath = nil 52 | } 53 | 54 | @available(iOS 11.0, *) 55 | public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { 56 | resetScrollViewFrame() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView+UISnapshotting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView+UISnapshotting.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 2017/06/03. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SpreadsheetView { 12 | public override func resizableSnapshotView(from rect: CGRect, afterScreenUpdates afterUpdates: Bool, withCapInsets capInsets: UIEdgeInsets) -> UIView? { 13 | if cornerView.frame.intersects(cornerView.convert(rect, to: self)) { 14 | return cornerView.resizableSnapshotView(from: rect.offsetBy(dx: -cornerView.frame.origin.x, dy: -cornerView.frame.origin.y), 15 | afterScreenUpdates: afterUpdates, 16 | withCapInsets: capInsets) 17 | } 18 | if columnHeaderView.frame.intersects(columnHeaderView.convert(rect, to: self)) { 19 | return columnHeaderView.resizableSnapshotView(from: rect.offsetBy(dx: -columnHeaderView.frame.origin.x, dy: -columnHeaderView.frame.origin.y), 20 | afterScreenUpdates: afterUpdates, 21 | withCapInsets: capInsets) 22 | } 23 | if rowHeaderView.frame.intersects(rowHeaderView.convert(rect, to: self)) { 24 | return rowHeaderView.resizableSnapshotView(from: rect.offsetBy(dx: -rowHeaderView.frame.origin.x, dy: -rowHeaderView.frame.origin.y), 25 | afterScreenUpdates: afterUpdates, 26 | withCapInsets: capInsets) 27 | } 28 | if tableView.frame.intersects(tableView.convert(rect, to: self)) { 29 | return tableView.resizableSnapshotView(from: rect.offsetBy(dx: -tableView.frame.origin.x, dy: -tableView.frame.origin.y), 30 | afterScreenUpdates: afterUpdates, 31 | withCapInsets: capInsets) 32 | } 33 | return super.resizableSnapshotView(from: rect, afterScreenUpdates: afterUpdates, withCapInsets: capInsets) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView+UIViewHierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView+UIViewHierarchy.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/19/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension SpreadsheetView { 12 | public override func insertSubview(_ view: UIView, at index: Int) { 13 | overlayView.insertSubview(view, at: index) 14 | } 15 | 16 | public override func exchangeSubview(at index1: Int, withSubviewAt index2: Int) { 17 | overlayView.exchangeSubview(at: index1, withSubviewAt: index2) 18 | } 19 | 20 | public override func addSubview(_ view: UIView) { 21 | overlayView.addSubview(view) 22 | } 23 | 24 | public override func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView) { 25 | overlayView.insertSubview(view, belowSubview: siblingSubview) 26 | } 27 | 28 | public override func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) { 29 | overlayView.insertSubview(view, aboveSubview: siblingSubview) 30 | } 31 | 32 | public override func bringSubview(toFront view: UIView) { 33 | overlayView.bringSubview(toFront: view) 34 | } 35 | 36 | public override func sendSubview(toBack view: UIView) { 37 | overlayView.sendSubview(toBack: view) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetView.h 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 3/22/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXPORT double SpreadsheetViewVersionNumber; 12 | FOUNDATION_EXPORT const unsigned char SpreadsheetViewVersionString[]; 13 | -------------------------------------------------------------------------------- /Framework/Sources/SpreadsheetViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpreadsheetViewDataSource.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/21/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Implement this protocol to provide data to an `SpreadsheetView`. 12 | public protocol SpreadsheetViewDataSource: class { 13 | /// Asks your data source object for the number of columns in the spreadsheet view. 14 | /// 15 | /// - Parameter spreadsheetView: The spreadsheet view requesting this information. 16 | /// - Returns: The number of columns in `spreadsheetView`. 17 | func numberOfColumns(in spreadsheetView: SpreadsheetView) -> Int 18 | /// Asks the number of rows in spreadsheet view. 19 | /// 20 | /// - Parameter spreadsheetView: The spreadsheet view requesting this information. 21 | /// - Returns: The number of rows in `spreadsheetView`. 22 | func numberOfRows(in spreadsheetView: SpreadsheetView) -> Int 23 | 24 | /// Asks the data source for the width to use for a row in a specified location. 25 | /// 26 | /// - Parameters: 27 | /// - spreadsheetView: The spreadsheet view requesting this information. 28 | /// - column: The index of the column. 29 | /// - Returns: A nonnegative floating-point value that specifies the width (in points) that column should be. 30 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, widthForColumn column: Int) -> CGFloat 31 | /// Asks the data source for the height to use for a row in a specified location. 32 | /// 33 | /// - Parameters: 34 | /// - spreadsheetView: The spreadsheet view requesting this information. 35 | /// - row: The index of the row. 36 | /// - Returns: A nonnegative floating-point value that specifies the height (in points) that row should be. 37 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, heightForRow row: Int) -> CGFloat 38 | 39 | /// Asks your data source object for the view that corresponds to the specified cell in the spreadsheetView. 40 | /// The cell that is returned must be retrieved from a call to `dequeueReusableCell(withReuseIdentifier:for:)` 41 | /// 42 | /// - Parameters: 43 | /// - spreadsheetView: The spreadsheet view requesting this information. 44 | /// - indexPath: The location of the cell 45 | /// - Returns: A cell object to be displayed at the location. 46 | /// If you return nil from this method, the blank cell will be displayed by default. 47 | func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? 48 | 49 | /// Asks your data source object for the array of cell ranges that indicate the range of merged cells in the spreadsheetView. 50 | /// 51 | /// - Parameter spreadsheetView: The spreadsheet view requesting this information. 52 | /// - Returns: An array of the cell ranges indicating the range of merged cells. 53 | func mergedCells(in spreadsheetView: SpreadsheetView) -> [CellRange] 54 | /// Asks your data source object for the number of columns to be frozen as a fixed column header in the spreadsheetView. 55 | /// 56 | /// - Parameter spreadsheetView: The spreadsheet view requesting this information. 57 | /// - Returns: The number of columns to be frozen 58 | func frozenColumns(in spreadsheetView: SpreadsheetView) -> Int 59 | /// Asks your data source object for the number of rows to be frozen as a fixed row header in the spreadsheetView. 60 | /// 61 | /// - Parameter spreadsheetView: The spreadsheet view requesting this information. 62 | /// - Returns: The number of rows to be frozen 63 | func frozenRows(in spreadsheetView: SpreadsheetView) -> Int 64 | } 65 | 66 | extension SpreadsheetViewDataSource { 67 | public func spreadsheetView(_ spreadsheetView: SpreadsheetView, cellForItemAt indexPath: IndexPath) -> Cell? { return nil } 68 | public func mergedCells(in spreadsheetView: SpreadsheetView) -> [CellRange] { return [] } 69 | public func frozenColumns(in spreadsheetView: SpreadsheetView) -> Int { return 0 } 70 | public func frozenRows(in spreadsheetView: SpreadsheetView) -> Int { return 0 } 71 | } 72 | -------------------------------------------------------------------------------- /Framework/SpreadsheetView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Framework/SpreadsheetView.xcodeproj/xcshareddata/xcschemes/HostApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Framework/SpreadsheetView.xcodeproj/xcshareddata/xcschemes/SpreadsheetView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Framework/Tests/CellRangeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellRangeTests.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/15/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SpreadsheetView 11 | 12 | class CellRangeTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | } 20 | 21 | func testCreation() { 22 | XCTAssertEqual(CellRange(from: (2, 3), to: (5, 6)), 23 | CellRange(from: Location(row: 2, column: 3), to: Location(row: 5, column: 6))) 24 | } 25 | 26 | func testContains() { 27 | let cellRange = CellRange(from: (2, 3), to: (6, 12)) 28 | XCTAssertTrue(cellRange.contains(CellRange(from: (4, 5), to: (6, 8)))) 29 | XCTAssertTrue(cellRange.contains(CellRange(from: (2, 3), to: (5, 11)))) 30 | XCTAssertTrue(cellRange.contains(CellRange(from: (2, 3), to: (6, 12)))) 31 | XCTAssertFalse(cellRange.contains(CellRange(from: (2, 3), to: (7, 12)))) 32 | XCTAssertFalse(cellRange.contains(CellRange(from: (2, 3), to: (6, 13)))) 33 | XCTAssertFalse(cellRange.contains(CellRange(from: (1, 3), to: (6, 8)))) 34 | XCTAssertFalse(cellRange.contains(CellRange(from: (2, 2), to: (6, 8)))) 35 | XCTAssertFalse(cellRange.contains(CellRange(from: (1, 2), to: (6, 8)))) 36 | 37 | XCTAssertTrue(cellRange.contains(IndexPath(row: 4, column: 5))) 38 | XCTAssertTrue(cellRange.contains(IndexPath(row: 2, column: 3))) 39 | XCTAssertTrue(cellRange.contains(IndexPath(row: 2, column: 12))) 40 | XCTAssertTrue(cellRange.contains(IndexPath(row: 6, column: 3))) 41 | XCTAssertTrue(cellRange.contains(IndexPath(row: 6, column: 12))) 42 | XCTAssertFalse(cellRange.contains(IndexPath(row: 1, column: 5))) 43 | XCTAssertFalse(cellRange.contains(IndexPath(row: 4, column: 2))) 44 | XCTAssertFalse(cellRange.contains(IndexPath(row: 4, column: 13))) 45 | XCTAssertFalse(cellRange.contains(IndexPath(row: 7, column: 5))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Framework/Tests/CellTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellTests.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/17/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SpreadsheetView 11 | 12 | class CellTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | } 20 | 21 | func testViewHierarchy() { 22 | let cell = Cell() 23 | XCTAssertNil(cell.backgroundView) 24 | XCTAssertNil(cell.selectedBackgroundView) 25 | XCTAssertEqual(cell.subviews, [cell.contentView]) 26 | 27 | let backgroundView = UIView() 28 | cell.backgroundView = backgroundView 29 | XCTAssertEqual(cell.subviews, [backgroundView, cell.contentView]) 30 | 31 | let selectedBackgroundView = UIView() 32 | cell.selectedBackgroundView = selectedBackgroundView 33 | XCTAssertEqual(cell.subviews, [backgroundView, selectedBackgroundView, cell.contentView]) 34 | 35 | cell.backgroundView = nil 36 | XCTAssertEqual(cell.subviews, [selectedBackgroundView, cell.contentView]) 37 | 38 | cell.backgroundView = backgroundView 39 | XCTAssertEqual(cell.subviews, [backgroundView, selectedBackgroundView, cell.contentView]) 40 | 41 | cell.backgroundView = nil 42 | cell.selectedBackgroundView = nil 43 | XCTAssertEqual(cell.subviews, [cell.contentView]) 44 | 45 | let view = UIView() 46 | cell.addSubview(view) 47 | XCTAssertEqual(cell.subviews, [cell.contentView, view]) 48 | 49 | cell.selectedBackgroundView = selectedBackgroundView 50 | XCTAssertEqual(cell.subviews, [selectedBackgroundView, cell.contentView, view]) 51 | 52 | cell.backgroundView = backgroundView 53 | XCTAssertEqual(cell.subviews, [backgroundView, selectedBackgroundView, cell.contentView, view]) 54 | } 55 | 56 | func testHasBorder() { 57 | let cell = Cell() 58 | XCTAssertEqual(cell.borders.top, .none) 59 | XCTAssertEqual(cell.borders.bottom, .none) 60 | XCTAssertEqual(cell.borders.left, .none) 61 | XCTAssertEqual(cell.borders.right, .none) 62 | XCTAssertFalse(cell.hasBorder) 63 | 64 | cell.borders.top = .solid(width: 1, color: .black) 65 | XCTAssertTrue(cell.hasBorder) 66 | 67 | cell.borders.top = .none 68 | cell.borders.bottom = .solid(width: 1, color: .black) 69 | XCTAssertTrue(cell.hasBorder) 70 | 71 | cell.borders.bottom = .none 72 | cell.borders.left = .solid(width: 1, color: .black) 73 | XCTAssertTrue(cell.hasBorder) 74 | 75 | cell.borders.left = .none 76 | cell.borders.right = .solid(width: 1, color: .black) 77 | XCTAssertTrue(cell.hasBorder) 78 | 79 | cell.borders.right = .none 80 | XCTAssertFalse(cell.hasBorder) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Framework/Tests/DataSourceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataSourceTests.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 5/7/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SpreadsheetView 11 | 12 | class DataSourceTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | } 20 | 21 | func testDataSourceProperties() { 22 | let parameters = Parameters() 23 | let viewController = defaultViewController(parameters: parameters) 24 | 25 | showViewController(viewController: viewController) 26 | waitRunLoop() 27 | 28 | guard let _ = viewController.view else { 29 | XCTFail("fails to create root view controller") 30 | return 31 | } 32 | 33 | let spreadsheetView = viewController.spreadsheetView 34 | 35 | XCTAssertNotNil(spreadsheetView.dataSource) 36 | XCTAssertEqual(spreadsheetView.numberOfColumns, parameters.numberOfColumns) 37 | XCTAssertEqual(spreadsheetView.numberOfRows, parameters.numberOfRows) 38 | 39 | XCTAssertEqual(spreadsheetView.mergedCells, parameters.mergedCells) 40 | 41 | XCTAssertEqual(spreadsheetView.frozenColumns, parameters.frozenColumns) 42 | XCTAssertEqual(spreadsheetView.frozenRows, parameters.frozenRows) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Framework/Tests/HelperFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HelperFunctions.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/30/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SpreadsheetView 11 | 12 | func waitRunLoop(secs: TimeInterval = 0) { 13 | RunLoop.main.run(until: Date(timeIntervalSinceNow: secs)) 14 | } 15 | 16 | func defaultViewController(parameters: Parameters) -> SpreadsheetViewController { 17 | let viewController = SpreadsheetViewController() 18 | 19 | viewController.numberOfColumns = { _ in return parameters.numberOfColumns } 20 | viewController.numberOfRows = { _ in return parameters.numberOfRows } 21 | viewController.widthForColumn = { return parameters.columns[$1] } 22 | viewController.heightForRow = { return parameters.rows[$1] } 23 | viewController.frozenColumns = { _ in return parameters.frozenColumns } 24 | viewController.frozenRows = { _ in return parameters.frozenRows } 25 | viewController.mergedCells = { _ in return parameters.mergedCells } 26 | viewController.cellForItemAt = { return $0.dequeueReusableCell(withReuseIdentifier: parameters.cell.reuseIdentifier, for: $1) } 27 | 28 | viewController.spreadsheetView.circularScrolling = parameters.circularScrolling 29 | 30 | viewController.spreadsheetView.intercellSpacing = parameters.intercellSpacing 31 | viewController.spreadsheetView.gridStyle = parameters.gridStyle 32 | viewController.spreadsheetView.register(parameters.cell.class, forCellWithReuseIdentifier: parameters.cell.reuseIdentifier) 33 | 34 | return viewController 35 | } 36 | 37 | func showViewController(viewController: UIViewController) { 38 | let window = UIWindow() 39 | window.backgroundColor = .white 40 | window.rootViewController = viewController 41 | window.makeKeyAndVisible() 42 | } 43 | 44 | func numberOfVisibleColumns(in view: SpreadsheetView, contentOffset: CGPoint = .zero, parameters: Parameters) -> Int { 45 | let contentInset: UIEdgeInsets 46 | if #available(iOS 11.0, *) { 47 | #if swift(>=3.2) 48 | contentInset = view.adjustedContentInset 49 | #else 50 | fatalError("unreachable code") 51 | #endif 52 | } else { 53 | contentInset = view.contentInset 54 | } 55 | let horizontalInset = contentInset.left + contentInset.right 56 | let verticalInset = contentInset.top + contentInset.bottom 57 | 58 | var columnCount = 0 59 | var width: CGFloat = 0 60 | let frame = CGRect(origin: view.frame.origin, 61 | size: CGSize(width: view.frame.width - horizontalInset, height: view.frame.height - verticalInset)) 62 | for columnWidth in parameters.columns { 63 | width += columnWidth + parameters.intercellSpacing.width 64 | if width > contentOffset.x { 65 | columnCount += 1 66 | } 67 | if width + parameters.intercellSpacing.width > contentOffset.x + frame.width { 68 | break 69 | } 70 | } 71 | return columnCount 72 | } 73 | 74 | func numberOfVisibleRows(in view: SpreadsheetView, contentOffset: CGPoint = .zero, parameters: Parameters) -> Int { 75 | let contentInset: UIEdgeInsets 76 | if #available(iOS 11.0, *) { 77 | #if swift(>=3.2) 78 | contentInset = view.adjustedContentInset 79 | #else 80 | fatalError("unreachable code") 81 | #endif 82 | } else { 83 | contentInset = view.contentInset 84 | } 85 | let horizontalInset = contentInset.left + contentInset.right 86 | let verticalInset = contentInset.top + contentInset.bottom 87 | 88 | var rowCount = 0 89 | var height: CGFloat = 0 90 | let frame = CGRect(origin: view.frame.origin, 91 | size: CGSize(width: view.frame.width - horizontalInset, height: view.frame.height - verticalInset)) 92 | for rowHeight in parameters.rows { 93 | height += rowHeight + parameters.intercellSpacing.height 94 | if height > contentOffset.y { 95 | rowCount += 1 96 | } 97 | if height + parameters.intercellSpacing.height > contentOffset.y + frame.height { 98 | break 99 | } 100 | } 101 | return rowCount 102 | } 103 | 104 | func calculateWidth(range: CountableRange, parameters: Parameters) -> CGFloat { 105 | return range.map { return parameters.columns[$0] }.reduce(0) { $0 + $1 + parameters.intercellSpacing.width } + (range.count > 0 ? parameters.intercellSpacing.width : 0) 106 | } 107 | 108 | func calculateHeight(range: CountableRange, parameters: Parameters) -> CGFloat { 109 | return range.map { return parameters.rows[$0] }.reduce(0) { $0 + $1 + parameters.intercellSpacing.height } + (range.count > 0 ? parameters.intercellSpacing.height : 0) 110 | } 111 | 112 | func randomArray(seeds: [T], count: Int) -> [T] { 113 | return (0.. Bool { 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Framework/Tests/HostApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Framework/Tests/HostApp/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 | -------------------------------------------------------------------------------- /Framework/Tests/HostApp/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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Framework/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Framework/Tests/PerformanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerformanceTests.swift 3 | // SpreadsheetView 4 | // 5 | // Created by Kishikawa Katsumi on 4/30/17. 6 | // Copyright © 2017 Kishikawa Katsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SpreadsheetView 11 | 12 | class PerformanceTests: XCTestCase { 13 | override func setUp() { 14 | super.setUp() 15 | } 16 | 17 | override func tearDown() { 18 | super.tearDown() 19 | } 20 | 21 | func testHashPerformance1() { 22 | var set = Set() 23 | measure { 24 | for r in 0..<400 { 25 | for c in 0..<400 { 26 | set.insert(Location(row: r, column: c)) 27 | } 28 | } 29 | } 30 | } 31 | 32 | func testHashPerformance2() { 33 | var set = Set
() 34 | measure { 35 | for r in 0..<400 { 36 | for c in 0..<400 { 37 | set.insert(Address(row: r, column: c, rowIndex: r, columnIndex: c)) 38 | } 39 | } 40 | } 41 | } 42 | 43 | func testHashPerformance3() { 44 | var set = Set() 45 | measure { 46 | for r in 0..<1000 { 47 | for c in 0..<1000 { 48 | set.insert(IndexPath(row: r, section: c)) 49 | } 50 | } 51 | } 52 | } 53 | 54 | func testCellRangePerformance1() { 55 | var set = Set() 56 | measure { 57 | for r in 0..<400 { 58 | for c in 0..<400 { 59 | set.insert(CellRange(from: (r, c), to: (r + 1, c + 1))) 60 | } 61 | } 62 | } 63 | } 64 | 65 | func testCellRangePerformance2() { 66 | var set = Set() 67 | measure { 68 | for r in 0..<400 { 69 | for c in 0..<400 { 70 | set.insert(IndexPathCellRange(from: (r, c), to: (r + 1, c + 1))) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func testForLoop() { 77 | var mergedCellLayouts = [Location: CellRange]() 78 | var mergedCells = [CellRange]() 79 | for r in 0..<100 { 80 | for c in 0..<100 { 81 | mergedCells.append(CellRange(from: (r, c), to: (r + 1, c + 1))) 82 | } 83 | } 84 | measure { 85 | for mergedCell in mergedCells { 86 | for column in mergedCell.from.column...mergedCell.to.column { 87 | for row in mergedCell.from.row...mergedCell.to.row { 88 | mergedCellLayouts[Location(row: row, column: column)] = mergedCell 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | func testForEach() { 96 | var mergedCellLayouts = [Location: CellRange]() 97 | var mergedCells = [CellRange]() 98 | for r in 0..<100 { 99 | for c in 0..<100 { 100 | mergedCells.append(CellRange(from: (r, c), to: (r + 1, c + 1))) 101 | } 102 | } 103 | measure { 104 | mergedCells.forEach { (mergedCell) in 105 | (mergedCell.from.column...mergedCell.to.column).forEach { (column) in 106 | (mergedCell.from.row...mergedCell.to.row).forEach { (row) in 107 | mergedCellLayouts[Location(row: row, column: column)] = mergedCell 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | func testSum() { 115 | let numbers = [Int](repeating: 20, count: 100000) 116 | measure { 117 | var sum = 0 118 | for n in numbers { 119 | sum += n 120 | } 121 | } 122 | } 123 | 124 | func testReduce() { 125 | let numbers = [Int](repeating: 20, count: 100000) 126 | measure { 127 | let _ = numbers.reduce(0) { $0 + $1 } 128 | } 129 | } 130 | } 131 | 132 | public final class IndexPathCellRange: NSObject { 133 | public let from: IndexPath 134 | public let to: IndexPath 135 | 136 | public let columnCount: Int 137 | public let rowCount: Int 138 | 139 | var size: CGSize? 140 | 141 | init(from: IndexPath, to: IndexPath) { 142 | guard from.column <= to.column && from.row <= to.row else { 143 | fatalError("The value of `from` must be less than or equal to the value of `to`") 144 | } 145 | self.from = from 146 | self.to = to 147 | columnCount = to.column - from.column + 1 148 | rowCount = to.row - from.row + 1 149 | } 150 | 151 | public convenience init(from: (row: Int, column: Int), to: (row: Int, column: Int)) { 152 | self.init(from: IndexPath(row: from.row, column: from.column), 153 | to: IndexPath(row: to.row, column: to.column)) 154 | } 155 | 156 | @available(*, unavailable) 157 | @objc(initWithFromIndexPath:toIndexPath:) 158 | public convenience init(from: NSIndexPath, to: NSIndexPath) { 159 | self.init(from: from as IndexPath, to: to as IndexPath) 160 | } 161 | 162 | @available(*, unavailable) 163 | @objc(cellRangeFromIndexPath:toIndexPath:) 164 | public class func cellRange(from: NSIndexPath, to: NSIndexPath) -> IndexPathCellRange { 165 | return self.init(from: from as IndexPath, to: to as IndexPath) 166 | } 167 | 168 | public func contains(indexPath: IndexPath) -> Bool { 169 | return indexPath.row >= from.row && indexPath.row <= to.row && 170 | indexPath.column >= from.column && indexPath.column <= to.column 171 | } 172 | } 173 | 174 | extension IndexPathCellRange { 175 | public override var description: String { 176 | return "R\(from.row)C\(from.column):R\(to.row)C\(to.column)" 177 | } 178 | 179 | public override var debugDescription: String { 180 | return description 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Framework/docs/Classes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classes Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Docs (68% documented)

17 |

View on GitHub

18 |
19 |
20 |
21 | 26 |
27 |
28 | 51 |
52 |
53 |
54 |

Classes

55 |

The following classes are available globally.

56 | 57 |
58 |
59 |
60 |
    61 |
  • 62 |
    63 | 64 | 65 | 66 | SpreadsheetView 67 | 68 |
    69 |
    70 |
    71 |
    72 |
    73 |
    74 |

    Undocumented

    75 | 76 | See more 77 |
    78 |
    79 |
    80 |
  • 81 |
82 |
83 |
84 |
85 | 89 |
90 |
91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Framework/docs/Protocols.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Protocols Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Docs (68% documented)

17 |

View on GitHub

18 |
19 |
20 |
21 | 26 |
27 |
28 | 51 |
52 |
53 |
54 |

Protocols

55 |

The following protocols are available globally.

56 | 57 |
58 |
59 |
60 |
    61 |
  • 62 |
    63 | 64 | 65 | 66 | SpreadsheetViewDelegate 67 | 68 |
    69 |
    70 |
    71 |
    72 |
    73 |
    74 |

    The SpreadsheetViewDelegate protocol defines methods that allow you to manage the selection and 75 | highlighting of cells in a spreadsheet view and to perform actions on those cells. 76 | The methods of this protocol are all optional.

    77 | 78 | See more 79 |
    80 |
    81 |

    Declaration

    82 |
    83 |

    Swift

    84 |
    public protocol SpreadsheetViewDelegate: class
    85 | 86 |
    87 |
    88 |
    89 |
    90 |
  • 91 |
92 |
93 |
94 |
    95 |
  • 96 |
    97 | 98 | 99 | 100 | SpreadsheetViewDataSource 101 | 102 |
    103 |
    104 |
    105 |
    106 |
    107 |
    108 |

    Implement this protocol to provide data to an SpreadsheetView.

    109 | 110 | See more 111 |
    112 |
    113 |

    Declaration

    114 |
    115 |

    Swift

    116 |
    public protocol SpreadsheetViewDataSource: class
    117 | 118 |
    119 |
    120 |
    121 |
    122 |
  • 123 |
124 |
125 |
126 |
127 | 131 |
132 |
133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /Framework/docs/Resources/Border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/Border.png -------------------------------------------------------------------------------- /Framework/docs/Resources/BothHeaders.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/BothHeaders.gif -------------------------------------------------------------------------------- /Framework/docs/Resources/CircularScrolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/CircularScrolling.gif -------------------------------------------------------------------------------- /Framework/docs/Resources/ColumnHeader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/ColumnHeader.gif -------------------------------------------------------------------------------- /Framework/docs/Resources/DailySchedule_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/DailySchedule_landscape.png -------------------------------------------------------------------------------- /Framework/docs/Resources/DailySchedule_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/DailySchedule_portrait.png -------------------------------------------------------------------------------- /Framework/docs/Resources/GanttChart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/GanttChart.png -------------------------------------------------------------------------------- /Framework/docs/Resources/Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/Grid.png -------------------------------------------------------------------------------- /Framework/docs/Resources/IntercellSpacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/IntercellSpacing.png -------------------------------------------------------------------------------- /Framework/docs/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/Logo.png -------------------------------------------------------------------------------- /Framework/docs/Resources/MergedCells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/MergedCells.png -------------------------------------------------------------------------------- /Framework/docs/Resources/RowHeader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/RowHeader.gif -------------------------------------------------------------------------------- /Framework/docs/Resources/Timetable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/Resources/Timetable.png -------------------------------------------------------------------------------- /Framework/docs/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation68%68% -------------------------------------------------------------------------------- /Framework/docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy. 7 | CFBundleName 8 | 9 | DocSetPlatformFamily 10 | 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/Classes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classes Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Docs (68% documented)

17 |

View on GitHub

18 |
19 |
20 |
21 | 26 |
27 |
28 | 51 |
52 |
53 |
54 |

Classes

55 |

The following classes are available globally.

56 | 57 |
58 |
59 |
60 |
    61 |
  • 62 |
    63 | 64 | 65 | 66 | SpreadsheetView 67 | 68 |
    69 |
    70 |
    71 |
    72 |
    73 |
    74 |

    Undocumented

    75 | 76 | See more 77 |
    78 |
    79 |
    80 |
  • 81 |
82 |
83 |
84 |
85 | 89 |
90 |
91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /Framework/docs/docsets/.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/docsets/.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Framework/docs/docsets/.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/docsets/.tgz -------------------------------------------------------------------------------- /Framework/docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/img/carat.png -------------------------------------------------------------------------------- /Framework/docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/img/dash.png -------------------------------------------------------------------------------- /Framework/docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Framework/docs/img/gh.png -------------------------------------------------------------------------------- /Framework/docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rake' 4 | gem 'xcjobs' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | rake (12.1.0) 5 | xcjobs (0.10.0) 6 | 7 | PLATFORMS 8 | ruby 9 | 10 | DEPENDENCIES 11 | rake 12 | xcjobs 13 | 14 | BUNDLED WITH 15 | 1.15.4 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kishikawa Katsumi 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'xcjobs' 2 | 3 | namespace :test do 4 | desc 'test on simulator' 5 | XCJobs::Test.new("simulator") do |t| 6 | configuration = ENV['CONFIGURATION'] || 'Release' 7 | destinations = eval(ENV['DESTINATIONS'] || '[]') 8 | testcase = ENV['TESTCASE'] 9 | swift_version = ENV['SWIFT_VERSION'] || '4.0' 10 | 11 | t.workspace = 'SpreadsheetView' 12 | t.scheme = 'SpreadsheetView' 13 | t.sdk = 'iphonesimulator' 14 | t.configuration = configuration 15 | 16 | destinations.each { |destination| t.add_destination(destination) } 17 | t.add_only_testing("SpreadsheetViewTests/#{testcase}") if testcase 18 | t.add_build_option('-enableCodeCoverage', 'YES') 19 | t.add_build_setting('ENABLE_TESTABILITY', 'YES') 20 | t.add_build_setting('ONLY_ACTIVE_ARCH', 'NO') 21 | t.add_build_setting('SWIFT_VERSION', swift_version) 22 | t.build_dir = 'build' 23 | t.after_action do 24 | build_coverage_reports() 25 | end 26 | end 27 | end 28 | 29 | namespace 'build-for-testing' do 30 | desc 'build for testing' 31 | XCJobs::BuildForTesting.new("simulator") do |t| 32 | configuration = ENV['CONFIGURATION'] || 'Release' 33 | 34 | t.workspace = 'SpreadsheetView' 35 | t.scheme = 'SpreadsheetView' 36 | t.sdk = 'iphonesimulator' 37 | t.configuration = configuration 38 | t.add_build_option('-enableCodeCoverage', 'YES') 39 | t.add_build_setting('ENABLE_TESTABILITY', 'YES') 40 | t.add_build_setting('ONLY_ACTIVE_ARCH', 'NO') 41 | t.build_dir = 'build' 42 | t.hide_shell_script_environment = true 43 | end 44 | end 45 | 46 | namespace 'test-without-building' do 47 | desc 'test on simulator without building' 48 | XCJobs::TestWithoutBuilding.new("simulator") do |t| 49 | configuration = ENV['CONFIGURATION'] || 'Release' 50 | destinations = eval(ENV['DESTINATIONS'] || '[]') 51 | testcase = ENV['TESTCASE'] 52 | 53 | t.workspace = 'SpreadsheetView' 54 | t.scheme = 'SpreadsheetView' 55 | t.sdk = 'iphonesimulator' 56 | t.configuration = configuration 57 | destinations.each { |destination| t.add_destination(destination) } 58 | t.add_only_testing("SpreadsheetViewTests/#{testcase}") if testcase 59 | t.add_build_option('-enableCodeCoverage', 'YES') 60 | t.build_dir = 'build' 61 | t.after_action do 62 | build_coverage_reports() 63 | end 64 | end 65 | end 66 | 67 | def build_coverage_reports() 68 | project_name = 'SpreadsheetView' 69 | profdata = Dir.glob(File.join('build', '/**/Coverage.profdata')).first 70 | Dir.glob(File.join('build', "/**/#{project_name}")) do |target| 71 | output = `xcrun llvm-cov report -instr-profile "#{profdata}" "#{target}" -arch=x86_64` 72 | if $?.success? 73 | puts output 74 | `xcrun llvm-cov show -instr-profile "#{profdata}" "#{target}" -arch=x86_64 -use-color=0 > coverage.txt` 75 | break 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /Resources/Border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/Border.png -------------------------------------------------------------------------------- /Resources/BothHeaders.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/BothHeaders.gif -------------------------------------------------------------------------------- /Resources/CircularScrolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/CircularScrolling.gif -------------------------------------------------------------------------------- /Resources/ColumnHeader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/ColumnHeader.gif -------------------------------------------------------------------------------- /Resources/DailySchedule_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/DailySchedule_landscape.png -------------------------------------------------------------------------------- /Resources/DailySchedule_portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/DailySchedule_portrait.png -------------------------------------------------------------------------------- /Resources/GanttChart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/GanttChart.png -------------------------------------------------------------------------------- /Resources/Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/Grid.png -------------------------------------------------------------------------------- /Resources/IntercellSpacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/IntercellSpacing.png -------------------------------------------------------------------------------- /Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/Logo.png -------------------------------------------------------------------------------- /Resources/MergedCells.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/MergedCells.png -------------------------------------------------------------------------------- /Resources/RowHeader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/RowHeader.gif -------------------------------------------------------------------------------- /Resources/Timetable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/394c0dda30833fa0a5718942055dbcbac60bfc05/Resources/Timetable.png -------------------------------------------------------------------------------- /SpreadsheetView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SpreadsheetView' 3 | s.version = '0.8.4' 4 | s.summary = 'Full configurable spreadsheet view user interfaces for iOS applications.' 5 | s.description = <<-DESC 6 | Full configurable spreadsheet view user interfaces for iOS applications. With this framework, 7 | you can easily create complex layouts like schedule, gantt chart or timetable as if you are using Excel. 8 | 9 | Features 10 | - Fixed column and row headers 11 | - Merge cells 12 | - Circular infinite scrolling automatically 13 | - Customize grids and borders for each cell 14 | - Customize inter cell spacing vertically and horizontally 15 | - Fast scrolling, memory efficient 16 | - `UICollectionView` like API 17 | - Well unit tested 18 | DESC 19 | s.homepage = 'https://github.com/kishikawakatsumi/SpreadsheetView' 20 | s.screenshots = 'https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/master/Resources/GanttChart.png', 'https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/master/Resources/Timetable.png', 'https://raw.githubusercontent.com/kishikawakatsumi/SpreadsheetView/master/Resources/DailySchedule_portrait.png' 21 | s.ios.deployment_target = '8.0' 22 | s.source_files = 'Framework/Sources/*.swift' 23 | s.pod_target_xcconfig = { 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 'APPLICATION_EXTENSION_API_ONLY' => 'YES' } 24 | s.frameworks = 'UIKit' 25 | s.source = { :git => 'https://github.com/kishikawakatsumi/SpreadsheetView.git', :tag => "v#{s.version}" } 26 | s.license = { :type => 'MIT', :file => 'LICENSE' } 27 | s.author = { 'Kishikawa Katsumi' => 'kishikawakatsumi@mac.com' } 28 | s.social_media_url = 'https://twitter.com/k_katsumi' 29 | end 30 | -------------------------------------------------------------------------------- /SpreadsheetView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 11 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 25 | 26 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------