Model2App
是一个简单的库,可以让您根据在 Swift 定义的仅数据模型快速生成一个 CRUD
iOS 应用。(CRUD
- 创建、读取、更新、删除)。您是否希望快速验证您下一个出色的 iOS 应用中的数据模型?Model2App
让您通过生成具有持久化层、验证以及许多其他功能的完整工作应用来节省数小时/数天。只需定义您的模型,敲击 ⌘ + R
并享受您的应用。
Model2App
使用 Realm
🔷 特性
✴️ 自动生成
Realm
)中持久化创建的对象的逻辑
✴️ 自定义默认应用配置
MenuIcons
)中选择任何菜单项图标、提供您自己的,或让 Model2App
为您选择一个
✴️ 其他特性
M2AConfig
类中定义的可覆盖的配置参数外,用于核心应用功能的多数类和方法都有open
访问修饰符,因此您可以在您的应用中对Model2App
框架的选定部分进行自定义或扩展。
✴️ 支持的控制类型
文本字段
数字字段
浮点小数字段
双精度小数字段
货币字段
电话字段
电子邮件字段
密码字段
URL字段
ZIP字段
开关
日期选择器
时间选择器
日期时间选择器
文本选择器
对象选择器
图片选择器
🔷 要求
🔷 安装
Model2App
可以通过CocoaPods和Carthage获得。
✴️ 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
的别称)来存储可选数字。
- 所有属性属性必须遵循 Realm 文档中指定的规则:[链接](https://realm.io/docs/swift/latest#property-cheatsheet)。简而言之,所有模型属性应声明为
🔷 示例应用
Model2AppTestApp
目录包含一个示例应用,该应用定义了一个非常简单的CRM相关的数据模型。打开 Model2AppTestApp/Model2AppTestApp.xcworkspace
并运行这个测试应用以查看 Model2App
库对数据模型的示例影响。
![]() |
![]() |
![]() |
---|
![]() |
![]() |
![]() |
---|
![]() |
![]() |
![]() |
---|
![]() |
![]() |
![]() |
---|
![]() |
![]() |
![]() |
---|
🔷 限制/已知问题
OptionalProperty
属性,您不能使用 #keyPath
安全地引用某个属性(例如从 propertyConfigurations
或 listViewCellProperties
定义中)。
🔷 路线图/未来版本的功能
《Model2App》0.1.0 版本只包含了一组有限的功能。还有很多功能可以扩展它的价值。
🔷 贡献
👨🏻🔧 欢迎通过各种方式对 Model2App
进行贡献,包括创建pull请求,遵循以下指南:
- Fork
Model2App
- 创建功能分支
- 提交您的更改,包括单元测试
- 将更改推送到分支
- 创建pull请求
🔷 致谢/感谢
🔷 作者
🔷 许可协议