【SwiftUI项目】0011、SwiftUI项目-费用跟踪-记账App项目-第3/3部分 -日期指定选定-新增费用页面

SwiftUI小功能模块系列
0001、SwiftUI自定义Tabbar动画效果
0002、SwiftUI自定义3D动画导航抽屉效果
0003、SwiftUI搭建瀑布流-交错网格-效果
0004、SwiftUI-<探探App>喜欢手势卡片
0005、SwiftUI-粘性动画指示器引导页
0006、SwiftUI自定义引导页动画
0007、SwiftUI聚光灯介绍说明
0008、SwiftUI-自定义启动闪屏动画-App启动闪屏曲线路径动画
0009、SwiftUI项目-费用跟踪-记账App项目-第1/3部分
0010、SwiftUI项目-费用跟踪-记账App项目-第2/3部分 -过滤日期详情明细页面以及抽离费用卡片
0011、SwiftUI项目-费用跟踪-记账App项目-第3/3部分 -日期指定选定-新增费用页面

技术:SwiftUI、SwiftUI3.0、费用跟踪、记账、随手记
运行环境:
SwiftUI3.0 + Xcode13.4.1 + MacOS12.5 + iPhone Simulator iPhone 13 Pro Max

概述

使用SwiftUI做一个记账/费用追踪/随手记的项目
本次添加日期选定模块 带有⭐️就是本次修改的内容

详细

一、运行效果

请添加图片描述

二、项目结构图

框选的是本次 比第一个版本新增的文件

在这里插入图片描述

三、程序实现 - 过程

思路:
1.创建首页 搭建头部 欢迎部分
2.搭建总费用模块
3. 搭建每笔支出收入的卡片
4. 将内容的第一个字母 作为图标展示
5. 视图模型提供 便捷的方法
6. 模型提供本地测试数据
7. 抽取公共模块 - 费用卡片
8. 当月/日期过滤费用详情页
9. 添加费用页面

1.创建一个项目命名为 ExpenseTracker

在这里插入图片描述
请添加图片描述

1.1.引入资源文件和颜色

颜色
BG #F2F3F6
Gray #A0B1C7
Green #12C7AA
Purple #8744E3
Red #ED4949
Yellow #F6C25A
Gradient1 #F2F3F6
Gradient2 #D06AF3
Gradient3 #F69080

随机图片5张

在这里插入图片描述

2. 创建一个虚拟文件New Group 命名为 View

在这里插入图片描述
在这里插入图片描述

2. 创建一个文件New File 选择SwiftUI View类型 命名为Home

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3. 创建一个虚拟文件New Group 命名为 Model

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3. 创建一个文件New File 选择SwiftUI View类型 命名为Expense 并改造成模型 继承于Identifiable,Hashable

主要是: 做模型 提供快速创建临时数据

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4. 创建一个虚拟文件New Group 命名为 ViewModel

在这里插入图片描述

在这里插入图片描述

4. 创建一个文件New File 选择SwiftUI View类型 命名为ExpenseViewModel 类型是class 继承于ObservableObject

主要是: 提供View展示的数据 。让view通过视图模型能快速转换得到想展示的值 。比如测试提供的price是int类型。那么视图模型就要提供int价格字符串价格的方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

5. 创建一个文件New File 选择SwiftUI View类型 命名为TransactionCardView

主要是用来显示首页列表的费用支付的卡片

在这里插入图片描述
在这里插入图片描述

6. 创建一个文件New File 选择SwiftUI View类型 命名为FilteredDetailView

主要展示 筛选日期的费用

在这里插入图片描述
在这里插入图片描述

7. 创建一个文件New File 选择SwiftUI View类型 命名为ExpenseCard - 抽离home的ExpenseCardView

主要 抽离是因为多个页面使用到总费用卡片

在这里插入图片描述
在这里插入图片描述

⭐️8. 创建一个文件New File 选择SwiftUI View类型 命名为NewExpense - 新增费用页面

在这里插入图片描述

Code

ContentView - 主窗口

主要是展示主窗口Home
添加了一个导航NavigationView

import SwiftUI

struct ContentView: View {
    
    
    var body: some View {
    
    
        NavigationView{
    
    
            Home().navigationBarHidden(true)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}

Home - 主页

  1. 展示头部的欢迎信息 和 查看当月的消费收入明细
  2. 费用总模块
  3. 每笔费用的详细 - 支出、收入、日期、内容
    改动 抽取 费用总模块跳转到当月的消费收入明细
    改动 ⭐️ 添加一个费用按钮、以及跳转到下一个页面
import SwiftUI

struct Home: View {
    
    
    @StateObject var expenseViewModel : ExpenseViewModel = .init()
    var body: some View{
    
    
        ScrollView(.vertical,showsIndicators: false) {
    
    
            VStack(spacing:12){
    
    
                HStack(spacing:15){
    
    
                    VStack(alignment:.center,spacing: 4){
    
    
                        Text("Welcome!")
                            .font(.caption)
                            .fontWeight(.semibold)
                            .foregroundColor(.gray)
                        
                        Text("宇夜iOS")
                            .font(.title2.bold())
                    }
                    .frame(maxWidth:.infinity,alignment: .leading)

                    NavigationLink {
    
    
                        FilteredDetailView()
                            .environmentObject(expenseViewModel)
                    } label: {
    
    
                        Image(systemName: "hexagon.fill")
                            .foregroundColor(.gray)
                            .overlay(content: {
    
    
                                Circle()
                                    .stroke(.white,lineWidth: 2)
                                    .padding(7)
                            })
                            .frame(width: 40, height: 40)
                            .background(Color.white,in: RoundedRectangle(cornerRadius: 10, style: .continuous))
                            .shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)
                    }
               
                }
                ExpenseCard()
                    .environmentObject(expenseViewModel)
                Transactions()

            }
            .padding()
        }
        .background{
    
    
            Color("BG")
                .ignoresSafeArea()
        }
        // 全屏覆盖
        .fullScreenCover(isPresented: $expenseViewModel.addNewExpense) {
    
    
                        expenseViewModel.clearData()
        } content: {
    
    
            NewExpense()
                .environmentObject(expenseViewModel)
        }
        .overlay(alignment: .bottomTrailing) {
    
    
            AddButton()
        }

        
    }
    // MARK: Transactions View
    // 卡片列表
    @ViewBuilder
    func Transactions() ->some View{
    
    
        VStack(spacing:15){
    
    
            Text("Transactions")
                .font(.title2.bold())
                .opacity(0.7)
                .frame(maxWidth:.infinity,alignment: .leading)
                .padding(.bottom)
            
            ForEach(expenseViewModel.expenses){
    
     expense in
                // MARK: Transaction Card View
                // 交易卡视图
                TransactionCardView(expense: expense)
                    .environmentObject(expenseViewModel)
            }
            
            
        }
    }
    
    // MARK: Add New Expense Button
    // 添加新的费用按钮
    @ViewBuilder
    func AddButton()->some View{
    
    
        Button {
    
    
            expenseViewModel.addNewExpense.toggle()
        } label: {
    
    
            Image(systemName: "plus")
                .font(.system(size: 25, weight: .medium))
                .foregroundColor(.white)
                .frame(width: 55, height: 55)
                .background{
    
    
                    Circle()
                        .fill(
                            .linearGradient(colors: [
                                Color("Gradient1"),
                                Color("Gradient2"),
                                Color("Gradient3"),
                            ], startPoint: .topLeading, endPoint: .bottomTrailing)
                        )
                }
                .shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)
        }
        .padding()
    }


    
  

}

struct Home_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}

TransactionCardView - 费用卡片视图

//
//  TransactionCardView.swift
//  ExpenseTracker (iOS)
//
//  Created by lyh on 2022/8/20.
//

import SwiftUI

struct TransactionCardView: View {
    
    
    var expense: Expense
    @EnvironmentObject var expenseViewModel: ExpenseViewModel
    var body: some View {
    
    
        
        HStack(spacing:12){
    
    
            // 显示出首字母
            if let first = expense.remark.first {
    
    
                Text(String(first))
                    .font(.title.bold())
                    .foregroundColor(.white)
                    .frame(width:50,height:50)
                    .background{
    
    
                        Circle()
                            .fill(Color(expense.color))
                    }
            }
            
            Text(expense.remark)
                .fontWeight(.semibold)
                .lineLimit(1)
                .frame(maxWidth:.infinity,alignment: .leading)
            
            VStack(alignment: .trailing, spacing: 7) {
    
    
                // 显示的价格
                let price = expenseViewModel.convertNumberToPrice(value: expense.type == .expense ? -expense.amount : expense.amount)
                Text(price)
                    .font(.callout)
                    .opacity(0.7)
                    .foregroundColor(expense.type == .expense ? Color("Red") : Color("Green"))

                Text(expense.date.formatted(date:.numeric,time:.omitted))
                    .font(.caption)
                    .opacity(0.5)
                
                
            }
            
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 15,style: .continuous)
                .fill(.white)
        )
        
    }
}

struct TransactionCardView_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        Home()
    }
}

Expense - 模型

主要提供模型数据 以及测试数据

//
//  Expense.swift
//  ExpenseTracker (iOS)
//
//  Created by lyh on 2022/8/20.
//

import SwiftUI

// 费用模型和样本数据
struct Expense: Identifiable,Hashable{
    
    
    var id = UUID().uuidString
    var remark: String
    var amount: Double
    var date: Date
    var type: ExpenseType
    var color: String
}

enum ExpenseType: String{
    
    
    case income = "Income"
    case expense = "expenses"
    case all = "ALL"
}

var sample_expenses: [Expense] = [
    Expense(remark: "Magic Keyboard", amount: 99, date: Date(timeIntervalSince1970: 1652987245), type: .expense, color: "Yellow"),
    Expense(remark: "Food", amount: 19, date: Date(timeIntervalSince1970: 1652814445), type: .expense, color: "Red"),
    Expense(remark: "Magic Trackpad", amount: 99, date: Date(timeIntervalSince1970: 1652382445), type: .expense, color: "Purple"),
    Expense(remark: "Uber Cab", amount: 20, date: Date(timeIntervalSince1970: 1652296045), type: .expense, color: "Green"),
    Expense(remark: "Amazon Purchase", amount: 299, date: Date(timeIntervalSince1970: 1652209645), type: .expense, color: "Yellow"),
    Expense(remark: "Stocks", amount: 2599, date: Date(timeIntervalSince1970: 1652036845), type: .income, color: "Purple"),
    Expense(remark: "In App Purchase", amount: 499, date: Date(timeIntervalSince1970: 1651864045), type: .income, color: "Red"),
    Expense(remark: "Movie Ticket", amount: 99, date: Date(timeIntervalSince1970: 1651691245), type: .expense, color: "Yellow"),
    Expense(remark: "Apple Music", amount: 25, date: Date(timeIntervalSince1970: 1651518445), type: .expense, color: "Green"),
    Expense(remark: "Snacks", amount: 49, date: Date(timeIntervalSince1970: 1651432045), type: .expense, color: "Purple"),
]

ExpenseViewModel - 视图模型

视图模型 主要提供 视图模型便利的方法

  1. 比如
    正在获取当前月份日期字符串
  2. 将费用换算成货币 - 计算总支付、支付、收入等部分
  3. 把数字转换成价格
  4. 添加了当月费用明细的tab选项 以及 将选定日期进行转换成字符串
  5. 添加 ⭐️ 添加是否显示过滤日期页面
  6. ⭐️添加新费用的数据
  7. ⭐️新费用 进行保存 或者 清空数据的情况
//
//  ExpenseViewModel.swift
//  ExpenseTracker (iOS)
//
//  Created by lyh on 2022/8/20.
//

import SwiftUI

class ExpenseViewModel: ObservableObject {
    
    
    @Published var startDate: Date = Date()
    @Published var endDate: Date = Date()
    @Published var currentMonthStartDate: Date = Date()

    
    // 费用/收入表
    @Published var tabName : ExpenseType = .expense
    // ⭐️MARK: Filter View
    // 过滤视图
    @Published var showFilterView : Bool = false
    
    // ⭐️新的费用属性
    @Published var addNewExpense : Bool = false
    @Published var amount : String = ""
    @Published var type : ExpenseType = .all
    @Published var date : Date = Date()
    @Published var remark : String = ""

    init(){
    
    
        // 读取当前月份的起始日期
        let calendar = Calendar.current
        let components = calendar.dateComponents([.year,.month], from: Date())
        
        startDate = calendar.date(from: components)!
        currentMonthStartDate = calendar.date(from: components)!

    }
    
    // //你可以自定义更多的数据(Core Data)
    @Published var expenses : [Expense] = sample_expenses
    
    // 正在获取当前月份日期字符串
    func currentMonthDateString()->String{
    
    
        return currentMonthStartDate.formatted(date: .abbreviated, time: .omitted) + " - " + Date().formatted(date: .abbreviated, time: .omitted)
    }
    
    // 将费用换算成货币
    func convertExpensesToCurrency(expenses: [Expense],type:ExpenseType = .all)-> String {
    
    
        var value : Double = 0
        value = expenses.reduce(0, {
    
     partialResult, expense in
            return partialResult + (type == .all ? (expense.type == .income ? expense.amount : -expense.amount) : (expense.type == type ? expense.amount : 0))

        })
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        
        return formatter.string(from: .init(value: value)) ?? "$0.00"
    }
    
    
    // 把数字转换成价格
    func convertNumberToPrice(value: Double)->String{
    
    
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        
        return formatter.string(from: .init(value: value)) ?? "$0.00"
    }
    
    // 将选定日期转换为字符串
    func convertDateToString() -> String{
    
    
        return startDate.formatted(date: .abbreviated, time: .omitted) + " - " +
        endDate.formatted(date: .abbreviated, time: .omitted)

    }
    
    
    // ⭐️清除所有数据
    func clearData(){
    
    
        date = Date()
        type = .all
        remark = ""
        amount = ""
    }
    
    // ⭐️保存数据
    func saveData(env: EnvironmentValues){
    
    
        // 在这里做动作
        print("Save")
        //这是为UI演示
        //替换为Core Data Actions
        let amountInDouble = (amount as NSString).doubleValue
        let colors = ["Yellow","Red","Purple","Green"]
        let expense = Expense(remark: remark, amount: amountInDouble, date: date, type: type, color: colors.randomElement() ?? "Yellow")
        withAnimation{
    
    expenses.append(expense)}
        expenses = expenses.sorted(by: {
    
     first, scnd in
            return scnd.date < first.date
        })
        env.dismiss()
    }



}


FilteredDetailView - 过滤日期详情页

主要展示 当个月的1号 到至今的一个收入与消费的详情页面
注意 没有实现 - 用户可以根据日期 展示指定日期的总费用 - 只实现效果

//
//  FilteredDetailView.swift
//  ExpenseTracker (iOS)
//
//  Created by lyh on 2022/8/21.
//

import SwiftUI

struct FilteredDetailView: View {
    
    
    @EnvironmentObject var expenseViewModel: ExpenseViewModel
    // 环境值
    @Environment(\.self) var env
    
    @Namespace var animation
    var body: some View {
    
    
        
        ScrollView(.vertical,showsIndicators: false){
    
    
            VStack(spacing:15){
    
    
                HStack(spacing:15){
    
    
                    // 返回按钮
                    Button  {
    
    
                        env.dismiss()
                    } label: {
    
    
                        Image(systemName: "arrow.backward.circle.fill")
                            .foregroundColor(.gray)
                            .frame(width: 40, height: 40)
                            .background(Color.white,in: RoundedRectangle(cornerRadius: 10, style: .continuous))
                            .shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)

                    }

                    
                    Text("Transactions")
                        .font(.title2.bold())
                        .frame(maxWidth:.infinity,alignment: .leading)

                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "slider.horizontal.3")
                            .foregroundColor(.gray)
                            .frame(width: 40, height: 40)
                            .background(Color.white,in: RoundedRectangle(cornerRadius: 10, style: .continuous))
                            .shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)
                    }

               
                }
                
                // 当前选择日期的费用卡视图
                ExpenseCard()
                    .environmentObject(expenseViewModel)
                
                CustomSegmentedControl()
                    .padding(.top)
                
                // 当前过滤日期与金额
                VStack(spacing:15) {
    
    
                    Text(expenseViewModel.convertDateToString())
                        .opacity(0.7)
                    Text(expenseViewModel.convertExpensesToCurrency(expenses: expenseViewModel.expenses, type: expenseViewModel.tabName))
                        .font(.title.bold())
                        .opacity(0.9)
                        .animation(.none,value:expenseViewModel.tabName)
                }
                .padding()
                .frame(maxWidth:.infinity)
                .background{
    
    
                    RoundedRectangle(cornerRadius: 15,style: .continuous)
                        .fill(.white)
                }
                .padding(.vertical,20)
                
                ForEach(expenseViewModel.expenses.filter{
    
    
                    return $0.type == expenseViewModel.tabName
                }){
    
     expense in
                    TransactionCardView(expense: expense)
                        .environmentObject(expenseViewModel)
                }
                
            }
            .padding()

        }
        .navigationBarHidden(true)
        .background{
    
    
            Color("BG")
                .ignoresSafeArea()
        }

    }
    
    // 自定义分段控制
    @ViewBuilder
    func CustomSegmentedControl()-> some View{
    
    
        HStack(spacing:0){
    
    
            
            ForEach([ExpenseType.income,ExpenseType.expense],id:\.rawValue) {
    
    tab in
                
                Text(tab.rawValue.capitalized)
                    .fontWeight(.semibold)
                    .foregroundColor(expenseViewModel.tabName == tab ? .white : .black)
                    .opacity(expenseViewModel.tabName == tab ? 1 : 0.7)
                    .padding(.vertical,12)
                    .frame(maxWidth:.infinity)
                    .background{
    
    
                         // 与匹配的几何 
                        if expenseViewModel.tabName == tab {
    
    
                            RoundedRectangle(cornerRadius: 10, style: .continuous)
                                .fill(
                                    LinearGradient(colors: [
                                        Color("Gradient1"),
                                        Color("Gradient2"),
                                        Color("Gradient3"),
                                    ], startPoint: .topLeading, endPoint: .bottomTrailing)
                                )
                                .matchedGeometryEffect(id: "TAB", in: animation)
                        }
                    }
                    .contentShape(Rectangle())
                    .onTapGesture {
    
    
                        withAnimation {
    
    expenseViewModel.tabName = tab}
                    }
                
            }
        }
        .padding(5)
        .background{
    
    
            RoundedRectangle(cornerRadius: 10, style: .continuous)
                .fill(.white)
        }
       
    }
}

struct FilteredDetailView_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        FilteredDetailView().environmentObject(ExpenseViewModel())
    }
}

ExpenseCard - 费用卡片 - 从首页抽离

为什么要抽离 因为页面公用了

  1. 首页用到
  2. 过滤日期页面用到
  3. ⭐️ 添加了一个 是否为过滤视图展示
    因为过滤日期页面直接进去 默认是显示当前月展示操作
    如果过滤日期页通过选择日期 就需要展示 当前选择日期的操作
import SwiftUI

struct ExpenseCard: View {
    
    
    
    @EnvironmentObject var expenseViewModel : ExpenseViewModel
    var isFilter : Bool = false
    var body: some View {
    
    
        GeometryReader{
    
    proxy in
            RoundedRectangle(cornerRadius: 20,style: .continuous)
                .fill(.linearGradient(colors: [
                Color("Gradient1"),
                Color("Gradient2"),
                Color("Gradient3")],startPoint: .topLeading, endPoint: .bottomTrailing))
            
            
            VStack(spacing: 15){
    
    
                VStack(spacing:15){
    
    
                    // MARK : Currently Going Month Date String
                    // 当前月日期字符串
                    // 如果是过滤日期 就显示选择的日期  否则显示 当前月的日期
                    Text(isFilter ?expenseViewModel.convertDateToString() : expenseViewModel.currentMonthDateString())
                        .font(.callout)
                        .fontWeight(.semibold)
                    
                    // MARK: Current Month Expenses Price
                    // 本月费用价格
                    
                    Text(expenseViewModel.convertExpensesToCurrency(expenses: expenseViewModel.expenses))
                        .font(.system(size: 35, weight: .bold))
                        .lineLimit(1)
                        .padding(.bottom,5)

                }
                .offset(y:-10)
                
                HStack(spacing: 15){
    
    
                    Image(systemName: "arrow.down")
                        .font(.caption.bold())
                        .foregroundColor(Color("Green"))
                        .frame(width:30,height: 30)
                        .background(.white.opacity(0.7),in:Circle())
                    
                    VStack(alignment: .leading, spacing: 4) {
    
    
                        Text("Income")
                            .font(.caption)
                            .opacity(0.7)
                        
                        Text(expenseViewModel.convertExpensesToCurrency(expenses: expenseViewModel.expenses, type: .income))
                            .font(.callout)
                            .fontWeight(.semibold)
                            .lineLimit(1)
                            .fixedSize()
                    }
                    .frame(maxWidth:.infinity,alignment: .leading)
                    
                    
                    Image(systemName: "arrow.up")
                        .font(.caption.bold())
                        .foregroundColor(Color("Red"))
                        .frame(width:30,height: 30)
                        .background(.white.opacity(0.7),in:Circle())
                    
                    VStack(alignment: .leading, spacing: 4) {
    
    
                        Text("Expenses")
                            .font(.caption)
                            .opacity(0.7)
                        
                        Text(expenseViewModel.convertExpensesToCurrency(expenses: expenseViewModel.expenses, type: .expense))
                            .font(.callout)
                            .fontWeight(.semibold)
                            .lineLimit(1)
                            .fixedSize()
                    }
                }
                .padding(.horizontal)
                .padding(.trailing)
                .offset(y:15)
            }
            .foregroundColor(.white)
            .frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .center)
        }
        .frame(height:220)
        .padding(.top)
    }
}

 MARK: Expense Gradient CardView
 费用卡片
//@ViewBuilder
//func ExpenseCardView() -> some View{
    
    
//
//}

⭐️NewExpense - 新增费用页

  1. 搭建关闭按钮
  2. 输入模块
  3. 选择日期
  4. 复选框 选择支出 还是 收入
  5. 保存操作 - 需要判断用户是否填写收入还是支出、和金额

//
//  NewExpense.swift
//  ExpenseTracker (iOS)
//
//  Created by lyh on 2022/8/22.
//

import SwiftUI

struct NewExpense: View {
    
    
    @EnvironmentObject var expenseViewModel : ExpenseViewModel
    // MARK: Enviroment Values
    
    @Environment(\.self) var env

    var body: some View {
    
    
        VStack{
    
    
            VStack(spacing:15){
    
    
                Text("Add Expenses")
                    .font(.title2)
                    .fontWeight(.semibold)
                    .opacity(0.5)
                //自定义TextField
                //对于货币符号
                if let symbol = expenseViewModel.convertNumberToPrice(value: 0).first{
    
    
                    TextField("0", text: $expenseViewModel.amount)
                        .font(.system(size: 35))
                        .foregroundColor(Color("Gradient2"))
                        .multilineTextAlignment(.center)
                        .keyboardType(.numberPad)
                        .background{
    
    
                            Text(expenseViewModel.amount == "" ? "0" : expenseViewModel.amount)
                                .font(.system(size: 35))
                                .opacity(0)
                                .overlay(alignment: .leading) {
    
    
                                    Text(String(symbol))
                                        .opacity(0.5)
                                        .offset(x: -15, y: 5)
                                }
                        }
                        .padding(.vertical,10)
                        .frame(maxWidth: .infinity)
                        .background{
    
    
                            Capsule()
                                .fill(.white)
                        }
                        .padding(.horizontal,20)
                        .padding(.top)
                }
                
                // 自定义标签
                Label{
    
    
                    TextField("Remark",text:$expenseViewModel.remark)
                        .padding(.leading,10)
                    
                } icon:{
    
    
                    Image(systemName:"list.bullet.rectangle.portrait.fill")
                        .font(.title3)
                        .foregroundColor(Color("Gray"))
                }
                .padding(.vertical,20)
                .padding(.horizontal,15)
                .background{
    
    
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .fill(.white)
                }
                .padding(.top,25)
                
                
                Label{
    
    
                    // 复选框
                    CustomCheckBoxes()
                } icon:{
    
    
                    Image(systemName:"arrow.up.arrow.down")
                        .font(.title3)
                        .foregroundColor(Color("Gray"))
                }
                .padding(.vertical,20)
                .padding(.horizontal,15)
                .background{
    
    
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .fill(.white)
                }
                .padding(.top,25)
                
                Label{
    
    
                    DatePicker.init("",
                                    selection: $expenseViewModel.date,
                                    in: Date.distantPast...Date(),
                                    displayedComponents:[.date])
                        .datePickerStyle(.compact)
                        .labelsHidden()
                        .frame(maxWidth:.infinity,alignment:.leading)
                        .padding(.leading,10)
                    
                } icon:{
    
    
                    Image(systemName:"calendar")
                        .font(.title3)
                        .foregroundColor(Color("Gray"))
                }
                .padding(.vertical,20)
                .padding(.horizontal,15)
                .background{
    
    
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .fill(.white)
                }
                .padding(.top,55)
                
            }
            .frame(maxHeight:.infinity,alignment:.center)
            
            // 保存按钮
            Button(action: {
    
    expenseViewModel.saveData(env: env)}) {
    
    
                Text("Save")
                    .font(.title3)
                    .fontWeight(.semibold)
                    .padding(.vertical,15)
                    .frame(maxWidth: .infinity)
                    .background{
    
    
                        RoundedRectangle(cornerRadius: 12, style: .continuous)
                            .fill(
                                LinearGradient(colors: [
                                    Color("Gradient1"),
                                    Color("Gradient2"),
                                    Color("Gradient3"),
                                ], startPoint: .topLeading, endPoint: .bottomTrailing)
                            )
                    }
                    .foregroundColor(.white)
                    .padding(.bottom,10)
            }
            .disabled(expenseViewModel.remark == "" || expenseViewModel.type == .all || expenseViewModel.amount == "")
            .opacity(expenseViewModel.remark == "" || expenseViewModel.type == .all || expenseViewModel.amount == "" ? 0.6 : 1)

        }
        .padding()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background{
    
    
            Color("BG")
                .ignoresSafeArea()
        }
        .overlay(alignment: .topTrailing) {
    
    
            // 关闭按钮
            Button  {
    
    
                env.dismiss()
            } label: {
    
    
                Image(systemName: "xmark")
                    .font(.title2)
                    .foregroundColor(.black)
            }
            .padding()

        }
    }
    
    
    // 复选框
    func CustomCheckBoxes()->some View{
    
    
        HStack(spacing:10){
    
    
            ForEach([ExpenseType.income,ExpenseType.expense],id: \.self){
    
     type in
                ZStack{
    
    
                    RoundedRectangle(cornerRadius: 2)
                        .stroke(.black,lineWidth: 2)
                        .opacity(0.5)
                        .frame(width: 20, height: 20)
                    
                    if expenseViewModel.type == type {
    
    
                        Image(systemName: "checkmark")
                            .font(.caption.bold())
                            .foregroundColor(Color("Green"))
                    }
                }
                .contentShape(Rectangle())
                .onTapGesture {
    
    
                    expenseViewModel.type = type
                }
                
                Text(type.rawValue.capitalized)
                    .font(.callout)
                    .fontWeight(.semibold)
                    .opacity(0.7)
                    .padding(.trailing,10)
            }
        }
        .frame(maxWidth:.infinity,alignment: .leading)
        .padding(.leading,10)
    }
        
}

struct NewExpense_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        NewExpense()
            .environmentObject(ExpenseViewModel())
    }
}

猜你喜欢

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