BxInputController
This framework will help iOS developers for simplify development general inputing controllers. Holds a ready-made solutions, such as standard solutions in Apple applications also have custom design such as a photo library, a choice of variant or suggestion. If you need custom inputting view (for example calendar for date putting) then you may add new rows or edit or inherited current rows. The component is limited only to the implementation of the paradigm, it essentially involves the use of S.O.L.I.D. approach. An important limitation is the compulsory use of Interface Builder to implement UI (xib or storyboard, but not from code).
Gif demo
Features
- Encapsulation UITableView DataSource/Delegate/Scrolling and other
- Automatical to register resources (xib, pdf, etc) for cells/headers/footer
- Library general types for inputting: string, date, value object (variant), pictures, rating, boolean
- Two style inputting: from keyboard or from selector
- Easy to use current solution and make a custom
- S.O.L.I.D. decision: binding View with data models
- UI creating from Interface Builder. Supporting xib and storyboards.
- Has settings for local or global change design and logic.
Requirements
- iOS 8.0+ : iOS 8.x/9.x/10.x/11.x/12.x/13.x
- Swift 3.0+ : Swift 3.2/4.x/5.x supported
Installation
CocoaPods
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapodsNow you can use seporated subpods BxInputController/Common and BxInputController/Photo, or all BxInputController.
BxInputController/Photo pod you can reject if you won't show Photo Library request in App.
To integrate BxInputController into your Xcode project using CocoaPods, specify it in your Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
target '<Your Target Name>' do
pod 'BxInputController/Common', '~> 2.7.4'
pod 'BxInputController/Photo', '~> 2.7.4'
endThen, run the following command:
$ pod installSwift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but BxInputController does support its use on supported platforms.
Once you have your Swift package set up, adding BxInputController as a dependency is as easy as adding it to the dependencies value of your Package.swift.
dependencies: [
.Package(url: "https://github.com/ByteriX/BxInputController.git", majorVersion: 1)
]Manually
If you prefer not to use either of the aforementioned dependency managers, you can integrate BxInputController into your project manually.
Embedded Framework
- Open up Terminal,
cdinto your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
$ git init- Add
BxInputControlleras a git submodule by running the following command:
$ git submodule add https://github.com/ByteriX/BxInputController.git-
Add all sources and resources from local copy of
BxInputControllerto the build phase of the project. -
And that's it!
Usage
Example for Gif Demo
class SimpleAllRowsController: BxInputController {
// Action
private let stringActionRow = BxInputActionStringRow(title: "string action")
private let customActionRow = BxInputActionCustomRow<BxInputActionStringRow>(title: "custom action")
// Boolean
private let switchRow = BxInputSwitchRow(title: "boolean switch", value: true)
private let checkRow = BxInputCheckRow(title: "boolean check", placeholder: "selected value")
// Date
private let dateRow = BxInputDateRow(title: "date", value: Date().addingTimeInterval(900000))
private let selectorDateRow = BxInputSelectorDateRow(title: "selector date")
// Pictures
private let selectorPicturesRow = BxInputSelectorPicturesRow(title: "selector pictures")
// Rate
private let rateRow = BxInputRateRow(title: "rate", maxValue: 10)
// Suggestions
private let selectorSuggestionsRow = BxInputSelectorSuggestionsRow<BxInputSelectorSuggestionsItemRow>(title: "selector suggestions")
// Text
private let shortTextRow = BxInputTextRow(title: "text value", placeholder: "short text")
private let selectorTextRow = BxInputSelectorTextRow(title: "text with selector", placeholder: "longest text")
// Variant
private let variantRow = BxInputVariantRow<BxInputVariantItem>(title: "variant")
private let selectorVariantRow = BxInputSelectorVariantRow<BxInputVariantItem>(title: "selector variant")
private let variantItems : [BxInputVariantItem] = [
BxInputVariantItem(id: "1", name: "value1"),
BxInputVariantItem(id: "2", name: "value2"),
BxInputVariantItem(id: "3", name: "value3"),
BxInputVariantItem(id: "4", name: "value4"),
]
private let suggestionItems = [
BxInputSelectorSuggestionsItemRow(title: "value 1"),
BxInputSelectorSuggestionsItemRow(title: "value 2"),
BxInputSelectorSuggestionsItemRow(title: "value 3"),
BxInputSelectorSuggestionsItemRow(title: "value 4"),
BxInputSelectorSuggestionsItemRow(title: "value 5")
]
override func viewDidLoad() {
super.viewDidLoad()
isEstimatedContent = false
stringActionRow.handler = {[weak self] (actionRow) -> Void in
guard let this = self else {
return
}
this.stringActionRow.value = "changed"
this.updateRow(this.stringActionRow)
}
stringActionRow.isImmediatelyDeselect = true
customActionRow.handler = {[weak self] (actionRow) -> Void in
guard let this = self else {
return
}
this.customActionRow.value = this.stringActionRow
this.updateRow(this.customActionRow)
}
customActionRow.isImmediatelyDeselect = true
selectorSuggestionsRow.children = suggestionItems
variantRow.items = variantItems
selectorVariantRow.items = variantItems
self.sections = [
BxInputSection(headerText: "Action", rows: [stringActionRow, customActionRow]),
BxInputSection(headerText: "Boolean", rows: [switchRow, checkRow]),
BxInputSection(headerText: "Date", rows: [dateRow, selectorDateRow]),
BxInputSection(headerText: "Pictures", rows: [selectorPicturesRow]),
BxInputSection(headerText: "Rate", rows: [rateRow]),
BxInputSection(headerText: "Suggestions", rows: [selectorSuggestionsRow]),
BxInputSection(headerText: "Text", rows: [shortTextRow, selectorTextRow]),
BxInputSection(headerText: "Variant", rows: [variantRow, selectorVariantRow])
]
}
}
Settings of BxInputController
You can use global settings, rewrited BxInputSettings.standart values, also you can make your instanse BxInputSettings and assigne its to BxInputController instanse by settings property.
Example for global changing
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
BxInputSettings.standart.valueFont = UIFont.boldSystemFont(ofSize: 17)
BxInputSettings.standart.titleFont = UIFont.systemFont(ofSize: 17)
BxInputSettings.standart.titleColor = UIColor.brown
BxInputSettings.standart.valueColor = UIColor.black
BxInputSettings.standart.placeholderColor = UIColor.red
BxInputSettings.standart.footerFont = UIFont.boldSystemFont(ofSize: 18)
BxInputSettings.standart.footerColor = UIColor.gray
BxInputSettings.standart.headerFont = UIFont.boldSystemFont(ofSize: 18)
BxInputSettings.standart.headerColor = UIColor.gray
BxInputSettings.standart.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return true
}
Get values from inputed
All Rows classes (which implement protocol BxInputRow) should encapsulate value for inputting, so it makes easy accessing possible. See for example.
Value access example
class InputController: BxInputController {
private var email = BxInputTextRow(title: "email value", value: "")
private var birthdayDate = BxInputDateRow(title: "birthday", placeholder: "YOUR BIRTHDAY")
private var selectedVariant = BxInputSelectorVariantRow<BxInputVariantItem>(title: "selected",
placeholder: "SELECT")
override func viewDidLoad() {
super.viewDidLoad()
selectedVariant.items = [
BxInputVariantItem(id: "1", name: "value1"),
BxInputVariantItem(id: "2", name: "value2"),
BxInputVariantItem(id: "3", name: "value3")
]
self.sections = [
BxInputSection(rows: [email, birthdayDate, selectedVariant])
]
}
func showInputedValues() {
print(email.value)
print(birthdayDate.value)
print(selectedVariant.selectedVariant.name)
}
}
Access rows
You can manage access with the help of property isEnabled from rows object, but it is not good idea, better use BxInputController method setEnabledRow for that, because this method encapsulate updating cells of row and its children.
Example using setEnabledRow
class InputController: BxInputController {
private var emailRow = BxInputTextRow(title: "email value", value: "")
override func viewDidLoad() {
super.viewDidLoad()
self.sections = [
BxInputSection(rows: [emailRow])
]
}
func changeAccess() {
setEnabledRow(emailRow, enabled: !emailRow.isEnabled, with: .fade)
}
}Change rows and sections
Also you can add, remove, reload or just updating rows or sections. There operation have posibility execution with animation or without that.
Example using deleteRow/addRow
class EnabledAllRowsController: SimpleAllRowsController {
private var choosePhoneRow = BxInputCheckRow(title: "use phone without email")
private var emailRow = BxInputTextRow(title: "email", value: "")
private var phoneRow = BxInputTextRow(title: "phone", value: "")
override func viewDidLoad() {
super.viewDidLoad()
self.sections = [
BxInputSection(rows: [choosePhoneRow, emailRow])
]
}
override func didChangeValue(for row: BxInputValueRow)
{
if row === choosePhoneRow {
if choosePhoneRow.value {
deleteRow(emailRow)
addRow(phoneRow, after: choosePhoneRow, with: .top)
} else {
deleteRow(phoneRow, with: .bottom)
addRow(emailRow, after: choosePhoneRow)
}
}
}
}Checking of value
If you need marked row, when it has incorrect value you can use BxInputChecker and BxInputRowDecorator inherited classes for this case. Just make expected use case on this subclasses and call addChecker from BxInputController when row is had in controller then row will be marked according to checking priority which you can choose (see BxInputRowCheckerPriority). If you would initialize chacking you need call checkAllRows param willSelect can execute scroll to first misstaken row.
Example for checking
override func viewDidLoad() {
super.viewDidLoad()
self.sections = [BxInputSection(rows: [emailRow, confirmEmailRow])]
addChecker(BxInputEmptyValueChecker<BxInputTextRow>(row: emailRow, placeholder: "Please put your email"), for: emailRow)
let emailChecker = BxInputEmailChecker<BxInputTextRow>(row: emailRow, subtitle: "incorrect email")
emailChecker.planPriority = .always
addChecker(emailChecker, for: emailRow)
addChecker(BxInputEqualValuesChecker<BxInputTextRow>(row: confirmEmailRow, comparisonRow: emailRow, subtitle: "Email and Confirm fields have different values"), for: confirmEmailRow)
}
Showing Section from different resourses
You can use different init for BxInputSection header/footer creation. It maught text, view, or custom BxInputSectionViewContent.
Other table transformation
You can scroll to needed row with method scrollRow.
If you need select (or activate) row, you may call selectRow.
For closing all selectors or/and keyboard you may call dissmissSelectors or dissmissAllRows.
table has Event of change value in overrided method didChangeValue(for row) in your custom class inherited from BxInputController (see last example).
Paradigm
Common
Well I have solved create myself solution, abstracting from the conventional concepts, because I have highly specialized task. I think now it is complex solution, please see UML class diagrams below. I used to use classical MVC paradigm in the 1th version. The problem of 1th version: hard linking View with Model, I solved this problem with helping interlayer Binder between View and Model. In the 2th I try to use complex solution basesd from mainstreams for iOS: MVVM, VIPER. The main problem classical paradigm is a hard link controller-View anyway.
Description with existing paradigmes
BxInputController is a UIViewController with the Facade functions, so it may has "presenter" name in VIPER. It manages all ViewModels objects that inherited from BxInputBaseSectionContentBinder, BxInputBaseRowBinder classes. ViewModels have back-reference with BxInputController, because they have the single field of a responsibility drawing UI and may has "interactor" name in VIPER.
Passive Model is objects inherited from BxInputSection with BxInputSectionContent and BxInputRow. It has any information about style of UI and passive data ("Entity" in VIPER). You can use prepared solutions or create yourself implementation, that have to make flexible behavior for this component. BxInputSection encapsulated all BxInputSectionContent and BxInputRow is a source of material for building UI and getting of inputed data. Its are a protocol oriented thin objects.
Relationships BxInputRow and BxInputSectionContent with UI in a concrete implementation may be shown as Model-ViewModel binding as in MVVM. This function have BxInputSectionContentBinder and BxInputRowBinder protocols, which implementation should use template classes.
BxInputSettings implements appearance pattern. All easy.
So actually it looks like MVVM paradigm, but I like naming this lite-VIPER:
Simple & Selector
Let's see at different implementations of BxInputRow, for represent of simple and selector row types. We will see only text rows for example. In yellow area are shown the selector type classes, the classes as BxInputTextRow and BxInputStandartTextCell is concrete implementation of simple type of putting text (please see UML class diagrams below). With simple type is all clear, let's see BxInputSelectorRow and BxInputChildSelectorRow, that is protocols of rows with selector. They have relationship one-to-many. Actually ViewModel for BxInputSelectorRow (for this example it is BxInputSelectorRowBinder) has responsibility of managing of itself children rows. When you select cell for this RowBinder, it should add new row BxInputChildSelectorTextRow with help parent (this is protocol of BxInputController), which presents all cells from rows. If you select again then cell should be closed, and children rows should be removed from BxInputController. In general children is many of objects, but for our example it is single object of the class BxInputChildSelectorTextRow. That class does not necessarily have to be public, because it is fully encapsulated in the parent class BxInputSelectorTextRow.
License
BxInputController is released under the MIT license. See LICENSE for details.



