【SwiftUI模块】0013、SwiftUI搭建-类似蚂蚁财富的基金累计盈亏的走势图

SwiftUI模块系列 - 已更新13篇
SwiftUI项目 - 已更新1个项目
往期Demo源码下载

技术:SwiftUI、SwiftUI3.0、基金、走势图、蚂蚁财富
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max

概述

使用SwiftUI做一个一个类似蚂蚁财富基金累计盈亏的走势图 效果

蚂蚁财富-基金盈亏效果图
在这里插入图片描述

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:
1.创建头部模块
3.搭建底部模块
2.搭建图表模块 - 单独抽取

  1. 绘制走势图的图表
  2. 设置背景
  3. 画出当前图表的指针表
  4. 通过拖拽手势 进行进行指针表 更新位置

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 - 主页

思路

  1. 主要就是展示
    头部
    图形(逻辑比较复杂就单独抽取封装)
    底部的卡片
//
//  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 - 主要是用来画图表

思路

  1. 绘制走势图的图表
  2. 设置背景
  3. 画出当前图表的指针表
  4. 通过拖拽手势 进行进行指针表 更新位置
//
//  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()
    }
}

猜你喜欢

转载自blog.csdn.net/qq_42816425/article/details/126495895