技术:SwiftUI、SwiftUI3.0、基金、走势图、蚂蚁财富
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max
概述
使用SwiftUI做一个一个类似
蚂蚁财富
的基金
累计盈亏
的走势图 效果
蚂蚁财富-基金盈亏效果图
详细
一、运行效果
二、项目结构图
三、程序实现 - 过程
思路:
1.创建头部模块
3.搭建底部模块
2.搭建图表模块 - 单独抽取
- 绘制走势图的图表
- 设置背景
- 画出当前图表的指针表
- 通过拖拽手势 进行进行指针表 更新位置
1.创建一个项目命名为 CustomScrollViewBottomShee
1.1.引入资源文件和颜色
颜色
BG#FCF9FF
Gradient1#9555AC
Gradient2#D9807D
随机图片9张
个人大图背景1张
logo1张扫描二维码关注公众号,回复: 14508957 查看本文章
2. 创建一个虚拟文件New Group
命名为 View
3. 创建一个虚拟文件New Group
命名为 Model
4. 创建一个文件New File
选择SwiftUI View
类型 命名为Home
5. 创建一个文件New File
选择SwiftUI View
类型 命名为LineGraph
Code
ContentView - 主窗口
主要是展示主窗口
Home
//
// ContentView.swift
// Shared
//
// Created by lyh on 2022/8/24.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Home()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Home - 主页
思路
- 主要就是展示
头部
图形
(逻辑比较复杂就单独抽取封装)
底部
的卡片
//
// Home.swift
// CustomScrollViewBottomShee (iOS)
//
// Created by lyh on 2022/8/24.
//
import SwiftUI
struct Home: View {
var body: some View {
VStack{
HStack{
Button {
} label: {
Image(systemName: "slider.vertical.3")
.font(.title2)
}
Spacer()
Button{
}label:{
Image("ijustine")
.resizable()
.aspectRatio( contentMode:.fit)
.frame(width: 45, height: 45)
.clipShape(Circle())
}
}
.padding()
.foregroundColor(.black)
VStack(spacing:10){
Text("Total Balance")
.fontWeight(.bold)
Text("$51 200")
.font(.system(size: 38,weight:.bold))
}
.padding(.top,20)
Button{
} label: {
HStack(spacing:5){
Text("Income")
Image(systemName: "chevron.down")
}
.font(.caption.bold())
.padding(.vertical,10)
.padding(.horizontal)
.background(.white, in: Capsule())
.foregroundColor(.black)
.shadow(color: .black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: .black.opacity(0.05), radius: 5, x: -5, y: -5)
}
// 图形视图…
LineGraph(data: samplePlot)
// 最大尺寸
.frame(height:220)
.padding(.top,25)
Text("Shorcuts")
.font(.title.bold())
.frame(maxWidth: .infinity,alignment: .leading)
.padding()
.padding(.top)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20){
CardView(image: "youtube", title: "YouTube", price: "$ 26", color: Color("Gradient1"))
CardView(image: "apple", title: "Apple", price: "$ 2600", color: Color("Gradient2"))
CardView(image: "xbox", title: "XBox", price: "$ 120", color: Color.green)
}
.padding()
}
}
.frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .top)
.background(Color("BG"))
}
// 卡片视图
@ViewBuilder
func CardView(image: String,title: String,price: String,color: Color)->some View{
VStack(spacing: 15){
Image(image)
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
.frame(width: 35, height: 35)
.padding()
.background(color,in: Circle())
Text(title)
.font(.title3.bold())
Text(price)
.fontWeight(.semibold)
.foregroundColor(.gray)
}
.padding(.vertical)
.padding(.horizontal,25)
.background(.white,in: RoundedRectangle(cornerRadius: 15))
// shadows...
.shadow(color: .black.opacity(0.05), radius: 5, x: 5, y: 5)
.shadow(color: .black.opacity(0.03), radius: 5, x: -5, y: -5)
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
// 图…的样本图
let samplePlot: [CGFloat] = [
989,1200,750,790,650,950,1200,600,500,600,890,1203,1400,900,1250,
1600,1200
]
LineGraph - 主要是用来画图表
思路
- 绘制走势图的图表
- 设置背景
- 画出当前图表的指针表
- 通过拖拽手势 进行进行指针表 更新位置
//
// LineGraph.swift
// CustomScrollViewBottomShee (iOS)
//
// Created by lyh on 2022/8/24.
//
import SwiftUI
// Custom View...
struct LineGraph: View {
// 当前图表
@State var currentPlot = ""
// 偏移
@State var offset : CGSize = .zero
@State var showPlot = false
@State var translation : CGFloat = 0
var data : [CGFloat]
var body: some View {
GeometryReader{
proxy in
let height = proxy.size.height
let width = (proxy.size.width) / CGFloat(data.count-1)
let maxPoint = (data.max() ?? 0) + 100
let points = data.enumerated().compactMap {
item -> CGPoint in
// 获取进度\和高度相乘
let progress = item.element / maxPoint
let pathHeight = progress * height
//宽度
let pathWidth = width * CGFloat(item.offset)
// 因为我们需要的是顶部而不是底部…
return CGPoint(x:pathWidth,y:-pathHeight + height)
}
ZStack{
// 将图转换为点
// 路径
Path {
path in
// 画点
path.move(to: CGPoint(x: 0, y: 0))
path.addLines(points)
}
.strokedPath(StrokeStyle(lineWidth: 2.5, lineCap: .round, lineJoin: .round))
.fill(
// Gradient...
// 渐变颜色
LinearGradient(colors: [
Color("Gradient1"),
Color("Gradient2"),
], startPoint: .leading, endPoint: .trailing)
)
FillBG()
// 剪裁的形状……
.clipShape(
Path {
path in
// 画点
path.move(to: CGPoint(x: 0, y: 0))
path.addLines(points)
path.addLine(to: CGPoint(x: proxy.size.width, y: height))
path.addLine(to: CGPoint(x:0, y: height))
}
)
.padding(.top,15)
}
.overlay(
// 阻力指标……
VStack(spacing:0){
Text(currentPlot)
.font(.caption.bold())
.foregroundColor(.white)
.padding(.vertical,6)
.padding(.horizontal,10)
.background(Color("Gradient1"),in:Capsule())
.offset(x:translation < 10 ? 30: 0)
.offset(x:translation > (proxy.size.width - 60) ? -30 : 0)
Rectangle()
.fill(Color("Gradient1"))
.frame(width:1,height:40)
Circle()
.fill(Color("Gradient1"))
.frame(width:22,height:22)
.overlay(
Circle()
.fill(.white)
.frame(width:10,height:10)
)
Rectangle()
.fill(Color("Gradient1"))
.frame(width:1,height:50)
}
// frame设置...
// 锻造计算
.frame(width:80,height:170)
// 170 / 2 = 85 - 15 = 70 => circle ring size...
.offset(y:70)
.offset(offset)
.opacity(showPlot ? 1 : 0),
alignment: .bottomLeading
)
.contentShape(Rectangle())
.gesture(DragGesture().onChanged({
value in
withAnimation{
showPlot = true}
let translation = value.location.x - 40
// 得到指数……
let index = max(min(Int((translation / width).rounded() + 1),data.count - 1),0)
currentPlot = "$ \(data[index])"
self.translation = translation
// 删除一半宽度……
offset = CGSize(width: points[index].x - 40, height: points[index].y - height)
}).onEnded({
value in
withAnimation{
showPlot = false}
}))
}
.overlay(
VStack(alignment: .leading){
let max = data.max() ?? 0
Text("$ \(Int(max))")
.font(.caption.bold())
Spacer()
Text("$ 0")
.font(.caption.bold())
}
.frame(maxWidth:.infinity,alignment: .leading)
)
.padding(.horizontal,10)
}
@ViewBuilder
func FillBG()->some View {
// 路径背景颜色…
LinearGradient(colors: [
Color("Gradient2").opacity(0.3),
Color("Gradient2").opacity(0.3),
Color("Gradient2").opacity(0.1)]
+ Array(repeating: Color("Gradient1").opacity(0.1), count: 4)
+ Array(repeating: Color.clear, count: 2),
startPoint: .top, endPoint: .bottom)
}
}
struct LineGraph_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}