├── .hound.yml
├── Tests
├── Assets
│ ├── icon.png
│ └── icon-highlighted.png
├── CustomTypes.swift
├── Model
│ ├── SectionSpec.swift
│ ├── IconSpec.swift
│ ├── DetailTextSpec.swift
│ └── SubtitleSpec.swift
├── ViewController
│ └── QuickTableViewControllerSpec.swift
├── View
│ ├── ReusableSpec.swift
│ └── ConfigurableSpec.swift
└── Row
│ ├── TapActionRowSpec.swift
│ ├── SwitchRowSpec.swift
│ └── OptionRowSpec.swift
├── Example-iOS
├── Assets.xcassets
│ ├── Contents.json
│ ├── iconmonstr-gear.imageset
│ │ ├── iconmonstr-gear.png
│ │ ├── iconmonstr-gear@2x.png
│ │ ├── iconmonstr-gear@3x.png
│ │ └── Contents.json
│ ├── iconmonstr-globe.imageset
│ │ ├── iconmonstr-globe.png
│ │ ├── iconmonstr-globe@2x.png
│ │ ├── iconmonstr-globe@3x.png
│ │ └── Contents.json
│ ├── iconmonstr-time.imageset
│ │ ├── iconmonstr-time.png
│ │ ├── iconmonstr-time@2x.png
│ │ ├── iconmonstr-time@3x.png
│ │ └── Contents.json
│ ├── iconmonstr-x-mark.imageset
│ │ ├── iconmonstr-x-mark.png
│ │ ├── iconmonstr-x-mark@2x.png
│ │ ├── iconmonstr-x-mark@3x.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── AppDelegate.swift
├── UINibs
│ ├── OptionCell.xib
│ ├── SwitchCell.xib
│ ├── TapActionCell.xib
│ └── UITableViewCell.xib
├── ViewControllers
│ ├── AppearanceViewController.swift
│ ├── RootViewController.swift
│ ├── DynamicTableViewController.swift
│ ├── DynamicTableView.swift
│ ├── ExampleViewController.swift
│ └── CustomizationViewController.swift
└── Base.lproj
│ └── LaunchScreen.xib
├── Example-tvOS
├── Assets.xcassets
│ ├── Contents.json
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── Launch Image.launchimage
│ │ └── Contents.json
├── Info.plist
├── AppDelegate.swift
└── ExampleViewController.swift
├── Gemfile
├── .codecov.yml
├── scripts
├── swiftlint.rb
└── push-docs.sh
├── QuickTableViewController.xcworkspace
├── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
└── contents.xcworkspacedata
├── Podfile
├── .jazzy.yml
├── Podfile.lock
├── .gitignore
├── Example-iOSUITests
├── Info.plist
└── ExampleUITests.swift
├── Example-tvOSUITests
├── Info.plist
└── ExampleUITests.swift
├── QuickTableViewController
├── Info-tvOSTests.plist
├── Info-iOSTests.plist
├── Info-tvOS.plist
├── Info-iOS.plist
└── QuickTableViewController.h
├── QuickTableViewController.podspec
├── .swiftlint.yml
├── Dangerfile
├── LICENSE
├── .travis.yml
├── Source
├── Protocol
│ ├── Row.swift
│ ├── Configurable.swift
│ ├── RowStyle.swift
│ ├── RowCompatible.swift
│ └── Reusable.swift
├── Model
│ ├── Section.swift
│ ├── DetailText.swift
│ ├── Icon.swift
│ ├── Subtitle.swift
│ ├── Deprecated.swift
│ └── RadioSection.swift
├── Views
│ ├── TapActionCell.swift
│ └── SwitchCell.swift
└── Rows
│ ├── TapActionRow.swift
│ ├── OptionRow.swift
│ ├── SwitchRow.swift
│ └── NavigationRow.swift
├── Package.swift
├── Makefile
├── Rakefile
├── QuickTableViewController.xcodeproj
└── xcshareddata
│ └── xcschemes
│ ├── Example-iOS.xcscheme
│ ├── Example-tvOS.xcscheme
│ ├── QuickTableViewController-tvOS.xcscheme
│ └── QuickTableViewController-iOS.xcscheme
└── Gemfile.lock
/.hound.yml:
--------------------------------------------------------------------------------
1 | rubocop:
2 | enabled: false
3 | swiftlint:
4 | enabled: true
5 | config_file: .swiftlint.yml
6 |
--------------------------------------------------------------------------------
/Tests/Assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Tests/Assets/icon.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Tests/Assets/icon-highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Tests/Assets/icon-highlighted.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gem "cocoapods"
4 | gem "danger"
5 | gem "jazzy", ">= 0.9.0"
6 | gem "rake"
7 | gem "xcpretty"
8 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | patch: off
4 | comment:
5 | layout: "diff, files"
6 | ignore:
7 | - "Carthage/**/*"
8 | - "Example*/**/*"
9 | - "Tests/**/*"
10 | - "Pods/**/*"
11 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear@2x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/iconmonstr-gear@3x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time@2x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/iconmonstr-time@3x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe@2x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/iconmonstr-globe@3x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark@2x.png
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twodayslate/QuickTableViewController/develop/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/iconmonstr-x-mark@3x.png
--------------------------------------------------------------------------------
/scripts/swiftlint.rb:
--------------------------------------------------------------------------------
1 | #!/usr/local/env ruby
2 |
3 | not_ci = !ENV["TRAVIS"] || ENV["TRAVIS"]&.empty?
4 |
5 | if not_ci
6 | system "${PODS_ROOT}/SwiftLint/swiftlint --config .swiftlint.yml"
7 | else
8 | puts "Skip SwiftLint"
9 | end
10 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/QuickTableViewController.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/QuickTableViewController.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/QuickTableViewController.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/scripts/push-docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd docs && pwd
4 |
5 | if [ "${TRAVIS_BRANCH}" = "master" ] && [ -n "$DANGER_GITHUB_API_TOKEN" ]; then
6 | echo "Updating gh-pages"
7 | git remote add upstream "https://${GH_PAGES_GITHUB_API_TOKEN}@github.com/bcylin/QuickTableViewController.git"
8 | git push --quiet upstream HEAD:gh-pages
9 | git remote remove upstream
10 | else
11 | echo "Skip gh-pages updates on ${TRAVIS_BRANCH}"
12 | fi
13 |
14 | cd -
15 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | install! "cocoapods", generate_multiple_pod_projects: true
2 | inhibit_all_warnings!
3 | use_frameworks!
4 |
5 | workspace "QuickTableViewController"
6 | project "QuickTableViewController"
7 |
8 | def linter
9 | pod "SwiftLint", podspec: "https://raw.githubusercontent.com/CocoaPods/Specs/master/Specs/4/0/1/SwiftLint/0.32.0/SwiftLint.podspec.json"
10 | end
11 |
12 | target "QuickTableViewController-iOSTests" do
13 | platform :ios, "8.0"
14 | linter
15 | end
16 |
--------------------------------------------------------------------------------
/.jazzy.yml:
--------------------------------------------------------------------------------
1 | clean: true
2 | author: bcylin
3 | author_url: https://github.com/bcylin
4 | github_url: https://github.com/bcylin/QuickTableViewController
5 | github_file_prefix: https://github.com/bcylin/QuickTableViewController/blob/v1.2.1
6 | xcodebuild_arguments: [-project, QuickTableViewController.xcodeproj, -scheme, QuickTableViewController-iOS]
7 | module: QuickTableViewController
8 | module_version: 1.2.1
9 | output: docs/output
10 | theme: fullwidth
11 | skip_undocumented: true
12 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - SwiftLint (0.32.0)
3 |
4 | DEPENDENCIES:
5 | - SwiftLint (from `https://raw.githubusercontent.com/CocoaPods/Specs/master/Specs/4/0/1/SwiftLint/0.32.0/SwiftLint.podspec.json`)
6 |
7 | EXTERNAL SOURCES:
8 | SwiftLint:
9 | :podspec: https://raw.githubusercontent.com/CocoaPods/Specs/master/Specs/4/0/1/SwiftLint/0.32.0/SwiftLint.podspec.json
10 |
11 | SPEC CHECKSUMS:
12 | SwiftLint: 009a898ef2a1c851f45e1b59349bf6ff2ddc990d
13 |
14 | PODFILE CHECKSUM: 96aa123a793662bd2268447e11f25f81eafaaaef
15 |
16 | COCOAPODS: 1.8.4
17 |
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-gear.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iconmonstr-gear.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iconmonstr-gear@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iconmonstr-gear@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-globe.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iconmonstr-globe.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iconmonstr-globe@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iconmonstr-globe@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-time.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iconmonstr-time.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iconmonstr-time@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iconmonstr-time@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/Launch Image.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "landscape",
5 | "idiom" : "tv",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "11.0",
8 | "scale" : "2x"
9 | },
10 | {
11 | "orientation" : "landscape",
12 | "idiom" : "tv",
13 | "extent" : "full-screen",
14 | "minimum-system-version" : "9.0",
15 | "scale" : "1x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/Example-iOS/Assets.xcassets/iconmonstr-x-mark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "iconmonstr-x-mark.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "iconmonstr-x-mark@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "iconmonstr-x-mark@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # https://github.com/github/gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 | test_output
26 | docs
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 | *.ipa
31 |
32 | ## CocoaPods
33 | Pods/
34 |
35 | ## Carthage
36 | Carthage/
37 |
--------------------------------------------------------------------------------
/Example-iOSUITests/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.2.1
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example-tvOSUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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.2.1
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/QuickTableViewController/Info-tvOSTests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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.2.1
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example-tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "size" : "1280x768",
5 | "idiom" : "tv",
6 | "filename" : "App Icon - App Store.imagestack",
7 | "role" : "primary-app-icon"
8 | },
9 | {
10 | "size" : "400x240",
11 | "idiom" : "tv",
12 | "filename" : "App Icon.imagestack",
13 | "role" : "primary-app-icon"
14 | },
15 | {
16 | "size" : "2320x720",
17 | "idiom" : "tv",
18 | "filename" : "Top Shelf Image Wide.imageset",
19 | "role" : "top-shelf-image-wide"
20 | },
21 | {
22 | "size" : "1920x720",
23 | "idiom" : "tv",
24 | "filename" : "Top Shelf Image.imageset",
25 | "role" : "top-shelf-image"
26 | }
27 | ],
28 | "info" : {
29 | "version" : 1,
30 | "author" : "xcode"
31 | }
32 | }
--------------------------------------------------------------------------------
/QuickTableViewController/Info-iOSTests.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.2.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/QuickTableViewController.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "QuickTableViewController"
3 | s.version = "1.2.1"
4 | s.summary = "A simple way to create a UITableView for settings."
5 | s.screenshots = "https://bcylin.github.io/QuickTableViewController/img/screenshot-1.png",
6 | "https://bcylin.github.io/QuickTableViewController/img/screenshot-2.png"
7 | s.homepage = "https://github.com/bcylin/QuickTableViewController"
8 | s.license = { type: "MIT", file: "LICENSE" }
9 | s.author = "bcylin"
10 |
11 | s.swift_version = "4.2"
12 | s.ios.deployment_target = "8.0"
13 | s.tvos.deployment_target = "9.0"
14 |
15 | s.source = { git: "https://github.com/bcylin/QuickTableViewController.git", tag: "v#{s.version}" }
16 | s.source_files = "Source/**/*.swift"
17 | s.requires_arc = true
18 | end
19 |
--------------------------------------------------------------------------------
/QuickTableViewController/Info-tvOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.2.1
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/QuickTableViewController/Info-iOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.2.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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.2.1
19 | CFBundleVersion
20 | 101
21 | LSRequiresIPhoneOS
22 |
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 | UIUserInterfaceStyle
28 | Automatic
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - cyclomatic_complexity
3 | - force_cast
4 | - function_body_length
5 | - identifier_name
6 | - line_length
7 | - vertical_whitespace
8 | opt_in_rules:
9 | - closure_end_indentation
10 | - closure_spacing
11 | - conditional_returns_on_newline
12 | - empty_count
13 | - explicit_init
14 | - explicit_top_level_acl
15 | - fatal_error_message
16 | - first_where
17 | - implicit_return
18 | - implicitly_unwrapped_optional
19 | - let_var_whitespace
20 | - multiline_parameters
21 | - nimble_operator
22 | - number_separator
23 | - object_literal
24 | - operator_usage_whitespace
25 | - overridden_super_call
26 | - private_outlet
27 | - prohibited_super_call
28 | - redundant_nil_coalescing
29 | - unneeded_parentheses_in_closure_argument
30 | - vertical_parameter_alignment_on_call
31 | included:
32 | - "Example-iOS"
33 | - "Example-iOSUITests"
34 | - "Example-tvOS"
35 | - "Example-tvOSUITests"
36 | - "Source"
37 | - "Tests"
38 | excluded:
39 | - Carthage
40 | - Pods
41 |
--------------------------------------------------------------------------------
/Dangerfile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # Sometimes it's a README fix, or something like that - which isn't relevant for
4 | # including in a project's CHANGELOG for example
5 | declared_trivial = (github.pr_title + github.pr_body).include? "#trivial"
6 |
7 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet
8 | warn "PR is classed as Work in Progress" if github.pr_title.include? "[WIP]"
9 |
10 | # Warn when there is a big PR
11 | warn "Big PR" if git.lines_of_code > 500
12 |
13 | # Ensure there is a summary for a PR
14 | fail "Please provide a summary in the Pull Request description" if github.pr_body.length < 5
15 |
16 | # Add a CHANGELOG entry for app changes
17 | if git.lines_of_code > 50 && !git.modified_files.include?("CHANGELOG.md") && !declared_trivial
18 | fail "Please update [CHANGELOG.md](https://github.com/bcylin/QuickTableViewController/blob/develop/CHANGELOG.md).", sticky: true
19 | end
20 |
21 | # Ensure a clean commits history
22 | if git.commits.any? { |c| c.message =~ /^Merge branch/ }
23 | fail "Please rebase to get rid of the merge commits in this PR", sticky: true
24 | end
25 |
--------------------------------------------------------------------------------
/Example-iOS/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" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 bcylin
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 |
--------------------------------------------------------------------------------
/Example-iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | QuickTableViewControllerExample
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.2.1
21 | CFBundleVersion
22 | 101
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode11.1
3 | cache:
4 | - bundler
5 | - cocoapods
6 | before_install:
7 | - export LANG=en_US.UTF-8
8 | - xcrun instruments -s devices
9 | install:
10 | - bundle install --without development --deployment --jobs=3 --retry=3
11 | - bundle exec rake launch:simulators
12 | - bundle exec pod install
13 | before_script:
14 | - if [ -n "$DANGER_GITHUB_API_TOKEN" ]; then bundle exec danger; fi
15 | script:
16 | - set -e
17 | - bundle exec rake "test:ios[QuickTableViewController-iOS]"
18 | - bash <(curl -s https://codecov.io/bash) -cF ios -J "QuickTableViewController"
19 | - bundle exec rake "test:ios[Example-iOS]"
20 | - make -B carthage
21 | - make -B docs
22 | after_success:
23 | - sh scripts/push-docs.sh
24 | notifications:
25 | email: false
26 | slack:
27 | rooms:
28 | secure: OBqhJVYItWtvqHFhgpGm0aJBzfiBQpCbP/Ak0/2OH9HZNA0jAWGEFcipdzpsHCSNpDHlsSC2lq8CD+Py1rgQDfG/pgoVSY9Da+OLwXHoSaEA61qQMkAmXFEv5KfMcCXG+L4L5vatIVo3BvKbTWifGfNY4ZgcqGRK6n+emiJLGhkdDvdj8TX+jJ3TbRE7uUCpwjh6tuj0icvz65ve7kRSBTKrMOynxZGpjZ+Ur0gUnilJIV8k8/jdTg0oxzk1BueYsOw3aV33R59Jn8Dwm4IhLFlzkdW8LxfXGbOSV9bvxYSV8ZzD+PecwpSc3j92eZZomLgLLCyOT/ato2X454asMhyE+drtu3dlY6NTiKXuVQYAZ0Zm1kreHwSdUyupO1L8LplHNzF2BIDWqOdr+EtsAsHbQpilA2O/KjDnLiD6EQsWbzi0/Y3Nqeo3adAd+q2LWJAHvdRPcrYJuuC2lE/KW+lnzL4NhCOht95vTnnbCzTsmnPBLzTIYen3DRcu4/jQ/jzhy/qCgdexUIwmam73iGPeH4CVdnyfTV0YIQuJOi2bbllSWm0qIxAJ7P9UGIKCI9nCE5TfmmUP60SW6hm7o4UE1xOfyLaPEf9v9/CZ92EHecStftBbK+wsTrJqy4NmZlRVSw9nsR26mqOm2R9wf5oWxKaMMQTD8dSQ0F293n8=
29 |
--------------------------------------------------------------------------------
/Source/Protocol/Row.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Row.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | /// Any type that conforms to this protocol is capable of representing a row in a table view.
30 | public protocol Row: class {
31 |
32 | /// The text of the row.
33 | var text: String { get }
34 |
35 | /// The detail text of the row.
36 | var detailText: DetailText? { get }
37 |
38 | /// A closure related to the action of the row.
39 | var action: ((Row) -> Void)? { get }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | //
3 | // Package.swift
4 | //
5 | // Copyright © 2016 bcylin.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in all
15 | // copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | // SOFTWARE.
24 | //
25 |
26 | import PackageDescription
27 |
28 | let package = Package(
29 | name: "QuickTableViewController",
30 | platforms: [
31 | .iOS(.v13),
32 | .tvOS(.v13),
33 | ],
34 | products: [
35 | .library(
36 | name: "QuickTableViewController",
37 | targets: ["QuickTableViewController"]),
38 | ],
39 | targets: [
40 | .target(
41 | name: "QuickTableViewController",
42 | path: "Source"),
43 | ],
44 | swiftLanguageVersions: [.v5]
45 | )
46 |
--------------------------------------------------------------------------------
/QuickTableViewController/QuickTableViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // QuickTableViewController.h
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 25/08/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | #import
28 |
29 | //! Project version number for QuickTableViewController.
30 | FOUNDATION_EXPORT double QuickTableViewControllerVersionNumber;
31 |
32 | //! Project version string for QuickTableViewController.
33 | FOUNDATION_EXPORT const unsigned char QuickTableViewControllerVersionString[];
34 |
35 | // In this header, you should import all the public headers of your framework using statements like #import
36 |
--------------------------------------------------------------------------------
/Example-tvOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example-tvOS
4 | //
5 | // Created by Ben on 19/04/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | @UIApplicationMain
30 | internal final class AppDelegate: UIResponder, UIApplicationDelegate {
31 |
32 | var window: UIWindow?
33 |
34 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
35 | window = UIWindow(frame: UIScreen.main.bounds)
36 | window?.rootViewController = UINavigationController(rootViewController: ExampleViewController())
37 | window?.makeKeyAndVisible()
38 | return true
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Tests/CustomTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomTypes.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 30/12/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | @testable import QuickTableViewController
29 |
30 | internal final class CustomCell: UITableViewCell {}
31 | internal final class CustomSwitchCell: SwitchCell {}
32 | internal final class CustomTapActionCell: TapActionCell {}
33 |
34 | internal final class CustomNavigationRow: NavigationRow {}
35 | internal final class CustomSwitchRow: SwitchRow {}
36 | internal final class CustomTapActionRow: TapActionRow {}
37 | internal final class CustomOptionRow: OptionRow {}
38 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | default: test
2 |
3 | test: unit-test ui-test
4 |
5 | unit-test:
6 | bundle exec rake \
7 | "test:ios[QuickTableViewController-iOS]" \
8 | "test:tvos[QuickTableViewController-tvOS]" \
9 |
10 | ui-test:
11 | bundle exec rake \
12 | "test:ios[Example-iOS]" \
13 | "test:tvos[Example-tvOS]"
14 |
15 | ci-test: unit-test ui-test
16 | make -B carthage
17 | make -B docs
18 |
19 | bump:
20 | ifeq (,$(strip $(version)))
21 | # Usage: make bump version=
22 | else
23 | ruby -pi -e "gsub(%r{\d+\.\d+\.\d+}, %{"$(version)"})" QuickTableViewController.podspec
24 | ruby -pi -e "gsub(%r{\d+\.\d+\.\d+}, %{"$(version)"})" .jazzy.yml
25 | xcrun agvtool new-marketing-version $(version)
26 | endif
27 |
28 | carthage:
29 | set -o pipefail && carthage build --no-skip-current --verbose | bundle exec xcpretty -c
30 |
31 | coverage:
32 | slather coverage -s --input-format profdata --workspace QuickTableViewController.xcworkspace --scheme QuickTableViewController-iOS QuickTableViewController.xcodeproj
33 |
34 | docs:
35 | test -d docs || git clone -b gh-pages --single-branch https://github.com/bcylin/QuickTableViewController.git docs
36 | cd docs && git fetch origin gh-pages && git clean -f -d
37 | cd docs && git checkout gh-pages && git reset --hard origin/gh-pages
38 | bundle exec jazzy --config .jazzy.yml
39 |
40 | for file in "html" "css" "js" "json"; do \
41 | echo "Cleaning whitespace in *."$$file ; \
42 | find docs/output -name "*."$$file -exec sed -E -i "" -e "s/[[:blank:]]*$$//" {} \; ; \
43 | done
44 | find docs -type f -execdir chmod 644 {} \;
45 |
46 | cp -rfv docs/output/* docs
47 | cd docs && \
48 | git add . && \
49 | git diff-index --quiet HEAD || \
50 | git commit -m "[CI] Update documentation at $(shell date +'%Y-%m-%d %H:%M:%S %z')"
51 |
52 | preview-docs:
53 | make -B docs
54 | open docs/index.html
55 |
56 | update-docs:
57 | make -B docs
58 | cd docs && git push origin HEAD:gh-pages
59 |
--------------------------------------------------------------------------------
/Tests/Model/SectionSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionTests.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 18/01/2016.
6 | // Copyright © 2016 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class SectionTests: XCTestCase {
31 |
32 | func testInitialiation() {
33 | // Given
34 | let row = NavigationRow(text: "", detailText: .none)
35 |
36 | // When
37 | let section = Section(title: "title", rows: [row], footer: "footer")
38 |
39 | // Then it should have the given parameters
40 | XCTAssertEqual(section.title, "title")
41 | XCTAssertEqual(section.rows.count, 1)
42 | XCTAssertEqual(section.rows.first as? NavigationRow, row)
43 | XCTAssertEqual(section.footer, "footer")
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/Source/Model/Section.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Section.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | /// A class that represents a section in a table view.
30 | open class Section {
31 |
32 | // MARK: - Initializer
33 |
34 | /// Initializes a section with a nullable title, containing rows and an optional footer.
35 | public init(title: String?, rows: [Row & RowStyle], footer: String? = nil) {
36 | self.title = title
37 | self.rows = rows
38 | self.footer = footer
39 | }
40 |
41 | // MARK: - Properties
42 |
43 | /// The text of the section title.
44 | public let title: String?
45 |
46 | /// The array of rows in the section.
47 | open var rows: [Row & RowStyle]
48 |
49 | /// The text of the section footer.
50 | open var footer: String?
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Source/Protocol/Configurable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Configurable.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 30/07/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// Any type that conforms to this protocol is able to take `Row & RowStyle` as the configuration.
30 | public protocol Configurable {
31 | /// Configure the receiver with an instance that conforms to `Row & RowStyle`.
32 | func configure(with row: Row & RowStyle)
33 | }
34 |
35 |
36 | extension UITableViewCell {
37 |
38 | internal func defaultSetUp(with row: Row & RowStyle) {
39 | textLabel?.text = row.text
40 | detailTextLabel?.text = row.detailText?.text
41 |
42 | // Reset the accessory view in case the cell is reused.
43 | accessoryView = nil
44 | accessoryType = row.accessoryType
45 |
46 | imageView?.image = row.icon?.image
47 | imageView?.highlightedImage = row.icon?.highlightedImage
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/Example-iOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example-iOS
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | @UIApplicationMain
30 | internal final class AppDelegate: UIResponder, UIApplicationDelegate {
31 |
32 | var window: UIWindow?
33 |
34 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
35 |
36 | if #available(iOS 9.0, *) {
37 | // See AppearanceViewController for the setups.
38 | UILabel.appearance(whenContainedInInstancesOf: [UITableViewCell.self]).textColor = .blue
39 | }
40 |
41 | window = UIWindow(frame: UIScreen.main.bounds)
42 | window?.backgroundColor = UIColor.white
43 | window?.rootViewController = UINavigationController(rootViewController: RootViewController())
44 | window?.makeKeyAndVisible()
45 | return true
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Source/Protocol/RowStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowStyle.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 30/07/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// Any type that conforms to this protocol carries the info for the UI.
30 | public protocol RowStyle {
31 |
32 | /// The type of the table view cell to display the row.
33 | var cellType: UITableViewCell.Type { get }
34 |
35 | /// The reuse identifier of the table view cell to display the row.
36 | var cellReuseIdentifier: String { get }
37 |
38 | /// The style of the table view cell to display the row.
39 | var cellStyle: UITableViewCell.CellStyle { get }
40 |
41 | /// The icon of the row.
42 | var icon: Icon? { get }
43 |
44 | /// The type of standard accessory view the cell should use.
45 | var accessoryType: UITableViewCell.AccessoryType { get }
46 |
47 | /// The flag that indicates whether the table view cell should trigger the action when selected.
48 | var isSelectable: Bool { get }
49 |
50 | /// The additional customization during cell configuration.
51 | var customize: ((UITableViewCell, Row & RowStyle) -> Void)? { get }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Source/Protocol/RowCompatible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowCompatible.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 10/12/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | /// This protocol defines the compatible interface of a `NavigationRow` regardless of its associated cell type.
30 | public protocol NavigationRowCompatible: Row, RowStyle {
31 | #if os(iOS)
32 | /// A closure that will be invoked when the accessory button is selected.
33 | var accessoryButtonAction: ((Row) -> Void)? { get }
34 | #endif
35 | }
36 |
37 |
38 | /// This protocol defines the compatible interface of a `TapActionRow` regardless of its associated cell type.
39 | public protocol TapActionRowCompatible: Row, RowStyle {}
40 |
41 |
42 | /// This protocol defines the compatible interface of an `OptionRow` regardless of its associated cell type.
43 | public protocol OptionRowCompatible: Row, RowStyle {
44 | /// The state of selection.
45 | var isSelected: Bool { get set }
46 | }
47 |
48 |
49 | /// This protocol defines the compatible interface of a `SwitchRow` regardless of its associated cell type.
50 | public protocol SwitchRowCompatible: Row, RowStyle {
51 | /// The state of the switch.
52 | var switchValue: Bool { get set }
53 | }
54 |
--------------------------------------------------------------------------------
/Source/Model/DetailText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailText.swift
3 | // QuickTableViewController
4 | //
5 | // Created by bcylin on 31/12/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// An enum that represents a detail text with `UITableViewCell.CellStyle`.
30 | public enum DetailText: Equatable {
31 |
32 | /// Does not show a detail text in `UITableViewCell.CellStyle.default`.
33 | case none
34 | /// Shows the detail text in `UITableViewCell.CellStyle.subtitle`.
35 | case subtitle(String)
36 | /// Shows the detail text in `UITableViewCell.CellStyle.value1`.
37 | case value1(String)
38 | /// Shows the detail text in `UITableViewCell.CellStyle.value2`.
39 | case value2(String)
40 |
41 | /// Returns the corresponding table view cell style.
42 | public var style: UITableViewCell.CellStyle {
43 | switch self {
44 | case .none: return .default
45 | case .subtitle: return .subtitle
46 | case .value1: return .value1
47 | case .value2: return .value2
48 | }
49 | }
50 |
51 | /// Returns the associated text of the case.
52 | public var text: String? {
53 | switch self {
54 | case .none:
55 | return nil
56 | case let .subtitle(text), let .value1(text), let .value2(text):
57 | return text
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/Source/Model/Icon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Icon.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A struct that represents the image used in a row.
30 | public enum Icon: Equatable {
31 |
32 | /// Icon with an image of the given name for the normal state.
33 | /// The "-highlighted" suffix is appended to the name for the highlighted image.
34 | case named(String)
35 | /// Icon with an image for the normal state.
36 | case image(UIImage)
37 | /// Icon with images for the normal and highlighted states.
38 | case images(normal: UIImage, highlighted: UIImage)
39 |
40 | /// The image for the normal state.
41 | public var image: UIImage? {
42 | switch self {
43 | case let .named(name):
44 | return UIImage(named: name)
45 | case let .image(image):
46 | return image
47 | case let .images(normal: image, highlighted: _):
48 | return image
49 | }
50 | }
51 |
52 | /// The image for the highlighted state.
53 | public var highlightedImage: UIImage? {
54 | switch self {
55 | case let .named(name):
56 | return UIImage(named: name + "-highlighted")
57 | case .image:
58 | return nil
59 | case let .images(normal: _, highlighted: image):
60 | return image
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/ViewController/QuickTableViewControllerSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuickTableViewControllerTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 21/01/2016.
6 | // Copyright © 2016 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class QuickTableViewControllerTests: XCTestCase {
31 |
32 | func testInitialization() {
33 | // When
34 | let controller = QuickTableViewController()
35 |
36 | // Then it should set up table view with style
37 | XCTAssertEqual(controller.tableView.style, .grouped)
38 | }
39 |
40 | func testInitialization_withStyle() {
41 | // When
42 | let controller = QuickTableViewController(style: .plain)
43 |
44 | // Then it should set up table view with style
45 | XCTAssertEqual(controller.tableView.style, .plain)
46 | }
47 |
48 | func testViewConfiguration() {
49 | // Given
50 | let controller = QuickTableViewController(style: .grouped)
51 |
52 | // When
53 | let view = controller.view
54 | let tableView = controller.tableView
55 |
56 | // Than it should set up table view
57 | XCTAssert(try XCTUnwrap(view?.subviews.contains(tableView)))
58 | XCTAssertEqual(tableView.dataSource as? QuickTableViewController, controller)
59 | XCTAssertEqual(tableView.delegate as? QuickTableViewController, controller)
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/Example-iOS/UINibs/OptionCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Example-iOS/UINibs/SwitchCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Example-iOS/UINibs/TapActionCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Source/Protocol/Reusable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reusable.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 21/08/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | extension UITableViewCell: Reusable {}
30 |
31 |
32 | internal protocol Reusable {
33 | static var reuseIdentifier: String { get }
34 | }
35 |
36 |
37 | internal extension Reusable {
38 |
39 | static var reuseIdentifier: String {
40 | let type = String(describing: self)
41 | return type.matches(of: String.typeDescriptionPattern).last ?? type
42 | }
43 |
44 | }
45 |
46 |
47 | internal extension String {
48 |
49 | static var typeDescriptionPattern: String {
50 | // For the types in the format of "(CustomCell in _B5334F301B8CC6AA00C64A6D)"
51 | return "^\\(([\\w\\d]+)\\sin\\s_[0-9A-F]+\\)$"
52 | }
53 |
54 | func matches(of pattern: String) -> [String] {
55 | let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
56 | #if swift(>=3.2)
57 | let fullText = NSRange(location: 0, length: count)
58 | #else
59 | let fullText = NSRange(location: 0, length: characters.count)
60 | #endif
61 |
62 | guard let matches = regex?.matches(in: self, options: [], range: fullText) else {
63 | return []
64 | }
65 |
66 | return matches.reduce([]) { accumulator, match in
67 | accumulator + (0..=4)
69 | return (self as NSString).substring(with: match.range(at: $0))
70 | #else
71 | return (self as NSString).substring(with: match.rangeAt($0))
72 | #endif
73 | }
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/AppearanceViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppearanceViewController.swift
3 | // Example-iOS
4 | //
5 | // Created by Ben on 30/01/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | import QuickTableViewController
29 |
30 | internal final class AppearanceViewController: QuickTableViewController {
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 | title = "UIAppearance"
35 |
36 | // Register the nib files to the table view for UIAppearance customization (in AppDelegate) to work.
37 | tableView.register(UINib(nibName: "SwitchCell", bundle: .main), forCellReuseIdentifier: "SwitchCell")
38 | tableView.register(UINib(nibName: "TapActionCell", bundle: .main), forCellReuseIdentifier: "TapActionCell")
39 | tableView.register(UINib(nibName: "UITableViewCell", bundle: .main), forCellReuseIdentifier: "UITableViewCell.subtitle")
40 | tableView.register(UINib(nibName: "OptionCell", bundle: .main), forCellReuseIdentifier: "UITableViewCell")
41 |
42 | tableContents = [
43 | Section(title: "Switch", rows: [
44 | SwitchRow(text: "SwitchCell", switchValue: true, action: { _ in })
45 | ]),
46 |
47 | Section(title: "Tap Action", rows: [
48 | TapActionRow(text: "TapActionCell", action: { _ in })
49 | ]),
50 |
51 | Section(title: "Navigation", rows: [
52 | NavigationRow(text: "UITableViewCell", detailText: .subtitle(".subtitle"), action: { _ in })
53 | ]),
54 |
55 | RadioSection(title: "Radio Buttons", options: [
56 | OptionRow(text: "UITableViewCell", isSelected: true, action: { _ in })
57 | ])
58 | ]
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/View/ReusableSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReusableTests.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 21/08/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class ReusableTests: XCTestCase {
31 |
32 | private class CustomCell: UITableViewCell {}
33 |
34 | private let pattern = String.typeDescriptionPattern
35 |
36 | func testTypeString_withInvalidPattern() {
37 | // Given
38 | let typeString = String(describing: type(of: self))
39 |
40 | // When
41 | let matches = typeString.matches(of: "\\")
42 |
43 | // Then it should return an empty array
44 | XCTAssert(matches.isEmpty)
45 | }
46 |
47 | func testTypeString_withSpecialFormat() {
48 | // Given
49 | let typeString = "(CustomCell in _B5334F301B8CC6AA00C64A6D)"
50 |
51 | // When
52 | let matches = typeString.matches(of: pattern)
53 |
54 | // Then it should match the pattern
55 | XCTAssertEqual(matches.count, 2)
56 | }
57 |
58 | func testTypeString_withNameOnly() {
59 | // Given
60 | let typeString = "CustomCell"
61 |
62 | // When
63 | let matches = typeString.matches(of: pattern)
64 |
65 | // Then it should not match the pattern
66 | XCTAssert(matches.isEmpty)
67 | }
68 |
69 | func testReuseIdentifier_withCustomType() {
70 | // When
71 | let identifier = CustomCell.reuseIdentifier
72 |
73 | // Then
74 | XCTAssertEqual(identifier, "CustomCell")
75 | }
76 |
77 | func testReuseIdentifier_withTypeInModule() {
78 | // When
79 | let identifier = SwitchCell.reuseIdentifier
80 |
81 | // Then
82 | XCTAssertEqual(identifier, "SwitchCell")
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/Tests/Row/TapActionRowSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TapActionRowTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 17/01/2016.
6 | // Copyright © 2016 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class TapActionRowTests: XCTestCase {
31 |
32 | // MARK: - Initialization
33 |
34 | func testInitialiation() {
35 | // Given
36 | var actionInvoked = false
37 |
38 | // When
39 | let row = TapActionRow(text: "title") { _ in actionInvoked = true }
40 |
41 | // Then it should have the given parameters
42 | XCTAssertEqual(row.text, "title")
43 | XCTAssertEqual(row.cellReuseIdentifier, "TapActionCell")
44 |
45 | // When
46 | row.action?(row)
47 |
48 | // Then
49 | XCTAssertEqual(actionInvoked, true)
50 | }
51 |
52 | // MARK: - Equatable
53 |
54 | func testEquatable_withIdenticalParameters() {
55 | // Given
56 | let one = TapActionRow(text: "Same", action: nil)
57 |
58 | // When
59 | let another = TapActionRow(text: "Same", action: nil)
60 |
61 | // Then
62 | XCTAssert(one == another)
63 | }
64 |
65 | func testEquatable_withDifferentTexts() {
66 | // Given
67 | let one = TapActionRow(text: "Same", action: nil)
68 |
69 | // When
70 | let another = TapActionRow(text: "Different", action: nil)
71 |
72 | // Then
73 | XCTAssert(one != another)
74 | }
75 |
76 | func testEquatable_withDifferentActions() {
77 | // Given
78 | let one = TapActionRow(text: "Same", action: nil)
79 |
80 | // When
81 | let another = TapActionRow(text: "Same", action: { _ in })
82 |
83 | // Then it should be equal regardless of the actions attached
84 | XCTAssert(one == another)
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/RootViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootViewController.swift
3 | // Example-iOS
4 | //
5 | // Created by Ben on 30/01/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | import QuickTableViewController
29 |
30 | internal final class RootViewController: QuickTableViewController {
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 |
35 | let titleLabel = UILabel()
36 | titleLabel.text = "QuickTableViewController"
37 | titleLabel.font = UIFont.boldSystemFont(ofSize: 17)
38 | title = " "
39 | navigationItem.titleView = titleLabel
40 |
41 | tableContents = [
42 | Section(title: "Default", rows: [
43 | NavigationRow(text: "Use default cell types", detailText: .none, action: { [weak self] _ in
44 | self?.navigationController?.pushViewController(ExampleViewController(), animated: true)
45 | })
46 | ]),
47 |
48 | Section(title: "Customization", rows: [
49 | NavigationRow(text: "Use custom cell types", detailText: .none, action: { [weak self] _ in
50 | self?.navigationController?.pushViewController(CustomizationViewController(), animated: true)
51 | })
52 | ]),
53 |
54 | Section(title: "UIAppearance", rows: [
55 | NavigationRow(text: "UILabel customization", detailText: .none, action: { [weak self] _ in
56 | self?.navigationController?.pushViewController(AppearanceViewController(), animated: true)
57 | })
58 | ]),
59 |
60 | Section(title: "Dynamic", rows: [
61 | NavigationRow(text: "Dynamic Rows", detailText: .none, action: { [weak self] _ in
62 | self?.navigationController?.pushViewController(DynamicViewController(), animated: true)
63 | })
64 | ])
65 | ]
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/Source/Views/TapActionCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TapActionCell.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 03/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A `UITableViewCell` subclass with the title text center aligned.
30 | open class TapActionCell: UITableViewCell {
31 |
32 | // TapActionCell on tvOS does not need customization.
33 | #if os(iOS)
34 |
35 | // MARK: - Initializer
36 |
37 | /**
38 | Overrides `UITableViewCell`'s designated initializer.
39 |
40 | - parameter style: Unused. It always uses `UITableViewCellStyle.default`.
41 | - parameter reuseIdentifier: A string used to identify the cell object if it is to be reused for drawing multiple rows of a table view.
42 |
43 | - returns: An initialized `TapActionCell` object.
44 | */
45 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
46 | super.init(style: .default, reuseIdentifier: reuseIdentifier)
47 | setUpAppearance()
48 | }
49 |
50 | /**
51 | Overrides the designated initializer that returns an object initialized from data in a given unarchiver.
52 |
53 | - parameter aDecoder: An unarchiver object.
54 |
55 | - returns: `self`, initialized using the data in decoder.
56 | */
57 | public required init?(coder aDecoder: NSCoder) {
58 | super.init(coder: aDecoder)
59 | setUpAppearance()
60 | }
61 |
62 | // MARK: UIView
63 |
64 | open override func tintColorDidChange() {
65 | super.tintColorDidChange()
66 | textLabel?.textColor = tintColor
67 | }
68 |
69 | // MARK: Private Methods
70 |
71 | private func setUpAppearance() {
72 | textLabel?.numberOfLines = 0
73 | textLabel?.textAlignment = .center
74 | textLabel?.textColor = tintColor
75 | }
76 |
77 | #endif
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/DynamicTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicTableViewController.swift
3 | // Example-iOS
4 | //
5 | // Created by Zac on 30/01/2018.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in all
15 | // copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | // SOFTWARE.
24 | //
25 |
26 | import UIKit
27 | import QuickTableViewController
28 |
29 | internal final class DynamicViewController: QuickTableViewController {
30 |
31 | var dynamicRows: [Row & RowStyle] = []
32 |
33 | private var cachedTableContents: [Section] = []
34 |
35 | override var tableContents: [Section] {
36 | get {
37 | return cachedTableContents
38 | }
39 | set {} // swiftlint:disable:this unused_setter_value
40 | }
41 |
42 | private let quickTableView = QuickTableView(frame: .zero, style: .grouped)
43 |
44 | override var tableView: UITableView {
45 | get {
46 | return quickTableView
47 | }
48 | set {} // swiftlint:disable:this unused_setter_value
49 | }
50 |
51 | private func buildContents() -> [Section] {
52 | let rows: [Row & RowStyle] = [
53 | TapActionRow(text: "AddCell", action: { [unowned self] _ in
54 | self.dynamicRows.append(
55 | NavigationRow(text: "UITableViewCell", detailText: .value1(String(describing: (self.dynamicRows.count + 1))), action: nil)
56 | )
57 | self.tableView.insertRows(at: [IndexPath(row: self.dynamicRows.count, section: 0)], with: .automatic)
58 | })
59 | ] + dynamicRows
60 |
61 | return [
62 | Section(title: "Tap Action", rows: rows)
63 | ]
64 | }
65 |
66 | override func viewDidLoad() {
67 | super.viewDidLoad()
68 | title = "Dynamic"
69 | cachedTableContents = buildContents()
70 | quickTableView.quickDelegate = self
71 | }
72 |
73 | }
74 |
75 | extension DynamicViewController: QuickTableViewDelegate {
76 | func quickReload() {
77 | cachedTableContents = buildContents()
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Source/Model/Subtitle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Subtitle.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | @available(*, deprecated, message: "Use `DetailText` instead.")
30 | public enum Subtitle: Equatable {
31 |
32 | /// Does not show a subtitle as `UITableViewCellStyle.default`.
33 | case none
34 | /// Shows the associated text in `UITableViewCellStyle.subtitle`.
35 | case belowTitle(String)
36 | /// Shows the associated text in `UITableViewCellStyle.value1`.
37 | case rightAligned(String)
38 | /// Shows the associated text in `UITableViewCellStyle.value2`.
39 | case leftAligned(String)
40 |
41 | /// Returns the corresponding table view cell style.
42 | public var style: UITableViewCell.CellStyle {
43 | return detailText.style
44 | }
45 |
46 | /// Returns the associated text of the case.
47 | public var text: String? {
48 | return detailText.text
49 | }
50 |
51 | @available(*, deprecated, message: "The conversion between Subtitle and DetailText.")
52 | internal var detailText: DetailText {
53 | switch self {
54 | case .none: return .none
55 | case let .belowTitle(text): return .subtitle(text)
56 | case let .rightAligned(text): return .value1(text)
57 | case let .leftAligned(text): return .value2(text)
58 | }
59 | }
60 |
61 | }
62 |
63 |
64 | internal extension DetailText {
65 |
66 | @available(*, deprecated, message: "The conversion between DetailText and Subtitle.")
67 | var subtitle: Subtitle {
68 | switch self {
69 | case .none: return .none
70 | case let .subtitle(text): return .belowTitle(text)
71 | case let .value1(text): return .rightAligned(text)
72 | case let .value2(text): return .leftAligned(text)
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/Example-iOS/UINibs/UITableViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "fileutils"
2 |
3 | ios_device = "iPhone 11"
4 | ios_version = "13.1"
5 | tvos_device = "Apple TV"
6 | tvos_version = "13.0"
7 |
8 |
9 | def xcodebuild(params)
10 | return ":" unless params[:scheme]
11 | [
12 | %(xcodebuild),
13 | %(-workspace QuickTableViewController.xcworkspace),
14 | %(-scheme #{params[:scheme]}),
15 | %(-sdk #{params[:simulator]}),
16 | params[:destination],
17 | params[:action],
18 | %(| xcpretty -c && exit ${PIPESTATUS[0]})
19 | ].reject(&:nil?).join " "
20 | end
21 |
22 |
23 | namespace :launch do
24 | desc "Launch iOS and tvOS simulators"
25 | task :simulators do
26 | sh %(xcrun instruments -w "#{ios_device} (#{ios_version}) [" || true)
27 | sh %(xcrun instruments -w "#{tvos_device} (#{tvos_version}) [" || true)
28 | end
29 | end
30 |
31 |
32 | namespace :build do
33 | desc "Build an iOS target with the specified scheme"
34 | task :ios, [:scheme] do |t, args|
35 | puts "Usage: rake 'build:ios[scheme]'" unless args[:scheme]
36 |
37 | sh xcodebuild(args.to_hash.merge({
38 | action: "clean build analyze",
39 | simulator: "iphonesimulator"
40 | }))
41 | exit $?.exitstatus if not $?.success?
42 | end
43 |
44 | desc "Build a tvOS target with the specified scheme"
45 | task :tvos, [:scheme] do |t, args|
46 | puts "Usage: rake 'build:tvos[scheme]'" unless args[:scheme]
47 |
48 | sh xcodebuild(args.to_hash.merge({
49 | action: "clean build analyze",
50 | simulator: "appletvsimulator"
51 | }))
52 | exit $?.exitstatus if not $?.success?
53 | end
54 | end
55 |
56 |
57 | namespace :test do
58 | desc "Run tests with the specified iOS scheme"
59 | task :ios, [:scheme] do |t, args|
60 | puts "Usage: rake 'test:ios[scheme]'" unless args[:scheme]
61 |
62 | sh xcodebuild(args.to_hash.merge({
63 | action: "-enableCodeCoverage YES clean build test",
64 | destination: %(-destination "name=#{ios_device},OS=#{ios_version}"),
65 | simulator: "iphonesimulator"
66 | }))
67 | exit $?.exitstatus if not $?.success?
68 | end
69 |
70 | desc "Run tests with the specified tvOS scheme"
71 | task :tvos, [:scheme] do |t, args|
72 | puts "Usage: rake 'test:tvos[scheme]'" unless args[:scheme]
73 |
74 | sh xcodebuild(args.to_hash.merge({
75 | action: "-enableCodeCoverage YES clean build test",
76 | destination: %(-destination "name=#{tvos_device},OS=#{tvos_version}"),
77 | simulator: "appletvsimulator"
78 | }))
79 | exit $?.exitstatus if not $?.success?
80 | end
81 | end
82 |
83 |
84 | desc "Bump versions"
85 | task :bump, [:version] do |t, args|
86 | version = args[:version]
87 | unless version
88 | puts "Usage: rake bump[version]"
89 | next
90 | end
91 |
92 | sh %(xcrun agvtool new-marketing-version #{version})
93 |
94 | podspec = "QuickTableViewController.podspec"
95 | text = File.read podspec
96 | File.write podspec, text.gsub(%r(\"\d+\.\d+\.\d+\"), "\"#{version}\"")
97 | puts "Updated #{podspec} to #{version}"
98 |
99 | jazzy = ".jazzy.yml"
100 | text = File.read jazzy
101 | File.write jazzy, text.gsub(%r(:\s\d+\.\d+\.\d+), ": #{version}")
102 | puts "Updated #{jazzy} to #{version}"
103 | end
104 |
--------------------------------------------------------------------------------
/Source/Rows/TapActionRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TapActionRow.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A class that represents a row that triggers certain action when selected.
30 | open class TapActionRow: TapActionRowCompatible, Equatable {
31 |
32 | // MARK: - Initializer
33 |
34 | /// Initializes a `TapActionRow` with a text, an action closure,
35 | /// and an optional customization closure.
36 | public init(
37 | text: String,
38 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
39 | action: ((Row) -> Void)?
40 | ) {
41 | self.text = text
42 | self.customize = customization
43 | self.action = action
44 | }
45 |
46 | // MARK: - Row
47 |
48 | /// The text of the row.
49 | public let text: String
50 |
51 | /// The detail text is disabled in `TapActionRow`.
52 | public let detailText: DetailText? = nil
53 |
54 | /// A closure that will be invoked when the row is selected.
55 | public let action: ((Row) -> Void)?
56 |
57 | // MARK: - RowStyle
58 |
59 | /// The type of the table view cell to display the row.
60 | public let cellType: UITableViewCell.Type = T.self
61 |
62 | /// The reuse identifier of the table view cell to display the row. The default value is **TapActionCell**.
63 | public let cellReuseIdentifier: String = T.reuseIdentifier
64 |
65 | /// The cell style is `.default`.
66 | public let cellStyle: UITableViewCell.CellStyle = .default
67 |
68 | /// The default icon is nil.
69 | public let icon: Icon? = nil
70 |
71 | /// The default accessory type is `.none`.
72 | public let accessoryType: UITableViewCell.AccessoryType = .none
73 |
74 | /// The `TapActionRow` is selectable when action is not nil.
75 | public var isSelectable: Bool {
76 | return action != nil
77 | }
78 |
79 | /// The additional customization during cell configuration.
80 | public let customize: ((UITableViewCell, Row & RowStyle) -> Void)?
81 |
82 | // MARK: - Equatable
83 |
84 | /// Returns true iff `lhs` and `rhs` have equal titles and detail texts.
85 | public static func == (lhs: TapActionRow, rhs: TapActionRow) -> Bool {
86 | return
87 | lhs.text == rhs.text &&
88 | lhs.detailText == rhs.detailText
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/DynamicTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicTableView.swift
3 | // Example-iOS
4 | //
5 | // Created by Zac on 14/02/2020.
6 | //
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy
8 | // of this software and associated documentation files (the "Software"), to deal
9 | // in the Software without restriction, including without limitation the rights
10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | // copies of the Software, and to permit persons to whom the Software is
12 | // furnished to do so, subject to the following conditions:
13 | //
14 | // The above copyright notice and this permission notice shall be included in all
15 | // copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | // SOFTWARE.
24 | //
25 |
26 | import Foundation
27 | import UIKit
28 |
29 | internal protocol QuickTableViewDelegate: class {
30 | func quickReload()
31 | }
32 |
33 | open class QuickTableView: UITableView {
34 | internal weak var quickDelegate: QuickTableViewDelegate?
35 |
36 | override open func reloadData() {
37 | self.quickDelegate?.quickReload()
38 | super.reloadData()
39 | }
40 |
41 | // MARK: Rows
42 |
43 | override open func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) {
44 | self.quickDelegate?.quickReload()
45 | super.reloadRows(at: indexPaths, with: animation)
46 | }
47 |
48 | override open func insertRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) {
49 | self.quickDelegate?.quickReload()
50 | super.insertRows(at: indexPaths, with: animation)
51 | }
52 |
53 | override open func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) {
54 | self.quickDelegate?.quickReload()
55 | super.deleteRows(at: indexPaths, with: animation)
56 | }
57 |
58 | override open func moveRow(at indexPath: IndexPath, to newIndexPath: IndexPath) {
59 | self.quickDelegate?.quickReload()
60 | super.moveRow(at: indexPath, to: newIndexPath)
61 | }
62 |
63 | // MARK: Sections
64 |
65 | override open func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) {
66 | self.quickDelegate?.quickReload()
67 | super.reloadSections(sections, with: animation)
68 | }
69 |
70 | override open func deleteSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) {
71 | self.quickDelegate?.quickReload()
72 | super.deleteSections(sections, with: animation)
73 | }
74 |
75 | override open func insertSections(_ sections: IndexSet, with animation: UITableView.RowAnimation) {
76 | self.quickDelegate?.quickReload()
77 | super.insertSections(sections, with: animation)
78 | }
79 |
80 | override open func reloadSectionIndexTitles() {
81 | self.quickDelegate?.quickReload()
82 | super.reloadSectionIndexTitles()
83 | }
84 |
85 | override open func moveSection(_ section: Int, toSection newSection: Int) {
86 | self.quickDelegate?.quickReload()
87 | super.moveSection(section, toSection: newSection)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Tests/Model/IconSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IconTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 17/01/2016.
6 | // Copyright © 2016 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class IconTests: XCTestCase {
31 |
32 | private let image = UIImage(named: "icon", in: Bundle(for: IconTests.self), compatibleWith: nil)!
33 | private let highlighted = UIImage(named: "icon-highlighted", in: Bundle(for: IconTests.self), compatibleWith: nil)!
34 |
35 | // MARK: - Initialization
36 |
37 | func testInitialiation() {
38 | // Given
39 | let image = self.image
40 |
41 | // When
42 | let icon = Icon.image(image)
43 |
44 | // Then
45 | XCTAssertEqual(icon.image, image)
46 | XCTAssertNil(icon.highlightedImage)
47 | }
48 |
49 | // MARK: - Equatable
50 |
51 | func testEquatable_withIdenticalParameters() {
52 | // Given
53 | let one = Icon.image(image)
54 |
55 | // When
56 | let another = Icon.image(image)
57 |
58 | // Then
59 | XCTAssert(one == another)
60 | }
61 |
62 | func testEquatable_withDifferentImages() {
63 | // Given
64 | let one = Icon.image(image)
65 |
66 | // When
67 | let another = Icon.image(highlighted)
68 |
69 | // Then
70 | XCTAssert(one != another)
71 | }
72 |
73 | func testEquatable_withDifferentHighlightedImages() {
74 | // Given
75 | let one = Icon.images(normal: image, highlighted: highlighted)
76 |
77 | // When
78 | let another = Icon.images(normal: image, highlighted: UIImage())
79 |
80 | // Then
81 | XCTAssert(one != another)
82 | }
83 |
84 | func testEquatable_withDifferentImageSpecifications() {
85 | // Given
86 | let one = Icon.image(image)
87 |
88 | // When
89 | let another = Icon.named("image")
90 |
91 | // Then
92 | XCTAssert(one != another)
93 | }
94 |
95 | func testEquatable_withIdenticalImageNames() {
96 | // Given
97 | let one = Icon.named("Same")
98 |
99 | // When
100 | let another = Icon.named("Same")
101 |
102 | // Then
103 | XCTAssert(one == another)
104 | }
105 |
106 | func testEquatable_withDifferentImageNames() {
107 | // Given
108 | let one = Icon.named("Same")
109 |
110 | // When
111 | let another = Icon.named("Different")
112 |
113 | // Then
114 | XCTAssert(one != another)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Example-iOS/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Tests/Model/DetailTextSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailTextTests.swift
3 | // QuickTableViewController
4 | //
5 | // Created by bcylin on 31/12/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class DetailTextTests: XCTestCase {
31 |
32 | func testCellStyle() {
33 | // It should return the descriptive name of the style
34 | XCTAssertEqual(DetailText.none.style, .default)
35 | XCTAssertEqual(DetailText.subtitle("text").style, .subtitle)
36 | XCTAssertEqual(DetailText.value1("text").style, .value1)
37 | XCTAssertEqual(DetailText.value2("text").style, .value2)
38 | }
39 |
40 | func testAssocitatedValue() {
41 | // It should return the associated text
42 | XCTAssertNil(DetailText.none.text)
43 | XCTAssertEqual(DetailText.subtitle("3").text, "3")
44 | XCTAssertEqual(DetailText.value1("1").text, "1")
45 | XCTAssertEqual(DetailText.value2("2").text, "2")
46 | }
47 |
48 | // MARK: - Equatable
49 |
50 | func testEquatableNone() {
51 | // Given
52 | let a = DetailText.none
53 | let b = DetailText.none
54 |
55 | // Then it should be equal when both are .none
56 | XCTAssert(a == b)
57 | }
58 |
59 | func testEquatableSubtitle() {
60 | // Given
61 | let a = DetailText.subtitle("Same")
62 | let b = DetailText.subtitle("Same")
63 | let c = DetailText.subtitle("Different")
64 | let d = DetailText.value1("Same")
65 | let e = DetailText.none
66 |
67 | // Then it should be equal only when both type and associated value match
68 | XCTAssert(a == b)
69 | XCTAssert(a != c)
70 | XCTAssert(a != d)
71 | XCTAssert(a != e)
72 | }
73 |
74 | func testEquatableValue1() {
75 | // Given
76 | let a = DetailText.value1("Same")
77 | let b = DetailText.value1("Same")
78 | let c = DetailText.value1("Different")
79 | let d = DetailText.value2("Same")
80 | let e = DetailText.none
81 |
82 | // Then it should be equal only when both type and associated value match
83 | XCTAssert(a == b)
84 | XCTAssert(a != c)
85 | XCTAssert(a != d)
86 | XCTAssert(a != e)
87 | }
88 |
89 | func testEquatableValue2() {
90 | // Given
91 | let a = DetailText.value2("Same")
92 | let b = DetailText.value2("Same")
93 | let c = DetailText.value2("Different")
94 | let d = DetailText.subtitle("Same")
95 | let e = DetailText.none
96 |
97 | // Then it should be equal only when both type and associated value match
98 | XCTAssert(a == b)
99 | XCTAssert(a != c)
100 | XCTAssert(a != d)
101 | XCTAssert(a != e)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Example-tvOS/ExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleViewController.swift
3 | // Example-tvOS
4 | //
5 | // Created by Ben on 19/04/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | import QuickTableViewController
29 |
30 | internal final class ExampleViewController: QuickTableViewController {
31 |
32 | override func viewDidLoad() {
33 | super.viewDidLoad()
34 | title = "QuickTableViewController"
35 |
36 | tableContents = [
37 | Section(title: "Switch", rows: [
38 | SwitchRow(text: "Setting 1", switchValue: true, action: showLog()),
39 | SwitchRow(text: "Setting 2", switchValue: false, action: showLog())
40 | ]),
41 |
42 | Section(title: "Tap Action", rows: [
43 | TapActionRow(text: "Tap action", action: showAlert())
44 | ]),
45 |
46 | Section(title: "Navigation", rows: [
47 | NavigationRow(text: "CellStyle.default", detailText: .none, action: showDetail()),
48 | NavigationRow(text: "CellStyle", detailText: .subtitle(".subtitle"), action: showDetail()),
49 | NavigationRow(text: "CellStyle", detailText: .value1(".value1")),
50 | NavigationRow(text: "CellStyle", detailText: .value2(".value2"))
51 | ]),
52 |
53 | RadioSection(title: "Radio Buttons", options: [
54 | OptionRow(text: "Option 1", isSelected: true, action: showLog()),
55 | OptionRow(text: "Option 2", isSelected: false, action: showLog()),
56 | OptionRow(text: "Option 3", isSelected: false, action: showLog())
57 | ], footer: "See RadioSection for more details.")
58 | ]
59 | }
60 |
61 | // MARK: - Private
62 |
63 | private func showAlert() -> (Row) -> Void {
64 | return { [weak self] row in
65 | let alert = UIAlertController(
66 | title: row.text,
67 | message: row.detailText.flatMap({ $0.text }),
68 | preferredStyle: .alert
69 | )
70 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
71 | self?.present(alert, animated: true, completion: nil)
72 | }
73 | }
74 |
75 | private func showDetail() -> (Row) -> Void {
76 | return { [weak self] row in
77 | let controller = UIViewController()
78 | controller.title = row.text + (row.detailText?.text ?? "")
79 | self?.navigationController?.pushViewController(controller, animated: true)
80 | }
81 | }
82 |
83 | private func showLog() -> (Row) -> Void {
84 | return {
85 | switch $0 {
86 | case let row as SwitchRowCompatible:
87 | print("\(row.text) = \(row.switchValue)")
88 | case let option as OptionRowCompatible where option.isSelected:
89 | print("\(option.text) is selected")
90 | default:
91 | break
92 | }
93 | }
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/Source/Rows/OptionRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionRow.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 30/07/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A class that represents a row of selectable option.
30 | open class OptionRow: OptionRowCompatible, Equatable {
31 |
32 | // MARK: - Initializer
33 |
34 | /// Initializes an `OptionRow` with a text, a selection state and an action closure.
35 | /// The detail text, icon, and the customization closure are optional.
36 | public init(
37 | text: String,
38 | detailText: DetailText? = nil,
39 | isSelected: Bool,
40 | icon: Icon? = nil,
41 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
42 | action: ((Row) -> Void)?
43 | ) {
44 | self.text = text
45 | self.detailText = detailText
46 | self.isSelected = isSelected
47 | self.icon = icon
48 | self.customize = customization
49 | self.action = action
50 | }
51 |
52 | // MARK: - OptionRowCompatible
53 |
54 | /// The state of selection.
55 | public var isSelected: Bool = false {
56 | didSet {
57 | guard isSelected != oldValue else {
58 | return
59 | }
60 | DispatchQueue.main.async {
61 | self.action?(self)
62 | }
63 | }
64 | }
65 |
66 | // MARK: - Row
67 |
68 | /// The text of the row.
69 | public let text: String
70 |
71 | /// The detail text of the row.
72 | public let detailText: DetailText?
73 |
74 | /// A closure that will be invoked when the `isSelected` is changed.
75 | public let action: ((Row) -> Void)?
76 |
77 | // MARK: - RowStyle
78 |
79 | /// The type of the table view cell to display the row.
80 | public let cellType: UITableViewCell.Type = T.self
81 |
82 | /// The reuse identifier of the table view cell to display the row. The default value is **UITableViewCell**.
83 | public let cellReuseIdentifier: String = T.reuseIdentifier
84 |
85 | /// The cell style is `.default`.
86 | public let cellStyle: UITableViewCell.CellStyle = .default
87 |
88 | /// The icon of the row.
89 | public let icon: Icon?
90 |
91 | /// Returns `.checkmark` when the row is selected, otherwise returns `.none`.
92 | public var accessoryType: UITableViewCell.AccessoryType {
93 | return isSelected ? .checkmark : .none
94 | }
95 |
96 | /// `OptionRow` is always selectable.
97 | public let isSelectable: Bool = true
98 |
99 | /// Additional customization during cell configuration.
100 | public let customize: ((UITableViewCell, Row & RowStyle) -> Void)?
101 |
102 | // MARK: - Equatable
103 |
104 | /// Returns true iff `lhs` and `rhs` have equal titles, detail texts, selection states, and icons.
105 | public static func == (lhs: OptionRow, rhs: OptionRow) -> Bool {
106 | return
107 | lhs.text == rhs.text &&
108 | lhs.detailText == rhs.detailText &&
109 | lhs.isSelected == rhs.isSelected &&
110 | lhs.icon == rhs.icon
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/Source/Model/Deprecated.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deprecated.swift
3 | // QuickTableViewController
4 | //
5 | // Created by bcylin on 01/01/2019.
6 | // Copyright © 2019 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | public extension Row {
30 |
31 | @available(*, deprecated, message: "Use `text` instead.")
32 | var title: String {
33 | return text
34 | }
35 |
36 | @available(*, deprecated, message: "Use `detailText` instead.")
37 | var subtitle: Subtitle? {
38 | return detailText?.subtitle
39 | }
40 |
41 | }
42 |
43 | ////////////////////////////////////////////////////////////////////////////////
44 |
45 | public extension NavigationRow {
46 |
47 | @available(*, deprecated, message: "Use `init(text:detailText:icon:customization:action:)` instead.")
48 | convenience init(
49 | title: String,
50 | subtitle: Subtitle,
51 | icon: Icon? = nil,
52 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
53 | action: ((Row) -> Void)? = nil
54 | ) {
55 | self.init(
56 | text: title,
57 | detailText: subtitle.detailText,
58 | icon: icon,
59 | customization: customization,
60 | action: action
61 | )
62 | }
63 |
64 | }
65 |
66 | ////////////////////////////////////////////////////////////////////////////////
67 |
68 | public extension OptionRow {
69 |
70 | @available(*, deprecated, message: "Use `init(text:detailText:isSelected:icon:customization:action:)` instead.")
71 | convenience init(
72 | title: String,
73 | isSelected: Bool,
74 | icon: Icon? = nil,
75 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
76 | action: ((Row) -> Void)?
77 | ) {
78 | self.init(
79 | text: title,
80 | detailText: nil,
81 | isSelected: isSelected,
82 | icon: icon,
83 | customization: customization,
84 | action: action
85 | )
86 | }
87 |
88 | }
89 |
90 | ////////////////////////////////////////////////////////////////////////////////
91 |
92 | public extension SwitchRow {
93 |
94 | @available(*, deprecated, message: "Use `init(text:detailText:switchValue:icon:customization:action:)` instead.")
95 | convenience init(
96 | title: String,
97 | switchValue: Bool,
98 | icon: Icon? = nil,
99 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
100 | action: ((Row) -> Void)?
101 | ) {
102 | self.init(
103 | text: title,
104 | detailText: nil,
105 | switchValue: switchValue,
106 | icon: icon,
107 | customization: customization,
108 | action: action
109 | )
110 | }
111 |
112 | }
113 |
114 | ////////////////////////////////////////////////////////////////////////////////
115 |
116 | public extension TapActionRow {
117 |
118 | @available(*, deprecated, message: "Use `init(text:customization:action:)` instead.")
119 | convenience init(
120 | title: String,
121 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
122 | action: ((Row) -> Void)?
123 | ) {
124 | self.init(
125 | text: title,
126 | customization: customization,
127 | action: action
128 | )
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/QuickTableViewController.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/QuickTableViewController.xcodeproj/xcshareddata/xcschemes/Example-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.2)
5 | activesupport (4.2.11.1)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | addressable (2.7.0)
11 | public_suffix (>= 2.0.2, < 5.0)
12 | algoliasearch (1.27.1)
13 | httpclient (~> 2.8, >= 2.8.3)
14 | json (>= 1.5.1)
15 | atomos (0.1.3)
16 | claide (1.0.3)
17 | claide-plugins (0.9.2)
18 | cork
19 | nap
20 | open4 (~> 1.3)
21 | cocoapods (1.8.4)
22 | activesupport (>= 4.0.2, < 5)
23 | claide (>= 1.0.2, < 2.0)
24 | cocoapods-core (= 1.8.4)
25 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
26 | cocoapods-downloader (>= 1.2.2, < 2.0)
27 | cocoapods-plugins (>= 1.0.0, < 2.0)
28 | cocoapods-search (>= 1.0.0, < 2.0)
29 | cocoapods-stats (>= 1.0.0, < 2.0)
30 | cocoapods-trunk (>= 1.4.0, < 2.0)
31 | cocoapods-try (>= 1.1.0, < 2.0)
32 | colored2 (~> 3.1)
33 | escape (~> 0.0.4)
34 | fourflusher (>= 2.3.0, < 3.0)
35 | gh_inspector (~> 1.0)
36 | molinillo (~> 0.6.6)
37 | nap (~> 1.0)
38 | ruby-macho (~> 1.4)
39 | xcodeproj (>= 1.11.1, < 2.0)
40 | cocoapods-core (1.8.4)
41 | activesupport (>= 4.0.2, < 6)
42 | algoliasearch (~> 1.0)
43 | concurrent-ruby (~> 1.1)
44 | fuzzy_match (~> 2.0.4)
45 | nap (~> 1.0)
46 | cocoapods-deintegrate (1.0.4)
47 | cocoapods-downloader (1.3.0)
48 | cocoapods-plugins (1.0.0)
49 | nap
50 | cocoapods-search (1.0.0)
51 | cocoapods-stats (1.1.0)
52 | cocoapods-trunk (1.4.1)
53 | nap (>= 0.8, < 2.0)
54 | netrc (~> 0.11)
55 | cocoapods-try (1.1.0)
56 | colored2 (3.1.2)
57 | concurrent-ruby (1.1.6)
58 | cork (0.3.0)
59 | colored2 (~> 3.1)
60 | danger (6.2.2)
61 | claide (~> 1.0)
62 | claide-plugins (>= 0.9.2)
63 | colored2 (~> 3.1)
64 | cork (~> 0.1)
65 | faraday (~> 0.9)
66 | faraday-http-cache (~> 2.0)
67 | git (~> 1.6)
68 | kramdown (~> 2.0)
69 | kramdown-parser-gfm (~> 1.0)
70 | no_proxy_fix
71 | octokit (~> 4.7)
72 | terminal-table (~> 1)
73 | escape (0.0.4)
74 | faraday (0.17.3)
75 | multipart-post (>= 1.2, < 3)
76 | faraday-http-cache (2.0.0)
77 | faraday (~> 0.8)
78 | ffi (1.12.2)
79 | fourflusher (2.3.1)
80 | fuzzy_match (2.0.4)
81 | gh_inspector (1.1.3)
82 | git (1.6.0)
83 | rchardet (~> 1.8)
84 | httpclient (2.8.3)
85 | i18n (0.9.5)
86 | concurrent-ruby (~> 1.0)
87 | jazzy (0.13.1)
88 | cocoapods (~> 1.5)
89 | mustache (~> 1.1)
90 | open4
91 | redcarpet (~> 3.4)
92 | rouge (>= 2.0.6, < 4.0)
93 | sassc (~> 2.1)
94 | sqlite3 (~> 1.3)
95 | xcinvoke (~> 0.3.0)
96 | json (2.3.0)
97 | kramdown (2.1.0)
98 | kramdown-parser-gfm (1.1.0)
99 | kramdown (~> 2.0)
100 | liferaft (0.0.6)
101 | minitest (5.14.0)
102 | molinillo (0.6.6)
103 | multipart-post (2.1.1)
104 | mustache (1.1.1)
105 | nanaimo (0.2.6)
106 | nap (1.1.0)
107 | netrc (0.11.0)
108 | no_proxy_fix (0.1.2)
109 | octokit (4.16.0)
110 | faraday (>= 0.9)
111 | sawyer (~> 0.8.0, >= 0.5.3)
112 | open4 (1.3.4)
113 | public_suffix (4.0.3)
114 | rake (13.0.1)
115 | rchardet (1.8.0)
116 | redcarpet (3.5.0)
117 | rouge (2.0.7)
118 | ruby-macho (1.4.0)
119 | sassc (2.2.1)
120 | ffi (~> 1.9)
121 | sawyer (0.8.2)
122 | addressable (>= 2.3.5)
123 | faraday (> 0.8, < 2.0)
124 | sqlite3 (1.4.2)
125 | terminal-table (1.8.0)
126 | unicode-display_width (~> 1.1, >= 1.1.1)
127 | thread_safe (0.3.6)
128 | tzinfo (1.2.6)
129 | thread_safe (~> 0.1)
130 | unicode-display_width (1.6.1)
131 | xcinvoke (0.3.0)
132 | liferaft (~> 0.0.6)
133 | xcodeproj (1.15.0)
134 | CFPropertyList (>= 2.3.3, < 4.0)
135 | atomos (~> 0.1.3)
136 | claide (>= 1.0.2, < 2.0)
137 | colored2 (~> 3.1)
138 | nanaimo (~> 0.2.6)
139 | xcpretty (0.3.0)
140 | rouge (~> 2.0.7)
141 |
142 | PLATFORMS
143 | ruby
144 |
145 | DEPENDENCIES
146 | cocoapods
147 | danger
148 | jazzy (>= 0.9.0)
149 | rake
150 | xcpretty
151 |
152 | BUNDLED WITH
153 | 2.0.2
154 |
--------------------------------------------------------------------------------
/Source/Model/RadioSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RadioSection.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 17/08/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import Foundation
28 |
29 | /// A section that allows only one option selected in a table view.
30 | open class RadioSection: Section {
31 |
32 | // MARK: - Initializer
33 |
34 | /// Initializes a section with a title, containing rows and an optional footer.
35 | public init(title: String?, options: [OptionRowCompatible], footer: String? = nil) {
36 | self.options = options
37 | super.init(title: title, rows: [], footer: footer)
38 | }
39 |
40 | private override init(title: String?, rows: [Row & RowStyle], footer: String? = nil) {
41 | fatalError("init with title, rows, and footer is not supported")
42 | }
43 |
44 | // MARK: - Section
45 |
46 | /// The array of rows in the section.
47 | open override var rows: [Row & RowStyle] {
48 | get {
49 | return options
50 | }
51 | set {
52 | options = newValue as? [OptionRowCompatible] ?? options
53 | }
54 | }
55 |
56 | // MARK: - RadioSection
57 |
58 | /// A boolean that indicates whether there's always one option selected.
59 | open var alwaysSelectsOneOption: Bool = false {
60 | didSet {
61 | if alwaysSelectsOneOption && selectedOption == nil {
62 | options.first?.isSelected = true
63 | }
64 | }
65 | }
66 |
67 | /// The array of options in the section. It's identical to the `rows`.
68 | open private(set) var options: [OptionRowCompatible]
69 |
70 | /// Returns the selected index, or nil when nothing is selected.
71 | open var indexOfSelectedOption: Int? {
72 | return options.firstIndex { $0.isSelected }
73 | }
74 |
75 | /// Returns the selected option row, or nil when nothing is selected.
76 | open var selectedOption: OptionRowCompatible? {
77 | if let index = indexOfSelectedOption {
78 | return options[index]
79 | } else {
80 | return nil
81 | }
82 | }
83 |
84 | /// Toggle the selection of the given option and keep options mutually exclusive.
85 | /// If `alwaysSelectOneOption` is set to true, it will not deselect the current selection.
86 | ///
87 | /// - Parameter option: The option to flip the `isSelected` state.
88 | /// - Returns: The indexes of changed options.
89 | open func toggle(_ option: OptionRowCompatible) -> IndexSet {
90 | if option.isSelected && alwaysSelectsOneOption {
91 | return []
92 | }
93 |
94 | defer {
95 | option.isSelected = !option.isSelected
96 | }
97 |
98 | if option.isSelected {
99 | // Deselect the selected option.
100 | return options.firstIndex(where: { $0 === option }).map { [$0] } ?? []
101 | }
102 |
103 | var toggledIndexes: IndexSet = []
104 |
105 | for (index, element) in options.enumerated() {
106 | switch element {
107 | case let target where target === option:
108 | toggledIndexes.insert(index)
109 | case _ where element.isSelected:
110 | toggledIndexes.insert(index)
111 | element.isSelected = false
112 | default:
113 | break
114 | }
115 | }
116 |
117 | return toggledIndexes
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/QuickTableViewController.xcodeproj/xcshareddata/xcschemes/QuickTableViewController-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Source/Rows/SwitchRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwitchRow.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A class that represents a row with a switch.
30 | open class SwitchRow: SwitchRowCompatible, Equatable {
31 |
32 | // MARK: - Initializer
33 |
34 | /// Initializes a `SwitchRow` with a title, a switch state and an action closure.
35 | /// The detail text, icon and the customization closure are optional.
36 | public init(
37 | text: String,
38 | detailText: DetailText? = nil,
39 | switchValue: Bool,
40 | icon: Icon? = nil,
41 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
42 | action: ((Row) -> Void)?
43 | ) {
44 | self.text = text
45 | self.detailText = detailText
46 | self.switchValue = switchValue
47 | self.icon = icon
48 | self.customize = customization
49 | self.action = action
50 | }
51 |
52 | // MARK: - SwitchRowCompatible
53 |
54 | /// The state of the switch.
55 | public var switchValue: Bool = false {
56 | didSet {
57 | guard switchValue != oldValue else {
58 | return
59 | }
60 | DispatchQueue.main.async {
61 | self.action?(self)
62 | }
63 | }
64 | }
65 |
66 | // MARK: - Row
67 |
68 | /// The text of the row.
69 | public let text: String
70 |
71 | /// The detail text of the row.
72 | public let detailText: DetailText?
73 |
74 | /// A closure that will be invoked when the `switchValue` is changed.
75 | public let action: ((Row) -> Void)?
76 |
77 | // MARK: - RowStyle
78 |
79 | /// The type of the table view cell to display the row.
80 | public let cellType: UITableViewCell.Type = T.self
81 |
82 | /// The reuse identifier of the table view cell to display the row. The default value is **SwitchCell**.
83 | public let cellReuseIdentifier: String = T.reuseIdentifier
84 |
85 | /// The cell style is `.default`.
86 | public let cellStyle: UITableViewCell.CellStyle = .default
87 |
88 | /// The icon of the row.
89 | public let icon: Icon?
90 |
91 | #if os(iOS)
92 |
93 | /// The default accessory type is `.none`.
94 | public let accessoryType: UITableViewCell.AccessoryType = .none
95 |
96 | /// The `SwitchRow` should not be selectable.
97 | public let isSelectable: Bool = false
98 |
99 | #elseif os(tvOS)
100 |
101 | /// Returns `.checkmark` when the `switchValue` is on, otherwise returns `.none`.
102 | public var accessoryType: UITableViewCell.AccessoryType {
103 | return switchValue ? .checkmark : .none
104 | }
105 |
106 | /// The `SwitchRow` is selectable on tvOS.
107 | public let isSelectable: Bool = true
108 |
109 | #endif
110 |
111 | /// The additional customization during cell configuration.
112 | public let customize: ((UITableViewCell, Row & RowStyle) -> Void)?
113 |
114 | // MARK: - Equatable
115 |
116 | /// Returns true iff `lhs` and `rhs` have equal titles, detail texts, switch values, and icons.
117 | public static func == (lhs: SwitchRow, rhs: SwitchRow) -> Bool {
118 | return
119 | lhs.text == rhs.text &&
120 | lhs.detailText == rhs.detailText &&
121 | lhs.switchValue == rhs.switchValue &&
122 | lhs.icon == rhs.icon
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/Source/Views/SwitchCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwitchCell.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 03/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// The `SwitchCellDelegate` protocol allows the adopting delegate to respond to the UI interaction. Not available on tvOS.
30 | @available(tvOS, unavailable, message: "SwitchCellDelegate is not available on tvOS.")
31 | public protocol SwitchCellDelegate: class {
32 | /// Tells the delegate that the switch control is toggled.
33 | func switchCell(_ cell: SwitchCell, didToggleSwitch isOn: Bool)
34 | }
35 |
36 |
37 | /// A `UITableViewCell` subclass that shows a `UISwitch` as the `accessoryView`.
38 | open class SwitchCell: UITableViewCell, Configurable {
39 |
40 | #if os(iOS)
41 |
42 | /// A `UISwitch` as the `accessoryView`. Not available on tvOS.
43 | @available(tvOS, unavailable, message: "switchControl is not available on tvOS.")
44 | public private(set) lazy var switchControl: UISwitch = {
45 | let control = UISwitch()
46 | control.addTarget(self, action: #selector(SwitchCell.didToggleSwitch(_:)), for: .valueChanged)
47 | return control
48 | }()
49 |
50 | #endif
51 |
52 | /// The switch cell's delegate object, which should conform to `SwitchCellDelegate`. Not available on tvOS.
53 | @available(tvOS, unavailable, message: "SwitchCellDelegate is not available on tvOS.")
54 | open weak var delegate: SwitchCellDelegate?
55 |
56 | // MARK: - Initializer
57 |
58 | /**
59 | Overrides `UITableViewCell`'s designated initializer.
60 |
61 | - parameter style: A constant indicating a cell style.
62 | - parameter reuseIdentifier: A string used to identify the cell object if it is to be reused for drawing multiple rows of a table view.
63 |
64 | - returns: An initialized `SwitchCell` object.
65 | */
66 | public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
67 | super.init(style: style, reuseIdentifier: reuseIdentifier)
68 | setUpAppearance()
69 | }
70 |
71 | /**
72 | Overrides the designated initializer that returns an object initialized from data in a given unarchiver.
73 |
74 | - parameter aDecoder: An unarchiver object.
75 |
76 | - returns: `self`, initialized using the data in decoder.
77 | */
78 | public required init?(coder aDecoder: NSCoder) {
79 | super.init(coder: aDecoder)
80 | setUpAppearance()
81 | }
82 |
83 | // MARK: - Configurable
84 |
85 | /// Set up the switch control (iOS) or accessory type (tvOS) with the provided row.
86 | open func configure(with row: Row & RowStyle) {
87 | #if os(iOS)
88 | if let row = row as? SwitchRowCompatible {
89 | switchControl.isOn = row.switchValue
90 | }
91 | accessoryView = switchControl
92 | #elseif os(tvOS)
93 | accessoryView = nil
94 | accessoryType = row.accessoryType
95 | #endif
96 | }
97 |
98 | // MARK: - Private
99 |
100 | @available(tvOS, unavailable, message: "UISwitch is not available on tvOS.")
101 | @objc
102 | private func didToggleSwitch(_ sender: UISwitch) {
103 | delegate?.switchCell(self, didToggleSwitch: sender.isOn)
104 | }
105 |
106 | private func setUpAppearance() {
107 | textLabel?.numberOfLines = 0
108 | detailTextLabel?.numberOfLines = 0
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/Tests/View/ConfigurableSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurableTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 27/12/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class ConfigurableTests: XCTestCase {
31 |
32 | func testConfiguration_default() {
33 | do {
34 | // Given
35 | let cell = SwitchCell()
36 | let row = SwitchRow(text: "", switchValue: true, action: nil)
37 |
38 | // When
39 | cell.configure(with: row)
40 |
41 | // Then
42 | #if os(iOS)
43 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
44 | XCTAssertEqual(cell.switchControl.isOn, true)
45 | #elseif os(tvOS)
46 | XCTAssertNil(cell.accessoryView)
47 | XCTAssertEqual(cell.accessoryType, .checkmark)
48 | #endif
49 | }
50 |
51 | do {
52 | // Given
53 | let cell = SwitchCell()
54 | let row = SwitchRow(text: "", switchValue: false, action: nil)
55 |
56 | // When
57 | cell.configure(with: row)
58 |
59 | // Then
60 | #if os(iOS)
61 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
62 | XCTAssertEqual(cell.switchControl.isOn, false)
63 | #elseif os(tvOS)
64 | XCTAssertNil(cell.accessoryView)
65 | XCTAssertEqual(cell.accessoryType, .none)
66 | #endif
67 | }
68 | }
69 |
70 | func testConfiguration_customRow() {
71 | do {
72 | // Given
73 | let cell = SwitchCell()
74 | let row = CustomSwitchRow(text: "", switchValue: true, action: nil)
75 |
76 | // When
77 | cell.configure(with: row)
78 |
79 | // Then
80 | #if os(iOS)
81 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
82 | XCTAssertEqual(cell.switchControl.isOn, true)
83 | #elseif os(tvOS)
84 | XCTAssertNil(cell.accessoryView)
85 | XCTAssertEqual(cell.accessoryType, .checkmark)
86 | #endif
87 | }
88 |
89 | do {
90 | // Given
91 | let cell = SwitchCell()
92 | let row = CustomSwitchRow(text: "", switchValue: false, action: nil)
93 |
94 | // When
95 | cell.configure(with: row)
96 |
97 | // Then
98 | #if os(iOS)
99 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
100 | XCTAssertEqual(cell.switchControl.isOn, false)
101 | #elseif os(tvOS)
102 | XCTAssertNil(cell.accessoryView)
103 | XCTAssertEqual(cell.accessoryType, .none)
104 | #endif
105 | }
106 | }
107 |
108 | func testConfiguration_customCell() {
109 | do {
110 | // Given
111 | let cell = CustomSwitchCell()
112 | let row = CustomSwitchRow(text: "", switchValue: true, action: nil)
113 |
114 | // When
115 | cell.configure(with: row)
116 |
117 | // Then
118 | #if os(iOS)
119 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
120 | XCTAssertEqual(cell.switchControl.isOn, true)
121 | #elseif os(tvOS)
122 | XCTAssertNil(cell.accessoryView)
123 | XCTAssertEqual(cell.accessoryType, .checkmark)
124 | #endif
125 | }
126 |
127 | do {
128 | // Given
129 | let cell = CustomSwitchCell()
130 | let row = CustomSwitchRow(text: "", switchValue: false, action: nil)
131 |
132 | // When
133 | cell.configure(with: row)
134 |
135 | // Then
136 | #if os(iOS)
137 | XCTAssertEqual(cell.accessoryView, cell.switchControl)
138 | XCTAssertEqual(cell.switchControl.isOn, false)
139 | #elseif os(tvOS)
140 | XCTAssertNil(cell.accessoryView)
141 | XCTAssertEqual(cell.accessoryType, .none)
142 | #endif
143 | }
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/QuickTableViewController.xcodeproj/xcshareddata/xcschemes/QuickTableViewController-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
79 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
98 |
104 |
105 |
106 |
107 |
109 |
110 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/Tests/Model/SubtitleSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SubtitleTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 28/10/2015.
6 | // Copyright © 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | @testable import QuickTableViewController
28 | import XCTest
29 |
30 | @available(*, deprecated, message: "Compatibility tests")
31 | internal final class SubtitleTests: XCTestCase {
32 |
33 | func testCompatibility() {
34 | XCTAssertEqual(Subtitle.none.detailText, .none)
35 | XCTAssertEqual(Subtitle.belowTitle("text").detailText, .subtitle("text"))
36 | XCTAssertEqual(Subtitle.rightAligned("text").detailText, .value1("text"))
37 | XCTAssertEqual(Subtitle.leftAligned("text").detailText, .value2("text"))
38 | }
39 |
40 | func testNavigationRow() {
41 | // Given
42 | let subtitle = Subtitle.belowTitle("detail text")
43 | let detailText = DetailText.subtitle("detail text")
44 | let icon = Icon.named("icon")
45 | var invoked = false
46 |
47 | // When
48 | let row = NavigationRow(title: "text", subtitle: subtitle, icon: icon, action: { _ in invoked = true })
49 |
50 | // Then it should have the given parameters
51 | XCTAssertEqual(row.text, "text")
52 | XCTAssertEqual(row.detailText, detailText)
53 | XCTAssertEqual(row.icon, icon)
54 | XCTAssertEqual(row.cellReuseIdentifier, "UITableViewCell.subtitle")
55 |
56 | // It should support deprecated properties
57 | XCTAssertEqual(row.title, "text")
58 | XCTAssertEqual(row.subtitle?.text, "detail text")
59 |
60 | // When
61 | row.action?(row)
62 |
63 | // Then
64 | XCTAssertEqual(invoked, true)
65 | }
66 |
67 | func testOptionRow() {
68 | // Given
69 | let icon = Icon.named("icon")
70 | var invoked = false
71 |
72 | // When
73 | let row = OptionRow(title: "text", isSelected: true, icon: icon) { _ in invoked = true }
74 |
75 | // Then it should have the given parameters
76 | XCTAssertEqual(row.text, "text")
77 | XCTAssertNil(row.detailText)
78 | XCTAssertEqual(row.isSelected, true)
79 |
80 | // With RowStyle
81 | XCTAssertEqual(row.cellReuseIdentifier, "UITableViewCell")
82 | XCTAssertEqual(row.cellStyle, .default)
83 | XCTAssertEqual(row.icon, icon)
84 | XCTAssertEqual(row.accessoryType, .checkmark)
85 | XCTAssertEqual(row.isSelectable, true)
86 | XCTAssertNil(row.customize)
87 |
88 | // It should support deprecated properties
89 | XCTAssertEqual(row.title, "text")
90 | XCTAssertNil(row.subtitle?.text)
91 |
92 | // When
93 | row.action?(row)
94 |
95 | // Then
96 | XCTAssertEqual(invoked, true)
97 | }
98 |
99 | func testSwitchRow() {
100 | // Given
101 | var invoked = false
102 |
103 | // When
104 | let row = SwitchRow(title: "text", switchValue: true) { _ in invoked = true }
105 |
106 | // Then it should have the given parameters
107 | XCTAssertEqual(row.text, "text")
108 | XCTAssertNil(row.detailText)
109 | XCTAssertEqual(row.switchValue, true)
110 | XCTAssertEqual(row.cellReuseIdentifier, "SwitchCell")
111 |
112 | // It should support deprecated properties
113 | XCTAssertEqual(row.title, "text")
114 | XCTAssertNil(row.subtitle?.text)
115 |
116 | // When
117 | row.action?(row)
118 |
119 | // Then
120 | XCTAssertEqual(invoked, true)
121 | }
122 |
123 | func testTapActionRow() {
124 | // Given
125 | var invoked = false
126 |
127 | // When
128 | let row = TapActionRow(title: "text") { _ in invoked = true }
129 |
130 | // Then it should initialize with given parameters
131 | XCTAssertEqual(row.text, "text")
132 | XCTAssertNil(row.detailText)
133 | XCTAssertEqual(row.cellReuseIdentifier, "TapActionCell")
134 |
135 | // It should support deprecated properties
136 | XCTAssertEqual(row.title, "text")
137 | XCTAssertNil(row.subtitle?.text)
138 |
139 | // When
140 | row.action?(row)
141 |
142 | // Then
143 | XCTAssertEqual(invoked, true)
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/Tests/Row/SwitchRowSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwitchRowTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 17/01/2016.
6 | // Copyright © 2016 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class SwitchRowTests: XCTestCase {
31 |
32 | // MARK: - Initialization
33 |
34 | func testInitialiation() {
35 | // Given
36 | let detailText = DetailText.subtitle("subtitle")
37 | let icon = Icon.named("icon")
38 | var actionInvoked = false
39 |
40 | // When
41 | let row = SwitchRow(text: "title", detailText: detailText, switchValue: true, icon: icon) { _ in actionInvoked = true }
42 |
43 | // Then it should have the given parameters
44 | XCTAssertEqual(row.text, "title")
45 | XCTAssertEqual(row.detailText, detailText)
46 | XCTAssertEqual(row.switchValue, true)
47 |
48 | // With RowStyle
49 | XCTAssertEqual(row.icon, icon)
50 | XCTAssertEqual(row.cellReuseIdentifier, "SwitchCell")
51 |
52 | // When
53 | row.action?(row)
54 |
55 | // Then
56 | XCTAssertEqual(actionInvoked, true)
57 | }
58 |
59 | // MARK: - Equatable
60 |
61 | func testEquatable_withIdenticalParameters() {
62 | // Given
63 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
64 |
65 | // When
66 | let another = SwitchRow(text: "Same", switchValue: true, action: nil)
67 |
68 | // Then
69 | XCTAssert(one == another)
70 | }
71 |
72 | func testEquatable_withDifferentTexts() {
73 | // Given
74 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
75 |
76 | // When
77 | let another = SwitchRow(text: "Different", switchValue: true, action: nil)
78 |
79 | // Then
80 | XCTAssert(one != another)
81 | }
82 |
83 | func testEquatable_withDifferentDetailTexts() {
84 | // Given
85 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
86 |
87 | // When
88 | let another = SwitchRow(text: "Same", detailText: .subtitle("Different"), switchValue: true, action: nil)
89 |
90 | // Then
91 | XCTAssert(one != another)
92 | }
93 |
94 | func testEquatable_withDifferentSwitchValues() {
95 | // Given
96 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
97 |
98 | // When
99 | let another = SwitchRow(text: "Same", switchValue: false, action: nil)
100 |
101 | // Then
102 | XCTAssert(one != another)
103 | }
104 |
105 | func testEquatable_withDifferentIcons() {
106 | // Given
107 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
108 |
109 | // When
110 | let another = SwitchRow(text: "Same", switchValue: true, icon: .image(UIImage()), action: nil)
111 |
112 | // Then
113 | XCTAssert(one != another)
114 | }
115 |
116 | func testEquatable_withDifferentActions() {
117 | // Given
118 | let one = SwitchRow(text: "Same", switchValue: true, action: nil)
119 |
120 | // When
121 | let another = SwitchRow(text: "Same", switchValue: true, action: { _ in })
122 |
123 | // Then it should be equal regardless of the actions attached
124 | XCTAssert(one == another)
125 | }
126 |
127 | // MARK: - Action Invocation
128 |
129 | func testActionInvocationWhenSwitchValueIsToggled() {
130 | let expectation = self.expectation(description: "it should invoke the action closure")
131 |
132 | // Given a switch row that's off
133 | let row = SwitchRow(text: "", switchValue: false) { _ in expectation.fulfill() }
134 |
135 | // When the switch value is toggled
136 | row.switchValue = true
137 |
138 | // Then
139 | waitForExpectations(timeout: 1)
140 | }
141 |
142 | func testActionInvocationWhenSwitchValueIsNotToggled() {
143 | let expectation = self.expectation(description: "it should not invoke the action closure")
144 | expectation.isInverted = true
145 |
146 | // Given a switch row that's off
147 | let row = SwitchRow(text: "", switchValue: false) { _ in expectation.fulfill() }
148 |
149 | // When the switch value is not toggled
150 | row.switchValue = false
151 |
152 | // Then
153 | waitForExpectations(timeout: 1)
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/ExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleViewController.swift
3 | // Example-iOS
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | import QuickTableViewController
29 |
30 | internal final class ExampleViewController: QuickTableViewController {
31 |
32 | // MARK: - Properties
33 |
34 | private let debugging = Section(title: nil, rows: [NavigationRow(text: "", detailText: .none)])
35 |
36 | // MARK: - UIViewController
37 |
38 | override func viewDidLoad() {
39 | super.viewDidLoad()
40 | title = "QuickTableViewController"
41 |
42 | let gear = #imageLiteral(resourceName: "iconmonstr-gear")
43 | let globe = #imageLiteral(resourceName: "iconmonstr-globe")
44 | let time = #imageLiteral(resourceName: "iconmonstr-time")
45 |
46 | tableContents = [
47 | Section(title: "Switch", rows: [
48 | SwitchRow(text: "Setting 1", switchValue: true, icon: .image(globe), action: didToggleSwitch()),
49 | SwitchRow(text: "Setting 2", switchValue: false, icon: .image(time), action: didToggleSwitch())
50 | ]),
51 |
52 | Section(title: "Tap Action", rows: [
53 | TapActionRow(text: "Tap action", action: showAlert())
54 | ]),
55 |
56 | Section(title: "Navigation", rows: [
57 | NavigationRow(text: "CellStyle.default", detailText: .none, icon: .image(gear)),
58 | NavigationRow(text: "CellStyle", detailText: .subtitle(".subtitle"), icon: .image(globe), accessoryButtonAction: showDetail()),
59 | NavigationRow(text: "CellStyle", detailText: .value1(".value1"), icon: .image(time), action: showDetail()),
60 | NavigationRow(text: "CellStyle", detailText: .value2(".value2"))
61 | ], footer: "UITableViewCellStyle.Value2 hides the image view."),
62 |
63 | RadioSection(title: "Radio Buttons", options: [
64 | OptionRow(text: "Option 1", isSelected: true, action: didToggleSelection()),
65 | OptionRow(text: "Option 2", isSelected: false, action: didToggleSelection()),
66 | OptionRow(text: "Option 3", isSelected: false, action: didToggleSelection())
67 | ], footer: "See RadioSection for more details."),
68 |
69 | debugging
70 | ]
71 | }
72 |
73 | // MARK: - UITableViewDataSource
74 |
75 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
76 | let cell = super.tableView(tableView, cellForRowAt: indexPath)
77 | // Alter the cells created by QuickTableViewController
78 | return cell
79 | }
80 |
81 | // MARK: - Private Methods
82 |
83 | private func didToggleSelection() -> (Row) -> Void {
84 | return { [weak self] in
85 | if let option = $0 as? OptionRowCompatible {
86 | let state = "\(option.text) is " + (option.isSelected ? "selected" : "deselected")
87 | self?.showDebuggingText(state)
88 | }
89 | }
90 | }
91 |
92 | private func didToggleSwitch() -> (Row) -> Void {
93 | return { [weak self] in
94 | if let row = $0 as? SwitchRowCompatible {
95 | let state = "\(row.text) = \(row.switchValue)"
96 | self?.showDebuggingText(state)
97 | }
98 | }
99 | }
100 |
101 | private func showAlert() -> (Row) -> Void {
102 | return { [weak self] _ in
103 | let alert = UIAlertController(title: "Action Triggered", message: nil, preferredStyle: .alert)
104 | alert.addAction(UIAlertAction(title: "OK", style: .cancel) { [weak self] _ in
105 | self?.dismiss(animated: true, completion: nil)
106 | })
107 | self?.present(alert, animated: true, completion: nil)
108 | }
109 | }
110 |
111 | private func showDetail() -> (Row) -> Void {
112 | return { [weak self] in
113 | let detail = $0.text + ($0.detailText?.text ?? "")
114 | let controller = UIViewController()
115 | controller.view.backgroundColor = .white
116 | controller.title = detail
117 | self?.navigationController?.pushViewController(controller, animated: true)
118 | self?.showDebuggingText(detail + " is selected")
119 | }
120 | }
121 |
122 | private func showDebuggingText(_ text: String) {
123 | print(text)
124 | debugging.rows = [NavigationRow(text: text, detailText: .none)]
125 | if let section = tableContents.firstIndex(where: { $0 === debugging }) {
126 | tableView.reloadSections([section], with: .none)
127 | }
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/Tests/Row/OptionRowSpec.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OptionRowTests.swift
3 | // QuickTableViewControllerTests
4 | //
5 | // Created by Ben on 17/08/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import QuickTableViewController
28 | import XCTest
29 |
30 | internal final class OptionRowTests: XCTestCase {
31 |
32 | // MARK: - Initialization
33 |
34 | func testInitialiation() {
35 | // Given
36 | let detailText = DetailText.subtitle("subtitle")
37 | let icon = Icon.named("icon")
38 | var actionInvoked = false
39 |
40 | // When
41 | let row = OptionRow(text: "title", detailText: detailText, isSelected: true, icon: icon) { _ in actionInvoked = true }
42 |
43 | // Then it should have the given parameters
44 | XCTAssertEqual(row.text, "title")
45 | XCTAssertEqual(row.detailText, detailText)
46 | XCTAssertEqual(row.isSelected, true)
47 |
48 | // With RowStyle
49 | XCTAssertEqual(row.cellReuseIdentifier, "UITableViewCell")
50 | XCTAssertEqual(row.cellStyle, .default)
51 | XCTAssertEqual(row.icon, icon)
52 | XCTAssertEqual(row.accessoryType, .checkmark)
53 | XCTAssertEqual(row.isSelectable, true)
54 | XCTAssertNil(row.customize)
55 |
56 | // When
57 | row.action?(row)
58 |
59 | // Then
60 | XCTAssertEqual(actionInvoked, true)
61 | }
62 |
63 | // MARK: - Equatable
64 |
65 | func testEquatable_withIdenticalParameters() {
66 | // Given
67 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
68 |
69 | // When
70 | let another = OptionRow(text: "Same", isSelected: true, action: nil)
71 |
72 | // Then
73 | XCTAssert(one == another)
74 | }
75 |
76 | func testEquatable_withDifferentTexts() {
77 | // Given
78 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
79 |
80 | // When
81 | let another = OptionRow(text: "Different", isSelected: true, action: nil)
82 |
83 | // Then
84 | XCTAssert(one != another)
85 | }
86 |
87 | func testEquatable_withDifferentDetailTexts() {
88 | // Given
89 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
90 |
91 | // When
92 | let another = OptionRow(text: "Same", detailText: .subtitle("Different"), isSelected: true, action: nil)
93 |
94 | // Then
95 | XCTAssert(one != another)
96 | }
97 |
98 | func testEquatable_withDifferentSelectionStates() {
99 | // Given
100 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
101 |
102 | // When
103 | let another = OptionRow(text: "Same", isSelected: false, action: nil)
104 |
105 | // Then
106 | XCTAssert(one != another)
107 | }
108 |
109 | func testEquatable_withDifferentIcons() {
110 | // Given
111 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
112 |
113 | // When
114 | let another = OptionRow(text: "Same", isSelected: true, icon: .image(UIImage()), action: nil)
115 |
116 | // Then
117 | XCTAssert(one != another)
118 | }
119 |
120 | func testEquatable_withDifferentActions() {
121 | // Given
122 | let one = OptionRow(text: "Same", isSelected: true, action: nil)
123 |
124 | // When
125 | let another = OptionRow(text: "Same", isSelected: true, action: { _ in })
126 |
127 | // Then it should be equal regardless of the actions attached
128 | XCTAssert(one == another)
129 | }
130 |
131 | // MARK: - Action Invocation
132 |
133 | func testActionInvocationWhenSelectionIsToggled() {
134 | let expectation = self.expectation(description: "it should invoke the action closure")
135 |
136 | // Given an option row that's not selected
137 | let row = OptionRow(text: "", isSelected: false) { _ in expectation.fulfill() }
138 |
139 | // When the selection is toggled
140 | row.isSelected = true
141 |
142 | // Then
143 | waitForExpectations(timeout: 1, handler: nil)
144 | XCTAssertEqual(row.accessoryType, .checkmark)
145 | }
146 |
147 | func testActionInvocationWhenSelectionIsNotToggled() {
148 | let expectation = self.expectation(description: "it should not invoke the action closure")
149 | expectation.isInverted = true
150 |
151 | // Given an option row that's not selected
152 | let row = OptionRow(text: "", isSelected: false) { _ in expectation.fulfill() }
153 |
154 | // When the selection is not toggled
155 | row.isSelected = false
156 |
157 | // Then
158 | waitForExpectations(timeout: 1, handler: nil)
159 | XCTAssertEqual(row.accessoryType, .none)
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/Source/Rows/NavigationRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationRow.swift
3 | // QuickTableViewController
4 | //
5 | // Created by Ben on 01/09/2015.
6 | // Copyright (c) 2015 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 |
29 | /// A class that represents a row that triggers certain navigation when selected.
30 | open class NavigationRow: NavigationRowCompatible, Equatable {
31 |
32 | // MARK: - Initializer
33 |
34 | #if os(iOS)
35 |
36 | /// Designated initializer on iOS. Returns a `NavigationRow` with a text and a detail text.
37 | /// The icon, customization, action and accessory button action closures are optional.
38 | public init(
39 | text: String,
40 | detailText: DetailText,
41 | icon: Icon? = nil,
42 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
43 | action: ((Row) -> Void)? = nil,
44 | accessoryButtonAction: ((Row) -> Void)? = nil
45 | ) {
46 | self.text = text
47 | self.detailText = detailText
48 | self.icon = icon
49 | self.customize = customization
50 | self.action = action
51 | self.accessoryButtonAction = accessoryButtonAction
52 | }
53 |
54 | #elseif os(tvOS)
55 |
56 | /// Designated initializer on tvOS. Returns a `NavigationRow` with a text and a detail text.
57 | /// The icon, customization and action closures are optional.
58 | public init(
59 | text: String,
60 | detailText: DetailText,
61 | icon: Icon? = nil,
62 | customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
63 | action: ((Row) -> Void)? = nil
64 | ) {
65 | self.text = text
66 | self.detailText = detailText
67 | self.icon = icon
68 | self.customize = customization
69 | self.action = action
70 | }
71 |
72 | #endif
73 |
74 | // MARK: - Row
75 |
76 | /// The text of the row.
77 | public let text: String
78 |
79 | /// The detail text of the row.
80 | public let detailText: DetailText?
81 |
82 | /// A closure that will be invoked when the row is selected.
83 | public let action: ((Row) -> Void)?
84 |
85 | #if os(iOS)
86 |
87 | /// A closure that will be invoked when the accessory button is selected.
88 | public let accessoryButtonAction: ((Row) -> Void)?
89 |
90 | #endif
91 |
92 | // MARK: - RowStyle
93 |
94 | /// The type of the table view cell to display the row.
95 | public let cellType: UITableViewCell.Type = T.self
96 |
97 | /// Returns the reuse identifier of the table view cell to display the row.
98 | public var cellReuseIdentifier: String {
99 | return T.reuseIdentifier + (detailText?.style.stringValue ?? "")
100 | }
101 |
102 | /// Returns the table view cell style for the specified subtitle.
103 | public var cellStyle: UITableViewCell.CellStyle {
104 | return detailText?.style ?? .default
105 | }
106 |
107 | /// The icon of the row.
108 | public let icon: Icon?
109 |
110 | /// Returns the accessory type with the disclosure indicator when `action` is not nil,
111 | /// and with the detail button when `accessoryButtonAction` is not nil.
112 | public var accessoryType: UITableViewCell.AccessoryType {
113 | #if os(iOS)
114 | switch (action, accessoryButtonAction) {
115 | case (nil, nil): return .none
116 | case (.some, nil): return .disclosureIndicator
117 | case (nil, .some): return .detailButton
118 | case (.some, .some): return .detailDisclosureButton
119 | }
120 | #elseif os(tvOS)
121 | return (action == nil) ? .none : .disclosureIndicator
122 | #endif
123 | }
124 |
125 | /// The `NavigationRow` is selectable when action is not nil.
126 | public var isSelectable: Bool {
127 | return action != nil
128 | }
129 |
130 | /// The additional customization during cell configuration.
131 | public let customize: ((UITableViewCell, Row & RowStyle) -> Void)?
132 |
133 | // MARK: Equatable
134 |
135 | /// Returns true iff `lhs` and `rhs` have equal titles, detail texts and icons.
136 | public static func == (lhs: NavigationRow, rhs: NavigationRow) -> Bool {
137 | return
138 | lhs.text == rhs.text &&
139 | lhs.detailText == rhs.detailText &&
140 | lhs.icon == rhs.icon
141 | }
142 |
143 | }
144 |
145 |
146 | private extension UITableViewCell.CellStyle {
147 |
148 | var stringValue: String {
149 | switch self {
150 | case .default: return ".default"
151 | case .subtitle: return ".subtitle"
152 | case .value1: return ".value1"
153 | case .value2: return ".value2"
154 | @unknown default: return ".default"
155 | }
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/Example-tvOSUITests/ExampleUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITests.swift
3 | // Example-tvOSUITests
4 | //
5 | // Created by Francesco Deliro on 07/10/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import XCTest
28 |
29 | internal final class ExampleUITests: XCTestCase {
30 |
31 | private lazy var app: XCUIApplication = XCUIApplication()
32 |
33 | override func setUp() {
34 | super.setUp()
35 | continueAfterFailure = false
36 | app.launch()
37 | }
38 |
39 | func testInteractions() {
40 |
41 | let tables = XCUIApplication().tables
42 | let existance = NSPredicate(format: "exists == true")
43 | let hasFocus = NSPredicate(format: "hasFocus == true")
44 |
45 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Setting 1").element, handler: nil)
46 | waitForExpectations(timeout: 5, handler: nil)
47 | XCUIRemote.shared.press(.select)
48 |
49 | XCUIRemote.shared.press(.down)
50 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Setting 2").element, handler: nil)
51 | waitForExpectations(timeout: 5, handler: nil)
52 | XCUIRemote.shared.press(.select)
53 |
54 | XCUIRemote.shared.press(.down)
55 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Tap action").element, handler: nil)
56 | waitForExpectations(timeout: 5, handler: nil)
57 | XCUIRemote.shared.press(.select)
58 |
59 | expectation(for: hasFocus, evaluatedWith: app.alerts["Tap action"].otherElements["OK"], handler: nil)
60 | waitForExpectations(timeout: 5, handler: nil)
61 | XCUIRemote.shared.press(.select)
62 |
63 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Tap action").element, handler: nil)
64 | waitForExpectations(timeout: 5, handler: nil)
65 |
66 | XCUIRemote.shared.press(.down)
67 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "CellStyle.default").element, handler: nil)
68 | waitForExpectations(timeout: 5, handler: nil)
69 | XCUIRemote.shared.press(.select)
70 |
71 | expectation(for: existance, evaluatedWith: app.otherElements.containing(.staticText, identifier: "CellStyle.default").element, handler: nil)
72 | waitForExpectations(timeout: 5, handler: nil)
73 | XCUIRemote.shared.press(.menu)
74 |
75 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "CellStyle.default").element, handler: nil)
76 | waitForExpectations(timeout: 5, handler: nil)
77 |
78 | XCUIRemote.shared.press(.down)
79 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: ".subtitle").element, handler: nil)
80 | waitForExpectations(timeout: 5, handler: nil)
81 | XCUIRemote.shared.press(.select)
82 |
83 | expectation(for: existance, evaluatedWith: app.otherElements.containing(.staticText, identifier: "CellStyle.subtitle").element, handler: nil)
84 | waitForExpectations(timeout: 5, handler: nil)
85 | XCUIRemote.shared.press(.menu)
86 |
87 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: ".subtitle").element, handler: nil)
88 | waitForExpectations(timeout: 5, handler: nil)
89 |
90 | XCUIRemote.shared.press(.down)
91 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: ".value1").element, handler: nil)
92 | waitForExpectations(timeout: 5, handler: nil)
93 | XCUIRemote.shared.press(.select)
94 |
95 | XCUIRemote.shared.press(.down)
96 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: ".value2").element, handler: nil)
97 | waitForExpectations(timeout: 5, handler: nil)
98 | XCUIRemote.shared.press(.select)
99 |
100 | XCUIRemote.shared.press(.down)
101 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Option 1").element, handler: nil)
102 | waitForExpectations(timeout: 5, handler: nil)
103 | XCUIRemote.shared.press(.select)
104 |
105 | XCUIRemote.shared.press(.down)
106 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Option 2").element, handler: nil)
107 | waitForExpectations(timeout: 5, handler: nil)
108 | XCUIRemote.shared.press(.select)
109 |
110 | XCUIRemote.shared.press(.down)
111 | expectation(for: hasFocus, evaluatedWith: tables.cells.containing(.staticText, identifier: "Option 3").element, handler: nil)
112 | waitForExpectations(timeout: 5, handler: nil)
113 | XCUIRemote.shared.press(.select)
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/Example-iOS/ViewControllers/CustomizationViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomizationViewController.swift
3 | // Example-iOS
4 | //
5 | // Created by Ben on 30/01/2018.
6 | // Copyright © 2018 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import UIKit
28 | import QuickTableViewController
29 |
30 | private final class CustomCell: UITableViewCell {}
31 | private final class CustomSwitchCell: SwitchCell {}
32 | private final class CustomTapActionCell: TapActionCell {}
33 | private final class CustomOptionCell: UITableViewCell {}
34 |
35 | private final class CustomSwitchRow: SwitchRow {}
36 | private final class CustomTapActionRow: TapActionRow {}
37 | private final class CustomNavigationRow: NavigationRow {}
38 | private final class CustomOptionRow: OptionRow {}
39 |
40 | internal final class CustomizationViewController: QuickTableViewController {
41 |
42 | private let debugging = Section(
43 | title: nil,
44 | rows: [NavigationRow(text: "", detailText: .none)],
45 | footer: "Select or toggle each row to show their cell reuse identifiers."
46 | )
47 |
48 | // MARK: - UIViewController
49 |
50 | override func viewDidLoad() {
51 | super.viewDidLoad()
52 | title = "Customization"
53 |
54 | tableContents = [
55 | debugging,
56 |
57 | Section(title: "Switch", rows: [
58 | SwitchRow(
59 | text: "SwitchRow\n",
60 | switchValue: true,
61 | customization: set(label: "0-0"),
62 | action: showLog()
63 | ),
64 | CustomSwitchRow(
65 | text: "CustomSwitchRow\n",
66 | switchValue: false,
67 | customization: set(label: "0-1"),
68 | action: showLog()
69 | )
70 | ]),
71 |
72 | Section(title: "Tap Action", rows: [
73 | TapActionRow(
74 | text: "TapActionRow\n",
75 | customization: set(label: "1-0"),
76 | action: showLog()
77 | ),
78 | CustomTapActionRow(
79 | text: "CustomTapActionRow\n",
80 | customization: set(label: "1-1"),
81 | action: showLog()
82 | )
83 | ]),
84 |
85 | Section(title: "Navigation", rows: [
86 | NavigationRow(
87 | text: "NavigationRow",
88 | detailText: .none,
89 | customization: set(label: "2-0"),
90 | action: showLog()
91 | ),
92 | NavigationRow(
93 | text: "NavigationRow",
94 | detailText: .subtitle(".subtitle"),
95 | customization: set(label: "2-1"),
96 | action: showLog()
97 | ),
98 | CustomNavigationRow(
99 | text: "CustomNavigationRow",
100 | detailText: .value1(".value1"),
101 | customization: set(label: "2-2"),
102 | action: showLog()
103 | ),
104 | CustomNavigationRow(
105 | text: "CustomNavigationRow",
106 | detailText: .value2(".value2"),
107 | customization: set(label: "2-3"),
108 | action: showLog()
109 | )
110 | ]),
111 |
112 | RadioSection(title: "Radio Buttons", options: [
113 | OptionRow(
114 | text: "OptionRow",
115 | isSelected: false,
116 | customization: set(label: "3-0"),
117 | action: showLog()
118 | ),
119 | CustomOptionRow(
120 | text: "CustomOptionRow",
121 | isSelected: false,
122 | customization: set(label: "3-1"),
123 | action: showLog()
124 | ),
125 | CustomOptionRow(
126 | text: "CustomOptionRow",
127 | isSelected: false,
128 | customization: set(label: "3-2"),
129 | action: showLog()
130 | )
131 | ]),
132 |
133 | Section(title: nil, rows: [
134 | NavigationRow(text: "Customization closure", detailText: .none, customization: { cell, _ in
135 | cell.accessibilityLabel = "4-0"
136 | cell.accessoryView = UIImageView(image: #imageLiteral(resourceName: "iconmonstr-x-mark"))
137 | })
138 | ])
139 | ]
140 |
141 | }
142 |
143 | // MARK: - UITableViewDelegate
144 |
145 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
146 | super.tableView(tableView, didSelectRowAt: indexPath)
147 | tableView.deselectRow(at: indexPath, animated: true)
148 | }
149 |
150 | // MARK: - Private
151 |
152 | private func set(label: String) -> ((UITableViewCell, Row & RowStyle) -> Void) {
153 | return { cell, _ in
154 | cell.accessibilityLabel = label
155 | }
156 | }
157 |
158 | private func showLog() -> (Row) -> Void {
159 | return { [weak self] in
160 | if let option = $0 as? OptionRowCompatible, !option.isSelected {
161 | return
162 | }
163 | let identifier = ($0 as! RowStyle).cellReuseIdentifier
164 | self?.debugging.rows = [
165 | NavigationRow(text: identifier, detailText: .none, customization: self?.set(label: "debug"))
166 | ]
167 | print(identifier)
168 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
169 | self?.tableView.reloadData()
170 | }
171 | }
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/Example-iOSUITests/ExampleUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleUITests.swift
3 | // Example-iOSUITests
4 | //
5 | // Created by Ben on 14/08/2017.
6 | // Copyright © 2017 bcylin.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | // SOFTWARE.
25 | //
26 |
27 | import XCTest
28 |
29 | internal final class ExampleUITests: XCTestCase {
30 |
31 | private lazy var app: XCUIApplication = XCUIApplication()
32 |
33 | override func setUp() {
34 | super.setUp()
35 | continueAfterFailure = false
36 | app.launch()
37 | }
38 |
39 | func testInteractions() {
40 | app.tables.staticTexts["Use default cell types"].tap()
41 |
42 | let tables = XCUIApplication().tables
43 | let existance = NSPredicate(format: "exists == true")
44 |
45 | tables.switches["Setting 1"].tap()
46 | expectation(for: existance, evaluatedWith: tables.staticTexts["Setting 1 = false"], handler: nil)
47 | waitForExpectations(timeout: 5, handler: nil)
48 |
49 | tables.switches["Setting 1"].tap()
50 | expectation(for: existance, evaluatedWith: tables.staticTexts["Setting 1 = true"], handler: nil)
51 | waitForExpectations(timeout: 5, handler: nil)
52 |
53 | tables.switches["Setting 2"].tap()
54 | expectation(for: existance, evaluatedWith: tables.staticTexts["Setting 2 = true"], handler: nil)
55 | waitForExpectations(timeout: 5, handler: nil)
56 |
57 | tables.switches["Setting 2"].tap()
58 | expectation(for: existance, evaluatedWith: tables.staticTexts["Setting 2 = false"], handler: nil)
59 | waitForExpectations(timeout: 5, handler: nil)
60 |
61 | tables.staticTexts["Tap action"].tap()
62 | addUIInterruptionMonitor(withDescription: "Action Triggered") {
63 | let button = $0.buttons["OK"]
64 | if button.exists {
65 | button.tap()
66 | return true
67 | }
68 | return false
69 | }
70 |
71 | tables.cells.containing(.staticText, identifier: ".subtitle").buttons["More Info"].tap()
72 | app.navigationBars.buttons.element(boundBy: 0).tap()
73 | expectation(for: existance, evaluatedWith: tables.staticTexts["CellStyle.subtitle is selected"], handler: nil)
74 | waitForExpectations(timeout: 5, handler: nil)
75 |
76 | tables.staticTexts[".value1"].tap()
77 | app.navigationBars.buttons.element(boundBy: 0).tap()
78 | expectation(for: existance, evaluatedWith: tables.staticTexts["CellStyle.value1 is selected"], handler: nil)
79 | waitForExpectations(timeout: 5, handler: nil)
80 |
81 | tables.staticTexts["Option 1"].tap()
82 | expectation(for: existance, evaluatedWith: tables.staticTexts["Option 1 is deselected"], handler: nil)
83 | waitForExpectations(timeout: 5, handler: nil)
84 | tables.staticTexts["Option 2"].tap()
85 | expectation(for: existance, evaluatedWith: tables.staticTexts["Option 2 is selected"], handler: nil)
86 | waitForExpectations(timeout: 5, handler: nil)
87 | tables.staticTexts["Option 3"].tap()
88 | expectation(for: existance, evaluatedWith: tables.staticTexts["Option 3 is selected"], handler: nil)
89 | waitForExpectations(timeout: 5, handler: nil)
90 | }
91 |
92 | func testCustomization() {
93 | app.tables.staticTexts["Use custom cell types"].tap()
94 |
95 | let tables = XCUIApplication().tables
96 | let existance = NSPredicate(format: "exists == true")
97 |
98 | tables.switches["0-0"].tap()
99 | expectation(for: existance, evaluatedWith: tables.staticTexts["CustomSwitchCell"], handler: nil)
100 | waitForExpectations(timeout: 5, handler: nil)
101 |
102 | tables.switches["0-1"].tap()
103 | expectation(for: existance, evaluatedWith: tables.staticTexts["SwitchCell"], handler: nil)
104 | waitForExpectations(timeout: 5, handler: nil)
105 |
106 | tables.cells["1-0"].tap()
107 | expectation(for: existance, evaluatedWith: tables.staticTexts["CustomTapActionCell"], handler: nil)
108 | waitForExpectations(timeout: 5, handler: nil)
109 |
110 | tables.cells["1-1"].tap()
111 | expectation(for: existance, evaluatedWith: tables.staticTexts["TapActionCell"], handler: nil)
112 | waitForExpectations(timeout: 5, handler: nil)
113 |
114 | tables.cells["2-0"].tap()
115 | expectation(for: existance, evaluatedWith: tables.staticTexts["UITableViewCell.default"], handler: nil)
116 | waitForExpectations(timeout: 5, handler: nil)
117 |
118 | tables.cells["2-1"].tap()
119 | expectation(for: existance, evaluatedWith: tables.staticTexts["CustomCell.subtitle"], handler: nil)
120 | waitForExpectations(timeout: 5, handler: nil)
121 |
122 | tables.cells["2-2"].tap()
123 | expectation(for: existance, evaluatedWith: tables.staticTexts["UITableViewCell.value1"], handler: nil)
124 | waitForExpectations(timeout: 5, handler: nil)
125 |
126 | tables.cells["2-3"].tap()
127 | expectation(for: existance, evaluatedWith: tables.staticTexts["CustomCell.value2"], handler: nil)
128 | waitForExpectations(timeout: 5, handler: nil)
129 |
130 | tables.cells["3-0"].tap()
131 | expectation(for: existance, evaluatedWith: tables.staticTexts["UITableViewCell"], handler: nil)
132 | waitForExpectations(timeout: 5, handler: nil)
133 |
134 | tables.cells["3-1"].tap()
135 | expectation(for: existance, evaluatedWith: tables.staticTexts["UITableViewCell"], handler: nil)
136 | waitForExpectations(timeout: 5, handler: nil)
137 |
138 | tables.cells["3-2"].tap()
139 | expectation(for: existance, evaluatedWith: tables.staticTexts["CustomOptionCell"], handler: nil)
140 | waitForExpectations(timeout: 5, handler: nil)
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------