├── .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 | --------------------------------------------------------------------------------