技术:SwiftUI、SwiftUI4.0、任务管理、TodoList、任务清单
运行环境:
SwiftUI4.0 + Xcode14 + MacOS12.6 + iPhone Simulator iPhone 14 Pro Max
SwiftUI搭建一个TodoList任务清单的App 1/2部分-UI部分
- 概述
- 详细
- 一、运行效果
- 二、项目结构图
- 三、程序实现 - 过程
- 1.创建一个项目命名为 `TaskManagement`
- 1.1.引入资源文件和颜色
- 2. 创建一个虚拟文件`New Group` 命名为 `View`
- 3. 创建一个虚拟文件`New Group` 命名为 `Model`
- 4. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Task` 删除预览图 并且继承`Identifiable` 作为模型
- 5. 创建一个虚拟文件`New Group` 命名为 `ViewModel`
- 6. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`TaskViewModel` 类型为`class` 删除预览图 并且继承`ObservableObject` 作为模型
- 6. 创建一个文件`New File` 选择`SwiftUI View`类型 命名为`Home`
- Code
概述
使用SwiftUI搭建一个TodoList任务清单的App 1/2部分(UI部分)
详细
一、运行效果
二、项目结构图
三、程序实现 - 过程
思路:
1.创建头部模块 日期+头像
2.搭建日期选择 - 一周时间
3.任务卡片
4.监听日期选择 更新任务数据
1.创建一个项目命名为 TaskManagement
1.1.引入资源文件和颜色
颜色
Black#2E2E2E
头像1张
随机图片3张
2. 创建一个虚拟文件New Group
命名为 View
3. 创建一个虚拟文件New Group
命名为 Model
4. 创建一个文件New File
选择SwiftUI View
类型 命名为Task
删除预览图 并且继承Identifiable
作为模型
5. 创建一个虚拟文件New Group
命名为 ViewModel
6. 创建一个文件New File
选择SwiftUI View
类型 命名为TaskViewModel
类型为class
删除预览图 并且继承ObservableObject
作为模型
主要是:作为视图模型
6. 创建一个文件New File
选择SwiftUI View
类型 命名为Home
Code
ContentView - 主窗口
主要是展示主窗口
Home
//
// ContentView.swift
// TaskManagement
//
// Created by 李宇鸿 on 2022/9/23.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Home()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Home - 主页
思路
1.创建头部模块 日期+头像
2.搭建日期选择 - 一周时间
3.任务卡片
4.监听日期选择 更新任务数据
//
// Home.swift
// TaskManagement
//
// Created by 李宇鸿 on 2022/9/23.
//
import SwiftUI
struct Home: View {
@StateObject var taskModel: TaskViewModel = TaskViewModel()
@Namespace var animation
var body: some View {
ScrollView(.vertical,showsIndicators: false){
// LazyVStack 和 固定头部
LazyVStack(spacing:15,pinnedViews: [.sectionHeaders]) {
Section {
// 2.当前星期视图
ScrollView(.horizontal,showsIndicators: false){
// 数据从视图模型获取
HStack(spacing:10){
ForEach(taskModel.currentWeek,id:\.self){day in
VStack{
Text(taskModel.extractDate(date: day, format: "dd"))
.font(.system(size:15))
// EEE将在周一、周二、....等返回
Text(taskModel.extractDate(date: day, format: "EEE"))
.font(.system(size:14))
Circle()
.fill(.white)
.frame(width:8,height: 8)
.opacity(taskModel.isToday(date: day) ? 1 : 0)
}
.foregroundStyle(taskModel.isToday(date: day) ? .primary : .secondary)
.foregroundColor(taskModel.isToday(date: day) ? .white: .black)
// MARK: Capsule Shape
// 胶囊的形状
.frame(width: 45,height: 90)
.background(
ZStack{
// 几何匹配效果
if taskModel.isToday(date: day){
Capsule()
.fill(.black)
.matchedGeometryEffect(id: "CURRENTDAY", in: animation)
}
}
)
.containerShape(Capsule())
.onTapGesture {
// 更新当前天
withAnimation {
taskModel.currentDay = day
}
}
}
}
.padding(.horizontal)
}
// 2. 任务视图
TaskView()
} header: {
// 1.头部视图
HeaderView()
}
}
}
// 防止任务视图超出安全区域
.ignoresSafeArea(.container,edges: .top)
}
// func 任务视图
func TaskView() -> some View{
LazyVStack(spacing:20) {
if let tasks = taskModel.filteredTasks{
if tasks.isEmpty{
Text("Not tasks found!!!!")
.font(.system(size: 16))
.fontWeight(.light)
.offset(y:100)
}
else{
ForEach(tasks){task in
TaskCardView(task: task)
}
}
}
else {
// 进程视图
ProgressView()
.offset(y:100)
}
}
.padding()
.padding(.top)
// 更新任务
.onChange(of: taskModel.currentDay) { newValue in
taskModel.filterTodayTasks()
}
}
// func 任务卡片视图
func TaskCardView(task: Task) -> some View{
HStack(alignment:.top,spacing: 15){
// Text(task.taskTitle)
VStack(spacing:10){
Circle()
.fill(taskModel.isCurrentHour(date: task.taskDate) ? .black : .clear)
.frame(width: 15,height: 15)
.background(
Circle()
.stroke(.black,lineWidth: 1)
.padding(-3)
)
.scaleEffect(!taskModel.isCurrentHour(date: task.taskDate) ? 0.8 : 1)
Rectangle()
.fill(.black)
.frame(width:3)
}
VStack{
HStack(alignment: .top,spacing:10) {
VStack(alignment: .leading,spacing:12) {
Text(task.taskTitle)
.font(.title2.bold())
Text(task.taskDescription)
.font(.callout)
.foregroundStyle(.secondary)
}
.hLeading()
Text(task.taskDate.formatted(date: .omitted, time: .shortened))
}
// 检查当前小时 如果是就显示参加会议的人数头像
if taskModel.isCurrentHour(date: task.taskDate){
// 团队成员
HStack(spacing:0){
HStack(spacing:-10){
ForEach(["User1","User2","User3"],id: \.self) { user in
Image(user)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 45,height: 45)
.clipShape(Circle())
.background(
Circle()
.stroke(.black, lineWidth: 5)
)
}
}
.hLeading()
// 检查按钮
Button {
} label: {
Image(systemName: "checkmark")
.foregroundStyle(.black)
.padding(10)
.background(Color.white,in:RoundedRectangle(cornerRadius: 10))
}
}
.padding(.top)
}
}
.foregroundColor(taskModel.isCurrentHour(date: task.taskDate) ? .white : .black)
.padding(taskModel.isCurrentHour(date: task.taskDate) ? 15 : 0)
.padding(.bottom,taskModel.isCurrentHour(date: task.taskDate) ? 0 : 10)
.hLeading()
.background(
Color("Black")
.cornerRadius(25)
.opacity(taskModel.isCurrentHour(date: task.taskDate) ? 1 : 0)
)
}
.hLeading()
}
// func 头部视图
func HeaderView() -> some View{
HStack(spacing:10){
VStack(alignment: .leading,spacing: 10) {
Text(Date().formatted(date:.abbreviated,time: .omitted))
.foregroundColor(.gray)
Text("Today")
.font(.largeTitle.bold())
}
.hLeading()
Button {
} label: {
Image("Profile")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 45,height: 45)
.clipShape(Circle())
}
}
.padding()
.padding(.top,getSafeArea().top)
.background(Color.white)
}
}
struct Home_Previews: PreviewProvider {
static var previews: some View {
Home()
}
}
// UI设计辅助函数
extension View {
func hLeading()-> some View{
self
.frame(maxWidth:.infinity,alignment: .leading)
}
func hTrailing()-> some View{
self
.frame(maxWidth:.infinity,alignment: .trailing)
}
func hCenter()-> some View{
self
.frame(maxWidth:.infinity,alignment: .center)
}
// 安全区域
func getSafeArea() -> UIEdgeInsets{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .zero
}
guard let safeArea = screen.windows.first?.safeAreaInsets else {
return .zero
}
return safeArea
}
}
Task - 模型
//
// Task.swift
// TaskManagement
//
// Created by 李宇鸿 on 2022/9/23.
//
import SwiftUI
// Task Model
struct Task: Identifiable{
var id = UUID().uuidString
var taskTitle: String
var taskDescription: String
var taskDate: Date
}
TaskManagement - 视图模型
//
// TaskViewModel.swift
// TaskManagement
//
// Created by 李宇鸿 on 2022/9/23.
//
import SwiftUI
class TaskViewModel: ObservableObject {
@Published var storedTasks: [Task] = [
Task(taskTitle: "Meeting", taskDescription: "Discuss team task for the day", taskDate: .init(timeIntervalSince1970: 1663930000)),
Task(taskTitle: "Icon set", taskDescription: "Edit icons for team task for next week", taskDate: .init(timeIntervalSince1970: 1663940000)),
Task(taskTitle: "Prototype", taskDescription: "Make and send prototype", taskDate: .init(timeIntervalSince1970: 1663950000)),
Task(taskTitle: "Check asset", taskDescription: "Start checking the assets", taskDate: .init(timeIntervalSince1970: 1663960000)),
Task(taskTitle: "Team party", taskDescription: "Make fun with team mates", taskDate: .init(timeIntervalSince1970: 1663970000)),
Task(taskTitle: "Client Meeting", taskDescription: "Explain project to clinet", taskDate: .init(timeIntervalSince1970: 1663998000)),
Task(taskTitle: "Next Project", taskDescription: "Discuss next project with team", taskDate: .init(timeIntervalSince1970: 1664030000)),
Task(taskTitle: "App Proposal", taskDescription: "Meet client for next App Proposal", taskDate: .init(timeIntervalSince1970: 1664100000)),
]
// 当前日期的周
@Published var currentWeek: [Date] = []
// 当前日期的今天
@Published var currentDay: Date = Date()
// 过滤获取今天的任务
@Published var filteredTasks : [Task]?
init(){
fetchCurrentWeek()
filterTodayTasks()
}
// 过滤器今天的任务
func filterTodayTasks(){
DispatchQueue.global(qos: .userInteractive).async {
let calendar = Calendar.current
let filtered = self.storedTasks.filter {
return calendar.isDate($0.taskDate, inSameDayAs: self.currentDay)
}
// 倒序排列
.sorted{ task1 ,task2 in
return task2.taskDate < task1.taskDate
}
DispatchQueue.main.async {
withAnimation {
self.filteredTasks = filtered
}
}
}
}
// 获取当前的星期
func fetchCurrentWeek(){
let today = Date()
let calendar = Calendar.current
let week = calendar.dateInterval(of: .weekOfMonth, for: today)
guard let firstWeekDay = week?.start else {
return
}
(1...7).forEach{ day in
if let weekday = calendar.date(byAdding: .day, value: day, to: firstWeekDay){
currentWeek.append(weekday)
}
}
}
// 提取日期
func extractDate(date: Date,format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: date)
}
// 检查当前日期是否为今天
func isToday(date:Date) -> Bool{
let calendar = Calendar.current
return calendar.isDate(currentDay, inSameDayAs: date)
}
// 正在检查当前的“小时”是否为任务“小时”
func isCurrentHour(date: Date) -> Bool{
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let currentHour = calendar.component(.hour, from: Date())
return hour == currentHour
}
}