Model2App 0.1.3

Model2App 0.1.3

Karol Kulesza 维护。



Model2App 0.1.3

Model2App: Turn your Swift data model into a working CRUD app.

CI Status Platform iOS Version License: MIT Twitter: @karolkulesza

Model2App 是一个简单的库,可以让您根据在 Swift 定义的仅数据模型快速生成一个 CRUD iOS 应用。(CRUD - 创建、读取、更新、删除)。您是否希望快速验证您下一个出色的 iOS 应用中的数据模型?Model2App 让您通过生成具有持久化层、验证以及许多其他功能的完整工作应用来节省数小时/数天。只需定义您的模型,敲击 ⌘ + R 并享受您的应用。😎

Model2App 使用 Realm❤️作为强力引擎,可视为其开发活动中的扩展,特别是在定义或验证更大项目中的数据模型阶段。

🔷特性

✴️自动生成

     基于您应用中定义的类列表的应用菜单
     每个模型类的对象列表视图
     基于模型属性列表的动态对象视图,用于创建、更新和查看给定类的对象
     基于属性类型或声明的控件类型(请参见下面的支持控件类型)的对象属性单元格
     处理不同控件类型的逻辑,以更改对象属性的值
     使用预定义规则或使用闭包的自定义规则创建/更新对象的验证逻辑
     在本地存储(Realm)中持久化创建的对象的逻辑
     调用对象更新会话和删除对象的逻辑
     设置对象之间关系的逻辑,在对象属性的情况下
     相关对象的分区,对象由其他对象引用(反向关系)
     从给定对象视图中创建相关对象的逻辑
     遍历(无限)相关对象的逻辑
     开箱即可进行缩放导航动画
     以及许多其他小特性

✴️自定义默认应用配置

     调整应用菜单(布局、顺序、背景、菜单项图标/布局/透明度、字体名称/大小/颜色、动画等)
     从提供的包(MenuIcons)中选择任何菜单项图标、提供您自己的,或让 Model2App 为您选择一个
     调整对象列表视图(单元格布局/背景、显示的对象属性、图像布局、动画等)
     调整对象视图属性单元格(单元格布局/背景、字体名称/大小/颜色、图像布局、占位符等)
     调整对象视图相关对象标题(标题布局/背景、字体名称/大小/颜色)
     调整选择列表视图(单元格布局/背景、字体名称/大小/颜色)
     从应用菜单中隐藏特定类,或从对象视图中隐藏给定类的特定属性
     调整默认动画配置:显示/消失动画持续时间、阻尼比或初始弹性速度
     指定单元格中显示的图像视图是否应为圆形

✴️其他特性

     同时支持 iPhone 和 iPad
     同时支持纵向和横向方向
     验证您的数据模型中声明的关系和声明的控件类型
     允许使用表情符号字符作为菜单图标图像
     灵活性 & 扩展性:除了在M2AConfig类中定义的可覆盖的配置参数外,用于核心应用功能的多数类和方法都有open访问修饰符,因此您可以在您的应用中对Model2App框架的选定部分进行自定义或扩展。

✴️支持的控制类型

     ✏️ 文本字段
     ✏️ 数字字段
     ✏️ 浮点小数字段
     ✏️ 双精度小数字段
     ✏️ 货币字段
     ✏️ 电话字段
     ✏️ 电子邮件字段
     ✏️ 密码字段
     ✏️ URL字段
     ✏️ ZIP字段
     ✏️ 开关
     ✏️ 日期选择器
     ✏️ 时间选择器
     ✏️ 日期时间选择器
     ✏️ 文本选择器
     ✏️ 对象选择器
     ✏️ 图片选择器

🔷要求

     Xcode 10.1+
     Swift 4.2+

🔷安装

Model2App可以通过CocoaPodsCarthage获得。

✴️CocoaPods

为了通过CocoaPods安装Model2App,只需将以下行添加到您的Podfile

pod 'Model2App'

然后运行以下命令

$ pod install

✴️Carthage

为了通过Carthage安装Model2App,只需将以下行添加到您的Cartfile

github "Q-Mobile/Model2App" ~> 0.1.0

然后运行以下命令

$ carthage update

请记住,将Carthage/Build/*中的所有*.framework文件添加到您的项目中(不仅仅是Model2App.framework),还要执行其他标准的Carthage步骤。

🔷用法

✴️模型定义

安装Model2App后,简单地通过子类化ModelClass来定义您的数据模型,如下面的示例所示或如本存储库中可用的示例应用(Model2AppTestApp)所示,并按下⌘ + R。 (注意:下面可见的样本数据模型只是示例应用中的一小部分,有关更复杂模型的详细信息,请参阅Model2AppTestApp源代码)

@objcMembers class Company : ModelClass {
    dynamic var name : String?
    dynamic var phoneNumber : String?
    dynamic var industry : String?
}

@objcMembers class Person : ModelClass {
    dynamic var firstName : String?
    dynamic var lastName : String?
    dynamic var salutation : String?
    dynamic var phoneNumber : String?
    dynamic var privateEmail : String?
    dynamic var workEmail : String?
    let isKeyOpinionLeader = OptionalProperty<Bool>()
    dynamic var birthday : Date?
    dynamic var website : String?
    dynamic var note : String?
    dynamic var picture : Data?
    dynamic var company : Company?
}

@objcMembers class Deal : ModelClass {
    dynamic var name : String?
    let value = OptionalProperty<Int>()
    dynamic var stage : String?
    dynamic var closingDate : Date?
    dynamic var company : Company?
}      

✴️自定义默认模型配置

如果您想自定义类/属性配置,只需覆盖ModelClass定义的一些或全部计算类型属性

@objcMembers class Company : ModelClass {
    // (model properties defined earlier)

    override class var pluralName: String { return "Companies" }
    override class var menuIconFileName: String { return "users" }
    override class var menuOrder: Int { return 2 }
    override class var inverseRelationships: [InverseRelationship] {
        return [
            InverseRelationship("employees", sourceType: Person.self, sourceProperty: #keyPath(Person.company)),
            InverseRelationship("deals", sourceType: Deal.self, sourceProperty: #keyPath(Deal.company))
        ]
    }

    override class var propertyConfigurations: [String: PropertyConfiguration] {
        return [
            #keyPath(name) : PropertyConfiguration(
                placeholder: "Enter company name",
                validationRules: [.Required]
            ),
            #keyPath(phoneNumber) : PropertyConfiguration(
                placeholder: "Enter phone number"
            ),
            #keyPath(industry) : PropertyConfiguration(
                controlType: .TextPicker,
                pickerValues: ["Consulting", "Education", "Financial Services", "Government", "Manufacturing", "Real Estate", "Technology", "Other"]
            )
        ]
    }
}

@objcMembers class Person : ModelClass {
    // (model properties defined earlier)

    override class var pluralName: String { return "People" }
    override class var menuIconFileName: String { return "user-1" }
    override class var menuIconIsFromAppBundle: Bool { return true }
    override class var menuOrder: Int { return 1 }

    override class var listViewCellProperties: [String] {
        return [#keyPath(picture), #keyPath(firstName), #keyPath(lastName)]
    }

    override class var listViewCellLayoutVisualFormats: [String] {
        return [
            "H:|-10-[picture]-[firstName]-5-[lastName(>=50)]-|" // OR: (with slightly weaker readability but more safe): "H:|-10-[#keyPath(picture)]-[#keyPath(firstName)]-5-[#keyPath(lastName)(>=50)]"
        ]
    }

    override class var propertyConfigurations: [String: PropertyConfiguration] {
        return [
            #keyPath(firstName) : PropertyConfiguration(
                controlType: .TextField,
                placeholder: "Enter first name",
                validationRules: [.Required]
            ),
            #keyPath(lastName) : PropertyConfiguration(
                controlType: .TextField,
                placeholder: "Enter last name",
                validationRules: [.Required]
            ),
            #keyPath(salutation) : PropertyConfiguration(
                controlType: .TextPicker,
                pickerValues: ["Mr.", "Ms.", "Mrs.", "Dr.", "Prof."],
                validationRules: [.Required]
            ),
            #keyPath(phoneNumber) : PropertyConfiguration(
                controlType: .PhoneField,
                placeholder: "Enter phone number",
                validationRules: [.MinLength(length: 9), .MaxLength(length: 12)]
            ),
            #keyPath(privateEmail) : PropertyConfiguration(
                controlType: .EmailField,
                placeholder: "Enter email address",
                validationRules: [.Email]
            ),
            #keyPath(workEmail) : PropertyConfiguration(
                controlType: .EmailField,
                placeholder: "Enter email address",
                validationRules: [.Required, .Email, .Custom(isValid: { object in
                    if let workEmail = object[#keyPath(workEmail)] as? String,
                       let privateEmail = object[#keyPath(privateEmail)] as? String,
                       workEmail == privateEmail {
                            UIUtilities.showValidationAlert("Work Email cannot be the same as Private Email.")
                            return false
                    }
                    return true
                })]
            ),
            #keyPath(birthday) : PropertyConfiguration(
                controlType: .DatePicker,
                validationRules: [.Required]
            ),
            #keyPath(website) : PropertyConfiguration(
                controlType: .URLField,
                placeholder: "Enter URL",
                validationRules: [.URL]
            ),
            #keyPath(note) : PropertyConfiguration(
                controlType: .TextField,
                placeholder: "Enter note",
                validationRules: [.MaxLength(length: 1000)]
            ),
            #keyPath(company) : PropertyConfiguration(
                validationRules: [.Required]
            ),
            #keyPath(picture) : PropertyConfiguration(
                controlType: .ImagePicker
            )
        ]
    }
}

@objcMembers class Deal : ModelClass {
    // (model properties defined earlier)

    override class var pluralName: String { return "Deals" }
    override class var menuIconFileName: String { return "money" }

    override class var listViewCellProperties: [String] {
        return [#keyPath(name), "value", #keyPath(stage)]
    }

    override class var listViewCellLayoutVisualFormats: [String] {
        return [
            "H:|-10@750-[name(>=50)]-(>=10)-[value(>=50)]-|",
            "H:|-10@750-[stage]-(>=10)-[value]",
            "V:|-10@750-[value]-10@750-|",
            "V:|-10@750-[name]-[stage]-|"
        ]
    }

    override class var propertyConfigurations: [String: PropertyConfiguration] {
        return [
            #keyPath(name) : PropertyConfiguration(
                controlType: .TextField,
                placeholder: "Enter deal name",
                validationRules: [.Required]
            ),
            "value" : PropertyConfiguration(
                controlType: .CurrencyField,
                placeholder: "Enter deal value",
                validationRules: [.Required]
            ),
            #keyPath(stage) : PropertyConfiguration(
                controlType: .TextPicker,
                pickerValues: ["Prospecting", "Qualified", "Reviewed", "Quote", "Won", "Lost"],
                validationRules: [.Required]
            ),
            #keyPath(company) : PropertyConfiguration(
                validationRules: [.Required]
            )
        ]
    }
}      

✴️可自定义的ModelClass类型属性

✏️ displayName - 此类的显示名称。如未提供,则从类名推断
✏️ pluralName - 此类的复数名称。用于命名对象列表或菜单项。如未提供,则使用<ClassName> - List
✏️ menuIconFileName - 应用根菜单中用于菜单图标的图片文件名称
✏️ menuIconIsFromAppBundle - 是否Model2App应在主应用包中查找菜单图标文件。如果为false,则使用Model2App的包
✏️ menuOrder - 此类在应用根菜单中的菜单项顺序
✏️ propertyConfigurations - 此类的属性配置字典
✏️ inverseRelationships - 此类的反向关系列表(如果存在其他类的to-one关系,并且您想显示相关对象的节,则应定义此关系)
✏️ listViewCellProperties - 用于此类列表视图单元格的属性列表。应包含在listViewCellLayoutVisualFormats中指定的所有属性
✏️ listViewCellLayoutVisualFormats - 列表视图单元格布局的视觉格式列表,使用Apple的Auto Layout视觉格式语言
✏️ isHiddenInRootView - 指定给定的模型类是否应在应用的根菜单中隐藏(在应仅在相关对象部分显示子实体时很有用,对于特定对象)

✴️ PropertyConfiguration的属性

✏️ controlType - 指定用于此属性的UI控件类型
✏️ placeholder - 指定在没有为此属性提供值时使用的占位符值
✏️ pickerValues - 指定此属性的可能选择器值的列表。仅对TextPicker控件类型有效
✏️ validationRules - 指定此属性的验证规则列表(在创建此类的新对象时评估)
✏️ isHidden - 指定该属性是否应在UI上隐藏

✴️支持的验证规则 (ValidationRule)

✏️ 必需
✏️ MinLength(length: Int)
✏️ MaxLength(length: Int)
✏️ MinValue(value: Double)
✏️ MaxValue(value: Double)
✏️ Email
✏️ URL
✏️ 自定义(isValid: (ModelClass) -> Bool)

✴️自定义默认应用配置

M2AConfig 类定义了默认的应用配置,应用可以选用继承自该配置。请参考示例应用 Model2AppTestApp 中的 M2AConfig 类源码和 AppConfig.swift 文件。

✴️模型定义说明

  • 如上所述,Model2App 在底层使用 Realm,因此在对模型进行定义时也有类似的考量。
    • 所有属性属性必须遵循 Realm 文档中指定的规则:[链接](https://realm.io/docs/swift/latest#property-cheatsheet)。简而言之,所有模型属性应声明为 @objc dynamic var(如果类本身使用 objcMembers 声明,则只需使用 dynamic var),除了 OptionalProperty(用于数字/布尔值),应仅使用 let 声明。
    • 字符串、日期和数据属性可以是可选的。对象属性(定义关系)必须是可选的。使用 OptionalProperty(Realm 的 RealmOptional 的别称)来存储可选数字。

🔷示例应用

📱这个仓库中的 Model2AppTestApp 目录包含一个示例应用,该应用定义了一个非常简单的CRM相关的数据模型。打开 Model2AppTestApp/Model2AppTestApp.xcworkspace 并运行这个测试应用以查看 Model2App 库对数据模型的示例影响。

🔷限制/已知问题

     ⚠️《Model2App》0.1.0 版本尚未处理数据模型迁移,因此如果您在应用首次启动后更改了数据模型,则会出错,并且您必须在下次启动之前删除应用,才能显示更新后的模型。处理模型迁移计划在未来版本中解决。

     ⚠️对于 OptionalProperty 属性,您不能使用 #keyPath 安全地引用某个属性(例如从 propertyConfigurationslistViewCellProperties 定义中)。

🔷路线图/未来版本的功能

《Model2App》0.1.0 版本只包含了一组有限的功能。还有很多功能可以扩展它的价值。

     ☘️在对象列表视图中搜索
     ☘️在对象列表视图中过滤
     ☘️在对象列表视图中排序
     ☘️处理级联删除
     ☘️处理模型迁移
     ☘️支持控制类型: “Slider”
     ☘️支持控制类型: “TextView”
     ☘️支持控制类型: “Button”
     ☘️支持一对一关系(到目前为止仅支持反方向的一对多关系)
     ☘️可以使用emoji作为菜单项图标,而不是使用图像
     ☘️...以及更多!请保持关注!❤️

🔷贡献

👨🏻‍🔧 欢迎通过各种方式对 Model2App 进行贡献,包括创建pull请求,遵循以下指南:

  1. Fork Model2App
  2. 创建功能分支
  3. 提交您的更改,包括单元测试
  4. 将更改推送到分支
  5. 创建pull请求

🔷致谢/感谢

     🎨《Model2App》使用的图标由Lucy G设计,来源于Flaticon
     💚特别感谢Realm背后的所有人。

🔷作者

     👨‍💻 Karol Kulesza(《@karolkulesza》)

🔷许可协议

     📄《Model2App》遵照 MIT 许可协议。有关更多信息,请参阅 LICENSE 文件。