项目背景
一直以来,对于中国传统颜色有种特别的青睐。
而在浏览一些资讯时看到博主发布的一些色彩搭配的内容,其中就谈到了色卡的概念,经过一些了解和学习,就想着打造一款色彩搭配App。
虽然App
尚未完全开发完成,但在此分享一下其中一些已经完成的功能构建方式,希望能够帮助到SwiftUI
开发者们。
那么,废话少说,让我们开始吧。
首先,创建一个新的SwiftUI
项目,命名为ColourAtla
。
页面搭建
我们简单分析下页面的组成结构,它是由一个Title
标题和一个色卡List
列表构成,然后在色卡列表中,长按单张色卡,就可以获得悬浮窗口效果。
理解完基本组成后,我们一块一块来构建内容。
标题搭建
首先是标题部分,我们使用OpaqueTypes
不透明类型的方法构建View
。示例:
//MARK: 标题 private var CardTitleView: some View { Text("世界最高级的颜色") .font(.system(size: 17)) .fontWeight(.bold) }
上述代码中,我们声明了一个私有的视图CardTitleView
,然后在里面构建了我们需要的标题Text
,并使用一些修饰符让内容看起来协调一些。
颜色扩展
标题创建完成后,我们来构建色卡列表,在构建色卡之前,为了能使用十六进制颜色值,我们给现有Color
颜色构建方式做一下Extension
扩展。示例:
我们创建一个新的Swift
文件,命名为ColorHexString
。
import SwiftUI extension Color { static func rgb(_ red: CGFloat, green: CGFloat, blue: CGFloat) -> Color { return Color(red: red / 255, green: green / 255, blue: blue / 255) } static func Hex(_ hex: UInt) -> Color { let r: CGFloat = CGFloat((hex & 0xFF0000) >> 16) let g: CGFloat = CGFloat((hex & 0x00FF00) >> 8) let b: CGFloat = CGFloat(hex & 0x0000FF) return rgb(r, green: g, blue: b) } }
上述代码中,我们给Color
进行了Extension
扩展。
我们通过接收一个UInt
十六进制颜色值,然后把它转换成RGB
颜色值,这样我们只需要输入十六进制颜色值,Color
就可以转换为可以使用的RGB
颜色值了。
单张色卡
接下来,我们回到ContentView
文件,先来构建单张色卡。示例:
上述代码中,我们给Color进行了Extension扩展。 我们通过接收一个UInt十六进制颜色值,然后把它转换成RGB颜色值,这样我们只需要输入十六进制颜色值,Color就可以转换为可以使用的RGB颜色值了。 单张色卡 接下来,我们回到ContentView文件,先来构建单张色卡。示例:
上述代码中,我们使用Struct
构造体创建视图的方式构建了单张色卡的内容。
在单张色卡,我们构建了一个Rectangle
矩形作为背景,然后填充了一个“中国红”,顺便调整了它的大小和圆角。
依此再构建了两个Text
,作为颜色名称和颜色值,最后使用ZStack
将三个元素组成一个整体。
为什么需要使用Struct
构造体的方式,而不使用OpaqueTypes
不透明类型的方法,这是因为我们构建的色卡之后需要提取参数出来,然后在ContentView
视图中遍历多张色卡,因此使用Struct
构造体的方式创建视图。
参数抽离
接下拉,为了更加方便地遍历出多张色卡,我们将相同的参数进行抽离替换。
在色卡列表中,我们抽离色卡背景色、色卡颜色名称、色卡颜色值3个参数,示例:
var cardBGColor: Color var cardColorName: String var cardColorRBG: String
抽离出参数后,由于我们没有给CardViewExamples
构造体中的参数赋值,因此我们在ContentView
视图调用时需要补充参数对应的参数值。
CardViewExamples(cardBGColor: Color.Hex(0xFF0000), cardColorName: "中国红", cardColorRBG: "#FF0000")
浮窗口
单张色卡完成后,我们来尝试完成长按色卡唤起悬浮窗口并实现复制颜色值。
在之前的章节中,我们学习过ContextMenu
上下文菜单的使用,在这里我们依旧使用的是ContextMenu
// 长按复制颜色值 .contextMenu { Button(action: { UIPasteboard.general.string = cardColorName }, label: { Text("复制颜色值") }) } .padding(.horizontal)
上述代码中,我们使用ContextMenu
上下文菜单创建了悬浮窗口。
当我们长按单张色卡时,我们就把cardColorName
卡片颜色值的内容复制到剪切板中,这样我们就实现了通过长按复制色卡颜色值的交互。
数据模型
模型创建
完成单张色卡创建后,我们需要根据单张色卡的内容遍历多张色卡,通用的方式是创建模型数组,再根据模型数组结合网络请求,获得放在服务端的Json数据,然后再在本地渲染。
我们创建一个Swift
文件,命名为Model.swift
。
import SwiftUI struct CardModel:Decodable { var cardBGColor: UIn var cardColorName: String var cardColorRBG: String }
上述代码中,我们创建了一个结构体CardModel
,遵循Decodable
协议。
Decodable
协议,用于将JSON
对象解析为结构体或类,这样我们就可以通过网络请求获得JSON对象
,然后通过Decodable
协议解析在本地。
由于我们的色卡背景颜色是十六进制的颜色值,因此cardBGColor
参数需要声明为UInt
类型,其他cardColorName
、cardColorRBG
都是String
字符串类型。
我们回到ContentView
文件中,首先我们先获得定义好的CardModel
结构体。
@State var cardItems: [CardModel] = []
然后,我们使用OpaqueTypes
不透明类型的方法创建一个卡片视图,我们将CardListView
视图展示在ContentView
中展示。
// MARK: 色卡列表视图 private var CardListView: some View { ScrollView(.vertical, showsIndicators: false, content: { ForEach(cardItems, id: \.cardColorRBG) { item in VStack(spacing: 20) { CardViewExamples(cardBGColor: Color.Hex(item.cardBGColor), cardColorName: item.cardColorName, cardColorRBG: item.cardColorRBG) } } }) }
这样,色卡列表视图的框架就创建好了。
但是我们在预览时没有看到色卡,这是因为我们的色卡列表视图只是搭建了框架,但是没有数据,下面我们来完成数据部分。
Json数据
色卡框架搭建好后,我们在云端创建好Json
数据。
[{"cardBGColor":16711680,"cardColorRBG":"#FF0000","cardColorName":"中国红"},{"cardBGColor":15226919,"cardColorRBG":"#E85827","cardColorName":"爱马仕橙"},{"cardBGColor":16439902,"cardColorRBG":"#FADA5E","cardColorName":"拿破里黄"},{"cardBGColor":35980,"cardColorRBG":"#008C8C","cardColorName":"马尔斯绿"},{"cardBGColor":3175035,"cardColorRBG":"#30727B","cardColorName":"不来梅蓝"},{"cardBGColor":6901074,"cardColorRBG":"#694D52","cardColorName":"莫兰迪色"},{"cardBGColor":5338771,"cardColorRBG":"#517693","cardColorName":"马耳他蓝"},{"cardBGColor":12199,"cardColorRBG":"#002FA7","cardColorName":"克菜因蓝"}]
上述代码中,我们在某平台创建了Json
数据,并且获得了可以访问Json
数据的API
接口。
我们将API接口地址复制,并在ContentView
中声明一个常量存储它。
let JsonURL = "https://api.npoint.io/dc5a1718e0e958613ade"
网络请求
API接口有了,本地色卡列表视图框架也建立好了,下一步我们来完成网络请求部分。
// MARK: 网络请求 func getColors() { let session = URLSession(configuration: .default) session.dataTask(with: URL(string: JsonURL)!) { data, _, _ in guard let jsonData = data else { return } do { let colors = try JSONDecoder().decode([CardModel].self, from: jsonData) self.cardItems = colors } catch { print(error) } } .resume() }
上述代码中,我们创建了一个网络请求的方法getColors
,我们使用官方提供的URLSession
网络请求框架,然后获得URL
地址JsonURL
的数据,并通过JSONDecoder
解析json
数据与CardModel
数组中的参数做匹配,匹配上后将数据存储到cardItems
中。
我们在ContentView
视图展示的时候调用getColors
方法。
.onAppear(perform: { getColors() })
我们点击模拟器上的运行操作,预览下效果。
Loading加载
由于网络请求需要时间,为了让我们ColourAtla
色卡App
的体验更好,在色卡列表视图还没有加载完成时,我们可以增加一个Loading
视图作为缺省展示。示例:
if cardItems.isEmpty { Spacer() ProgressView() Spacer() } else { CardListView }
我们最后预览下整体效果。
以上就是本章全部内容。