swiftUI 2.0了 可以尝试在自己项目中写小页面了。 注: 本文使用的是2.0 所以你的iOS版本得是14.0+ (本人从swift2.0用到现在的5.0 真的改的东西太多了,不推荐用UI1.0)
另外推荐一个网站 不知道的问题可以去直接提 SwiftUI官方论坛 点我起飞
官方对SwiftUI的介绍 基本上你想要的都有,没有的,也别搜了,教程估计就是UIKit包装一下用而已 点我起飞
不要再去某站找那些付费知识了,我就想不通 又不是商业项目 也不是你自己的开源库 就一些基础知识 也好意思收费? 我还看到一个hxd 付费看了,结果评论说 没有用,作者回复(1.0是有用的,你把版本改一下)
我?
这是这么神奇操作!!!
言归正传
本文分为 在UIKit中使用SwiftUI
和 使用SwiftUI搭建一个app
目前来说 你直接从头到尾把项目改成swiftUI 有点扯淡。 在当前项目中引用UI模块比较靠谱。 而且在真实项目中才能发现问题,找到问题,解决问题。
写静态数据,恕我直言 帮助不大。
首先在有点击事件的页面中导入UI库
import UIKit
import SwiftUI
找到点击事件 判断版本来做跳转
if #available(iOS 14.0.0, *) {
let swiftUIvc = UIHostingController(rootView: AEMemberUserAdmin())
controller?.navigationController?.pushViewController(swiftUIvc, animated: true)
} else {
let vc = MemberAdminCenterController.instance()
controller?.navigationController?.pushViewController(vc, animated: true)
}
UIHostingController的view 在1.0中 不太适配整体页面。 不过现在已经被修复了。
看图理解 正常开发思路 tableview 上方名字时间等是headerView 下方看需求能滑就是footer不能滑就加的vc的view上。
来看看swiftUI中是怎么写的 (因为项目一直用的HandyJSON 所以处理返回的JSON时多了一次赋值。 大家理解一下,后面用的时候就不会了)
import SwiftUI
import UIKit
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
Text("正儿八经的VC")
}
}
run一下 看看是不是能成功跳转了
设置nav的title (系统提供了多种样式 接下来的代码只是为了写上面的那张图)
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
Text("正儿八经的VC")
.navigationBarTitle(Text("成员管理"), displayMode: .large)
}
}
如果你是用swiftUI搭了一个tabbar 用上面的方法设置nav是会有警告 是因为2.0已经优化了这种方法。 在tabbarItem中 加上.navigationViewStyle(StackNavigationViewStyle())
就可以了。
有兴趣的可以去尝试一波
如果你的项目中 重写了nav 使用了自定义的 那么这时候应该就能看到 navBar上有两个返回按钮 一个是你在Base中写的 一个是系统的。如果发生了这种事情
1 在跳转时 设置原来的baseNav隐藏
2 在跳转时 直接为vc设置title
我目前用的是第二种方法,但是有些页面是动态的, 等我尝试以后在写进来
去除.navigationBarTitle(Text("成员管理"), displayMode: .large)
在跳转时加上
let v = UIHostingController(rootView: AEMemberUserAdmin())
v.title = "成员管理"
设置每个用户的cell
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
List {
Text("用户1")
Text("用户2")
}
.navigationBarTitle(Text("成员管理"), displayMode: .large)
}
}
到了此处 就应该发现了 怎么隐藏分割线 代码来了
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
List {
Text("123123")
Text("123123")
}.onAppear {
UITableView.appearance().separatorStyle = .none
}
}
}
是不是相当眼熟。
但是在14.0 没问题 在14+上 没有卵用…
所以 为了一劳永逸 在1.0和2.0中都能隐藏分割线 就是下面的代码了
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
List {
Text("123123").frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
Text("123123").frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}.onAppear {
UITableView.appearance().separatorStyle = .none
}
}
}
有点粗暴 但是能实现就好 还有一种方法 设置List的样式 但是颜色以及系统的适配 也比较麻烦
在List {}后面加上 .listStyle(SidebarListStyle())
也能隐藏分割线
好了 然后先写个自定义的Cell
@available(iOS 14.0.0, *)
struct AEMemberCellView: View {
var body: some View {
Text("cell")
}
}
这里先写死4个数据 下面会接上JSON
var body: some View {
List {
ForEach(0..<4) { idx in
AEMemberCellView().frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}
}.onAppear {
// UITableView.appearance().separatorStyle = .none
setupApi()
}
}
接下来设置List 的 headerView headerView是自定义的。 一看上去就知道 一个Text 肯定是不能实现的。
headerView
@available(iOS 14.0.0, *)
struct AEGroupMemberHeaderView: View {
var body: some View {
// 从上到下布局
VStack {
// 默认控件是居中显示的 这里使用最简单的方法让在左边显示 由于不知道名字具体长度,不使用frame来设置 往下看就知道padding 为何是8和0了 苹果默认是16
HStack {
Text("学校名称").font(Font.system(size: 18, weight: .medium))
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
Spacer()
}
// 从左到右布局
HStack {
// siwftUI中 padding的默认值是 16 所有不用在设置间距 只需要加个弹簧 确保两个Text 一左一右
Text("已开通园所会员老师\(2)人").font(Font.system(size: 14, weight: .medium))
Spacer()
Text("2022-02-24到期").font(Font.system(size: 12))
}
}
}
}
给LIst 设置header
var body: some View {
List {
Section {
// 由于header中带有弹簧 所以直接在此处设置header的宽是屏幕宽-32
AEGroupMemberHeaderView().frame(width: screenWidth-32, height: 80, alignment: .top)
}
ForEach(0..<4) { idx in
AEMemberCellView().frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}
}.onAppear {
// UITableView.appearance().separatorStyle = .none
setupApi()
}
}
需求是 添加会员按钮 一直在页面中 所以不能为List直接设置Footer
定义添加按钮的View
@available(iOS 14.0.0, *)
struct AEGroupMemberFooterView: View {
var body: some View {
VStack {
Text("还有28个会员名额").font(Font.system(size: 12)).padding(.bottom, 4)
Button("添加园所会员") {
debugPrint("btnClicked")
}
.frame(width: 180, height: 40, alignment: .center)
.background(Color.red)
.cornerRadius(29)
}
}
}
完整的代码
import SwiftUI
import UIKit
import HandyJSON
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
List {
Section {
AEGroupMemberHeaderView(vo: model).frame(width: screenWidth-32, height: 80, alignment: .leading)
}
ForEach(0..<4) { idx in
AEMemberCellView().frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}
}.onAppear {
// UITableView.appearance().separatorStyle = .none
}
AEGroupMemberFooterView(vo: model).frame(width: screenWidth, height: 98+screenBottomHeight, alignment: .bottom)
}
}
@available(iOS 14.0.0, *)
struct AEMemberCellView: View {
var body: some View {
Text("cell")
}
}
@available(iOS 14.0.0, *)
struct AEGroupMemberHeaderView: View {
var body: some View {
VStack {
HStack {
Text(vo.title).font(Font.system(size: 18, weight: .medium))
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
Spacer()
}
HStack {
Text("已开通园所会员老师\(1)人").font(Font.system(size: 14, weight: .medium))
Spacer()
Text("\(2)到期").font(Font.system(size: 12))
}
}
}
}
@available(iOS 14.0.0, *)
struct AEGroupMemberFooterView: View {
var body: some View {
VStack {
Text("还有28个会员名额").font(Font.system(size: 12)).padding(.bottom, 4)
Button("添加园所会员") {
debugPrint("btnClicked")
}
.frame(width: 180, height: 40, alignment: .center)
.background(Color.red)
.cornerRadius(29)
}
}
}
前言 SwiftUI中 要根据模型数据来展示UI 必须要给模型数据添加包装器
官网介绍数据的包装器类型—点我飞机直达
本人研究了一下 根据个人见解 说一下
声明 修饰符
1
@State
官网介绍(状态值更改时,视图会使外观无效并重新计算主体。) 常用语基础类型,也能修饰对象 但是 修饰了对象后 对象数据更新 视图不会更新。 简单理解 修饰Int Bool Double 类型。
2
@StateObject
官网介绍(当可观察对象的已发布属性更改时,SwiftUI将更新依赖于这些属性的任何视图的部分) 用于当前页面中JSON数据的修饰。 简单理解 api返回的json 在转成对象后 使用它来修饰。
3
@ObservableObject
官网介绍(它订阅可观察对象并在可观察对象发生更改时使视图无效) 和@StateObject相比 多了一个自带方法 func update()
简单理解 一次修改用它的地方都会改
4
@EnvironmentObject
官网介绍(只要可观察对象发生变化,环境对象就会使当前视图无效。如果将属性声明为环境对象,请确保通过调用其修饰符在祖先视图上设置相应的模型对象。environmentObject(_) 大概的一是就是 共享的JSON数据,比如用户信息 会在很多页面使用,所以使用它修饰UserJson。 但是要全局用 就得在程序初始化时声明。UIKit中使用SwiftUI时 用不到 等用swiftUI搭建App在细说。 简单理解 多页面共享一个数据。
5 其他 还有很多修饰符 等用到在给慢慢说。
页面出现时 请求网络数据 api老页面已经存在 就不用再费劲去重新写个网络请求 判断请求状态什么的了。
这里用的是MOYA
在 struct AEMemberUserAdmin: View { }
中写个方法 用于获取数据
AEGroupMemberVoJson
是老页面使用的model 继承自HandJSON
private func setupApi() {
let target = BPMultiTarget.target(AEMemberApi.loadGroupMembers)
NetProvider.provider.requestModelData(target: target, modelType: AEGroupMemberVoJson.self) { (res) in
guard let vo = res as? AEGroupMemberVoJson else { return }
debugPrint("\(vo.kName)")
} failClosure: { (error) in
BPProgressHUD.showError(error)
}
}
接下来就是调用方法 来获取数据了 这里就要介绍一下 swiftUI的生命周期,正常的api我们在UIKit中 在viewDidLoad()
中调用。
swiftUI的相同方法
onAppear()
等于viewDidLoad()
onDisappear()
等于viewDidDisappear()
理解了这个 在来看完整代码
import SwiftUI
import UIKit
import HandyJSON
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
var body: some View {
List {
Section {
AEGroupMemberHeaderView(vo: model).frame(width: screenWidth-32, height: 80, alignment: .leading)
}
ForEach(0..<4) { idx in
AEMemberCellView().frame(width: UIScreen.main.bounds.size.width, height: 50, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}
}.onAppear {
setupApi()
}
AEGroupMemberFooterView(vo: model).frame(width: screenWidth, height: 98+screenBottomHeight, alignment: .bottom)
}
private func setupApi() {
let target = BPMultiTarget.target(AEMemberApi.loadGroupMembers)
NetProvider.provider.requestModelData(target: target, modelType: AEGroupMemberVoJson.self) { (res) in
guard let vo = res as? AEGroupMemberVoJson else { return }
debugPrint("\(vo.kName)")
} failClosure: { (error) in
BPProgressHUD.showError(error)
}
}
}
正常来讲 此时数据已经有了 需要把数据绑定到视图上。需求中要去用户信息是可更改的。所有我选择直接继承自ObservableObject 继承ObservableObject需要给定一个identifier
注:
swiftUI 使用的model 需要继承自Codable协议 如果对象没有满足Codable协议 则需要额外修饰对象中的属性 使用@Published
代码
@available(iOS 14.0.0, *)
class AEGroupMemberVo: ObservableObject {
@Published var title = "Great Expectations"
@Published var openedCount: Int = 0
@Published var expiredDate: Double = 0.0
@Published var remainedMemberNums: Int = 0
let identifier = UUID()
}
先看cell实现 下面在贴上全部代码 以及完成后的图
现在主流SwiftUI 设置Image 有两种 一个SDWebImage 要求iOS 13,kf 要去iOS 10.0
都是一句代码的事,项目中一直用的KF 外加作为喵神的小迷弟 就不考虑SD了
飞机直达 kf 介绍
不多比比了 直接看代码吧
@available(iOS 14.0.0, *)
struct AEMemberCellView: View {
@StateObject var cellVo: AEGroupMemberUserVo
var body: some View {
HStack {
Image(uiImage: UIImage(named: "user_placeholder")!)
.resizable()
.frame(width: 44, height: 44, alignment: .leading)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("\(cellVo.userName)").font(Font.system(size: 14))
.lineLimit(1) // 行数
if cellVo.groupMemberAdmin {
Text("管理员").frame(width: 42, height: 16, alignment: .center)
.font(Font.system(size: 10))
.background(Color(UIColor("#EDD29C")!))
.foregroundColor(Color(UIColor("#71541B")!))
}
}
Text("\(cellVo.phone)").font(Font.system(size: 12))
.foregroundColor(Color(UIColor.textLightBlack()!))
}
Spacer()
Text(getUserLever(cellVo.courseLevel)).font(Font.system(size: 12))
Image("arrow_right_gray_small")
.padding(.trailing, 32)
}
}
private func getUserLever(_ lever: String) -> String {
switch lever {
case "L1":
return "新手教师培训"
case "L2":
return "成熟教师培训"
case "L3":
return "骨干教师培训"
default:
return "管理岗教师培训"
}
}
}
import SwiftUI
import UIKit
import HandyJSON
@available(iOS 14.0.0, *)
struct AEMemberUserAdmin: View {
@StateObject var model: AEGroupMemberVo = AEGroupMemberVo()
var body: some View {
List {
Section {
AEGroupMemberHeaderView(vo: model).frame(width: screenWidth-32, height: 80, alignment: .leading)
}
ForEach(0..<model.memberList.count, id: \.self) { idx in
AEMemberCellView(cellVo: model.memberList[idx]).frame(width: screenWidth, height: 70, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
}
}.onAppear {
setupApi()
}
AEGroupMemberFooterView(vo: model).frame(width: screenWidth, height: 98+screenBottomHeight, alignment: .bottom)
}
private func setupApi() {
let target = BPMultiTarget.target(AEMemberApi.loadGroupMembers)
NetProvider.provider.requestModelData(target: target, modelType: AEGroupMemberVoJson.self) { (res) in
guard let vo = res as? AEGroupMemberVoJson else { return }
DispatchQueue.main.async {
model.title = vo.kName
model.openedCount = vo.openedCount
model.expiredDate = vo.expiredDate
model.remainedMemberNums = vo.remainedMemberNums
var list: [AEGroupMemberUserVo] = []
for item in vo.memberList {
let member = AEGroupMemberUserVo()
member.userId = item.userId
member.userName = item.userName
member.phone = item.phone
member.groupMemberAdmin = item.groupMemberAdmin
member.courseLevel = item.courseLevel
list.append(member)
}
model.memberList = list
}
} failClosure: { (error) in
BPProgressHUD.showError(error)
}
}
}
@available(iOS 14.0.0, *)
struct AEMemberCellView: View {
@StateObject var cellVo: AEGroupMemberUserVo
var body: some View {
HStack {
Image(uiImage: UIImage(named: "user_placeholder")!)
.resizable()
.frame(width: 44, height: 44, alignment: .leading)
.clipShape(Circle())
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("\(cellVo.userName)").font(Font.system(size: 14))
.lineLimit(1)
if cellVo.groupMemberAdmin {
Text("管理员").frame(width: 42, height: 16, alignment: .center)
.font(Font.system(size: 10))
.background(Color(UIColor("#EDD29C")!))
.foregroundColor(Color(UIColor("#71541B")!))
}
}
Text("\(cellVo.phone)").font(Font.system(size: 12))
.foregroundColor(Color(UIColor.textLightBlack()!))
}
Spacer()
Text(getUserLever(cellVo.courseLevel)).font(Font.system(size: 12))
Image("arrow_right_gray_small")
.padding(.trailing, 32)
}
}
private func getUserLever(_ lever: String) -> String {
switch lever {
case "L1":
return "新手教师培训"
case "L2":
return "成熟教师培训"
case "L3":
return "骨干教师培训"
default:
return "管理岗教师培训"
}
}
}
@available(iOS 14.0.0, *)
struct AEGroupMemberHeaderView: View {
@StateObject var vo: AEGroupMemberVo
var body: some View {
VStack {
HStack {
Text(vo.title).font(Font.system(size: 18, weight: .medium))
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
Spacer()
}
HStack {
Text("已开通园所会员老师\(vo.openedCount)人").font(Font.system(size: 14, weight: .medium))
Spacer()
Text("\(vo.expiredDate.dateDayString)到期").font(Font.system(size: 12))
}
}
}
}
@available(iOS 14.0.0, *)
struct AEGroupMemberFooterView: View {
@StateObject var vo: AEGroupMemberVo
var body: some View {
VStack {
HStack {
Text("还有")
.foregroundColor(Color(UIColor.textLightBlack()!))
+ Text("\(vo.remainedMemberNums)个")
.foregroundColor(Color(UIColor("#C08C26")!))
+ Text("会员名额")
.foregroundColor(Color(UIColor.textLightBlack()!))
}.padding(.bottom, 4).font(Font.system(size: 12))
Button("添加园所会员") {
debugPrint("btnClicked")
}
.frame(width: 225, height: 44, alignment: .center)
.background(Color(UIColor("#F1D8A4")!))
.cornerRadius(22)
.foregroundColor(Color(UIColor("#71541B")!))
}
}
}
@available(iOS 14.0.0, *)
class AEGroupMemberVo: ObservableObject {
@Published var title = "Great Expectations"
@Published var openedCount: Int = 0
@Published var expiredDate: Double = 0.0
@Published var remainedMemberNums: Int = 0
@Published var memberList: [AEGroupMemberUserVo] = []
let identifier = UUID()
}
@available(iOS 14.0.0, *)
class AEGroupMemberUserVo: ObservableObject {
@Published var userId: Int = -1
@Published var userName: String = ""
@Published var phone: String = ""
@Published var groupMemberAdmin: Bool = false
@Published var courseLevel: String = ""
let identifier = UUID()
}
成员编辑页已经有了,在不改成swiftUI的情况下跳转。 编辑页在成功信息编辑成功后 会进行闭包回调。用于刷新上个页面
1 View中点击button进行跳转
@available(iOS 14.0.0, *)
struct AEGroupMemberFooterView: View {
@StateObject var vo: AEGroupMemberVo
// 新增成员的回调
public var addBlock: (()->Void)?
var body: some View {
VStack {
HStack {
Text("还有")
.foregroundColor(Color(UIColor.textLightBlack()!))
+ Text("\(vo.remainedMemberNums)个")
.foregroundColor(Color(UIColor("#C08C26")!))
+ Text("会员名额")
.foregroundColor(Color(UIColor.textLightBlack()!))
}.padding(.bottom, 4).font(Font.system(size: 12))
Button("添加园所会员") {
addMemberClick()
}
.frame(width: 225, height: 44, alignment: .center)
.background(Color(UIColor("#F1D8A4")!))
.cornerRadius(22)
.foregroundColor(Color(UIColor("#71541B")!))
}.padding(.top, 24)
}
private func addMemberClick() {
if vo.remainedMemberNums == 0 {
BPProgressHUD.showToast(text: "没有名额了")
return
}
let vc = MemberG1AdminAddViewController.instance()
vc.isHasCourseAuthority = vo.hasCourseAuthority
vc.addMemberSuccess = addBlock
// 获取最顶层的NavigationController 一般项目中应该都有这个方法
getCurrentNavigationController()?.pushViewController(vc, animated: true)
}
}
在footer中添加了 闭包 在用footer的地方会自动提示
AEGroupMemberFooterView(vo: <#T##AEGroupMemberVo#>, addBlock: <#T##(() -> Void)?##(() -> Void)?##() -> Void#>)
整体body代码
var body: some View {
List {
Section {
AEGroupMemberHeaderView(vo: model).frame(width: screenWidth-32, height: 80, alignment: .leading)
}
ForEach(0..<model.memberList.count, id: \.self) { idx in
AEMemberCellView(cellVo: model.memberList[idx]).frame(width: screenWidth, height: 70, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
.onTapGesture {
editMemberUser(idx)
}
}
}.onAppear {
setupApi()
}
AEGroupMemberFooterView(vo: model) {
setupApi()
}.frame(width: screenWidth, height: 98+screenBottomHeight, alignment: .top)
}
系统自带的是 直接传一个body 进行跳转 反正没啥用 除了死页面会用。
这里使用手势 获取响应事件 做一些判断在执行跳转
List {
Section {
AEGroupMemberHeaderView(vo: model).frame(width: screenWidth-32, height: 80, alignment: .leading)
}
ForEach(0..<model.memberList.count, id: \.self) { idx in
AEMemberCellView(cellVo: model.memberList[idx]).frame(width: screenWidth, height: 70, alignment: .leading)
.listRowInsets(EdgeInsets(top: -1, leading: 16, bottom: -1, trailing: 16))
.background(Color(.systemBackground))
.onTapGesture {
// 手势响应事件
editMemberUser(idx)
}
}
}.onAppear {
setupApi()
}
跳转的也是已经有的 编辑成员也 就不贴代码了。 就是用idx获取list 在进行跳转
@StateObject 只能刷新一次UI
所以需要修改 @StateObject 改为 @ObservedObject 注意凡是需要动态刷新的地方修饰符都要修改
1 修改修饰符
@ObservedObject var model: AEGroupMemberVo = AEGroupMemberVo()
2 model中调用willSet 方法
@available(iOS 14.0.0, *)
class AEGroupMemberVo: ObservableObject {
@Published var title = "Great Expectations"
@Published var openedCount: Int = 0
@Published var expiredDate: Double = 0.0
@Published var remainedMemberNums: Int = 0
@Published var hasCourseAuthority: Bool = false
@Published var memberList: [AEGroupMemberUserVo] = [] {
willSet {
objectWillChange.send()
}
}
// 编辑页要使用
@Published var memberJsonList: [AEMemberUserVoJson] = []
// let identifier = UUID()
}
@available(iOS 14.0.0, *)
class AEGroupMemberUserVo: ObservableObject {
@Published var userId: Int = -1
@Published var userName: String = ""
@Published var phone: String = ""
@Published var groupMemberAdmin: Bool = false
@Published var courseLevel: String = "" {
willSet {
objectWillChange.send()
}
}
// let identifier = UUID()
}
结束了。 一个页面完成
最后 吐槽一下 都2.0的 写个sRGB颜色 还得自己去扩充方法 隔壁安卓几年前就支持 直接是用十六进制颜色表了。 真…
由于我项目里本来 UIColor 就做了扩展 就没在写了。 网上搜一下 很多。 因为支持 Color(UIColor(xx))
我就直接用了
明天在写用SwiftUI搭建一个App 下班了