├── .swift-version
├── Logo.png
├── Former-Demo
├── Former-Demo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── check.imageset
│ │ │ ├── ok2.png
│ │ │ └── Contents.json
│ │ ├── start_logo.imageset
│ │ │ ├── start_logo.png
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── appicon-60@2x.png
│ │ │ ├── appicon-60@3x.png
│ │ │ ├── appicon-Small@2x.png
│ │ │ ├── appicon-Small@3x.png
│ │ │ ├── appicon-Small-40@2x.png
│ │ │ ├── appicon-Small-40@3x.png
│ │ │ └── Contents.json
│ │ └── header_logo.imageset
│ │ │ ├── header_logo.png
│ │ │ └── Contents.json
│ ├── Commons
│ │ ├── Login.swift
│ │ └── Profile.swift
│ ├── Helpers
│ │ ├── UIColor+FormerDemo.swift
│ │ └── String+FormerDemo.swift
│ ├── Cells
│ │ ├── ProfileFieldCell.swift
│ │ ├── ProfileLabelCell.swift
│ │ ├── DynamicHeightCell.swift
│ │ ├── CenterLabelCell.swift
│ │ ├── ProfileImageCell.swift
│ │ └── ColorListCell.swift
│ ├── Info.plist
│ ├── Controllers
│ │ ├── TextSelectorViewContorller.swift
│ │ ├── CustomCellViewController.swift
│ │ ├── TopViewController.swift
│ │ ├── DefaultsViewController.swift
│ │ └── LoginViewController.swift
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Resources
│ │ ├── ColorListCell.xib
│ │ ├── DynamicHeightCell.xib
│ │ ├── ProfileImageCell.xib
│ │ ├── ProfileFieldCell.xib
│ │ ├── ProfileLabelCell.xib
│ │ └── LoginViewController.storyboard
│ └── Views
│ │ └── FormerInputAccessoryView.swift
└── Former-Demo.xcodeproj
│ └── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Former.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── Former.xcscheme
├── Package.swift
├── .gitignore
├── CONTRIBUTING.md
├── Former
├── RowFormers
│ ├── CustomRowFormer.swift
│ ├── BaseRowFormer.swift
│ ├── LabelRowFormer.swift
│ ├── DatePickerRowFormer.swift
│ ├── CheckRowFormer.swift
│ ├── SegmentedRowFormer.swift
│ ├── SwitchRowFormer.swift
│ ├── StepperRowFormer.swift
│ ├── SliderRowFormer.swift
│ ├── PickerRowFormer.swift
│ ├── SelectorDatePickerRowFormer.swift
│ ├── InlineDatePickerRowFormer.swift
│ ├── TextFieldRowFormer.swift
│ ├── InlinePickerRowFormer.swift
│ └── TextViewRowFormer.swift
├── Former.h
├── ViewFormers
│ ├── CustomViewFormer.swift
│ ├── LabelViewFormer.swift
│ └── BaseViewFormer.swift
├── Cells
│ ├── FormCell.swift
│ ├── FormCheckCell.swift
│ ├── FormPickerCell.swift
│ ├── FormDatePickerCell.swift
│ ├── FormSwitchCell.swift
│ ├── FormStepperCell.swift
│ ├── FormSegmentedCell.swift
│ ├── FormSliderCell.swift
│ ├── FormLabelCell.swift
│ ├── FormTextViewCell.swift
│ ├── FormInlineDatePickerCell.swift
│ ├── FormInlinePickerCell.swift
│ ├── FormSelectorPickerCell.swift
│ ├── FormSelectorDatePickerCell.swift
│ └── FormTextFieldCell.swift
├── Views
│ ├── FormHeaderFooterView.swift
│ ├── FormLabelHeaderView.swift
│ └── FormLabelFooterView.swift
├── Info.plist
├── Helpers
│ └── UITableViewCell+Former.swift
├── Controllers
│ └── FormViewController.swift
└── Commons
│ ├── FormerProtocol.swift
│ ├── ViewFormer.swift
│ ├── RowFormer.swift
│ └── SectionFormer.swift
├── .github
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── Former.podspec
├── LICENSE
└── CODE_OF_CONDUCT.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2
2 |
--------------------------------------------------------------------------------
/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Logo.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/check.imageset/ok2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/check.imageset/ok2.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/start_logo.imageset/start_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/start_logo.imageset/start_logo.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-60@2x.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-60@3x.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/header_logo.imageset/header_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/header_logo.imageset/header_logo.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small@2x.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small@3x.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small-40@2x.png
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ra1028/Former/HEAD/Former-Demo/Former-Demo/Assets.xcassets/AppIcon.appiconset/appicon-Small-40@3x.png
--------------------------------------------------------------------------------
/Former.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Former.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Former",
7 | platforms: [.iOS(.v10)],
8 | products: [
9 | .library(name: "Former", targets: ["Former"]),
10 | ],
11 | targets: [
12 | .target(name: "Former", path: "Former")
13 | ]
14 | )
15 |
16 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Commons/Login.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Login.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/8/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class Login {
12 |
13 | static let sharedInstance = Login()
14 |
15 | var username: String?
16 | var password: String?
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | */build/*
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | profile
14 | *.moved-aside
15 | DerivedData
16 | .idea/
17 | *.hmap
18 | *.xccheckout
19 | #CocoaPods
20 | Pods
21 | Tests/Pods
22 | Tests/Podfile.lock
23 | Examples/Objective-C/Podfile.lock
24 | Examples/Swift/Podfile.lock
25 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/check.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ok2.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/header_logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "header_logo.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Assets.xcassets/start_logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "start_logo.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 | If you're interesting in helping us improve and maintain Former, it is highly encouraged that you fork the repository and submit a pull request with your updates.
3 |
4 | If you do chose to submit a pull request, please make sure to clearly document what changes you have made in the description of the PR.
5 |
6 | We will try to get this merged as soon as possible and included in an official cocoapod release as soon as it makes sense to do so.
7 |
--------------------------------------------------------------------------------
/Former/RowFormers/CustomRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomRowFormer.swift
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 11/5/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class CustomRowFormer
12 | : BaseRowFormer, Formable {
13 |
14 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
15 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Former/Former.h:
--------------------------------------------------------------------------------
1 | //
2 | // Former.h
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 9/12/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Former.
12 | FOUNDATION_EXPORT double FormerVersionNumber;
13 |
14 | //! Project version string for Former.
15 | FOUNDATION_EXPORT const unsigned char FormerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Former/ViewFormers/CustomViewFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomViewFormer.swift
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 11/7/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class CustomViewFormer
12 | : BaseViewFormer {
13 |
14 | // MARK: Public
15 |
16 | required public init(instantiateType: Former.InstantiateType = .Class, viewSetup: ((T) -> Void)? = nil) {
17 | super.init(instantiateType: instantiateType, viewSetup: viewSetup)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Commons/Profile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Profile.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 10/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class Profile {
12 |
13 | static let sharedInstance = Profile()
14 |
15 | var image: UIImage?
16 | var name: String?
17 | var gender: String?
18 | var birthDay: Date?
19 | var introduction: String?
20 | var moreInformation = false
21 | var nickname: String?
22 | var location: String?
23 | var phoneNumber: String?
24 | var job: String?
25 | }
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Helpers/UIColor+FormerDemo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColorExt.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/5/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 |
13 | class func formerColor() -> UIColor {
14 | return UIColor(red: 0.14, green: 0.16, blue: 0.22, alpha: 1)
15 | }
16 |
17 | class func formerSubColor() -> UIColor {
18 | return UIColor(red: 0.9, green: 0.55, blue: 0.08, alpha: 1)
19 | }
20 |
21 | class func formerHighlightedSubColor() -> UIColor {
22 | return UIColor(red: 1, green: 0.7, blue: 0.12, alpha: 1)
23 | }
24 | }
--------------------------------------------------------------------------------
/Former.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = "Former"
3 | spec.version = "1.8.1"
4 | spec.author = { "ra1028" => "r.fe51028.r@gmail.com" }
5 | spec.homepage = "https://github.com/ra1028"
6 | spec.summary = "Former is a fully customizable Swift library for easy creating UITableView based form."
7 | spec.source = { :git => "https://github.com/ra1028/Former.git", :tag => spec.version.to_s }
8 | spec.license = { :type => "MIT", :file => "LICENSE" }
9 | spec.platform = :ios, '10.0'
10 | spec.source_files = "Former", "Former/**/*.{swift}"
11 | spec.requires_arc = true
12 | spec.ios.deployment_target = '10.0'
13 | spec.ios.frameworks = ['UIKit', 'Foundation']
14 | spec.swift_version = '4.2'
15 | end
16 |
--------------------------------------------------------------------------------
/Former/Cells/FormCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/25/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormCell: UITableViewCell, FormableRow {
12 |
13 | // MARK: Public
14 |
15 | required public init?(coder aDecoder: NSCoder) {
16 | super.init(coder: aDecoder)
17 | setup()
18 | }
19 |
20 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
21 | super.init(style: style, reuseIdentifier: reuseIdentifier)
22 | setup()
23 | }
24 |
25 | open func updateWithRowFormer(_ rowFormer: RowFormer) {}
26 |
27 | open func setup() {
28 | contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
29 | textLabel?.backgroundColor = .clear
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/ProfileFieldCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileFieldCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 10/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class ProfileFieldCell: UITableViewCell, TextFieldFormableRow {
13 |
14 | @IBOutlet weak var titleLabel: UILabel!
15 | @IBOutlet weak var textField: UITextField!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | titleLabel.textColor = .formerColor()
20 | textField.textColor = .formerSubColor()
21 | }
22 |
23 | func formTextField() -> UITextField {
24 | return textField
25 | }
26 |
27 | func formTitleLabel() -> UILabel? {
28 | return titleLabel
29 | }
30 |
31 | func updateWithRowFormer(_ rowFormer: RowFormer) {}
32 | }
33 |
--------------------------------------------------------------------------------
/Former/Views/FormHeaderFooterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormHeaderFooterView.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormHeaderFooterView: UITableViewHeaderFooterView, FormableView {
12 |
13 | // MARK: Public
14 |
15 | required public init?(coder aDecoder: NSCoder) {
16 | super.init(coder: aDecoder)
17 | setup()
18 | }
19 |
20 | override public init(reuseIdentifier: String?) {
21 | super.init(reuseIdentifier: reuseIdentifier)
22 | setup()
23 | }
24 |
25 | open func updateWithViewFormer(_ viewFormer: ViewFormer) {}
26 |
27 | open func setup() {
28 | contentView.backgroundColor = .groupTableViewBackground
29 | contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Former/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/ProfileLabelCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileLabelCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 10/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class ProfileLabelCell: UITableViewCell, InlineDatePickerFormableRow, InlinePickerFormableRow {
13 |
14 | @IBOutlet weak var titleLabel: UILabel!
15 | @IBOutlet weak var displayLabel: UILabel!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | titleLabel.textColor = .formerColor()
20 | displayLabel.textColor = .formerSubColor()
21 | }
22 |
23 | func formTitleLabel() -> UILabel? {
24 | return titleLabel
25 | }
26 |
27 | func formDisplayLabel() -> UILabel? {
28 | return displayLabel
29 | }
30 |
31 | func updateWithRowFormer(_ rowFormer: RowFormer) {}
32 | }
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/Former/ViewFormers/LabelViewFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LabelViewFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol LabelFormableView: FormableView {
12 |
13 | func formTitleLabel() -> UILabel
14 | }
15 |
16 | public final class LabelViewFormer: BaseViewFormer where T: LabelFormableView {
17 |
18 | // MARK: Public
19 |
20 | public var text: String?
21 |
22 | required public init(instantiateType: Former.InstantiateType = .Class, viewSetup: ((T) -> Void)? = nil) {
23 | super.init(instantiateType: instantiateType, viewSetup: viewSetup)
24 | }
25 |
26 | public override func initialized() {
27 | super.initialized()
28 | viewHeight = 30
29 | }
30 |
31 | public override func update() {
32 | super.update()
33 | view.formTitleLabel().text = text
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/DynamicHeightCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicHeightCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/7/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class DynamicHeightCell: UITableViewCell {
13 |
14 | var title: String? {
15 | get { return titleLabel.text }
16 | set { titleLabel.text = newValue }
17 | }
18 |
19 | var body: String? {
20 | get { return bodyLabel.text }
21 | set { bodyLabel.text = newValue }
22 | }
23 | var bodyColor: UIColor? {
24 | get { return bodyLabel.textColor }
25 | set { bodyLabel.textColor = newValue }
26 | }
27 |
28 | override func awakeFromNib() {
29 | super.awakeFromNib()
30 | selectionStyle = .none
31 | titleLabel.textColor = .formerColor()
32 | }
33 |
34 | // MARK: Private
35 |
36 | @IBOutlet private weak var titleLabel: UILabel!
37 | @IBOutlet private weak var bodyLabel: UILabel!
38 | }
39 |
--------------------------------------------------------------------------------
/Former/ViewFormers/BaseViewFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseViewFormer.swift
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 10/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class BaseViewFormer
12 | : ViewFormer, ConfigurableForm {
13 |
14 | // MARK: Public
15 |
16 | public var view: T {
17 | return viewInstance as! T
18 | }
19 |
20 | required public init(
21 | instantiateType: Former.InstantiateType = .Class,
22 | viewSetup: ((T) -> Void)? = nil) {
23 | super.init(
24 | viewType: T.self,
25 | instantiateType: instantiateType,
26 | viewSetup: viewSetup
27 | )
28 | }
29 |
30 | public final func viewUpdate(update: ((T) -> Void)) -> Self {
31 | update(view)
32 | return self
33 | }
34 |
35 | open func viewInitialized(_ view: T) {}
36 |
37 | override func viewInstanceInitialized(_ view: UITableViewHeaderFooterView) {
38 | viewInitialized(view as! T)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 ra1028
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 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Helpers/String+FormerDemo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringExt.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/10/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension String {
12 |
13 | static func mediumDateShortTime(date: Date) -> String {
14 | let dateFormatter = DateFormatter()
15 | dateFormatter.locale = .current
16 | dateFormatter.timeStyle = .short
17 | dateFormatter.dateStyle = .medium
18 | return dateFormatter.string(from: date)
19 | }
20 |
21 | static func mediumDateNoTime(date: Date) -> String {
22 | let dateFormatter = DateFormatter()
23 | dateFormatter.locale = .current
24 | dateFormatter.timeStyle = .none
25 | dateFormatter.dateStyle = .medium
26 | return dateFormatter.string(from: date)
27 | }
28 |
29 | static func fullDate(date: Date) -> String {
30 | let dateFormatter = DateFormatter()
31 | dateFormatter.locale = .current
32 | dateFormatter.timeStyle = .none
33 | dateFormatter.dateStyle = .full
34 | return dateFormatter.string(from: date)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Former/RowFormers/BaseRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseRowFormer.swift
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 10/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class BaseRowFormer: RowFormer {
12 |
13 | // MARK: Public
14 |
15 | public var cell: T {
16 | return cellInstance as! T
17 | }
18 |
19 | required public init(
20 | instantiateType: Former.InstantiateType = .Class,
21 | cellSetup: ((T) -> Void)? = nil) {
22 | super.init(
23 | cellType: T.self,
24 | instantiateType: instantiateType,
25 | cellSetup: cellSetup
26 | )
27 | }
28 |
29 | @discardableResult
30 | public final func cellSetup(_ handler: @escaping ((T) -> Void)) -> Self {
31 | cellSetup = { handler(($0 as! T)) }
32 | return self
33 | }
34 |
35 | @discardableResult
36 | public final func cellUpdate(_ update: ((T) -> Void)) -> Self {
37 | update(cell)
38 | return self
39 | }
40 |
41 | open func cellInitialized(_ cell: T) {}
42 |
43 | // MARK: Internal
44 |
45 | override func cellInstanceInitialized(_ cell: UITableViewCell) {
46 | cellInitialized(cell as! T)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Former/Helpers/UITableViewCell+Former.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableViewCellExt.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UITableViewCell {
12 |
13 | // For SelectorRow
14 |
15 | override open var inputView: UIView? {
16 | if let pickerRow = self as? SelectorPickerFormableRow {
17 | return pickerRow.selectorPickerView
18 | } else if let datePickerRow = self as? SelectorDatePickerFormableRow {
19 | return datePickerRow.selectorDatePicker
20 | }
21 | return super.inputView
22 | }
23 |
24 | override open var inputAccessoryView: UIView? {
25 | if let pickerRow = self as? SelectorPickerFormableRow {
26 | return pickerRow.selectorAccessoryView
27 | } else if let datePickerRow = self as? SelectorDatePickerFormableRow {
28 | return datePickerRow.selectorAccessoryView
29 | }
30 | return super.inputAccessoryView
31 | }
32 |
33 | override open var canBecomeFirstResponder: Bool {
34 | if self is SelectorPickerFormableRow ||
35 | self is SelectorDatePickerFormableRow {
36 | return true
37 | }
38 | return super.canBecomeFirstResponder
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Former/Cells/FormCheckCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormCheckCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormCheckCell: FormCell, CheckFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 |
17 | public func formTitleLabel() -> UILabel? {
18 | return titleLabel
19 | }
20 |
21 | open override func setup() {
22 | super.setup()
23 |
24 | let titleLabel = UILabel()
25 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
26 | contentView.insertSubview(titleLabel, at: 0)
27 | self.titleLabel = titleLabel
28 |
29 | let constraints = [
30 | NSLayoutConstraint.constraints(
31 | withVisualFormat: "V:|-0-[label]-0-|",
32 | options: [],
33 | metrics: nil,
34 | views: ["label": titleLabel]
35 | ),
36 | NSLayoutConstraint.constraints(
37 | withVisualFormat: "H:|-15-[label(>=0)]",
38 | options: [],
39 | metrics: nil,
40 | views: ["label": titleLabel]
41 | )
42 | ].flatMap { $0 }
43 | contentView.addConstraints(constraints)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Former/Cells/FormPickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormPickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/2/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormPickerCell: FormCell, PickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var pickerView: UIPickerView!
16 |
17 | public func formPickerView() -> UIPickerView {
18 | return pickerView
19 | }
20 |
21 | open override func setup() {
22 | super.setup()
23 |
24 | let pickerView = UIPickerView()
25 | pickerView.translatesAutoresizingMaskIntoConstraints = false
26 | contentView.insertSubview(pickerView, at: 0)
27 | self.pickerView = pickerView
28 |
29 | let constraints = [
30 | NSLayoutConstraint.constraints(
31 | withVisualFormat: "V:|-15-[picker]-15-|",
32 | options: [],
33 | metrics: nil,
34 | views: ["picker": pickerView]
35 | ),
36 | NSLayoutConstraint.constraints(
37 | withVisualFormat: "H:|-0-[picker]-0-|",
38 | options: [],
39 | metrics: nil,
40 | views: ["picker": pickerView]
41 | )
42 | ].flatMap { $0 }
43 | contentView.addConstraints(constraints)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/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 | "size" : "29x29",
15 | "idiom" : "iphone",
16 | "filename" : "appicon-Small@2x.png",
17 | "scale" : "2x"
18 | },
19 | {
20 | "size" : "29x29",
21 | "idiom" : "iphone",
22 | "filename" : "appicon-Small@3x.png",
23 | "scale" : "3x"
24 | },
25 | {
26 | "size" : "40x40",
27 | "idiom" : "iphone",
28 | "filename" : "appicon-Small-40@2x.png",
29 | "scale" : "2x"
30 | },
31 | {
32 | "size" : "40x40",
33 | "idiom" : "iphone",
34 | "filename" : "appicon-Small-40@3x.png",
35 | "scale" : "3x"
36 | },
37 | {
38 | "size" : "60x60",
39 | "idiom" : "iphone",
40 | "filename" : "appicon-60@2x.png",
41 | "scale" : "2x"
42 | },
43 | {
44 | "size" : "60x60",
45 | "idiom" : "iphone",
46 | "filename" : "appicon-60@3x.png",
47 | "scale" : "3x"
48 | },
49 | {
50 | "idiom" : "ios-marketing",
51 | "size" : "1024x1024",
52 | "scale" : "1x"
53 | }
54 | ],
55 | "info" : {
56 | "version" : 1,
57 | "author" : "xcode"
58 | }
59 | }
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UIStatusBarStyle
32 | UIStatusBarStyleLightContent
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSPhotoLibraryUsageDescription
40 | DEMO
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Former/Cells/FormDatePickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormDatePickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormDatePickerCell: FormCell, DatePickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var datePicker: UIDatePicker!
16 |
17 | public func formDatePicker() -> UIDatePicker {
18 | return datePicker
19 | }
20 |
21 | open override func setup() {
22 | super.setup()
23 |
24 | let datePicker = UIDatePicker()
25 | datePicker.translatesAutoresizingMaskIntoConstraints = false
26 | contentView.insertSubview(datePicker, at: 0)
27 | self.datePicker = datePicker
28 |
29 | let constraints = [
30 | NSLayoutConstraint.constraints(
31 | withVisualFormat: "V:|-15-[picker]-15-|",
32 | options: [],
33 | metrics: nil,
34 | views: ["picker": datePicker]
35 | ),
36 | NSLayoutConstraint.constraints(
37 | withVisualFormat: "H:|-0-[picker]-0-|",
38 | options: [],
39 | metrics: nil,
40 | views: ["picker": datePicker]
41 | )
42 | ].flatMap { $0 }
43 | contentView.addConstraints(constraints)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Former/Views/FormLabelHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormLabelHeaderView.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormLabelHeaderView: FormHeaderFooterView, LabelFormableView {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 |
17 | public func formTitleLabel() -> UILabel {
18 | return titleLabel
19 | }
20 |
21 | override open func setup() {
22 | super.setup()
23 |
24 | let titleLabel = UILabel()
25 | titleLabel.textColor = .lightGray
26 | titleLabel.font = .systemFont(ofSize: 14)
27 | titleLabel.numberOfLines = 0
28 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
29 | contentView.insertSubview(titleLabel, at: 0)
30 | self.titleLabel = titleLabel
31 |
32 | let constraints = [
33 | NSLayoutConstraint.constraints(
34 | withVisualFormat: "V:[label(>=0)]-5-|",
35 | options: [],
36 | metrics: nil,
37 | views: ["label": titleLabel]
38 | ),
39 | NSLayoutConstraint.constraints(
40 | withVisualFormat: "H:|-15-[label]-15-|",
41 | options: [],
42 | metrics: nil,
43 | views: ["label": titleLabel]
44 | )
45 | ].flatMap { $0 }
46 | contentView.addConstraints(constraints)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Former/Views/FormLabelFooterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormLabelFooterView.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormLabelFooterView: FormHeaderFooterView, LabelFormableView {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 |
17 | public func formTitleLabel() -> UILabel {
18 | return titleLabel
19 | }
20 |
21 | override open func setup() {
22 | super.setup()
23 |
24 | let titleLabel = UILabel()
25 | titleLabel.textColor = .lightGray
26 | titleLabel.font = .systemFont(ofSize: 14)
27 | titleLabel.textAlignment = .center
28 | titleLabel.numberOfLines = 0
29 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
30 | contentView.insertSubview(titleLabel, at: 0)
31 | self.titleLabel = titleLabel
32 |
33 | let constraints = [
34 | NSLayoutConstraint.constraints(
35 | withVisualFormat: "V:|-5-[label]-5-|",
36 | options: [],
37 | metrics: nil,
38 | views: ["label": titleLabel]
39 | ),
40 | NSLayoutConstraint.constraints(
41 | withVisualFormat: "H:|-15-[label]-15-|",
42 | options: [],
43 | metrics: nil,
44 | views: ["label": titleLabel]
45 | )
46 | ].flatMap { $0 }
47 | contentView.addConstraints(constraints)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/CenterLabelCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CenterLabelCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/8/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class CenterLabelCell: FormCell, LabelFormableRow {
13 |
14 | // MARK: Public
15 |
16 | func formTextLabel() -> UILabel? {
17 | return titleLabel
18 | }
19 |
20 | func formSubTextLabel() -> UILabel? {
21 | return nil
22 | }
23 |
24 | weak var titleLabel: UILabel!
25 |
26 | override func setup() {
27 | super.setup()
28 |
29 | let titleLabel = UILabel()
30 | titleLabel.textColor = .formerColor()
31 | titleLabel.font = .boldSystemFont(ofSize: 15)
32 | titleLabel.textAlignment = .center
33 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
34 | contentView.addSubview(titleLabel)
35 | self.titleLabel = titleLabel
36 |
37 | let constraints = [
38 | NSLayoutConstraint.constraints(
39 | withVisualFormat: "V:|-0-[titleLabel]-0-|",
40 | options: [],
41 | metrics: nil,
42 | views: ["titleLabel": titleLabel]
43 | ),
44 | NSLayoutConstraint.constraints(
45 | withVisualFormat: "H:|-0-[titleLabel]-0-|",
46 | options: [],
47 | metrics: nil,
48 | views: ["titleLabel": titleLabel]
49 | )
50 | ].flatMap { $0 }
51 | contentView.addConstraints(constraints)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Former/Cells/FormSwitchCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSwitchCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/27/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormSwitchCell: FormCell, SwitchFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var switchButton: UISwitch!
17 |
18 | public func formTitleLabel() -> UILabel? {
19 | return titleLabel
20 | }
21 |
22 | public func formSwitch() -> UISwitch {
23 | return switchButton
24 | }
25 |
26 | open override func setup() {
27 | super.setup()
28 |
29 | let titleLabel = UILabel()
30 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
31 | contentView.insertSubview(titleLabel, at: 0)
32 | self.titleLabel = titleLabel
33 |
34 | let switchButton = UISwitch()
35 | accessoryView = switchButton
36 | self.switchButton = switchButton
37 |
38 | let constraints = [
39 | NSLayoutConstraint.constraints(
40 | withVisualFormat: "V:|-0-[label]-0-|",
41 | options: [],
42 | metrics: nil,
43 | views: ["label": titleLabel]
44 | ),
45 | NSLayoutConstraint.constraints(
46 | withVisualFormat: "H:|-15-[label(>=0)]",
47 | options: [],
48 | metrics: nil,
49 | views: ["label": titleLabel]
50 | )
51 | ].flatMap { $0 }
52 | contentView.addConstraints(constraints)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/ProfileImageCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileImageCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 10/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class ProfileImageCell: UITableViewCell, LabelFormableRow {
13 |
14 | // MARK: Public
15 |
16 | @IBOutlet weak var iconView: UIImageView!
17 | @IBOutlet weak var titleLabel: UILabel!
18 |
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 | titleLabel.textColor = .formerColor()
22 | iconView.backgroundColor = .formerColor()
23 | iconView.layer.cornerRadius = iconView.bounds.height / 2
24 | }
25 |
26 | override func setHighlighted(_ highlighted: Bool, animated: Bool) {
27 | if iconViewColor == nil {
28 | iconViewColor = iconView.backgroundColor
29 | }
30 | super.setHighlighted(highlighted, animated: animated)
31 | if let color = iconViewColor {
32 | iconView.backgroundColor = color
33 | }
34 | }
35 |
36 | override func setSelected(_ selected: Bool, animated: Bool) {
37 | if iconViewColor == nil {
38 | iconViewColor = iconView.backgroundColor
39 | }
40 | super.setSelected(selected, animated: animated)
41 | if let color = iconViewColor {
42 | iconView.backgroundColor = color
43 | }
44 | }
45 |
46 | func formTextLabel() -> UILabel? {
47 | return titleLabel
48 | }
49 |
50 | func formSubTextLabel() -> UILabel? {
51 | return nil
52 | }
53 |
54 | func updateWithRowFormer(_ rowFormer: RowFormer) {}
55 |
56 | // MARK: Private
57 |
58 | private var iconViewColor: UIColor?
59 | }
60 |
--------------------------------------------------------------------------------
/Former/RowFormers/LabelRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LabelRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/24/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol LabelFormableRow: FormableRow {
12 |
13 | func formTextLabel() -> UILabel?
14 | func formSubTextLabel() -> UILabel?
15 | }
16 |
17 | open class LabelRowFormer
18 | : BaseRowFormer, Formable where T: LabelFormableRow {
19 |
20 | // MARK: Public
21 |
22 | open var text: String?
23 | open var subText: String?
24 | open var textDisabledColor: UIColor? = .lightGray
25 | open var subTextDisabledColor: UIColor? = .lightGray
26 |
27 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
28 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
29 | }
30 |
31 | open override func update() {
32 | super.update()
33 |
34 | let textLabel = cell.formTextLabel()
35 | let subTextLabel = cell.formSubTextLabel()
36 | textLabel?.text = text
37 | subTextLabel?.text = subText
38 |
39 | if enabled {
40 | _ = textColor.map { textLabel?.textColor = $0 }
41 | _ = subTextColor.map { subTextLabel?.textColor = $0 }
42 | textColor = nil
43 | subTextColor = nil
44 | } else {
45 | if textColor == nil { textColor = textLabel?.textColor ?? .black }
46 | if subTextColor == nil { subTextColor = subTextLabel?.textColor ?? .black }
47 | textLabel?.textColor = textDisabledColor
48 | subTextLabel?.textColor = subTextDisabledColor
49 | }
50 | }
51 |
52 | // MARK: Private
53 |
54 | private final var textColor: UIColor?
55 | private final var subTextColor: UIColor?
56 | }
57 |
--------------------------------------------------------------------------------
/Former/Controllers/FormViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FomerViewController.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormViewController: UIViewController {
12 |
13 | // MARK: Public
14 |
15 | public let tableView: UITableView = {
16 | let tableView = UITableView(frame: CGRect.zero, style: .grouped)
17 | tableView.backgroundColor = .clear
18 | tableView.contentInset.bottom = 10
19 | tableView.sectionHeaderHeight = 0
20 | tableView.sectionFooterHeight = 0
21 | tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.01))
22 | tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.01))
23 | tableView.translatesAutoresizingMaskIntoConstraints = false
24 | return tableView
25 | }()
26 | public private(set) lazy var former: Former = Former(tableView: self.tableView)
27 |
28 | open override func viewDidLoad() {
29 | super.viewDidLoad()
30 | setup()
31 | }
32 |
33 | // MARK: Private
34 |
35 | private final func setup() {
36 | view.backgroundColor = .groupTableViewBackground
37 | view.insertSubview(tableView, at: 0)
38 | let tableConstraints = [
39 | NSLayoutConstraint.constraints(
40 | withVisualFormat: "V:|-0-[table]-0-|",
41 | options: [],
42 | metrics: nil,
43 | views: ["table": tableView]
44 | ),
45 | NSLayoutConstraint.constraints(
46 | withVisualFormat: "H:|-0-[table]-0-|",
47 | options: [],
48 | metrics: nil,
49 | views: ["table": tableView]
50 | )
51 | ].flatMap { $0 }
52 | view.addConstraints(tableConstraints)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Former/RowFormers/DatePickerRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol DatePickerFormableRow: FormableRow {
12 |
13 | func formDatePicker() -> UIDatePicker
14 | }
15 |
16 | open class DatePickerRowFormer
17 | : BaseRowFormer, Formable where T: DatePickerFormableRow {
18 |
19 | // MARK: Public
20 |
21 | open var date: Date = Date()
22 |
23 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
24 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
25 | }
26 |
27 | @discardableResult
28 | public final func onDateChanged(_ handler: @escaping ((Date) -> Void)) -> Self {
29 | onDateChanged = handler
30 | return self
31 | }
32 |
33 | open override func initialized() {
34 | super.initialized()
35 | rowHeight = 216
36 | }
37 |
38 | open override func cellInitialized(_ cell: T) {
39 | super.cellInitialized(cell)
40 | cell.formDatePicker().addTarget(self, action: #selector(DatePickerRowFormer.dateChanged(datePicker:)), for: .valueChanged)
41 | }
42 |
43 | open override func update() {
44 | super.update()
45 |
46 | cell.selectionStyle = .none
47 | let datePicker = cell.formDatePicker()
48 | datePicker.setDate(date, animated: false)
49 | datePicker.isUserInteractionEnabled = enabled
50 | datePicker.layer.opacity = enabled ? 1 : 0.5
51 | }
52 |
53 | // MARK: Private
54 |
55 | private final var onDateChanged: ((Date) -> Void)?
56 |
57 | @objc private dynamic func dateChanged(datePicker: UIDatePicker) {
58 | if enabled {
59 | let date = datePicker.date
60 | self.date = date
61 | onDateChanged?(date)
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Controllers/TextSelectorViewContorller.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextSelectorViewContorller.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/10/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class TextSelectorViewContoller: FormViewController {
13 |
14 | // MARK: Public
15 |
16 | var texts = [String]() {
17 | didSet {
18 | reloadForm()
19 | }
20 | }
21 |
22 | var selectedText: String? {
23 | didSet {
24 | former.rowFormers.forEach {
25 | if let LabelRowFormer = $0 as? LabelRowFormer
26 | , LabelRowFormer.text == selectedText {
27 | LabelRowFormer.cellUpdate({ $0.accessoryType = .checkmark })
28 | }
29 | }
30 | }
31 | }
32 |
33 | var onSelected: ((String) -> Void)?
34 |
35 | private func reloadForm() {
36 |
37 | // Create RowFormers
38 |
39 | let rowFormers = texts.map { text -> LabelRowFormer in
40 | return LabelRowFormer() { [weak self] in
41 | if let sSelf = self {
42 | $0.titleLabel.textColor = .formerColor()
43 | $0.titleLabel.font = .boldSystemFont(ofSize: 16)
44 | $0.tintColor = .formerSubColor()
45 | $0.accessoryType = (text == sSelf.selectedText) ? .checkmark : .none
46 | }
47 | }.configure {
48 | $0.text = text
49 | }.onSelected { [weak self] _ in
50 | self?.onSelected?(text)
51 | _ = self?.navigationController?.popViewController(animated: true)
52 | }
53 | }
54 |
55 | // Create SectionFormers
56 |
57 | let sectionFormer = SectionFormer(rowFormers: rowFormers)
58 |
59 | former.removeAll().append(sectionFormer: sectionFormer).reload()
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Former/Cells/FormStepperCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormStepperCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/30/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormStepperCell: FormCell, StepperFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var displayLabel: UILabel!
17 | public private(set) weak var stepper: UIStepper!
18 |
19 | public func formTitleLabel() -> UILabel? {
20 | return titleLabel
21 | }
22 |
23 | public func formDisplayLabel() -> UILabel? {
24 | return displayLabel
25 | }
26 |
27 | public func formStepper() -> UIStepper {
28 | return stepper
29 | }
30 |
31 | open override func setup() {
32 | super.setup()
33 |
34 | let titleLabel = UILabel()
35 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
36 | contentView.insertSubview(titleLabel, at: 0)
37 | self.titleLabel = titleLabel
38 |
39 | let displayLabel = UILabel()
40 | displayLabel.textColor = .lightGray
41 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
42 | contentView.insertSubview(displayLabel, at: 0)
43 | self.displayLabel = displayLabel
44 |
45 | let stepper = UIStepper()
46 | accessoryView = stepper
47 | self.stepper = stepper
48 |
49 | let constraints = [
50 | NSLayoutConstraint.constraints(
51 | withVisualFormat: "V:|-0-[title]-0-|",
52 | options: [],
53 | metrics: nil,
54 | views: ["title": titleLabel]
55 | ),
56 | NSLayoutConstraint.constraints(
57 | withVisualFormat: "V:|-0-[display]-0-|",
58 | options: [],
59 | metrics: nil,
60 | views: ["display": displayLabel]
61 | ),
62 | NSLayoutConstraint.constraints(
63 | withVisualFormat: "H:|-15-[title]-(>=0)-[display]-5-|",
64 | options: [],
65 | metrics: nil,
66 | views: ["title": titleLabel, "display": displayLabel]
67 | )
68 | ].flatMap { $0 }
69 | contentView.addConstraints(constraints)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Former/RowFormers/CheckRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CheckRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol CheckFormableRow: FormableRow {
12 |
13 | func formTitleLabel() -> UILabel?
14 | }
15 |
16 | open class CheckRowFormer
17 | : BaseRowFormer, Formable where T: CheckFormableRow {
18 |
19 | // MARK: Public
20 |
21 | open var checked = false
22 | open var customCheckView: UIView?
23 | open var titleDisabledColor: UIColor? = .lightGray
24 |
25 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
26 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
27 | }
28 |
29 | @discardableResult
30 | public final func onCheckChanged(_ handler: @escaping ((Bool) -> Void)) -> Self {
31 | onCheckChanged = handler
32 | return self
33 | }
34 |
35 | open override func update() {
36 | super.update()
37 |
38 | if let customCheckView = customCheckView {
39 | cell.accessoryView = customCheckView
40 | customCheckView.isHidden = checked ? false : true
41 | } else {
42 | cell.accessoryType = checked ? .checkmark : .none
43 | }
44 | let titleLabel = cell.formTitleLabel()
45 | if enabled {
46 | _ = titleColor.map { titleLabel?.textColor = $0 }
47 | titleColor = nil
48 | } else {
49 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
50 | titleLabel?.textColor = titleDisabledColor
51 | }
52 | }
53 |
54 | open override func cellSelected(indexPath: IndexPath) {
55 | former?.deselect(animated: true)
56 | if enabled {
57 | checked = !checked
58 | onCheckChanged?(checked)
59 | if let customCheckView = customCheckView {
60 | cell.accessoryView = customCheckView
61 | customCheckView.isHidden = checked ? false : true
62 | } else {
63 | cell.accessoryType = checked ? .checkmark : .none
64 | }
65 | }
66 | }
67 |
68 | // MARK: Private
69 |
70 | private final var titleColor: UIColor?
71 | private final var onCheckChanged: ((Bool) -> Void)?
72 | }
73 |
--------------------------------------------------------------------------------
/Former/Cells/FormSegmentedCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSegmentedCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/30/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormSegmentedCell: FormCell, SegmentedFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var segmentedControl: UISegmentedControl!
17 |
18 | public func formTitleLabel() -> UILabel? {
19 | return titleLabel
20 | }
21 |
22 | public func formSegmented() -> UISegmentedControl {
23 | return segmentedControl
24 | }
25 |
26 | open override func setup() {
27 | super.setup()
28 |
29 | let titleLabel = UILabel()
30 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
31 | contentView.insertSubview(titleLabel, at: 0)
32 | self.titleLabel = titleLabel
33 |
34 | let segmentedControl = UISegmentedControl()
35 | segmentedControl.translatesAutoresizingMaskIntoConstraints = false
36 | contentView.insertSubview(segmentedControl, at: 0)
37 | self.segmentedControl = segmentedControl
38 |
39 | let constraints = [
40 | NSLayoutConstraint.constraints(
41 | withVisualFormat: "V:|-0-[title]-0-|",
42 | options: [],
43 | metrics: nil,
44 | views: ["title": titleLabel]
45 | ),
46 | NSLayoutConstraint.constraints(
47 | withVisualFormat: "V:[segment(30)]",
48 | options: [],
49 | metrics: nil,
50 | views: ["segment": segmentedControl]
51 | ),
52 | NSLayoutConstraint.constraints(
53 | withVisualFormat: "H:|-15-[title(>=0)]->=0-[segment(>=0)]-15-|",
54 | options: [],
55 | metrics: nil,
56 | views: ["title": titleLabel, "segment": segmentedControl]
57 | )
58 | ].flatMap { $0 }
59 | let centerYConst = NSLayoutConstraint(
60 | item: segmentedControl,
61 | attribute: .centerY,
62 | relatedBy: .equal,
63 | toItem: contentView,
64 | attribute: .centerY,
65 | multiplier: 1,
66 | constant: 0
67 | )
68 | contentView.addConstraints(constraints + [centerYConst])
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Former/RowFormers/SegmentedRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SegmentedRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/30/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SegmentedFormableRow: FormableRow {
12 |
13 | func formSegmented() -> UISegmentedControl
14 | func formTitleLabel() -> UILabel?
15 | }
16 |
17 | open class SegmentedRowFormer
18 | : BaseRowFormer, Formable where T: SegmentedFormableRow {
19 |
20 | // MARK: Public
21 |
22 | open var segmentTitles = [String]()
23 | open var selectedIndex: Int = 0
24 | open var titleDisabledColor: UIColor? = .lightGray
25 |
26 | required public init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
27 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
28 | }
29 |
30 | @discardableResult
31 | public final func onSegmentSelected(_ handler: @escaping ((Int, String) -> Void)) -> Self {
32 | onSegmentSelected = handler
33 | return self
34 | }
35 |
36 | open override func cellInitialized(_ cell: T) {
37 | super.cellInitialized(cell)
38 | cell.formSegmented().addTarget(self, action: #selector(SegmentedRowFormer.valueChanged(segment:)), for: .valueChanged)
39 | }
40 |
41 | open override func update() {
42 | super.update()
43 |
44 | cell.selectionStyle = .none
45 | let titleLabel = cell.formTitleLabel()
46 | let segment = cell.formSegmented()
47 | segment.removeAllSegments()
48 | for (index, title) in segmentTitles.enumerated() {
49 | segment.insertSegment(withTitle: title, at: index, animated: false)
50 | }
51 | segment.selectedSegmentIndex = selectedIndex
52 | segment.isEnabled = enabled
53 |
54 | if enabled {
55 | _ = titleColor.map { titleLabel?.textColor = $0 }
56 | titleColor = nil
57 | } else {
58 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
59 | titleLabel?.textColor = titleDisabledColor
60 | }
61 | }
62 |
63 | // MARK: Private
64 |
65 | private final var onSegmentSelected: ((Int, String) -> Void)?
66 | private final var titleColor: UIColor?
67 |
68 | @objc private dynamic func valueChanged(segment: UISegmentedControl) {
69 | if enabled {
70 | let index = segment.selectedSegmentIndex
71 | let selectedTitle = segment.titleForSegment(at: index)!
72 | selectedIndex = index
73 | onSegmentSelected?(selectedIndex, selectedTitle)
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Former/Cells/FormSliderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSliderCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormSliderCell: FormCell, SliderFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var displayLabel: UILabel!
17 | public private(set) weak var slider: UISlider!
18 |
19 | public func formTitleLabel() -> UILabel? {
20 | return titleLabel
21 | }
22 |
23 | public func formDisplayLabel() -> UILabel? {
24 | return displayLabel
25 | }
26 |
27 | public func formSlider() -> UISlider {
28 | return slider
29 | }
30 |
31 | open override func setup() {
32 | super.setup()
33 |
34 | let titleLabel = UILabel()
35 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
36 | contentView.insertSubview(titleLabel, at: 0)
37 | self.titleLabel = titleLabel
38 |
39 | let displayLabel = UILabel()
40 | displayLabel.textColor = .lightGray
41 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
42 | contentView.insertSubview(displayLabel, at: 0)
43 | self.displayLabel = displayLabel
44 |
45 | let slider = UISlider()
46 | slider.translatesAutoresizingMaskIntoConstraints = false
47 | contentView.insertSubview(slider, at: 0)
48 | self.slider = slider
49 |
50 | let constraints = [
51 | NSLayoutConstraint.constraints(
52 | withVisualFormat: "V:|-10-[title(>=0)]->=0-[slider(>=0)]-10-|",
53 | options: [],
54 | metrics: nil,
55 | views: ["title": titleLabel, "slider": slider]
56 | ),
57 | NSLayoutConstraint.constraints(
58 | withVisualFormat: "V:|-10-[display(>=0)]->=0-[slider(>=0)]",
59 | options: [],
60 | metrics: nil,
61 | views: ["display": displayLabel, "slider": slider]
62 | ),
63 | NSLayoutConstraint.constraints(
64 | withVisualFormat: "H:|-15-[title(>=0)]->=0-[display(>=0)]-15-|",
65 | options: [],
66 | metrics: nil,
67 | views: ["title": titleLabel, "display": displayLabel]
68 | ),
69 | NSLayoutConstraint.constraints(
70 | withVisualFormat: "H:|-20-[slider]-20-|",
71 | options: [],
72 | metrics: nil,
73 | views: ["slider": slider]
74 | )
75 | ].flatMap { $0 }
76 | contentView.addConstraints(constraints)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Former/Cells/FormLabelCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormLabelCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/24/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormLabelCell: FormCell, LabelFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var subTextLabel: UILabel!
17 |
18 | public func formTextLabel() -> UILabel? {
19 | return titleLabel
20 | }
21 |
22 | public func formSubTextLabel() -> UILabel? {
23 | return subTextLabel
24 | }
25 |
26 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
27 | super.updateWithRowFormer(rowFormer)
28 | rightConst.constant = (accessoryType == .none) ? -15 : 0
29 | }
30 |
31 | open override func setup() {
32 | super.setup()
33 |
34 | let titleLabel = UILabel()
35 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
36 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
37 | self.contentView.insertSubview(titleLabel, at: 0)
38 | self.titleLabel = titleLabel
39 |
40 | let subTextLabel = UILabel()
41 | subTextLabel.textColor = .lightGray
42 | subTextLabel.textAlignment = .right
43 | subTextLabel.translatesAutoresizingMaskIntoConstraints = false
44 | self.contentView.insertSubview(subTextLabel, at: 0)
45 | self.subTextLabel = subTextLabel
46 |
47 | let constraints = [
48 | NSLayoutConstraint.constraints(
49 | withVisualFormat: "V:|-0-[title]-0-|",
50 | options: [],
51 | metrics: nil,
52 | views: ["title": titleLabel]
53 | ),
54 | NSLayoutConstraint.constraints(
55 | withVisualFormat: "V:|-0-[sub]-0-|",
56 | options: [],
57 | metrics: nil,
58 | views: ["sub": subTextLabel]
59 | ),
60 | NSLayoutConstraint.constraints(
61 | withVisualFormat: "H:|-15-[title]-10-[sub(>=0)]",
62 | options: [],
63 | metrics: nil,
64 | views: ["title": titleLabel, "sub": subTextLabel]
65 | )
66 | ].flatMap { $0 }
67 | let rightConst = NSLayoutConstraint(
68 | item: subTextLabel,
69 | attribute: .trailing,
70 | relatedBy: .equal,
71 | toItem: contentView,
72 | attribute: .trailing,
73 | multiplier: 1,
74 | constant: 0
75 | )
76 | contentView.addConstraints(constraints + [rightConst])
77 | self.rightConst = rightConst
78 | }
79 |
80 | // MARK: Private
81 |
82 | private weak var rightConst: NSLayoutConstraint!
83 | }
84 |
--------------------------------------------------------------------------------
/Former/Cells/FormTextViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormTextViewCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/28/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormTextViewCell: FormCell, TextViewFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var textView: UITextView!
16 | public private(set) weak var titleLabel: UILabel!
17 |
18 | public func formTextView() -> UITextView {
19 | return textView
20 | }
21 |
22 | public func formTitleLabel() -> UILabel? {
23 | return titleLabel
24 | }
25 |
26 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
27 | super.updateWithRowFormer(rowFormer)
28 | leftConst.constant = titleLabel.text?.isEmpty ?? true ? 5 : 15
29 | }
30 |
31 | open override func setup() {
32 | super.setup()
33 |
34 | let titleLabel = UILabel()
35 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
36 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
37 | contentView.insertSubview(titleLabel, at: 0)
38 | self.titleLabel = titleLabel
39 |
40 | let textView = UITextView()
41 | textView.backgroundColor = .clear
42 | textView.font = .systemFont(ofSize: 17)
43 | textView.translatesAutoresizingMaskIntoConstraints = false
44 | contentView.insertSubview(textView, at: 0)
45 | self.textView = textView
46 |
47 | let constraints = [
48 | NSLayoutConstraint.constraints(
49 | withVisualFormat: "V:|-10-[label(>=0)]",
50 | options: [],
51 | metrics: nil,
52 | views: ["label": titleLabel]
53 | ),
54 | NSLayoutConstraint.constraints(
55 | withVisualFormat: "V:|-0-[text]-0-|",
56 | options: [],
57 | metrics: nil,
58 | views: ["text": textView]
59 | ),
60 | NSLayoutConstraint.constraints(
61 | withVisualFormat: "H:[label]-5-[text]-10-|",
62 | options: [],
63 | metrics: nil,
64 | views: ["label": titleLabel, "text": textView]
65 | )
66 | ].flatMap { $0 }
67 | let leftConst = NSLayoutConstraint(
68 | item: titleLabel,
69 | attribute: .leading,
70 | relatedBy: .equal,
71 | toItem: contentView,
72 | attribute: .leading,
73 | multiplier: 1,
74 | constant: 15
75 | )
76 | contentView.addConstraints(constraints + [leftConst])
77 | self.leftConst = leftConst
78 | }
79 |
80 | // MARK: Private
81 |
82 | private weak var leftConst: NSLayoutConstraint!
83 | private weak var rightConst: NSLayoutConstraint!
84 | }
85 |
--------------------------------------------------------------------------------
/Former/Cells/FormInlineDatePickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormInlineDatePickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormInlineDatePickerCell: FormCell, InlineDatePickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var displayLabel: UILabel!
17 |
18 | private weak var rightConst: NSLayoutConstraint!
19 |
20 | public func formTitleLabel() -> UILabel? {
21 | return titleLabel
22 | }
23 |
24 | public func formDisplayLabel() -> UILabel? {
25 | return displayLabel
26 | }
27 |
28 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
29 | super.updateWithRowFormer(rowFormer)
30 |
31 | rightConst.constant = (accessoryType == .none && accessoryView == nil) ? -15 : 0
32 | }
33 |
34 | open override func setup() {
35 | super.setup()
36 |
37 | let titleLabel = UILabel()
38 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
39 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
40 | contentView.insertSubview(titleLabel, at: 0)
41 | self.titleLabel = titleLabel
42 |
43 | let displayLabel = UILabel()
44 | displayLabel.textColor = .lightGray
45 | displayLabel.textAlignment = .right
46 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
47 | contentView.insertSubview(displayLabel, at: 0)
48 | self.displayLabel = displayLabel
49 |
50 | let constraints = [
51 | NSLayoutConstraint.constraints(
52 | withVisualFormat: "V:|-0-[title]-0-|",
53 | options: [],
54 | metrics: nil,
55 | views: ["title": titleLabel]
56 | ),
57 | NSLayoutConstraint.constraints(
58 | withVisualFormat: "V:|-0-[display]-0-|",
59 | options: [],
60 | metrics: nil,
61 | views: ["display": displayLabel]
62 | ),
63 | NSLayoutConstraint.constraints(
64 | withVisualFormat: "H:|-15-[title]-10-[display(>=0)]",
65 | options: [],
66 | metrics: nil,
67 | views: ["title": titleLabel, "display": displayLabel]
68 | )
69 | ].flatMap { $0 }
70 | let rightConst = NSLayoutConstraint(
71 | item: displayLabel,
72 | attribute: .trailing,
73 | relatedBy: .equal,
74 | toItem: contentView,
75 | attribute: .trailing,
76 | multiplier: 1,
77 | constant: 0
78 | )
79 | contentView.addConstraints(constraints + [rightConst])
80 | self.rightConst = rightConst
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Former/Cells/FormInlinePickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormInlinePickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/2/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormInlinePickerCell: FormCell, InlinePickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var titleLabel: UILabel!
16 | public private(set) weak var displayLabel: UILabel!
17 |
18 | public func formTitleLabel() -> UILabel? {
19 | return titleLabel
20 | }
21 |
22 | public func formDisplayLabel() -> UILabel? {
23 | return displayLabel
24 | }
25 |
26 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
27 | super.updateWithRowFormer(rowFormer)
28 | rightConst.constant = (accessoryType == .none && accessoryView == nil) ? -15 : 0
29 | }
30 |
31 | open override func setup() {
32 | super.setup()
33 |
34 | let titleLabel = UILabel()
35 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
36 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
37 | contentView.insertSubview(titleLabel, at: 0)
38 | self.titleLabel = titleLabel
39 |
40 | let displayLabel = UILabel()
41 | displayLabel.textColor = .lightGray
42 | displayLabel.textAlignment = .right
43 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
44 | contentView.insertSubview(displayLabel, at: 0)
45 | self.displayLabel = displayLabel
46 |
47 | let constraints = [
48 | NSLayoutConstraint.constraints(
49 | withVisualFormat: "V:|-0-[title]-0-|",
50 | options: [],
51 | metrics: nil,
52 | views: ["title": titleLabel]
53 | ),
54 | NSLayoutConstraint.constraints(
55 | withVisualFormat: "V:|-0-[display]-0-|",
56 | options: [],
57 | metrics: nil,
58 | views: ["display": displayLabel]
59 | ),
60 | NSLayoutConstraint.constraints(
61 | withVisualFormat: "H:|-15-[title]-10-[display(>=0)]",
62 | options: [],
63 | metrics: nil,
64 | views: ["title": titleLabel, "display": displayLabel]
65 | )
66 | ].flatMap { $0 }
67 | let rightConst = NSLayoutConstraint(
68 | item: displayLabel,
69 | attribute: .trailing,
70 | relatedBy: .equal,
71 | toItem: contentView,
72 | attribute: .trailing,
73 | multiplier: 1,
74 | constant: 0
75 | )
76 | contentView.addConstraints(constraints + [rightConst])
77 | self.rightConst = rightConst
78 | }
79 |
80 | // MARK: Private
81 |
82 | private weak var rightConst: NSLayoutConstraint!
83 | }
84 |
--------------------------------------------------------------------------------
/Former/RowFormers/SwitchRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwitchRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/27/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SwitchFormableRow: FormableRow {
12 |
13 | func formSwitch() -> UISwitch
14 | func formTitleLabel() -> UILabel?
15 | }
16 |
17 | open class SwitchRowFormer
18 | : BaseRowFormer, Formable where T: SwitchFormableRow {
19 |
20 | // MARK: Public
21 |
22 | open var switched = false
23 | open var switchWhenSelected = false
24 | open var titleDisabledColor: UIColor? = .lightGray
25 |
26 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
27 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
28 | }
29 |
30 | @discardableResult
31 | public final func onSwitchChanged(_ handler: @escaping ((Bool) -> Void)) -> Self {
32 | onSwitchChanged = handler
33 | return self
34 | }
35 |
36 | open override func cellInitialized(_ cell: T) {
37 | super.cellInitialized(cell)
38 | cell.formSwitch().addTarget(self, action: #selector(SwitchRowFormer.switchChanged(_:)), for: .valueChanged)
39 | }
40 |
41 | open override func update() {
42 | super.update()
43 |
44 | if !switchWhenSelected {
45 | if selectionStyle == nil { selectionStyle = cell.selectionStyle }
46 | cell.selectionStyle = .none
47 | } else {
48 | _ = selectionStyle.map { cell.selectionStyle = $0 }
49 | selectionStyle = nil
50 | }
51 |
52 | let titleLabel = cell.formTitleLabel()
53 | let switchButton = cell.formSwitch()
54 | switchButton.isOn = switched
55 | switchButton.isEnabled = enabled
56 |
57 | if enabled {
58 | _ = titleColor.map { titleLabel?.textColor = $0 }
59 | titleColor = nil
60 | } else {
61 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
62 | titleLabel?.textColor = titleDisabledColor
63 | }
64 | }
65 |
66 | open override func cellSelected(indexPath: IndexPath) {
67 | former?.deselect(animated: true)
68 | if switchWhenSelected && enabled {
69 | let switchButton = cell.formSwitch()
70 | switchButton.setOn(!switchButton.isOn, animated: true)
71 | switchChanged(switchButton)
72 | }
73 | }
74 |
75 | // MARK: Private
76 |
77 | private final var onSwitchChanged: ((Bool) -> Void)?
78 | private final var titleColor: UIColor?
79 | private final var selectionStyle: UITableViewCell.SelectionStyle?
80 |
81 | @objc private dynamic func switchChanged(_ switchButton: UISwitch) {
82 | if self.enabled {
83 | let switched = switchButton.isOn
84 | self.switched = switched
85 | onSwitchChanged?(switched)
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
17 | window = UIWindow(frame: UIScreen.main.bounds)
18 | let navigationController = UINavigationController(rootViewController: TopViewContoller())
19 | configureNavigationBar(navigationBar: navigationController.navigationBar)
20 | window!.rootViewController = navigationController
21 | window!.makeKeyAndVisible()
22 | return true
23 | }
24 |
25 | private func configureNavigationBar(navigationBar: UINavigationBar) {
26 | navigationBar.tintColor = .white
27 | navigationBar.titleTextAttributes = [
28 | NSAttributedString.Key.foregroundColor: UIColor.white,
29 | NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20)
30 | ]
31 | navigationBar.isTranslucent = false
32 | navigationBar.shadowImage = UIImage()
33 | navigationBar.barTintColor = .formerColor()
34 | }
35 |
36 | func applicationWillResignActive(_ application: UIApplication) {
37 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
38 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
39 | }
40 |
41 | func applicationDidEnterBackground(_ application: UIApplication) {
42 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
44 | }
45 |
46 | func applicationWillEnterForeground(_ application: UIApplication) {
47 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
48 | }
49 |
50 | func applicationDidBecomeActive(_ application: UIApplication) {
51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
52 | }
53 |
54 | func applicationWillTerminate(_ application: UIApplication) {
55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
56 | }
57 |
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Former.xcodeproj/xcshareddata/xcschemes/Former.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Former/Cells/FormSelectorPickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSelectorPickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/24/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormSelectorPickerCell: FormCell, SelectorPickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | open var selectorPickerView: UIPickerView?
16 | open var selectorAccessoryView: UIView?
17 |
18 | public private(set) weak var titleLabel: UILabel!
19 | public private(set) weak var displayLabel: UILabel!
20 |
21 | public func formTitleLabel() -> UILabel? {
22 | return titleLabel
23 | }
24 |
25 | public func formDisplayLabel() -> UILabel? {
26 | return displayLabel
27 | }
28 |
29 | public func formDefaultDisplayLabelText() -> String? {
30 | return nil
31 | }
32 |
33 | public func formDefaultSelectedRow() -> Int? {
34 | return 0
35 | }
36 |
37 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
38 | super.updateWithRowFormer(rowFormer)
39 | rightConst.constant = (accessoryType == .none) ? -15 : 0
40 | }
41 |
42 | open override func setup() {
43 | super.setup()
44 |
45 | let titleLabel = UILabel()
46 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
47 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
48 | contentView.insertSubview(titleLabel, at: 0)
49 | self.titleLabel = titleLabel
50 |
51 | let displayLabel = UILabel()
52 | displayLabel.textColor = .lightGray
53 | displayLabel.textAlignment = .right
54 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
55 | contentView.insertSubview(displayLabel, at: 0)
56 | self.displayLabel = displayLabel
57 |
58 | let constraints = [
59 | NSLayoutConstraint.constraints(
60 | withVisualFormat: "V:|-0-[title]-0-|",
61 | options: [],
62 | metrics: nil,
63 | views: ["title": titleLabel]
64 | ),
65 | NSLayoutConstraint.constraints(
66 | withVisualFormat: "V:|-0-[display]-0-|",
67 | options: [],
68 | metrics: nil,
69 | views: ["display": displayLabel]
70 | ),
71 | NSLayoutConstraint.constraints(
72 | withVisualFormat: "H:|-15-[title]-10-[display(>=0)]",
73 | options: [],
74 | metrics: nil,
75 | views: ["title": titleLabel, "display": displayLabel]
76 | )
77 | ].flatMap { $0 }
78 | let rightConst = NSLayoutConstraint(
79 | item: displayLabel,
80 | attribute: .trailing,
81 | relatedBy: .equal,
82 | toItem: contentView,
83 | attribute: .trailing,
84 | multiplier: 1,
85 | constant: 0
86 | )
87 | contentView.addConstraints(constraints + [rightConst])
88 | self.rightConst = rightConst
89 | }
90 |
91 | // MARK: Private
92 |
93 | private weak var rightConst: NSLayoutConstraint!
94 | }
95 |
--------------------------------------------------------------------------------
/Former/Cells/FormSelectorDatePickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormSelectorDatePickerCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/25/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormSelectorDatePickerCell: FormCell, SelectorDatePickerFormableRow {
12 |
13 | // MARK: Public
14 |
15 | open var selectorDatePicker: UIDatePicker?
16 | open var selectorAccessoryView: UIView?
17 |
18 | public private(set) weak var titleLabel: UILabel!
19 | public private(set) weak var displayLabel: UILabel!
20 |
21 | public func formTitleLabel() -> UILabel? {
22 | return titleLabel
23 | }
24 |
25 | public func formDisplayLabel() -> UILabel? {
26 | return displayLabel
27 | }
28 |
29 | open func formDefaultDisplayLabelText() -> String? {
30 | return "Not Set"
31 | }
32 |
33 | open func formDefaultDisplayDate() -> NSDate? {
34 | return NSDate()
35 | }
36 |
37 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
38 | super.updateWithRowFormer(rowFormer)
39 | rightConst.constant = (accessoryType == .none) ? -15 : 0
40 | }
41 |
42 | open override func setup() {
43 | super.setup()
44 |
45 | let titleLabel = UILabel()
46 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal)
47 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
48 | contentView.insertSubview(titleLabel, at: 0)
49 | self.titleLabel = titleLabel
50 |
51 | let displayLabel = UILabel()
52 | displayLabel.textColor = .lightGray
53 | displayLabel.textAlignment = .right
54 | displayLabel.translatesAutoresizingMaskIntoConstraints = false
55 | contentView.insertSubview(displayLabel, at: 0)
56 | self.displayLabel = displayLabel
57 |
58 | let constraints = [
59 | NSLayoutConstraint.constraints(
60 | withVisualFormat: "V:|-0-[title]-0-|",
61 | options: [],
62 | metrics: nil,
63 | views: ["title": titleLabel]
64 | ),
65 | NSLayoutConstraint.constraints(
66 | withVisualFormat: "V:|-0-[display]-0-|",
67 | options: [],
68 | metrics: nil,
69 | views: ["display": displayLabel]
70 | ),
71 | NSLayoutConstraint.constraints(
72 | withVisualFormat: "H:|-15-[title]-10-[display(>=0)]",
73 | options: [],
74 | metrics: nil,
75 | views: ["title": titleLabel, "display": displayLabel]
76 | )
77 | ].flatMap { $0 }
78 | let rightConst = NSLayoutConstraint(
79 | item: displayLabel,
80 | attribute: .trailing,
81 | relatedBy: .equal,
82 | toItem: contentView,
83 | attribute: .trailing,
84 | multiplier: 1,
85 | constant: 0
86 | )
87 | contentView.addConstraints(constraints + [rightConst])
88 | self.rightConst = rightConst
89 | }
90 |
91 | // MARK: Private
92 |
93 | private weak var rightConst: NSLayoutConstraint!
94 | }
95 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dave.greco@icloud.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Controllers/CustomCellViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomCellViewController.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/7/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class CustomCellViewController: FormViewController {
13 |
14 | // MARK: Public
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | configure()
19 | }
20 |
21 | // MARK: Private
22 |
23 | private func configure() {
24 | title = "Custom Cell"
25 |
26 | // Create RowFormers
27 |
28 | let colors = [
29 | UIColor(red: 0.1, green: 0.74, blue: 0.61, alpha: 1),
30 | UIColor(red: 0.12, green: 0.81, blue: 0.43, alpha: 1),
31 | UIColor(red: 0.17, green: 0.59, blue: 0.87, alpha: 1),
32 | UIColor(red: 0.61, green: 0.34, blue: 0.72, alpha: 1),
33 | UIColor(red: 0.2, green: 0.29, blue: 0.37, alpha: 1),
34 | UIColor(red: 0.95, green: 0.77, blue: 0, alpha: 1),
35 | UIColor(red: 0.91, green: 0.49, blue: 0.02, alpha: 1),
36 | UIColor(red: 0.91, green: 0.29, blue: 0.21, alpha: 1),
37 | UIColor(red: 0.93, green: 0.94, blue: 0.95, alpha: 1),
38 | UIColor(red: 0.58, green: 0.65, blue: 0.65, alpha: 1),
39 | ]
40 |
41 | let dynamicHeightRow = CustomRowFormer(instantiateType: .Nib(nibName: "DynamicHeightCell")) {
42 | $0.title = "Dynamic height"
43 | $0.body = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
44 | $0.bodyColor = colors[0]
45 | }.configure {
46 | $0.rowHeight = UITableView.automaticDimension
47 | }
48 |
49 | let colorListRow = CustomRowFormer(instantiateType: .Nib(nibName: "ColorListCell")) {
50 | $0.colors = colors
51 | $0.select(item: 0)
52 | $0.onColorSelected = { color in
53 | dynamicHeightRow.cellUpdate {
54 | $0.bodyColor = color
55 | }
56 | }
57 | }.configure {
58 | $0.rowHeight = 60
59 | }
60 |
61 | // Create Headers
62 |
63 | let createSpaceHeader: (() -> ViewFormer) = {
64 | return CustomViewFormer()
65 | .configure {
66 | $0.viewHeight = 30
67 | }
68 | }
69 |
70 | // Create Section
71 |
72 | let dynamicHeightSection = SectionFormer(rowFormer: dynamicHeightRow)
73 | .set(headerViewFormer: createSpaceHeader())
74 | let colorListSection = SectionFormer(rowFormer: colorListRow)
75 | .set(headerViewFormer: createSpaceHeader())
76 |
77 | former.append(sectionFormer: dynamicHeightSection, colorListSection)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Former/Commons/FormerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormerProtocols.swift
3 | // Former
4 | //
5 | // Created by Ryo Aoyama on 10/22/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: Inline RowFormer
12 |
13 | public protocol InlineForm: class {
14 |
15 | var inlineRowFormer: RowFormer { get }
16 | func editingDidBegin()
17 | func editingDidEnd()
18 | }
19 |
20 | public protocol ConfigurableInlineForm: InlineForm {
21 |
22 | associatedtype InlineCellType: UITableViewCell
23 | }
24 |
25 | extension ConfigurableInlineForm where Self: RowFormer {
26 |
27 | @discardableResult
28 | public func inlineCellSetup(_ handler: @escaping ((InlineCellType) -> Void)) -> Self {
29 | inlineRowFormer.cellSetup { handler($0 as! InlineCellType) }
30 | return self
31 | }
32 |
33 | @discardableResult
34 | public func inlineCellUpdate(update: ((InlineCellType) -> Void)) -> Self {
35 | update(inlineRowFormer.cellInstance as! InlineCellType)
36 | return self
37 | }
38 | }
39 |
40 | // MARK: Selector RowFormer
41 |
42 | public protocol SelectorForm: class {
43 |
44 | func editingDidBegin()
45 | func editingDidEnd()
46 | }
47 |
48 | public protocol UpdatableSelectorForm: SelectorForm {
49 |
50 | associatedtype SelectorViewType: UIView
51 | var selectorView: SelectorViewType { get }
52 | }
53 |
54 | extension UpdatableSelectorForm where Self: RowFormer {
55 |
56 | @discardableResult
57 | public func selectorViewUpdate(update: ((SelectorViewType) -> Void)) -> Self {
58 | update(selectorView)
59 | return self
60 | }
61 | }
62 |
63 | // MARK: RowFormer
64 |
65 | public protocol Formable: SelectableForm, UpdatableForm, ConfigurableForm {}
66 |
67 | public protocol SelectableForm: class {}
68 |
69 | public extension SelectableForm where Self: RowFormer {
70 |
71 | @discardableResult
72 | func onSelected(_ handler: @escaping ((Self) -> Void)) -> Self {
73 | onSelected = {
74 | handler($0 as! Self)
75 | }
76 | return self
77 | }
78 | }
79 |
80 | public protocol UpdatableForm: class {}
81 |
82 | public extension UpdatableForm where Self: RowFormer {
83 |
84 | @discardableResult
85 | func update(_ handler: ((Self) -> Void)) -> Self {
86 | handler(self)
87 | update()
88 | return self
89 | }
90 |
91 | @discardableResult
92 | func onUpdate(_ handler: @escaping ((Self) -> Void)) -> Self {
93 | onUpdate = {
94 | handler($0 as! Self)
95 | }
96 | return self
97 | }
98 | }
99 |
100 | public protocol ConfigurableForm: class {}
101 |
102 | public extension ConfigurableForm where Self: RowFormer {
103 |
104 | @discardableResult
105 | func configure(handler: ((Self) -> Void)) -> Self {
106 | handler(self)
107 | return self
108 | }
109 | }
110 |
111 | public extension ConfigurableForm where Self: ViewFormer {
112 |
113 | @discardableResult
114 | func configure(handler: ((Self) -> Void)) -> Self {
115 | handler(self)
116 | return self
117 | }
118 |
119 | @discardableResult
120 | func update(_ handler: ((Self) -> Void)) -> Self {
121 | handler(self)
122 | self.update()
123 | return self
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Former/RowFormers/StepperRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StepperRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/30/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol StepperFormableRow: FormableRow {
12 |
13 | func formStepper() -> UIStepper
14 | func formTitleLabel() -> UILabel?
15 | func formDisplayLabel() -> UILabel?
16 | }
17 |
18 | open class StepperRowFormer
19 | : BaseRowFormer, Formable where T: StepperFormableRow {
20 |
21 | // MARK: Public
22 |
23 | open var value: Double = 0
24 | open var titleDisabledColor: UIColor? = .lightGray
25 | open var displayDisabledColor: UIColor? = .lightGray
26 |
27 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
28 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
29 | }
30 |
31 | @discardableResult
32 | public final func onValueChanged(_ handler: @escaping ((Double) -> Void)) -> Self {
33 | onValueChanged = handler
34 | return self
35 | }
36 |
37 | @discardableResult
38 | public final func displayTextFromValue(_ handler: @escaping ((Double) -> String?)) -> Self {
39 | displayTextFromValue = handler
40 | return self
41 | }
42 |
43 | open override func cellInitialized(_ cell: T) {
44 | super.cellInitialized(cell)
45 | cell.formStepper().addTarget(self, action: #selector(StepperRowFormer.valueChanged(stepper:)), for: .valueChanged)
46 | }
47 |
48 | open override func update() {
49 | super.update()
50 |
51 | cell.selectionStyle = .none
52 | let titleLabel = cell.formTitleLabel()
53 | let displayLabel = cell.formDisplayLabel()
54 | let stepper = cell.formStepper()
55 | stepper.value = value
56 | stepper.isEnabled = enabled
57 | displayLabel?.text = displayTextFromValue?(value) ?? "\(value)"
58 |
59 | if enabled {
60 | _ = titleColor.map { titleLabel?.textColor = $0 }
61 | _ = displayColor.map { displayLabel?.textColor = $0 }
62 | _ = stepperTintColor.map { stepper.tintColor = $0 }
63 | titleColor = nil
64 | displayColor = nil
65 | stepperTintColor = nil
66 | } else {
67 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
68 | if displayColor == nil { displayColor = displayLabel?.textColor ?? .black }
69 | if stepperTintColor == nil { stepperTintColor = stepper.tintColor }
70 | titleLabel?.textColor = titleDisabledColor
71 | displayLabel?.textColor = displayDisabledColor
72 | stepper.tintColor = stepperTintColor?.withAlphaComponent(0.5)
73 | }
74 | }
75 |
76 | // MARK: Private
77 |
78 | private final var onValueChanged: ((Double) -> Void)?
79 | private final var displayTextFromValue: ((Double) -> String?)?
80 | private final var titleColor: UIColor?
81 | private final var displayColor: UIColor?
82 | private final var stepperTintColor: UIColor?
83 |
84 | @objc private dynamic func valueChanged(stepper: UIStepper) {
85 | let value = stepper.value
86 | self.value = value
87 | cell.formDisplayLabel()?.text = displayTextFromValue?(value) ?? "\(value)"
88 | onValueChanged?(value)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/ColorListCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Former/Cells/FormTextFieldCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormTextFieldCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/25/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | open class FormTextFieldCell: FormCell, TextFieldFormableRow {
12 |
13 | // MARK: Public
14 |
15 | public private(set) weak var textField: UITextField!
16 | public private(set) weak var titleLabel: UILabel!
17 |
18 | public func formTextField() -> UITextField {
19 | return textField
20 | }
21 |
22 | public func formTitleLabel() -> UILabel? {
23 | return titleLabel
24 | }
25 |
26 | open override func updateWithRowFormer(_ rowFormer: RowFormer) {
27 | super.updateWithRowFormer(rowFormer)
28 | leftConst.constant = titleLabel.text?.isEmpty ?? true ? 5 : 15
29 | rightConst.constant = (textField.textAlignment == .right) ? -15 : 0
30 | }
31 |
32 | open override func setup() {
33 |
34 | super.setup()
35 |
36 | let titleLabel = UILabel()
37 | titleLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: NSLayoutConstraint.Axis.horizontal)
38 | titleLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .horizontal)
39 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
40 | contentView.insertSubview(titleLabel, at: 0)
41 | self.titleLabel = titleLabel
42 |
43 | let textField = UITextField()
44 | textField.backgroundColor = .clear
45 | textField.clearButtonMode = .whileEditing
46 | textField.translatesAutoresizingMaskIntoConstraints = false
47 | contentView.insertSubview(textField, at: 0)
48 | self.textField = textField
49 |
50 | let constraints = [
51 | NSLayoutConstraint.constraints(
52 | withVisualFormat: "V:|-0-[label]-0-|",
53 | options: [],
54 | metrics: nil,
55 | views: ["label": titleLabel]
56 | ),
57 | NSLayoutConstraint.constraints(
58 | withVisualFormat: "V:|-0-[field]-0-|",
59 | options: [],
60 | metrics: nil,
61 | views: ["field": textField]
62 | ),
63 | NSLayoutConstraint.constraints(
64 | withVisualFormat: "H:[label]-10-[field]",
65 | options: [],
66 | metrics: nil,
67 | views: ["label": titleLabel, "field": textField]
68 | )
69 | ].flatMap { $0 }
70 | let leftConst = NSLayoutConstraint(
71 | item: titleLabel,
72 | attribute: .leading,
73 | relatedBy: .equal,
74 | toItem: contentView,
75 | attribute: .leading,
76 | multiplier: 1,
77 | constant: 15
78 | )
79 | let rightConst = NSLayoutConstraint(
80 | item: textField,
81 | attribute: .trailing,
82 | relatedBy: .equal,
83 | toItem: contentView,
84 | attribute: .trailing,
85 | multiplier: 1,
86 | constant: 0
87 | )
88 | contentView.addConstraints(constraints + [leftConst, rightConst])
89 | self.leftConst = leftConst
90 | self.rightConst = rightConst
91 | }
92 |
93 | // MARK: Private
94 |
95 | private weak var leftConst: NSLayoutConstraint!
96 | private weak var rightConst: NSLayoutConstraint!
97 | }
98 |
--------------------------------------------------------------------------------
/Former/Commons/ViewFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderFooterFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/26/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol FormableView: class {
12 |
13 | func updateWithViewFormer(_ viewFormer: ViewFormer)
14 | }
15 |
16 | open class ViewFormer {
17 |
18 | // MARK: Public
19 |
20 | open var viewHeight: CGFloat = 10
21 |
22 | internal init(
23 | viewType: T.Type,
24 | instantiateType: Former.InstantiateType,
25 | viewSetup: ((T) -> Void)? = nil) {
26 | self.viewType = viewType
27 | self.instantiateType = instantiateType
28 | self.viewSetup = { viewSetup?(($0 as! T)) }
29 | initialized()
30 | }
31 |
32 | @discardableResult
33 | public func dynamicViewHeight(_ handler: @escaping ((UITableView, /*section:*/Int) -> CGFloat)) -> Self {
34 | dynamicViewHeight = handler
35 | return self
36 | }
37 |
38 | open func initialized() {}
39 |
40 | public func update() {
41 | if let formableView = viewInstance as? FormableView {
42 | formableView.updateWithViewFormer(self)
43 | }
44 | }
45 |
46 | // MARK: Internal
47 |
48 | internal final var dynamicViewHeight: ((UITableView, Int) -> CGFloat)?
49 |
50 | internal final var viewInstance: UITableViewHeaderFooterView {
51 | if _viewInstance == nil {
52 | var view: UITableViewHeaderFooterView?
53 | switch instantiateType {
54 | case .Class:
55 | view = viewType.init(reuseIdentifier: nil)
56 | case .Nib(nibName: let nibName):
57 | view = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewHeaderFooterView
58 | assert(view != nil, "[Former] Failed to load header footer view from nib (\(nibName)).")
59 | case .NibTag(nibName: let nibName, tag: let tag):
60 | let nibChildren = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)!
61 | view = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewHeaderFooterView
62 | assert(view != nil, "[Former] Failed to load header footer view from nib (nibName: \(nibName)), tag: (\(tag)).")
63 | case .NibBundle(nibName: let nibName, bundle: let bundle):
64 | view = bundle.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewHeaderFooterView
65 | assert(view != nil, "[Former] Failed to load header footer view from nib (nibName: \(nibName)), bundle: (\(bundle)).")
66 | case .NibBundleTag(nibName: let nibName, bundle: let bundle, tag: let tag):
67 | let nibChildren = bundle.loadNibNamed(nibName, owner: nil, options: nil)!
68 | view = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewHeaderFooterView
69 | assert(view != nil, "[Former] Failed to load header footer from nib (nibName: \(nibName), bundle: \(bundle), tag: \(tag)).")
70 | }
71 | _viewInstance = view
72 | viewInstanceInitialized(view!)
73 | viewSetup(view!)
74 | }
75 | return _viewInstance!
76 | }
77 |
78 | internal func viewInstanceInitialized(_ view: UITableViewHeaderFooterView) {}
79 |
80 | // MARK: Private
81 |
82 | private var _viewInstance: UITableViewHeaderFooterView?
83 | private final let viewType: UITableViewHeaderFooterView.Type
84 | private final let instantiateType: Former.InstantiateType
85 | private final let viewSetup: ((UITableViewHeaderFooterView) -> Void)
86 | }
87 |
--------------------------------------------------------------------------------
/Former/RowFormers/SliderRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SliderRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/31/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SliderFormableRow: FormableRow {
12 |
13 | func formSlider() -> UISlider
14 | func formTitleLabel() -> UILabel?
15 | func formDisplayLabel() -> UILabel?
16 | }
17 |
18 | open class SliderRowFormer
19 | : BaseRowFormer, Formable where T: SliderFormableRow {
20 |
21 | // MARK: Public
22 |
23 | open var value: Float = 0
24 | open var titleDisabledColor: UIColor? = .lightGray
25 | open var displayDisabledColor: UIColor? = .lightGray
26 |
27 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
28 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
29 | }
30 |
31 | open override func initialized() {
32 | super.initialized()
33 | rowHeight = 88
34 | }
35 |
36 | @discardableResult
37 | public final func onValueChanged(_ handler: @escaping ((Float) -> Void)) -> Self {
38 | onValueChanged = handler
39 | return self
40 | }
41 |
42 | @discardableResult
43 | public final func displayTextFromValue(_ handler: @escaping ((Float) -> String)) -> Self {
44 | displayTextFromValue = handler
45 | return self
46 | }
47 |
48 | @discardableResult
49 | public final func adjustedValueFromValue(_ handler: @escaping ((Float) -> Float)) -> Self {
50 | adjustedValueFromValue = handler
51 | return self
52 | }
53 |
54 | open override func cellInitialized(_ cell: T) {
55 | super.cellInitialized(cell)
56 | cell.formSlider().addTarget(self, action: #selector(SliderRowFormer.valueChanged(slider:)), for: .valueChanged)
57 | }
58 |
59 | open override func update() {
60 | super.update()
61 |
62 | cell.selectionStyle = .none
63 | let titleLabel = cell.formTitleLabel()
64 | let displayLabel = cell.formDisplayLabel()
65 | let slider = cell.formSlider()
66 | slider.value = adjustedValueFromValue?(value) ?? value
67 | slider.isEnabled = enabled
68 | displayLabel?.text = displayTextFromValue?(value) ?? "\(value)"
69 |
70 | if enabled {
71 | _ = titleColor.map { titleLabel?.textColor = $0 }
72 | _ = displayColor.map { displayLabel?.textColor = $0 }
73 | titleColor = nil
74 | displayColor = nil
75 | } else {
76 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
77 | if displayColor == nil { displayColor = displayLabel?.textColor ?? .black }
78 | titleLabel?.textColor = titleDisabledColor
79 | displayLabel?.textColor = displayDisabledColor
80 | }
81 | }
82 |
83 | // MARK: Private
84 |
85 | private final var onValueChanged: ((Float) -> Void)?
86 | private final var displayTextFromValue: ((Float) -> String)?
87 | private final var adjustedValueFromValue: ((Float) -> Float)?
88 | private final var titleColor: UIColor?
89 | private final var displayColor: UIColor?
90 |
91 | @objc private dynamic func valueChanged(slider: UISlider) {
92 | let displayLabel = cell.formDisplayLabel()
93 | let value = slider.value
94 | let adjustedValue = adjustedValueFromValue?(value) ?? value
95 | self.value = adjustedValue
96 | slider.value = adjustedValue
97 | displayLabel?.text = displayTextFromValue?(adjustedValue) ?? "\(adjustedValue)"
98 | onValueChanged?(adjustedValue)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Controllers/TopViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TopViewController.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/5/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class TopViewContoller: FormViewController {
13 |
14 | // MARK: Public
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | configure()
19 | }
20 |
21 | override func viewWillAppear(_ animated: Bool) {
22 | super.viewWillAppear(animated)
23 | former.deselect(animated: true)
24 | }
25 |
26 | // MARK: Private
27 |
28 | private func configure() {
29 | let logo = UIImage(named: "header_logo")!
30 | navigationItem.titleView = UIImageView(image: logo)
31 | let backBarButton = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
32 | navigationItem.backBarButtonItem = backBarButton
33 |
34 | // Create RowFormers
35 |
36 | let createMenu: ((String, (() -> Void)?) -> RowFormer) = { text, onSelected in
37 | return LabelRowFormer() {
38 | $0.titleLabel.textColor = .formerColor()
39 | $0.titleLabel.font = .boldSystemFont(ofSize: 16)
40 | $0.accessoryType = .disclosureIndicator
41 | }.configure {
42 | $0.text = text
43 | }.onSelected { _ in
44 | onSelected?()
45 | }
46 | }
47 | let editProfileRow = createMenu("Edit Profile") { [weak self] in
48 | self?.navigationController?.pushViewController(EditProfileViewController(), animated: true)
49 | }
50 | let addEventRow = createMenu("Add Event") { [weak self] in
51 | self?.navigationController?.pushViewController(AddEventViewController(), animated: true)
52 | }
53 | let loginRow = createMenu("Login") { [weak self] in
54 | _ = self.map { LoginViewController.present(viewController: $0) }
55 | self?.former.deselect(animated: true)
56 | }
57 | let exampleRow = createMenu("Examples") { [weak self] in
58 | self?.navigationController?.pushViewController(ExampleViewController(), animated: true)
59 | }
60 | let customCellRow = createMenu("Custom Cell") { [weak self] in
61 | self?.navigationController?.pushViewController(CustomCellViewController(), animated: true)
62 | }
63 | let defaultRow = createMenu("All Defaults") { [weak self] in
64 | self?.navigationController?.pushViewController(DefaultsViewController(), animated: true)
65 | }
66 |
67 | // Create Headers and Footers
68 |
69 | let createHeader: ((String) -> ViewFormer) = { text in
70 | return LabelViewFormer()
71 | .configure {
72 | $0.text = text
73 | $0.viewHeight = 40
74 | }
75 | }
76 |
77 | let createFooter: ((String) -> ViewFormer) = { text in
78 | return LabelViewFormer()
79 | .configure {
80 | $0.text = text
81 | $0.viewHeight = 100
82 | }
83 | }
84 |
85 | // Create SectionFormers
86 |
87 | let realExampleSection = SectionFormer(rowFormer: editProfileRow, addEventRow, loginRow)
88 | .set(headerViewFormer: createHeader("Real Examples"))
89 | let useCaseSection = SectionFormer(rowFormer: exampleRow, customCellRow)
90 | .set(headerViewFormer: createHeader("Use Case"))
91 | let defaultSection = SectionFormer(rowFormer: defaultRow)
92 | .set(headerViewFormer: createHeader("Default UI"))
93 | .set(footerViewFormer: createFooter("Former is a fully customizable Swift5+ library for easy creating UITableView based form.\n\nMIT License (MIT)"))
94 |
95 | former.append(sectionFormer: realExampleSection, useCaseSection, defaultSection)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Former/RowFormers/PickerRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PickerRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/2/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol PickerFormableRow: FormableRow {
12 |
13 | func formPickerView() -> UIPickerView
14 | }
15 |
16 | open class PickerItem {
17 |
18 | public let title: String
19 | public let value: S?
20 |
21 | public init(title: String, value: S? = nil) {
22 | self.title = title
23 | self.value = value
24 | }
25 | }
26 |
27 | open class PickerRowFormer
28 | : BaseRowFormer, Formable where T: PickerFormableRow {
29 |
30 | // MARK: Public
31 |
32 | open var pickerItems: [PickerItem] = []
33 | open var selectedRow: Int = 0
34 |
35 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
36 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
37 | }
38 |
39 | deinit {
40 | let picker = cell.formPickerView()
41 | picker.delegate = nil
42 | picker.dataSource = nil
43 | }
44 |
45 | @discardableResult
46 | public final func onValueChanged(_ handler: @escaping ((PickerItem) -> Void)) -> Self {
47 | onValueChanged = handler
48 | return self
49 | }
50 |
51 | open override func initialized() {
52 | super.initialized()
53 | rowHeight = 216
54 | }
55 |
56 | open override func cellInitialized(_ cell: T) {
57 | let picker = cell.formPickerView()
58 | picker.delegate = observer
59 | picker.dataSource = observer
60 | }
61 |
62 | open override func update() {
63 | super.update()
64 | cell.selectionStyle = .none
65 |
66 | // UPDATES SELECTED ROW TO 0, IN CASE THE COUNT OF UPDATED PICKER ITEMS ARRAY IS LESS THAN PRIOR ARRAY
67 | self.selectedRow = 0
68 |
69 | // RELOADS PICKER VIEW TO UPDATE ITEMS IN INLINE PICKER
70 | let picker = cell.formPickerView()
71 |
72 | picker.reloadAllComponents()
73 | picker.selectRow(selectedRow, inComponent: 0, animated: false)
74 | picker.isUserInteractionEnabled = enabled
75 | picker.layer.opacity = enabled ? 1 : 0.5
76 | }
77 |
78 | // MARK: Private
79 |
80 | fileprivate final var onValueChanged: ((PickerItem) -> Void)?
81 |
82 | private lazy var observer: Observer = Observer(pickerRowFormer: self)
83 | }
84 |
85 | private class Observer
86 | : NSObject, UIPickerViewDelegate, UIPickerViewDataSource where T: PickerFormableRow {
87 |
88 | fileprivate weak var pickerRowFormer: PickerRowFormer?
89 |
90 | init(pickerRowFormer: PickerRowFormer) {
91 | self.pickerRowFormer = pickerRowFormer
92 | }
93 |
94 | fileprivate dynamic func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
95 | guard let pickerRowFormer = pickerRowFormer else { return }
96 | if pickerRowFormer.enabled {
97 | pickerRowFormer.selectedRow = row
98 | let pickerItem = pickerRowFormer.pickerItems[row]
99 | pickerRowFormer.onValueChanged?(pickerItem)
100 | }
101 | }
102 |
103 | fileprivate dynamic func numberOfComponents(in: UIPickerView) -> Int {
104 | return 1
105 | }
106 |
107 | fileprivate dynamic func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
108 | guard let pickerRowFormer = pickerRowFormer else { return 0 }
109 | return pickerRowFormer.pickerItems.count
110 | }
111 |
112 | fileprivate dynamic func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
113 | guard let pickerRowFormer = pickerRowFormer else { return nil }
114 | return pickerRowFormer.pickerItems[row].title
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Cells/ColorListCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorListCell.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/8/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class ColorListCell: UITableViewCell {
12 |
13 | // MARK: Public
14 |
15 | var colors = [UIColor]()
16 | var onColorSelected: ((UIColor) -> Void)?
17 |
18 | override func awakeFromNib() {
19 | super.awakeFromNib()
20 | configure()
21 | }
22 |
23 | func select(item: Int, animated: Bool = false) {
24 | let indexPath = IndexPath(item: item, section: 0)
25 | collectionView.selectItem(at: indexPath, animated: animated, scrollPosition: [])
26 | }
27 |
28 | // MARK: Private
29 |
30 | @IBOutlet private weak var collectionView: UICollectionView!
31 |
32 | private func configure() {
33 | selectionStyle = .none
34 | collectionView.delegate = self
35 | collectionView.dataSource = self
36 | collectionView.register(ColorCell.self, forCellWithReuseIdentifier: "ColorCell")
37 | }
38 | }
39 |
40 | extension ColorListCell: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
41 |
42 | func numberOfSections(in collectionView: UICollectionView) -> Int {
43 | return 1
44 | }
45 |
46 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
47 | return colors.count
48 | }
49 |
50 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
51 | let length = collectionView.bounds.height
52 | return CGSize(width: length, height: length)
53 | }
54 |
55 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
56 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ColorCell", for: indexPath) as! ColorCell
57 | cell.color = colors[indexPath.item]
58 | return cell
59 | }
60 |
61 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
62 | let color = colors[indexPath.item]
63 | onColorSelected?(color)
64 | }
65 | }
66 |
67 | private class ColorCell: UICollectionViewCell {
68 |
69 | // MARK: Public
70 |
71 | var color: UIColor? {
72 | get { return contentView.backgroundColor }
73 | set { contentView.backgroundColor = newValue }
74 | }
75 | override var isSelected: Bool {
76 | didSet { selectedView.isHidden = !isSelected }
77 | }
78 |
79 | override var isHighlighted: Bool {
80 | didSet { contentView.alpha = isHighlighted ? 0.9 : 1 }
81 | }
82 |
83 | override init(frame: CGRect) {
84 | super.init(frame: frame)
85 | configure()
86 | }
87 |
88 | required init?(coder aDecoder: NSCoder) {
89 | fatalError("init(coder:) has not been implemented")
90 | }
91 |
92 | // MARK: Private
93 |
94 | private weak var selectedView: UIView!
95 |
96 | private func configure() {
97 | let selectedView = UIView()
98 | selectedView.layer.borderWidth = 4
99 | selectedView.layer.borderColor = selectedView.tintColor.cgColor
100 | selectedView.isUserInteractionEnabled = false
101 | selectedView.isHidden = !isSelected
102 | selectedView.translatesAutoresizingMaskIntoConstraints = false
103 | contentView.addSubview(selectedView)
104 | self.selectedView = selectedView
105 |
106 | let constraints = [
107 | NSLayoutConstraint.constraints(
108 | withVisualFormat: "V:|-0-[view]-0-|",
109 | options: [],
110 | metrics: nil,
111 | views: ["view": selectedView]
112 | ),
113 | NSLayoutConstraint.constraints(
114 | withVisualFormat: "H:|-0-[view]-0-|",
115 | options: [],
116 | metrics: nil,
117 | views: ["view": selectedView]
118 | )
119 | ].flatMap { $0 }
120 | contentView.addConstraints(constraints)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/DynamicHeightCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/ProfileImageCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/ProfileFieldCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/ProfileLabelCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Former/Commons/RowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol FormableRow: class {
12 |
13 | func updateWithRowFormer(_ rowFormer: RowFormer)
14 | }
15 |
16 | open class RowFormer {
17 |
18 | // MARK: Public
19 |
20 | public internal(set) final weak var former: Former?
21 | public final let cellType: UITableViewCell.Type
22 | public final var rowHeight: CGFloat = 44
23 | public final var isEditing = false
24 | public final var enabled = true { didSet { update() } }
25 | open var canBecomeEditing: Bool {
26 | return false
27 | }
28 |
29 | internal init(
30 | cellType: T.Type,
31 | instantiateType: Former.InstantiateType,
32 | cellSetup: ((T) -> Void)? = nil) {
33 | self.cellType = cellType
34 | self.instantiateType = instantiateType
35 | self.cellSetup = { cellSetup?(($0 as! T)) }
36 | initialized()
37 | }
38 |
39 | @discardableResult
40 | public final func cellSetup(_ handler: @escaping ((UITableViewCell) -> Void)) -> Self {
41 | cellSetup = handler
42 | return self
43 | }
44 |
45 | @discardableResult
46 | public final func dynamicRowHeight(_ handler: @escaping ((UITableView, IndexPath) -> CGFloat)) -> Self {
47 | dynamicRowHeight = handler
48 | return self
49 | }
50 |
51 | open func initialized() {}
52 |
53 | open func update() {
54 | cellInstance.isUserInteractionEnabled = enabled
55 | onUpdate?(self)
56 |
57 | if let formableRow = cellInstance as? FormableRow {
58 | formableRow.updateWithRowFormer(self)
59 | }
60 |
61 | if let inlineRow = self as? InlineForm {
62 | let inlineRowFormer = inlineRow.inlineRowFormer
63 | inlineRowFormer.update()
64 |
65 | if let inlineFormableRow = inlineRowFormer.cellInstance as? FormableRow {
66 | inlineFormableRow.updateWithRowFormer(inlineRowFormer)
67 | }
68 | }
69 | }
70 |
71 | open func cellSelected(indexPath: IndexPath) {
72 | if enabled {
73 | onSelected?(self)
74 | }
75 | }
76 |
77 | // MARK: Internal
78 |
79 | internal final var cellSetup: ((UITableViewCell) -> Void)?
80 | internal final var onSelected: ((RowFormer) -> Void)?
81 | internal final var onUpdate: ((RowFormer) -> Void)?
82 | internal final var dynamicRowHeight: ((UITableView, IndexPath) -> CGFloat)?
83 |
84 | internal final var cellInstance: UITableViewCell {
85 | if _cellInstance == nil {
86 | var cell: UITableViewCell?
87 | switch instantiateType {
88 | case .Class:
89 | cell = cellType.init(style: .default, reuseIdentifier: nil)
90 | case .Nib(nibName: let nibName):
91 | cell = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewCell
92 | assert(cell != nil, "[Former] Failed to load cell from nib (\(nibName)).")
93 | case .NibTag(nibName: let nibName, tag: let tag):
94 | let nibChildren = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)!
95 | cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell
96 | assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName)), tag: (\(tag)).")
97 | case .NibBundle(nibName: let nibName, bundle: let bundle):
98 | cell = bundle.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewCell
99 | assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName), bundle: \(bundle)).")
100 | case .NibBundleTag(nibName: let nibName, bundle: let bundle, tag: let tag):
101 | let nibChildren = bundle.loadNibNamed(nibName, owner: nil, options: nil)!
102 | cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell
103 | assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName), bundle: \(bundle), tag: \(tag)).")
104 | }
105 | _cellInstance = cell
106 | cellInstanceInitialized(cell!)
107 | cellSetup?(cell!)
108 | }
109 | return _cellInstance!
110 | }
111 |
112 | internal func cellInstanceInitialized(_ cell: UITableViewCell) {}
113 |
114 | // MARK: Private
115 |
116 | private final var _cellInstance: UITableViewCell?
117 | private final let instantiateType: Former.InstantiateType
118 | }
119 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Resources/LoginViewController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Controllers/DefaultsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultsViewController.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class DefaultsViewController: FormViewController {
13 |
14 | // MARK: Public
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | configure()
19 | }
20 |
21 | // MARK: Private
22 |
23 | private var enabled = true
24 |
25 | private func configure() {
26 | title = "Default UI"
27 |
28 | // Create RowFomers
29 |
30 | let disableRowText: ((Bool) -> String) = {
31 | return ($0 ? "Enable" : "Disable") + " All Cells"
32 | }
33 | let disableRow = LabelRowFormer()
34 | .configure {
35 | $0.text = disableRowText(false)
36 | }.onSelected(disableRowSelected)
37 |
38 | let labelRow = LabelRowFormer()
39 | .configure {
40 | $0.text = "Text"
41 | $0.subText = "SubText"
42 | }.onSelected { [weak self] _ in
43 | self?.former.deselect(animated: true)
44 | }
45 |
46 | let textFieldRow = TextFieldRowFormer() {
47 | $0.titleLabel.text = "TextField"
48 | }.configure {
49 | $0.placeholder = "Placeholder"
50 | }
51 |
52 | let textViewRow = TextViewRowFormer {
53 | $0.titleLabel.text = "TextView"
54 | }.configure {
55 | $0.placeholder = "Placeholder"
56 | }
57 |
58 | let checkRow = CheckRowFormer{
59 | $0.titleLabel.text = "Check"
60 | }
61 |
62 | let switchRow = SwitchRowFormer() {
63 | $0.titleLabel.text = "Switch"
64 | }
65 |
66 | let stepperRow = StepperRowFormer(){
67 | $0.titleLabel.text = "Stepper"
68 | }.displayTextFromValue { "\(Int($0))" }
69 |
70 | let segmentRow = SegmentedRowFormer() {
71 | $0.titleLabel.text = "Segmented"
72 | }.configure {
73 | $0.segmentTitles = ["Opt1", "Opt2", "Opt3"]
74 | $0.selectedIndex = UISegmentedControl.noSegment
75 | }
76 |
77 | let sliderRow = SliderRowFormer(){
78 | $0.titleLabel.text = "Slider"
79 | }.displayTextFromValue { "\(Float(round($0 * 10) / 10))" }
80 |
81 | let selectorPickerRow = SelectorPickerRowFormer() {
82 | $0.titleLabel.text = "SelectorPicker"
83 | }.configure {
84 | $0.pickerItems = [SelectorPickerItem(
85 | title: "",
86 | displayTitle: NSAttributedString(string: "Not Set"),
87 | value: nil)]
88 | + (1...20).map { SelectorPickerItem(title: "Option\($0)") }
89 | }
90 |
91 | let selectorDatePickerRow = SelectorDatePickerRowFormer {
92 | $0.titleLabel.text = "SelectorDatePicker"
93 | }.displayTextFromDate(String.mediumDateShortTime)
94 |
95 | let inlinePickerRow = InlinePickerRowFormer() {
96 | $0.titleLabel.text = "InlinePicker"
97 | }.configure {
98 | $0.pickerItems = [InlinePickerItem(
99 | title: "",
100 | displayTitle: NSAttributedString(string: "Not set"),
101 | value: nil)]
102 | + (1...20).map { InlinePickerItem(title: "Option\($0)") }
103 | }
104 |
105 | let inlineDateRow = InlineDatePickerRowFormer() {
106 | $0.titleLabel.text = "InlineDatePicker"
107 | }.displayTextFromDate(String.mediumDateShortTime)
108 |
109 | let pickerRow = PickerRowFormer()
110 | .configure {
111 | $0.pickerItems = (1...20).map { PickerItem(title: "Option\($0)") }
112 | }
113 |
114 | let datePickerRow = DatePickerRowFormer()
115 |
116 | // Create SectionFormers
117 |
118 | let sectionFormer1 = SectionFormer(rowFormer: disableRow)
119 |
120 | let sectionFormer2 = SectionFormer(rowFormer:
121 | labelRow, textFieldRow, textViewRow,
122 | checkRow, switchRow, stepperRow,
123 | segmentRow, sliderRow, selectorPickerRow,
124 | selectorDatePickerRow, inlinePickerRow, inlineDateRow
125 | )
126 |
127 | let sectionFormer3 = SectionFormer(rowFormer: pickerRow, datePickerRow)
128 | .set(footerViewFormer: CustomViewFormer())
129 |
130 | former.append(sectionFormer: sectionFormer1, sectionFormer2, sectionFormer3)
131 | }
132 |
133 | private func disableRowSelected(rowFormer: RowFormer) {
134 | guard let disableRow = rowFormer as? LabelRowFormer else { return }
135 | self.former.deselect(animated: true)
136 | self.former[1...2].flatMap { $0.rowFormers }.forEach {
137 | $0.enabled = !enabled
138 | }
139 | disableRow.text = (enabled ? "Enable" : "Disable") + " All Cells"
140 | disableRow.update()
141 | self.enabled = !self.enabled
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Views/FormerInputAccessoryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormerInputAccessoryView.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/13/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class FormerInputAccessoryView: UIToolbar {
13 |
14 | private weak var former: Former?
15 | private weak var leftArrow: UIBarButtonItem!
16 | private weak var rightArrow: UIBarButtonItem!
17 |
18 | init(former: Former) {
19 | super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 0, height: 44)))
20 | self.former = former
21 | configure()
22 | }
23 |
24 | required init?(coder aDecoder: NSCoder) {
25 | fatalError("init(coder:) has not been implemented")
26 | }
27 |
28 | func update() {
29 | leftArrow.isEnabled = former?.canBecomeEditingPrevious() ?? false
30 | rightArrow.isEnabled = former?.canBecomeEditingNext() ?? false
31 | }
32 |
33 | private func configure() {
34 | barTintColor = .white
35 | tintColor = .formerSubColor()
36 | clipsToBounds = true
37 | isUserInteractionEnabled = true
38 |
39 | let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
40 | let leftArrow = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem(rawValue: 105)!, target: self, action: #selector(FormerInputAccessoryView.handleBackButton))
41 | let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
42 | space.width = 20
43 | let rightArrow = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem(rawValue: 106)!, target: self, action: #selector(FormerInputAccessoryView.handleForwardButton))
44 | let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(FormerInputAccessoryView.handleDoneButton))
45 | let rightSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
46 | setItems([leftArrow, space, rightArrow, flexible, doneButton, rightSpace], animated: false)
47 | self.leftArrow = leftArrow
48 | self.rightArrow = rightArrow
49 |
50 | let topLineView = UIView()
51 | topLineView.backgroundColor = UIColor(white: 0, alpha: 0.3)
52 | topLineView.translatesAutoresizingMaskIntoConstraints = false
53 | addSubview(topLineView)
54 |
55 | let bottomLineView = UIView()
56 | bottomLineView.backgroundColor = UIColor(white: 0, alpha: 0.3)
57 | bottomLineView.translatesAutoresizingMaskIntoConstraints = false
58 | addSubview(bottomLineView)
59 |
60 | let leftLineView = UIView()
61 | leftLineView.backgroundColor = UIColor(white: 0, alpha: 0.3)
62 | leftLineView.translatesAutoresizingMaskIntoConstraints = false
63 | addSubview(leftLineView)
64 |
65 | let rightLineView = UIView()
66 | rightLineView.backgroundColor = UIColor(white: 0, alpha: 0.3)
67 | rightLineView.translatesAutoresizingMaskIntoConstraints = false
68 | addSubview(rightLineView)
69 |
70 | let constraints = [
71 | NSLayoutConstraint.constraints(
72 | withVisualFormat: "V:|-0-[topLine(0.5)]",
73 | options: [],
74 | metrics: nil,
75 | views: ["topLine": topLineView]
76 | ),
77 | NSLayoutConstraint.constraints(
78 | withVisualFormat: "V:[bottomLine(0.5)]-0-|",
79 | options: [],
80 | metrics: nil,
81 | views: ["bottomLine": bottomLineView]
82 | ),
83 | NSLayoutConstraint.constraints(
84 | withVisualFormat: "V:|-10-[leftLine]-10-|",
85 | options: [],
86 | metrics: nil,
87 | views: ["leftLine": leftLineView]
88 | ),
89 | NSLayoutConstraint.constraints(
90 | withVisualFormat: "V:|-10-[rightLine]-10-|",
91 | options: [],
92 | metrics: nil,
93 | views: ["rightLine": rightLineView]
94 | ),
95 | NSLayoutConstraint.constraints(
96 | withVisualFormat: "H:|-0-[topLine]-0-|",
97 | options: [],
98 | metrics: nil,
99 | views: ["topLine": topLineView]
100 | ),
101 | NSLayoutConstraint.constraints(
102 | withVisualFormat: "H:|-0-[bottomLine]-0-|",
103 | options: [],
104 | metrics: nil,
105 | views: ["bottomLine": bottomLineView]
106 | ),
107 | NSLayoutConstraint.constraints(
108 | withVisualFormat: "H:|-84-[leftLine(0.5)]",
109 | options: [],
110 | metrics: nil,
111 | views: ["leftLine": leftLineView]
112 | ),
113 | NSLayoutConstraint.constraints(
114 | withVisualFormat: "H:[rightLine(0.5)]-74-|",
115 | options: [],
116 | metrics: nil,
117 | views: ["rightLine": rightLineView]
118 | )
119 | ]
120 | addConstraints(constraints.flatMap { $0 })
121 | }
122 |
123 | @objc private dynamic func handleBackButton() {
124 | update()
125 | former?.becomeEditingPrevious()
126 | }
127 |
128 | @objc private dynamic func handleForwardButton() {
129 | update()
130 | former?.becomeEditingNext()
131 | }
132 |
133 | @objc private dynamic func handleDoneButton() {
134 | former?.endEditing()
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Former/RowFormers/SelectorDatePickerRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectorDatePickerRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/24/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SelectorDatePickerFormableRow: FormableRow {
12 |
13 | var selectorDatePicker: UIDatePicker? { get set } // Needs NOT to set instance.
14 | var selectorAccessoryView: UIView? { get set } // Needs NOT to set instance.
15 |
16 | func formTitleLabel() -> UILabel?
17 | func formDisplayLabel() -> UILabel?
18 |
19 |
20 | func formDefaultDisplayDate() -> NSDate?
21 |
22 | func formDefaultDisplayLabelText() -> String? //If formDefaultDisplayDate() returns a real date, the return value from this is ignored
23 |
24 | }
25 |
26 | open class SelectorDatePickerRowFormer
27 | : BaseRowFormer, Formable, UpdatableSelectorForm where T: SelectorDatePickerFormableRow {
28 |
29 | // MARK: Public
30 |
31 | override open var canBecomeEditing: Bool {
32 | return enabled
33 | }
34 |
35 | open var date: Date? = nil
36 | open var inputAccessoryView: UIView?
37 | open var titleDisabledColor: UIColor? = .lightGray
38 | open var displayDisabledColor: UIColor? = .lightGray
39 | open var titleEditingColor: UIColor?
40 | open var displayEditingColor: UIColor?
41 |
42 | public private(set) final lazy var selectorView: UIDatePicker = { [unowned self] in
43 | let datePicker = UIDatePicker()
44 | datePicker.addTarget(self, action: #selector(SelectorDatePickerRowFormer.dateChanged(datePicker:)), for: .valueChanged)
45 | return datePicker
46 | }()
47 |
48 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
49 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
50 | }
51 |
52 | @discardableResult
53 | public final func onDateChanged(_ handler: @escaping ((Date) -> Void)) -> Self {
54 | onDateChanged = handler
55 | return self
56 | }
57 |
58 | @discardableResult
59 | public final func displayTextFromDate(_ handler: @escaping ((Date) -> String)) -> Self {
60 | displayTextFromDate = handler
61 | return self
62 | }
63 |
64 | open override func update() {
65 | super.update()
66 |
67 | cell.selectorDatePicker = selectorView
68 | cell.selectorAccessoryView = inputAccessoryView
69 |
70 | let titleLabel = cell.formTitleLabel()
71 | let displayLabel = cell.formDisplayLabel()
72 |
73 | if let date = date {
74 | displayLabel?.text = displayTextFromDate?(date) ?? "\(date)"
75 | } else if let defaultDate = cell.formDefaultDisplayDate() {
76 | self.date = defaultDate as Date
77 | displayLabel?.text = displayTextFromDate?(defaultDate as Date) ?? "\(defaultDate)"
78 | } else if let defaultDisplayText = cell.formDefaultDisplayLabelText() {
79 | displayLabel?.text = defaultDisplayText
80 | }
81 |
82 | if self.enabled {
83 | _ = titleColor.map { titleLabel?.textColor = $0 }
84 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
85 | titleColor = nil
86 | displayTextColor = nil
87 | } else {
88 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
89 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
90 | titleLabel?.textColor = titleDisabledColor
91 | displayLabel?.textColor = displayDisabledColor
92 | }
93 | }
94 |
95 | open override func cellSelected(indexPath: IndexPath) {
96 | former?.deselect(animated: true)
97 | }
98 |
99 | public func editingDidBegin() {
100 | if enabled {
101 | let titleLabel = cell.formTitleLabel()
102 | let displayLabel = cell.formDisplayLabel()
103 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
104 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
105 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
106 | _ = displayEditingColor.map { displayEditingColor = $0 }
107 | isEditing = true
108 | }
109 | }
110 |
111 | public func editingDidEnd() {
112 | isEditing = false
113 | let titleLabel = cell.formTitleLabel()
114 | let displayLabel = cell.formDisplayLabel()
115 | if enabled {
116 | _ = titleColor.map { titleLabel?.textColor = $0 }
117 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
118 | titleColor = nil
119 | displayTextColor = nil
120 | } else {
121 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
122 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
123 | titleLabel?.textColor = titleDisabledColor
124 | displayLabel?.textColor = displayDisabledColor
125 | }
126 | }
127 |
128 | // MARK: Private
129 |
130 | private final var onDateChanged: ((Date) -> Void)?
131 | private final var displayTextFromDate: ((Date) -> String)?
132 | private final var titleColor: UIColor?
133 | private final var displayTextColor: UIColor?
134 |
135 | @objc private dynamic func dateChanged(datePicker: UIDatePicker) {
136 | let date = datePicker.date
137 | self.date = date
138 | cell.formDisplayLabel()?.text = displayTextFromDate?(date) ?? "\(date)"
139 | onDateChanged?(date)
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Former/RowFormers/InlineDatePickerRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InlineDatePickerRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/1/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol InlineDatePickerFormableRow: FormableRow {
12 |
13 | func formTitleLabel() -> UILabel?
14 | func formDisplayLabel() -> UILabel?
15 | }
16 |
17 | open class InlineDatePickerRowFormer
18 | : BaseRowFormer, Formable, ConfigurableInlineForm where T: InlineDatePickerFormableRow {
19 |
20 | // MARK: Public
21 |
22 | public typealias InlineCellType = FormDatePickerCell
23 |
24 | public let inlineRowFormer: RowFormer
25 | override open var canBecomeEditing: Bool {
26 | return enabled
27 | }
28 |
29 | open var date: Date = Date()
30 | open var displayDisabledColor: UIColor? = .lightGray
31 | open var titleDisabledColor: UIColor? = .lightGray
32 | open var displayEditingColor: UIColor?
33 | open var titleEditingColor: UIColor?
34 |
35 | required public init(
36 | instantiateType: Former.InstantiateType = .Class,
37 | cellSetup: ((T) -> Void)?) {
38 | inlineRowFormer = DatePickerRowFormer(instantiateType: .Class)
39 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
40 | }
41 |
42 | @discardableResult
43 | public final func onDateChanged(_ handler: @escaping ((Date) -> Void)) -> Self {
44 | onDateChanged = handler
45 | return self
46 | }
47 |
48 | public final func onEditingBegin(handler: @escaping ((Date, T) -> Void)) -> Self {
49 | onEditingBegin = handler
50 | return self
51 | }
52 |
53 | public final func onEditingEnded(handler: @escaping ((Date, T) -> Void)) -> Self {
54 | onEditingEnded = handler
55 | return self
56 | }
57 |
58 | @discardableResult
59 | public final func displayTextFromDate(_ handler: @escaping ((Date) -> String)) -> Self {
60 | displayTextFromDate = handler
61 | return self
62 | }
63 |
64 | open override func update() {
65 | super.update()
66 |
67 | let titleLabel = cell.formTitleLabel()
68 | let displayLabel = cell.formDisplayLabel()
69 | displayLabel?.text = displayTextFromDate?(date) ?? "\(date)"
70 |
71 | if enabled {
72 | if isEditing {
73 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
74 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
75 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
76 | _ = displayEditingColor.map { displayLabel?.textColor = $0 }
77 | } else {
78 | _ = titleColor.map { titleLabel?.textColor = $0 }
79 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
80 | titleColor = nil
81 | displayTextColor = nil
82 | }
83 | } else {
84 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
85 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
86 | _ = titleDisabledColor.map { titleLabel?.textColor = $0 }
87 | _ = displayDisabledColor.map { displayLabel?.textColor = $0 }
88 | }
89 |
90 | let inlineRowFormer = self.inlineRowFormer as! DatePickerRowFormer
91 | inlineRowFormer.configure {
92 | $0.onDateChanged(dateChanged)
93 | $0.enabled = enabled
94 | $0.date = date
95 | }.update()
96 | }
97 |
98 | open override func cellSelected(indexPath: IndexPath) {
99 | former?.deselect(animated: true)
100 | }
101 |
102 | private func dateChanged(date: Date) {
103 | if enabled {
104 | self.date = date
105 | cell.formDisplayLabel()?.text = displayTextFromDate?(date) ?? "\(date)"
106 | onDateChanged?(date)
107 | }
108 | }
109 |
110 | public func editingDidBegin() {
111 | if enabled {
112 | let titleLabel = cell.formTitleLabel()
113 | let displayLabel = cell.formDisplayLabel()
114 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
115 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
116 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
117 | _ = displayEditingColor.map { displayLabel?.textColor = $0 }
118 | isEditing = true
119 | }
120 | onEditingBegin?(date, cell)
121 | }
122 |
123 | public func editingDidEnd() {
124 | let titleLabel = cell.formTitleLabel()
125 | let displayLabel = cell.formDisplayLabel()
126 | if enabled {
127 | _ = titleColor.map { titleLabel?.textColor = $0 }
128 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
129 | titleColor = nil
130 | displayTextColor = nil
131 | } else {
132 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
133 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
134 | titleLabel?.textColor = titleDisabledColor
135 | displayLabel?.textColor = displayDisabledColor
136 | }
137 | isEditing = false
138 | onEditingEnded?(date, cell)
139 | }
140 |
141 | // MARK: Private
142 |
143 | private final var onDateChanged: ((Date) -> Void)?
144 | private final var onEditingBegin: ((Date, T) -> Void)?
145 | private final var onEditingEnded: ((Date, T) -> Void)?
146 | private final var displayTextFromDate: ((Date) -> String)?
147 | private final var titleColor: UIColor?
148 | private final var displayTextColor: UIColor?
149 | }
150 |
--------------------------------------------------------------------------------
/Former/RowFormers/TextFieldRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/25/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol TextFieldFormableRow: FormableRow {
12 |
13 | func formTextField() -> UITextField
14 | func formTitleLabel() -> UILabel?
15 | }
16 |
17 | open class TextFieldRowFormer
18 | : BaseRowFormer, Formable where T: TextFieldFormableRow {
19 |
20 | // MARK: Public
21 |
22 | override open var canBecomeEditing: Bool {
23 | return enabled
24 | }
25 |
26 | open var text: String?
27 | open var placeholder: String?
28 | open var attributedPlaceholder: NSAttributedString?
29 | open var textDisabledColor: UIColor? = .lightGray
30 | open var titleDisabledColor: UIColor? = .lightGray
31 | open var titleEditingColor: UIColor?
32 | open var returnToNextRow = true
33 |
34 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
35 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
36 | }
37 |
38 | @discardableResult
39 | public final func onTextChanged(_ handler: @escaping ((String) -> Void)) -> Self {
40 | onTextChanged = handler
41 | return self
42 | }
43 |
44 | @discardableResult
45 | public final func onReturn(_ handler: @escaping ((String) -> Void)) -> Self {
46 | onReturn = handler
47 | return self
48 | }
49 |
50 | open override func cellInitialized(_ cell: T) {
51 | super.cellInitialized(cell)
52 | let textField = cell.formTextField()
53 | textField.delegate = observer
54 | let events: [(Selector, UIControl.Event)] = [(#selector(TextFieldRowFormer.textChanged(textField:)), .editingChanged),
55 | (#selector(TextFieldRowFormer.editingDidBegin(textField:)), .editingDidBegin),
56 | (#selector(TextFieldRowFormer.editingDidEnd(textField:)), .editingDidEnd)]
57 | events.forEach {
58 | textField.addTarget(self, action: $0.0, for: $0.1)
59 | }
60 | }
61 |
62 | open override func update() {
63 | super.update()
64 |
65 | cell.selectionStyle = .none
66 | let titleLabel = cell.formTitleLabel()
67 | let textField = cell.formTextField()
68 | textField.text = text
69 | _ = placeholder.map { textField.placeholder = $0 }
70 | _ = attributedPlaceholder.map { textField.attributedPlaceholder = $0 }
71 | textField.isUserInteractionEnabled = false
72 |
73 | if enabled {
74 | if isEditing {
75 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
76 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
77 | } else {
78 | _ = titleColor.map { titleLabel?.textColor = $0 }
79 | titleColor = nil
80 | }
81 | _ = textColor.map { textField.textColor = $0 }
82 | textColor = nil
83 | } else {
84 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
85 | if textColor == nil { textColor = textField.textColor ?? .black }
86 | titleLabel?.textColor = titleDisabledColor
87 | textField.textColor = textDisabledColor
88 | }
89 | }
90 |
91 | open override func cellSelected(indexPath: IndexPath) {
92 | let textField = cell.formTextField()
93 | if !textField.isEditing {
94 | textField.isUserInteractionEnabled = true
95 | textField.becomeFirstResponder()
96 | }
97 | }
98 |
99 | // MARK: Fileprivate
100 |
101 | fileprivate final var onReturn: ((String) -> Void)?
102 |
103 | // MARK: Private
104 |
105 | private final var onTextChanged: ((String) -> Void)?
106 | private final var textColor: UIColor?
107 | private final var titleColor: UIColor?
108 |
109 | private lazy var observer: Observer = Observer(textFieldRowFormer: self)
110 |
111 | @objc private dynamic func textChanged(textField: UITextField) {
112 | if enabled {
113 | let text = textField.text ?? ""
114 | self.text = text
115 | onTextChanged?(text)
116 | }
117 | }
118 |
119 | @objc private dynamic func editingDidBegin(textField: UITextField) {
120 | let titleLabel = cell.formTitleLabel()
121 | if titleColor == nil { textColor = textField.textColor ?? .black }
122 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
123 | }
124 |
125 | @objc private dynamic func editingDidEnd(textField: UITextField) {
126 | let titleLabel = cell.formTitleLabel()
127 | if enabled {
128 | _ = titleColor.map { titleLabel?.textColor = $0 }
129 | titleColor = nil
130 | } else {
131 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
132 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
133 | }
134 | cell.formTextField().isUserInteractionEnabled = false
135 | }
136 | }
137 |
138 | private class Observer: NSObject, UITextFieldDelegate where T: TextFieldFormableRow {
139 |
140 | fileprivate weak var textFieldRowFormer: TextFieldRowFormer?
141 |
142 | init(textFieldRowFormer: TextFieldRowFormer) {
143 | self.textFieldRowFormer = textFieldRowFormer
144 | }
145 |
146 | fileprivate dynamic func textFieldShouldReturn(_ textField: UITextField) -> Bool {
147 | guard let textFieldRowFormer = textFieldRowFormer else { return false }
148 | if let returnHandler = textFieldRowFormer.onReturn {
149 | returnHandler(textField.text ?? "")
150 | return false
151 | }
152 | if textFieldRowFormer.returnToNextRow {
153 | let returnToNextRow = (textFieldRowFormer.former?.canBecomeEditingNext() ?? false) ?
154 | textFieldRowFormer.former?.becomeEditingNext :
155 | textFieldRowFormer.former?.endEditing
156 | _ = returnToNextRow?()
157 | }
158 | return !textFieldRowFormer.returnToNextRow
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/Former/Commons/SectionFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/23/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public final class SectionFormer {
12 |
13 | // MARK: Public
14 |
15 | public init(rowFormer: RowFormer...) {
16 | self.rowFormers = rowFormer
17 | }
18 |
19 | public init(rowFormers: [RowFormer] = []) {
20 | self.rowFormers = rowFormers
21 | }
22 |
23 | /// All RowFormers. Default is empty.
24 | public private(set) var rowFormers = [RowFormer]()
25 |
26 | /// ViewFormer of applying section header. Default is applying simply 10px spacing section header.
27 | public private(set) var headerViewFormer: ViewFormer? = ViewFormer(viewType: FormHeaderFooterView.self, instantiateType: .Class)
28 |
29 | /// ViewFormer of applying section footer. Default is nil.
30 | public private(set) var footerViewFormer: ViewFormer?
31 |
32 | /// Return all row count.
33 | public var numberOfRows: Int {
34 | return rowFormers.count
35 | }
36 |
37 | /// Returns the first element of RowFormers, or `nil` if `self.rowFormers` is empty.
38 | public var firstRowFormer: RowFormer? {
39 | return rowFormers.first
40 | }
41 |
42 | /// Returns the last element of RowFormers, or `nil` if `self.rowFormers` is empty.
43 | public var lastRowFormer: RowFormer? {
44 | return rowFormers.last
45 | }
46 |
47 | public subscript(index: Int) -> RowFormer {
48 | return rowFormers[index]
49 | }
50 |
51 | public subscript(range: Range) -> [RowFormer] {
52 | return Array(rowFormers[range])
53 | }
54 |
55 | /// Append RowFormer to last index.
56 | @discardableResult
57 | public func append(rowFormer: RowFormer...) -> Self {
58 | add(rowFormers: rowFormer)
59 | return self
60 | }
61 |
62 | /// Add RowFormers to last index.
63 | @discardableResult
64 | public func add(rowFormers: [RowFormer]) -> Self {
65 | self.rowFormers += rowFormers
66 | return self
67 | }
68 |
69 | /// Insert RowFormer to any index.
70 | @discardableResult
71 | public func insert(rowFormer: RowFormer..., toIndex: Int) -> Self {
72 | let count = self.rowFormers.count
73 | if count == 0 || toIndex >= count {
74 | add(rowFormers: rowFormers)
75 | return self
76 | }
77 | self.rowFormers.insert(contentsOf: rowFormers, at: toIndex)
78 | return self
79 | }
80 |
81 | /// Insert RowFormers to any index.
82 | @discardableResult
83 | public func insert(rowFormers: [RowFormer], toIndex: Int) -> Self {
84 | let count = self.rowFormers.count
85 | if count == 0 || toIndex >= count {
86 | add(rowFormers: rowFormers)
87 | return self
88 | }
89 | self.rowFormers.insert(contentsOf: rowFormers, at: toIndex)
90 | return self
91 | }
92 |
93 | /// Insert RowFormer to above other SectionFormer.
94 | @discardableResult
95 | public func insert(rowFormer: RowFormer..., above: RowFormer) -> Self {
96 | for (row, rowFormer) in self.rowFormers.enumerated() {
97 | if rowFormer === above {
98 | insert(rowFormers: [rowFormer], toIndex: row)
99 | return self
100 | }
101 | }
102 | add(rowFormers: rowFormers)
103 | return self
104 | }
105 |
106 | /// Insert RowFormers to above other SectionFormer.
107 | @discardableResult
108 | public func insert(rowFormers: [RowFormer], above: RowFormer) -> Self {
109 | for (row, rowFormer) in self.rowFormers.enumerated() {
110 | if rowFormer === above {
111 | insert(rowFormers: [rowFormer], toIndex: row)
112 | return self
113 | }
114 | }
115 | add(rowFormers: rowFormers)
116 | return self
117 | }
118 |
119 | /// Insert RowFormer to below other SectionFormer.
120 | @discardableResult
121 | public func insert(rowFormer: RowFormer..., below: RowFormer) -> Self {
122 | for (row, rowFormer) in self.rowFormers.enumerated() {
123 | if rowFormer === below {
124 | insert(rowFormers: [rowFormer], toIndex: row + 1)
125 | return self
126 | }
127 | }
128 | add(rowFormers: rowFormers)
129 | return self
130 | }
131 |
132 | /// Insert RowFormers to below other SectionFormer.
133 | @discardableResult
134 | public func insert(rowFormers: [RowFormer], below: RowFormer) -> Self {
135 | for (row, rowFormer) in self.rowFormers.enumerated() {
136 | if rowFormer === below {
137 | insert(rowFormers: [rowFormer], toIndex: row + 1)
138 | return self
139 | }
140 | }
141 | add(rowFormers: rowFormers)
142 | return self
143 | }
144 |
145 | /// Remove RowFormers from instances of RowFormer.
146 | @discardableResult
147 | public func remove(rowFormer: RowFormer...) -> Self {
148 | var removedCount = 0
149 | for (index, rowFormer) in self.rowFormers.enumerated() {
150 | if rowFormers.contains(where: { $0 === rowFormer }) {
151 | remove(atIndex: index)
152 | removedCount += 1
153 | if removedCount >= rowFormers.count {
154 | return self
155 | }
156 | }
157 | }
158 | return self
159 | }
160 |
161 | /// Remove RowFormers from instances of RowFormer.
162 | @discardableResult
163 | public func remove(rowFormers: [RowFormer]) -> Self {
164 | var removedCount = 0
165 | for (index, rowFormer) in self.rowFormers.enumerated() {
166 | if rowFormers.contains(where: { $0 === rowFormer }) {
167 | remove(atIndex: index)
168 | removedCount += 1
169 | if removedCount >= rowFormers.count {
170 | return self
171 | }
172 | }
173 | }
174 | return self
175 | }
176 |
177 | /// Remove RowFormer from index.
178 | @discardableResult
179 | public func remove(atIndex: Int) -> Self {
180 | rowFormers.remove(at: atIndex)
181 | return self
182 | }
183 |
184 | /// Remove RowFormers from range.
185 | @discardableResult
186 | public func remove(range: Range) -> Self {
187 | rowFormers.removeSubrange(range)
188 | return self
189 | }
190 |
191 | /// Set ViewFormer to apply section header.
192 | @discardableResult
193 | public func set(headerViewFormer viewFormer: ViewFormer?) -> Self {
194 | headerViewFormer = viewFormer
195 | return self
196 | }
197 |
198 | /// Set ViewFormer to apply section footer.
199 | @discardableResult
200 | public func set(footerViewFormer viewFormer: ViewFormer?) -> Self {
201 | footerViewFormer = viewFormer
202 | return self
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Former-Demo/Former-Demo/Controllers/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 11/8/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Former
11 |
12 | final class LoginViewController: UIViewController {
13 |
14 | // MARK: Public
15 |
16 | required init?(coder aDecoder: NSCoder) {
17 | super.init(coder: aDecoder)
18 | modalPresentationStyle = .custom
19 | transitioningDelegate = self
20 | }
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | configure()
25 | }
26 |
27 | class func present(viewController: UIViewController) {
28 | let storyboard = UIStoryboard(name: "LoginViewController", bundle: nil)
29 | let loginVC = storyboard.instantiateInitialViewController() as! LoginViewController
30 | viewController.present(loginVC, animated: true, completion: nil)
31 | }
32 |
33 | // MARK: Private
34 |
35 | private lazy var former: Former = Former(tableView: self.tableView)
36 | private var idRow: TextFieldRowFormer?
37 | private var passwordRow: TextFieldRowFormer?
38 | private var loginRow: LabelRowFormer?
39 |
40 | @IBOutlet private weak var tableView: UITableView!
41 | @IBOutlet private weak var dimmingView: UIControl!
42 |
43 | private func configure() {
44 | tableView.backgroundColor = .formerColor()
45 | tableView.layer.cornerRadius = 5
46 | tableView.sectionHeaderHeight = 0
47 | tableView.sectionFooterHeight = 0
48 | tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.01))
49 | tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0.01))
50 |
51 | // Create RowFormers
52 |
53 | let idRow = TextFieldRowFormer() {
54 | $0.textField.textColor = .formerSubColor()
55 | $0.textField.font = .systemFont(ofSize: 15)
56 | }.configure {
57 | $0.placeholder = "User name"
58 | $0.text = Login.sharedInstance.username
59 | }.onTextChanged { [weak self] in
60 | Login.sharedInstance.username = $0
61 | self?.switchLoginRow()
62 | }
63 | let passwordRow = TextFieldRowFormer() {
64 | $0.textField.textColor = .formerSubColor()
65 | $0.textField.font = .systemFont(ofSize: 15)
66 | $0.textField.keyboardType = .decimalPad
67 | $0.textField.isSecureTextEntry = true
68 | }.configure {
69 | $0.placeholder = "Password"
70 | $0.text = Login.sharedInstance.password
71 | }.onTextChanged { [weak self] in
72 | Login.sharedInstance.password = $0
73 | self?.switchLoginRow()
74 | }
75 |
76 | let loginRow = LabelRowFormer()
77 | .configure {
78 | $0.text = "Login"
79 | }.onSelected { [weak self] _ in
80 | self?.dismiss(animated: true, completion: nil)
81 | }
82 |
83 | self.idRow = idRow
84 | self.passwordRow = passwordRow
85 | self.loginRow = loginRow
86 |
87 | switchLoginRow()
88 |
89 | // Create Headers
90 |
91 | let descriptionHeader = LabelViewFormer() {
92 | $0.contentView.backgroundColor = .clear
93 | $0.titleLabel.textColor = .white
94 | }.configure {
95 | $0.viewHeight = 80
96 | $0.text = "Welcome to the Former demo app\nPlease login"
97 | }
98 | let createSpaceHeader: (() -> ViewFormer) = {
99 | return CustomViewFormer() {
100 | $0.contentView.backgroundColor = .clear
101 | }.configure {
102 | $0.viewHeight = 30
103 | }
104 | }
105 |
106 | // Create SectionFormers
107 |
108 | let idSection = SectionFormer(rowFormer: idRow)
109 | .set(headerViewFormer: descriptionHeader)
110 | let passwordSection = SectionFormer(rowFormer: passwordRow)
111 | .set(headerViewFormer: createSpaceHeader())
112 | let loginSection = SectionFormer(rowFormer: loginRow)
113 | .set(headerViewFormer: createSpaceHeader())
114 |
115 | former.append(sectionFormer: idSection, passwordSection, loginSection)
116 | }
117 |
118 | private func switchLoginRow() {
119 | let enabled = !(idRow?.text?.isEmpty ?? true) &&
120 | !(passwordRow?.text?.isEmpty ?? true)
121 | loginRow?.enabled = enabled
122 | }
123 |
124 | @IBAction func tapBackground(sender: UIControl) {
125 | dismiss(animated: true, completion: nil)
126 | }
127 | }
128 |
129 | extension LoginViewController: UIViewControllerTransitioningDelegate {
130 |
131 | func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
132 | return FadeTransitionAnimator()
133 | }
134 |
135 | func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
136 | return FadeTransitionAnimator(forwardTransition: false)
137 | }
138 | }
139 |
140 | private final class FadeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
141 |
142 | var forwardTransition = true
143 |
144 | init(forwardTransition: Bool = true) {
145 | super.init()
146 | self.forwardTransition = forwardTransition
147 | }
148 |
149 | @objc func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
150 | return 0.5
151 | }
152 |
153 | @objc func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
154 | guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
155 | let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
156 | else { return }
157 |
158 | #if swift(>=2.3)
159 | let containerView = transitionContext.containerView
160 | #else
161 | let containerView = transitionContext.containerView!
162 | #endif
163 |
164 | let duration = transitionDuration(using: transitionContext)
165 |
166 | if forwardTransition {
167 | containerView.addSubview(toVC.view)
168 | UIView.animate(withDuration: duration, delay: 0,
169 | usingSpringWithDamping: 1, initialSpringVelocity: 0,
170 | options: .beginFromCurrentState,
171 | animations: {
172 | toVC.view.alpha = 0
173 | toVC.view.alpha = 1
174 | }) { _ in
175 | transitionContext.completeTransition(true)
176 | }
177 | } else {
178 | UIView.animate(withDuration: duration, delay: 0,
179 | usingSpringWithDamping: 1, initialSpringVelocity: 0,
180 | options: .beginFromCurrentState,
181 | animations: {
182 | fromVC.view.alpha = 0
183 | }) { _ in
184 | transitionContext.completeTransition(true)
185 | }
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/Former/RowFormers/InlinePickerRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InlinePickerRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 8/2/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol InlinePickerFormableRow: FormableRow {
12 |
13 | func formTitleLabel() -> UILabel?
14 | func formDisplayLabel() -> UILabel?
15 | }
16 |
17 | open class InlinePickerItem: PickerItem {
18 |
19 | public let displayTitle: NSAttributedString?
20 | public init(title: String, displayTitle: NSAttributedString? = nil, value: S? = nil) {
21 | self.displayTitle = displayTitle
22 | super.init(title: title, value: value)
23 | }
24 | }
25 |
26 | open class InlinePickerRowFormer
27 | : BaseRowFormer, Formable, ConfigurableInlineForm where T: InlinePickerFormableRow {
28 |
29 | // MARK: Public
30 |
31 | public typealias InlineCellType = FormPickerCell
32 |
33 | public let inlineRowFormer: RowFormer
34 | override open var canBecomeEditing: Bool {
35 | return enabled
36 | }
37 |
38 | open var pickerItems: [InlinePickerItem] = []
39 | open var selectedRow: Int = 0
40 | open var titleDisabledColor: UIColor? = .lightGray
41 | open var displayDisabledColor: UIColor? = .lightGray
42 | open var titleEditingColor: UIColor?
43 | open var displayEditingColor: UIColor?
44 |
45 | required public init(
46 | instantiateType: Former.InstantiateType = .Class,
47 | cellSetup: ((T) -> Void)?) {
48 | inlineRowFormer = PickerRowFormer(instantiateType: .Class)
49 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
50 | }
51 |
52 | @discardableResult
53 | public final func onValueChanged(_ handler: @escaping ((InlinePickerItem) -> Void)) -> Self {
54 | onValueChanged = handler
55 | return self
56 | }
57 |
58 | public final func onEditingBegin(handler: @escaping ((InlinePickerItem, T) -> Void)) -> Self {
59 | onEditingBegin = handler
60 | return self
61 | }
62 |
63 | public final func onEditingEnded(handler: @escaping ((InlinePickerItem, T) -> Void)) -> Self {
64 | onEditingEnded = handler
65 | return self
66 | }
67 |
68 | open override func update() {
69 | super.update()
70 |
71 | let titleLabel = cell.formTitleLabel()
72 | let displayLabel = cell.formDisplayLabel()
73 | if pickerItems.isEmpty {
74 | displayLabel?.text = ""
75 | } else {
76 |
77 | // Sets selected row to 0 to avoid 'index out of range' error. This is in case the updated picker items array count
78 | // is less than the prior array count.
79 | if pickerItems.count <= selectedRow {
80 | selectedRow = 0
81 | }
82 |
83 | displayLabel?.text = pickerItems[selectedRow].title
84 | _ = pickerItems[selectedRow].displayTitle.map { displayLabel?.attributedText = $0 }
85 | }
86 |
87 | if enabled {
88 | if isEditing {
89 | if titleColor == nil { titleColor = titleLabel?.textColor }
90 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
91 |
92 | if pickerItems[selectedRow].displayTitle == nil {
93 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
94 | _ = displayEditingColor.map { displayLabel?.textColor = $0 }
95 | }
96 | } else {
97 | _ = titleColor.map { titleLabel?.textColor = $0 }
98 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
99 | titleColor = nil
100 | displayTextColor = nil
101 | }
102 | } else {
103 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
104 | titleLabel?.textColor = titleDisabledColor
105 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
106 | displayLabel?.textColor = displayDisabledColor
107 | }
108 |
109 | let inlineRowFormer = self.inlineRowFormer as! PickerRowFormer
110 | inlineRowFormer.configure {
111 | $0.pickerItems = pickerItems
112 | $0.selectedRow = selectedRow
113 | $0.enabled = enabled
114 | }.onValueChanged(valueChanged).update()
115 | }
116 |
117 | open override func cellSelected(indexPath: IndexPath) {
118 | former?.deselect(animated: true)
119 | }
120 |
121 | public func editingDidBegin() {
122 | if enabled {
123 | let titleLabel = cell.formTitleLabel()
124 | let displayLabel = cell.formDisplayLabel()
125 |
126 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
127 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
128 |
129 | if pickerItems[selectedRow].displayTitle == nil {
130 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
131 | _ = displayEditingColor.map { displayLabel?.textColor = $0 }
132 | }
133 | isEditing = true
134 | }
135 | onEditingBegin?(pickerItems[selectedRow], cell)
136 | }
137 |
138 | public func editingDidEnd() {
139 | isEditing = false
140 | let titleLabel = cell.formTitleLabel()
141 | let displayLabel = cell.formDisplayLabel()
142 |
143 | if enabled {
144 | _ = titleColor.map { titleLabel?.textColor = $0 }
145 | titleColor = nil
146 |
147 | if pickerItems[selectedRow].displayTitle == nil {
148 | _ = displayTextColor.map { displayLabel?.textColor = $0 }
149 | }
150 | displayTextColor = nil
151 | } else {
152 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
153 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
154 | titleLabel?.textColor = titleDisabledColor
155 | displayLabel?.textColor = displayDisabledColor
156 | }
157 | onEditingEnded?(pickerItems[selectedRow], cell)
158 | }
159 |
160 | // MARK: Private
161 |
162 | private final var onValueChanged: ((InlinePickerItem) -> Void)?
163 | private final var onEditingBegin: ((InlinePickerItem, T) -> Void)?
164 | private final var onEditingEnded: ((InlinePickerItem, T) -> Void)?
165 | private final var titleColor: UIColor?
166 | private final var displayTextColor: UIColor?
167 |
168 | private func valueChanged(pickerItem: PickerItem) {
169 | if enabled {
170 | let inlineRowFormer = self.inlineRowFormer as! PickerRowFormer
171 | let inlinePickerItem = pickerItem as! InlinePickerItem
172 | let displayLabel = cell.formDisplayLabel()
173 |
174 | selectedRow = inlineRowFormer.selectedRow
175 | displayLabel?.text = inlinePickerItem.title
176 | if let displayTitle = inlinePickerItem.displayTitle {
177 | displayLabel?.attributedText = displayTitle
178 | } else {
179 | if displayTextColor == nil { displayTextColor = displayLabel?.textColor ?? .black }
180 | _ = displayEditingColor.map { displayLabel?.textColor = $0 }
181 | }
182 | onValueChanged?(inlinePickerItem)
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Former/RowFormers/TextViewRowFormer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextViewRowFormer.swift
3 | // Former-Demo
4 | //
5 | // Created by Ryo Aoyama on 7/28/15.
6 | // Copyright © 2015 Ryo Aoyama. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol TextViewFormableRow: FormableRow {
12 |
13 | func formTitleLabel() -> UILabel?
14 | func formTextView() -> UITextView
15 | }
16 |
17 | open class TextViewRowFormer
18 | : BaseRowFormer, Formable where T: TextViewFormableRow {
19 |
20 | // MARK: Public
21 |
22 | override open var canBecomeEditing: Bool {
23 | return enabled
24 | }
25 |
26 | open var text: String?
27 | open var placeholder: String?
28 | open var attributedPlaceholder: NSAttributedString?
29 | open var textDisabledColor: UIColor? = .lightGray
30 | open var titleDisabledColor: UIColor? = .lightGray
31 | open var titleEditingColor: UIColor?
32 |
33 | public required init(instantiateType: Former.InstantiateType = .Class, cellSetup: ((T) -> Void)? = nil) {
34 | super.init(instantiateType: instantiateType, cellSetup: cellSetup)
35 | }
36 |
37 | deinit {
38 | cell.formTextView().delegate = nil
39 | }
40 |
41 | @discardableResult
42 | public final func onTextChanged(_ handler: @escaping ((String) -> Void)) -> Self {
43 | onTextChanged = handler
44 | return self
45 | }
46 |
47 | open override func initialized() {
48 | super.initialized()
49 | rowHeight = 110
50 | }
51 |
52 | open override func cellInitialized(_ cell: T) {
53 | cell.formTextView().delegate = observer
54 | }
55 |
56 | open override func update() {
57 | super.update()
58 |
59 | cell.selectionStyle = .none
60 | let textView = cell.formTextView()
61 | let titleLabel = cell.formTitleLabel()
62 | textView.text = text
63 | textView.isUserInteractionEnabled = false
64 |
65 | if placeholderLabel == nil {
66 | let placeholderLabel = UILabel()
67 | placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
68 | textView.insertSubview(placeholderLabel, at: 0)
69 | self.placeholderLabel = placeholderLabel
70 | let constraints = [
71 | NSLayoutConstraint.constraints(
72 | withVisualFormat: "V:|-8-[label(>=0)]",
73 | options: [],
74 | metrics: nil,
75 | views: ["label": placeholderLabel]
76 | ),
77 | NSLayoutConstraint.constraints(
78 | withVisualFormat: "H:|-5-[label]-0-|",
79 | options: [],
80 | metrics: nil,
81 | views: ["label": placeholderLabel]
82 | )
83 | ].flatMap { $0 }
84 | textView.addConstraints(constraints)
85 | }
86 | _ = placeholder.map { placeholderLabel?.text = $0 }
87 | if let attributedPlaceholder = attributedPlaceholder {
88 | placeholderLabel?.text = nil
89 | placeholderLabel?.attributedText = attributedPlaceholder
90 | }
91 | updatePlaceholderColor(text: textView.text)
92 |
93 | if enabled {
94 | if isEditing {
95 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
96 | _ = titleEditingColor.map { titleLabel?.textColor = $0 }
97 | } else {
98 | _ = titleColor.map { titleLabel?.textColor = $0 }
99 | titleColor = nil
100 | }
101 | _ = textColor.map { textView.textColor = $0 }
102 | textColor = nil
103 | } else {
104 | if titleColor == nil { titleColor = titleLabel?.textColor ?? .black }
105 | if textColor == nil { textColor = textView.textColor ?? .black }
106 | titleLabel?.textColor = titleDisabledColor
107 | textView.textColor = textDisabledColor
108 | }
109 | }
110 |
111 | open override func cellSelected(indexPath: IndexPath) {
112 | let textView = cell.formTextView()
113 | textView.becomeFirstResponder()
114 | textView.isUserInteractionEnabled = enabled
115 | }
116 |
117 | // MARK: Private
118 |
119 | fileprivate final var onTextChanged: ((String) -> Void)?
120 | fileprivate final var textColor: UIColor?
121 | fileprivate final var titleColor: UIColor?
122 | fileprivate final var _attributedString: NSAttributedString?
123 | fileprivate final weak var placeholderLabel: UILabel?
124 | fileprivate final lazy var observer: Observer = Observer(textViewRowFormer: self)
125 |
126 | fileprivate final func updatePlaceholderColor(text: String?) {
127 | if attributedPlaceholder == nil {
128 | placeholderLabel?.textColor = (text?.isEmpty ?? true) ?
129 | UIColor(red: 0, green: 0, blue: 0.098 / 255, alpha: 0.22) :
130 | .clear
131 | } else {
132 | if text?.isEmpty ?? true {
133 | _ = _attributedString.map { placeholderLabel?.attributedText = $0 }
134 | _attributedString = nil
135 | } else {
136 | if _attributedString == nil { _attributedString = placeholderLabel?.attributedText }
137 | placeholderLabel?.attributedText = nil
138 | }
139 | }
140 | }
141 | }
142 |
143 | private class Observer:
144 | NSObject, UITextViewDelegate where T: TextViewFormableRow {
145 |
146 | fileprivate weak var textViewRowFormer: TextViewRowFormer?
147 |
148 | init(textViewRowFormer: TextViewRowFormer) {
149 | self.textViewRowFormer = textViewRowFormer
150 | }
151 |
152 | fileprivate dynamic func textViewDidChange(_ textView: UITextView) {
153 | guard let textViewRowFormer = textViewRowFormer else { return }
154 | if textViewRowFormer.enabled {
155 | let text = textView.text ?? ""
156 | textViewRowFormer.text = text
157 | textViewRowFormer.onTextChanged?(text)
158 | textViewRowFormer.updatePlaceholderColor(text: text)
159 | }
160 | }
161 |
162 | fileprivate dynamic func textViewDidBeginEditing(_ textView: UITextView) {
163 | guard let textViewRowFormer = textViewRowFormer else { return }
164 | if textViewRowFormer.enabled {
165 | let titleLabel = textViewRowFormer.cell.formTitleLabel()
166 | if textViewRowFormer.titleColor == nil {
167 | textViewRowFormer.titleColor = titleLabel?.textColor ?? .black
168 | }
169 | _ = textViewRowFormer.titleEditingColor.map { titleLabel?.textColor = $0 }
170 | textViewRowFormer.isEditing = true
171 | }
172 | }
173 |
174 | fileprivate dynamic func textViewDidEndEditing(_ textView: UITextView) {
175 | guard let textViewRowFormer = textViewRowFormer else { return }
176 | let titleLabel = textViewRowFormer.cell.formTitleLabel()
177 | textViewRowFormer.cell.formTextView().isUserInteractionEnabled = false
178 |
179 | if textViewRowFormer.enabled {
180 | _ = textViewRowFormer.titleColor.map { titleLabel?.textColor = $0 }
181 | textViewRowFormer.titleColor = nil
182 | } else {
183 | if textViewRowFormer.titleColor == nil {
184 | textViewRowFormer.titleColor = titleLabel?.textColor ?? .black
185 | }
186 | titleLabel?.textColor = textViewRowFormer.titleDisabledColor
187 | }
188 | textViewRowFormer.isEditing = false
189 | }
190 | }
191 |
--------------------------------------------------------------------------------