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).
- 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.
- 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
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapods
Now 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'
end
Then, run the following command:
$ pod install
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)
]
If you prefer not to use either of the aforementioned dependency managers, you can integrate BxInputController
into your project manually.
- Open up Terminal,
cd
into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
$ git init
- Add
BxInputController
as 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
BxInputController
to the build phase of the project. -
And that's it!
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])
]
}
}
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.
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
}
All Rows classes (which implement protocol BxInputRow
) should encapsulate value for inputting, so it makes easy accessing possible. See for 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)
}
}
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.
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)
}
}
Also you can add, remove, reload or just updating rows or sections. There operation have posibility execution with animation or without that.
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)
}
}
}
}
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.
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)
}
You can use different init
for BxInputSection
header/footer creation. It maught text, view, or custom BxInputSectionViewContent
.
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).
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.
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:
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
.
BxInputController is released under the MIT license. See LICENSE for details.