BxInputController
此框架将帮助 iOS 开发者简化通用输入控制器开发。它包含了一些现成的解决方案,如苹果应用程序中的标准解决方案,以及自定义设计,如照片库、选择器或建议。如果您需要自定义输入视图(例如日期输入的日历),则可以添加新行、编辑或继承当前行。该组件仅限于实现范式,实质上涉及使用 S.O.L.I.D. 方法。一个重要限制是必须使用 Interface Builder(xib 或 storyboard,但不能从代码)来实现 UI。
Gif 演示
特性
- 封装UITableView DataSource/Delegate/Scrolling和其他功能
- 自动注册资源(xib, pdf等)为单元格/头部/底部
- 通用输入类型:字符串、日期、值对象(变体)、图片、评分、布尔值
- 两种输入风格:从键盘或从选择器
- 易于使用现有解决方案并定制个性化
- S.O.L.I.D.原则:绑定视图与数据模型
- 通过Interface Builder创建UI。支持xib和storyboards。
- 提供本地或全局更改设计和逻辑的设置。
需求
- 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支持
安装
CocoaPods
CocoaPods是一个Cocoa项目的依赖管理器。您可以使用以下命令安装它
$ gem install cocoapods
现在您可以单独使用子库BxInputController/Common
和BxInputController/Photo
,或使用全部的BxInputController
。如果您不希望在应用中请求相册权限,可以拒绝使用BxInputController/Photo
。要使用CocoaPods将BxInputController集成到Xcode项目中,请在您的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
然后,运行以下命令
$ pod install
Swift 包管理器
Swift 包管理器是一种用于自动化 Swift 代码分发的工具,并集成到 swift
编译器中。它目前处于早期开发阶段,但 BxInputController 支持其在支持平台上的使用。
设置好您的 Swift 包后,将 BxInputController 添加为依赖项,就像将其添加到您的 Package.swift
文件的 dependencies
值中一样简单。
dependencies: [
.Package(url: "https://github.com/ByteriX/BxInputController.git", majorVersion: 1)
]
手动方式
如果您不希望使用上述任何依赖管理器,可以将 BxInputController
手动集成到项目中。
嵌入式框架
- 打开终端,使用
cd
命令进入顶级项目目录,然后运行以下命令(如果您的项目不是作为 git 仓库初始化)
$ git init
- 通过运行以下命令将
BxInputController
添加为 git 子模块
$ git submodule add https://github.com/ByteriX/BxInputController.git
-
将
BxInputController
本地的所有源代码和资源添加到项目的构建阶段。 -
就是如此!
使用方法
gif演示
的示例
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])
]
}
}
BxInputController 设置
您可以使用全局设置,改写 BxInputSettings.standart
值,也可以创建自己的 BxInputSettings
实例,并通过 settings
属性将其分配给 BxInputController
实例。
全局更改的示例
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
}
从输入获取值
所有Rows类(实现协议BxInputRow
)应封装输入值,以便可轻松访问。例如参见。
值访问示例
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)
}
}
访问行
您可以使用行对象的属性 isEnabled
来管理访问,但这并不是一个好主意,更好的方式是使用 BxInputController
的 setEnabledRow
方法,因为这个方法封装了更新行及其子元素单元格的操作。
使用 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)
}
}
更改行和部分
您还可以添加、移除、重新加载或仅更新行或部分。这些操作可以选择带有动画或无动画方式执行。
使用 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)
}
}
}
}
检查值
如果您需要标记行,当它有错误值时,您可以使用继承自这种情况的 BxInputChecker
和 BxInputRowDecorator
类。只需在这些子类上进行预期的使用,当行被 BxInputController
控制时,调用 addChecker
,则行将根据您选择的检查优先级(见 BxInputRowCheckerPriority
)进行标记。如果您要初始化检查,则需要调用 checkAllRows
参数,willSelect
可以执行滚动到第一个错误行。
检查示例
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)
}
从不同资源显示部分
您可以使用不同的 init
为 BxInputSection
创建头部/尾部。它可以接受文本、视图或自定义 BxInputSectionViewContent
。
其他表格转换
您可以使用 scrollRow
方法滚动到所需的行。如果您需要选择(或激活)行,您可以调用 selectRow
。对于关闭所有选择器或/和键盘,您可以调用 dissmissSelectors
或 dissmissAllRows
。表格在您的自定义类继承自 BxInputController
的重写方法 didChangeValue(for row)
中具有更改值的事件(见最后一个示例)。
范型
通用
我已解决创建自己的解决方案,与传统概念抽象,因为我有高度专门的任务。现在,我认为这是一个复杂的解决方案,请参见下面的UML类图。我曾在第1版中使用了经典MVC范式。第1版的问题:难于将View与Model关联,我通过在View和Model之间帮助中间层Binder来解决此问题。在第2版中,我试图使用基于主流iOS的复杂解决方案:MVVM,VIPER。经典范式的主要问题是无论如何都会有控制器-View硬连接。
与现有范式的描述
BxInputController
是一个具有外观函数的UIViewController,因此它可能在VIPER中具有“presenter”名称。它管理所有继承自BxInputBaseSectionContentBinder
和BxInputBaseRowBinder
类的基本ViewModel对象。ViewModels与BxInputController
具有回参照,因为它们有一个绘制UI的单个责任字段,并且在VIPER中可能有“interactor”名称。
被动模型是继承自BxInputSection
具有BxInputSectionContent
和BxInputRow
的对象。它包含有关UI样式的任何信息以及被动数据(在VIPER中为“实体”)。您可以使用预先准备好的解决方案或创建自己的实现,这些实现必须使此类组件具有灵活的行为。BxInputSection
封装了所有BxInputSectionContent
,而BxInputRow
是构建UI的素材来源和获取输入数据的途径。它们是协议驱动的瘦对象。
在具体实现中,BxInputRow
和BxInputSectionContent
与UI的关系可能表现为MVVM中的Model-ViewModel绑定。此功能具有BxInputSectionContentBinder
和BxInputRowBinder
协议,实现应使用模板类。
BxInputSettings
实现了外观模式。很简单。所以实际上它看起来像是MVVM范式,但我更喜欢将其命名为轻量级VIPER。
简单 & 选择器
让我们来看看不同类型的 BxInputRow
的实现,用于表示简单和选择行类型。例如,我们只看到文本行。在黄色区域显示了选择类型类,例如 BxInputTextRow
和 BxInputStandartTextCell
是简单类型放置文本的具体实现(请参见下面的UML类图)。简单类型都很清晰,让我们看看 BxInputSelectorRow
和 BxInputChildSelectorRow
,它们是具有选择器的行协议。它们是一对多关系。实际上,为 BxInputSelectorRow
(在这个例子中是 BxInputSelectorRowBinder
)的 ViewModel 负责管理自己的 子
行。当你选择该 RowBinder
的单元格时,它应该使用 父
(这是 BxInputController
的协议)通过帮助添加新的 BxInputChildSelectorTextRow
行。如果再次选择,则应该关闭单元格,并从 BxInputController
中删除 子
行。一般来说,子
是许多对象,但在这个例子中它是一个 BxInputChildSelectorTextRow
类的单一对象。这个类不一定必须是公共的,因为它完全封装在父类 BxInputSelectorTextRow
中。
许可证
BxInputController
采用 MIT 许可证发布。有关详细信息,请参阅 LICENSE。