BxInputController 2.22

BxInputController 2.22

测试已测试
语言 SwiftSwift
许可证 MIT
发布最新发布2023 年 9 月
SPM支持 SPM

Sergey Balalaev 维护。



  • Sergey Balalaev

BxInputController

此框架将帮助 iOS 开发者简化通用输入控制器开发。它包含了一些现成的解决方案,如苹果应用程序中的标准解决方案,以及自定义设计,如照片库、选择器或建议。如果您需要自定义输入视图(例如日期输入的日历),则可以添加新行、编辑或继承当前行。该组件仅限于实现范式,实质上涉及使用 S.O.L.I.D. 方法。一个重要限制是必须使用 Interface Builder(xib 或 storyboard,但不能从代码)来实现 UI。

Gif 演示

Demo 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/CommonBxInputController/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 来管理访问,但这并不是一个好主意,更好的方式是使用 BxInputControllersetEnabledRow 方法,因为这个方法封装了更新行及其子元素单元格的操作。

使用 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)
            }
        }
    }
}

检查值

如果您需要标记行,当它有错误值时,您可以使用继承自这种情况的 BxInputCheckerBxInputRowDecorator 类。只需在这些子类上进行预期的使用,当行被 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)
}

从不同资源显示部分

您可以使用不同的 initBxInputSection 创建头部/尾部。它可以接受文本、视图或自定义 BxInputSectionViewContent

其他表格转换

您可以使用 scrollRow 方法滚动到所需的行。如果您需要选择(或激活)行,您可以调用 selectRow。对于关闭所有选择器或/和键盘,您可以调用 dissmissSelectorsdissmissAllRows。表格在您的自定义类继承自 BxInputController 的重写方法 didChangeValue(for row) 中具有更改值的事件(见最后一个示例)。

范型

通用

我已解决创建自己的解决方案,与传统概念抽象,因为我有高度专门的任务。现在,我认为这是一个复杂的解决方案,请参见下面的UML类图。我曾在第1版中使用了经典MVC范式。第1版的问题:难于将View与Model关联,我通过在View和Model之间帮助中间层Binder来解决此问题。在第2版中,我试图使用基于主流iOS的复杂解决方案:MVVM,VIPER。经典范式的主要问题是无论如何都会有控制器-View硬连接。

Common paradigm

与现有范式的描述

BxInputController是一个具有外观函数的UIViewController,因此它可能在VIPER中具有“presenter”名称。它管理所有继承自BxInputBaseSectionContentBinderBxInputBaseRowBinder类的基本ViewModel对象。ViewModels与BxInputController具有回参照,因为它们有一个绘制UI的单个责任字段,并且在VIPER中可能有“interactor”名称。

被动模型是继承自BxInputSection具有BxInputSectionContentBxInputRow的对象。它包含有关UI样式的任何信息以及被动数据(在VIPER中为“实体”)。您可以使用预先准备好的解决方案或创建自己的实现,这些实现必须使此类组件具有灵活的行为。BxInputSection封装了所有BxInputSectionContent,而BxInputRow是构建UI的素材来源和获取输入数据的途径。它们是协议驱动的瘦对象。

在具体实现中,BxInputRowBxInputSectionContent与UI的关系可能表现为MVVM中的Model-ViewModel绑定。此功能具有BxInputSectionContentBinderBxInputRowBinder协议,实现应使用模板类。

BxInputSettings实现了外观模式。很简单。所以实际上它看起来像是MVVM范式,但我更喜欢将其命名为轻量级VIPER。

Common paradigm

简单 & 选择器

让我们来看看不同类型的 BxInputRow 的实现,用于表示简单和选择行类型。例如,我们只看到文本行。在黄色区域显示了选择类型类,例如 BxInputTextRowBxInputStandartTextCell 是简单类型放置文本的具体实现(请参见下面的UML类图)。简单类型都很清晰,让我们看看 BxInputSelectorRowBxInputChildSelectorRow,它们是具有选择器的行协议。它们是一对多关系。实际上,为 BxInputSelectorRow(在这个例子中是 BxInputSelectorRowBinder)的 ViewModel 负责管理自己的 行。当你选择该 RowBinder 的单元格时,它应该使用 (这是 BxInputController 的协议)通过帮助添加新的 BxInputChildSelectorTextRow 行。如果再次选择,则应该关闭单元格,并从 BxInputController 中删除 行。一般来说, 是许多对象,但在这个例子中它是一个 BxInputChildSelectorTextRow 类的单一对象。这个类不一定必须是公共的,因为它完全封装在父类 BxInputSelectorTextRow 中。

Common paradigm

许可证

BxInputController 采用 MIT 许可证发布。有关详细信息,请参阅 LICENSE。