一同养成写作习惯!这是我参与「日新方案 4 月更文应战」的第21天,点击检查活动详情。
今天职言:外表干净是尊重别人,内心干净是尊重自己,言行干净是尊重魂灵。
承接上一章的内容,咱们持续完结运用CoreData
结构搭建一个简单的ToDo
待办事项App
。
这一章节,咱们完结一下NewToDoView
新建事项页面。
咱们先新建一个新页面,命名为NewToDoView
。
点击Xcode
顶部导航栏,File
文件,New
新建,挑选File
创立文件,挑选iOS
中的SwiftUI File
类型的文件,命名为NewToDoView.swift
。
页面UI设计
咱们还是从上往下构建UI
页面。
TopNavBar顶部导航栏视图
首先是TopNavBar
顶部导航栏,称号不能和之前创立的重复,它由一个Text
标题一个closeButton
封闭按钮组成。
// 顶部导航栏
struct TopNavBar: View {
var body: some View {
HStack {
Text("新建事项")
.font(.system(.title))
.bold()
Spacer()
Button(action: {
}) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray)
.font(.title)
}
}
}
}
InputNameView输入框视图
然后是事项称号的输入框TextField
。
TextField
输入框需求有两个参数绑定,一个是内容绑定,即咱们TextField
输入框需求记录什么内容。第二个是isEditing
输入状况绑定,协助咱们检测它是否正在输入,后边咱们会用到输入的状况的检测。
咱们在NewToDoView
视图中,运用@State
声明两个变量。
@State var name: String
@State var isEditing = false
然后咱们再构建InputNameView
输入框视图的内容,再绑定参数。
//输入框
struct InputNameView: View {
@Binding var name: String
@Binding var isEditing: Bool
var body: some View {
TextField("请输入", text: $name, onEditingChanged: { (editingChanged) in
self.isEditing = editingChanged
})
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.bottom)
}
}
最后在NewToDoView
视图中展现InputNameView
输入框视图的内容,这里用VStack
笔直排布将InputNameView
输入框视图和TopNavBar
顶部导航栏排在一同。
VStack {
TopNavBar()
InputNameView(name: $name, isEditing: $isEditing)
}
由于咱们NewToDoView
视图需求预览,因此要想在模拟器中看到作用,还需求在NewToDoView_Previews
预览视图中增加参数。
运转一下,咱们看下作用。
下面咱们持续,接下来是事项的优先级挑选,咱们先完结UI
的部分。
PrioritySelectView优先级挑选视图
咱们命名一个PrioritySelectView
优先级挑选视图,这里当然也能够用代码整合的方式减少下代码量,咱们将相同的修饰符抽离出来,然后再在PrioritySelectView
优先级挑选视图展现内容。
// 挑选优先级
struct PrioritySelectView: View {
var body: some View {
HStack {
PrioritySelectRow(name: "高", color: Color.red)
PrioritySelectRow(name: "中", color: Color.orange)
PrioritySelectRow(name: "低", color: Color.green)
}
}
}
// 挑选优先级
struct PrioritySelectRow: View {
var name: String
var color:Color
var body: some View {
Text(name)
.frame(width: 80)
.font(.system(.headline))
.padding(10)
.background(color)
.foregroundColor(.white)
.cornerRadius(8)
}
}
咱们把PrioritySelectView
加到NewToDoView
视图中看下作用。
VStack {
TopNavBar()
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView()
}
SaveButton保存按钮视图
接下来是SaveButton
保存按钮的制作,咱们让按钮下面留点底边距。
咱们也加进去NewToDoView
视图看看作用。
// 保存按钮
struct SaveButton: View {
var body: some View {
Button(action: {
}) {
Text("保存")
.font(.system(.headline))
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
.padding([.top,.bottom])
}
}
NewToDoView页面方位调整
然后,咱们调整下方位,咱们希望这个NewToDoView
页面是从底部弹出来,然后内容也都在底部展现而不是居中,咱们能够调整下整个NewToDoView
页面的方位。
咱们用VStack
和Spacer
将NewToDoView
视图顶到底部,然后依据InputNameView
输入框是否处于输入状况isEditing
,来进行偏移,也便是当咱们点击InputNameView
输入框正在输入的时分,整个视图能够向上移动,这样咱们的keyboard
键盘输入就有方位正对应了,这是一个小技巧
。
然后整个NewToDoView
页面页面咱们再置底一点,运用.edgesIgnoringSafeArea
安全区域空出底部区域,这样好看许多。
VStack {
Spacer()
VStack {
TopNavBar()
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView()
SaveButton()
}
.padding()
.background(Color.white)
.cornerRadius(10, antialiased: true)
.offset(y: isEditing ? -320 : 0)
}.edgesIgnoringSafeArea(.bottom)
交互逻辑设计
好了,咱们完结了NewToDoView
页面的制作了,下面是逻辑部分。
PrioritySelectView优先级挑选逻辑
首先是咱们的PrioritySelectView
优先级的挑选,咱们希望点击挑选哪个优先级,哪个优先级就“亮起”
,这样咱们好知道选中
的是哪一个。
相同,咱们需求贮存priority
优先级状况,priority
优先级是存储在NewToDoView
新增事项页面里的,这里用@State
状况。
//NewToDoView视图中界说
@State var priority: Priority
然后,咱们完善下PrioritySelectView
优先级的挑选页面,依据选中状况展现布景色彩,假如没选中,咱们就变成.systemGray4
灰色。
// 挑选优先级
struct PrioritySelectView: View {
@Binding var priority: Priority
var body: some View {
HStack {
PrioritySelectRow(name: "高", color: priority == .high ? Color.red : Color(.systemGray4))
.onTapGesture { self.priority = .high }
PrioritySelectRow(name: "中", color: priority == .normal ? Color.orange : Color(.systemGray4))
.onTapGesture { self.priority = .normal }
PrioritySelectRow(name: "低", color: priority == .low ? Color.green : Color(.systemGray4))
.onTapGesture { self.priority = .low }
}
}
}
咱们完善下NewToDoView
视图的绑定联系,顺便给个示例数据
预览下模拟器结果。
struct NewToDoView: View {
@State var name: String
@State var isEditing = false
@State var priority: Priority
var body: some View {
VStack {
Spacer()
VStack {
TopNavBar()
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView(priority: $priority)
SaveButton()
}
.padding()
.background(Color.white)
.cornerRadius(10, antialiased: true)
.offset(y: isEditing ? -320 : 0)
}.edgesIgnoringSafeArea(.bottom)
}
}
struct NewToDoView_Previews: PreviewProvider {
static var previews: some View {
NewToDoView(name: "", todoItems: .constant([]), priority: .normal)
}
}
页面弹出逻辑
让咱们回到ContentView
主页,咱们将两个页面联动
起来。
页面弹出的交互逻辑是,当咱们点击ContentView
主页右上角的增加按钮时,翻开NewToDoView
新增事项页面。
理解了逻辑之后,咱们现在ContentView
主页写逻辑,先声明一个变量showNewTask
,表明咱们是否翻开了NewToDoView
新增事项页面,默许是false
。
@State private var showNewTask = false
@State private var offset: CGFloat = .zero //运用.animation避免报错,iOS15的特性
假如showNewTask
状况为true
时,咱们显示NewToDoView
新增事项页面,咱们能够把NewToDoView
新增事项页面放在ContentView
主页的ZStack
包裹着。
//点击增加时翻开弹窗
if showNewTask {
NewToDoView(name: "", priority: .normal)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0),value: offset)
}
然后咱们增加点击事件,当咱们在ContentView
主页点击增加按钮的时分,showNewTask
状况变为为true
。
// 顶部导航栏
struct TopBarMenu: View {
@Binding var showNewTask: Bool
var body: some View {
HStack {
Text("待办事项")
.font(.system(size: 40, weight: .black))
Spacer()
Button(action: {
//翻开弹窗
self.showNewTask = true
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle).foregroundColor(.blue)
}
}
.padding()
}
}
//ContentView视图
TopBarMenu(showNewTask: $showNewTask)
如同基本完结了作用,但由于咱们是运用ZStack
包裹的方式,而不是用ModelView
模态弹窗或许NavigationView
导航栏进入新的页面,所以咱们还需求做一个MaskView
蒙层遮住布景,让它看起来像是弹窗
的作用。
MaskView蒙层逻辑
//蒙层
struct MaskView : View {
var bgColor: Color
var body: some View {
VStack {
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(bgColor)
.edgesIgnoringSafeArea(.all)
}
}
然后把MaskView
蒙层加到翻开NewToDoView
新增事项页面的逻辑里,一起,支撑咱们点击MaskView
蒙层封闭弹窗。
//蒙层
MaskView(bgColor: .black)
.opacity(0.5)
.onTapGesture {
self.showNewTask = false
}
好!咱们完成了怎样弹出NewToDoView
新增事项页面,咱们回到NewToDoView.swift
文件,咱们完成怎么点击封闭弹窗。
页面封闭逻辑
在NewToDoView
新增事项页面封闭有两种
,一种是点击封闭按钮
封闭弹窗。
// 顶部导航栏
struct TopNavBar: View {
@Binding var showNewTask: Bool
var body: some View {
HStack {
Text("新建事项")
.font(.system(.title))
.bold()
Spacer()
Button(action: {
//封闭弹窗
self.showNewTask = false
}) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray)
.font(.title)
}
}
}
}
//NewToDoView视图
struct NewToDoView: View {
@State var name: String
@State var isEditing = false
@State var priority: Priority
@Binding var showNewTask: Bool
var body: some View {
VStack {
Spacer()
VStack {
TopNavBar(showNewTask: $showNewTask)
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView(priority: $priority)
SaveButton()
}
.padding()
.background(Color.white)
.cornerRadius(10, antialiased: true)
.offset(y: isEditing ? -320 : 0)
}.edgesIgnoringSafeArea(.bottom)
}
}
咱们发现体系报错
了,这是由于咱们运用@Binding
绑定了是否展现页面showNewTask
的布尔值,还需求在ContentView主页
建立相关。
//ContentView视图
NewToDoView(name: "", priority: .normal, showNewTask: $showNewTask)
这样,咱们就完结
了第一种封闭弹窗的交互:点击封闭按钮
封闭弹窗。
另一种封闭弹窗的交互是,咱们新建一个事项,满意条件后(内容不为空),这是咱们点击saveButton
保存按钮,封闭弹窗
。
咱们再回到NewToDoView.swift
文件。首先咱们保存要校验下InputNameView
输入框内容是否为空
,为空
的时分咱们不封闭
弹窗。当InputNameView
输入框内容不为空
的时分,咱们才允许封闭弹窗
。
// 保存按钮
struct SaveButton: View {
@Binding var name:String
@Binding var showNewTask: Bool
var body: some View {
Button(action: {
//判别输入框是否为空
if self.name.trimmingCharacters(in: .whitespaces) == "" {
return
}
//封闭弹窗
self.showNewTask = false
}) {
Text("保存")
.font(.system(.headline))
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
.padding([.top,.bottom])
}
}
//NewToDoView视图
SaveButton(name: $name, showNewTask: $showNewTask)
咱们回到ContentView.swift
文件中,运转模拟器体验下。
咱们完结了基础的封闭弹窗操作,能够点击封闭按钮封闭,也能够输入新建事项后,点击保存封闭弹窗。
增加新事项逻辑
咱们在NewToDoView
增加完事项后,输入的内容
和挑选的优先级
就会在ContentView
主页List
列表中创立一条数据,下面咱们来完结增加新事项逻辑。
咱们看回NewToDoView.swift
文件,咱们完成了有输入内容时,点击保存按钮封闭弹窗,但没有完成addTask
新增数据,下面咱们来完成它。
// 保存按钮
struct SaveButton: View {
@Binding var name:String
@Binding var showNewTask: Bool
@Binding var todoItems: [ToDoItem]
@Binding var priority:Priority
var body: some View {
Button(action: {
//判别输入框是否为空
if self.name.trimmingCharacters(in: .whitespaces) == "" {
return
}
//增加一条新数据
self.addTask(name: self.name, priority: self.priority)
//封闭弹窗
self.showNewTask = false
}) {
Text("保存")
.font(.system(.headline))
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
.padding([.top,.bottom])
}
//增加新事项办法
private func addTask(name: String, priority: Priority, isCompleted: Bool = false) {
let task = ToDoItem(name: name, priority: priority, isCompleted: isCompleted)
todoItems.append(task)
}
}
咱们界说一个addTask
增加事项的private
私有办法,增加的参数是name
内容、priority
优先级、isCompleted
是否完结,默许为否false
。然后实例化它,调用办法的时分在todoItems
数组中增加一条数据。然后,咱们点击SaveBotton
保存成功时调用addTask
增加新事项办法。
//NewToDoView视图
struct NewToDoView: View {
@State var name: String
@State var isEditing = false
@State var priority: Priority
@Binding var showNewTask: Bool
@Binding var todoItems: [ToDoItem]
var body: some View {
VStack {
Spacer()
VStack {
TopNavBar(showNewTask: $showNewTask)
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView(priority: $priority)
SaveButton(name: $name, showNewTask: $showNewTask, todoItems: $todoItems, priority: $priority)
}
.padding()
.background(Color.white)
.cornerRadius(10, antialiased: true)
.offset(y: isEditing ? -320 : 0)
}.edgesIgnoringSafeArea(.bottom)
}
}
struct NewToDoView_Previews: PreviewProvider {
static var previews: some View {
NewToDoView(name: "", priority: .normal, showNewTask: .constant(true), todoItems: .constant([]))
}
}
一起,咱们在NewToDoView
视图绑定相关联系,并在NewToDoView_Previews
预览视图中也绑定好联系。
当然别忘了,还要在ContentView
主页视图绑定参数。
// ContentView视图
NewToDoView(name: "", priority: .normal, showNewTask: $showNewTask, todoItems: $todoItems)
恭喜你,咱们就完结了ContentView
主页视图和NewToDoView
新建事项视图的悉数交互逻辑!
未完待续
但还没有悉数完结,咱们只是完结了一个简单的ToDo
待办事项的App
,还没有完成CoreData
数据耐久化。
由于篇幅过长,上篇
咱们完结了ContentView
主页视图的制作,中篇
咱们完结NewToDoView
新建事项视图的制作,当然还有他们之间的交互
。
CoreData
数据耐久化结构的运用将再分出下篇
,咱们看看怎么运用CoreData
数据耐久化结构,真正完成一个能够保存数据
的App
。
本章完整代码如下:
//ToDoItem.swift
import Foundation
enum Priority: Int {
case low = 0
case normal = 1
case high = 2
}
class ToDoItem: ObservableObject, Identifiable {
var id = UUID()
@Published var name: String = ""
@Published var priority: Priority = .high
@Published var isCompleted: Bool = false
init(name: String, priority: Priority = .normal, isCompleted: Bool = false) {
self.name = name
self.priority = priority
self.isCompleted = isCompleted
}
}
//ContentView.swift
import CoreData
import SwiftUI
struct ContentView: View {
@State var todoItems: [ToDoItem] = []
@State private var showNewTask = false
@State private var offset: CGFloat = .zero//运用.animation避免报错,iOS15的特性
//去掉List布景色彩
init() {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
}
var body: some View {
ZStack {
VStack {
TopBarMenu(showNewTask: $showNewTask)
ToDoListView(todoItems: $todoItems)
}
//判别事项数量为0时展现缺省图
if todoItems.count == 0 {
NoDataView()
}
//点击增加时翻开弹窗
if showNewTask {
//蒙层
MaskView(bgColor: .black)
.opacity(0.5)
.onTapGesture {
self.showNewTask = false
}
NewToDoView(name: "", priority: .normal, showNewTask: $showNewTask, todoItems: $todoItems)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0),value: offset)
}
}
}
}
//蒙层
struct MaskView : View {
var bgColor: Color
var body: some View {
VStack {
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(bgColor)
.edgesIgnoringSafeArea(.all)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// 顶部导航栏
struct TopBarMenu: View {
@Binding var showNewTask: Bool
var body: some View {
HStack {
Text("待办事项")
.font(.system(size: 40, weight: .black))
Spacer()
Button(action: {
//翻开弹窗
self.showNewTask = true
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle).foregroundColor(.blue)
}
}
.padding()
}
}
// 缺省图
struct NoDataView: View {
var body: some View {
Image("image01")
.resizable()
.scaledToFit()
}
}
// 列表
struct ToDoListView: View {
@Binding var todoItems: [ToDoItem]
var body: some View {
List {
ForEach(todoItems) { todoItem in
ToDoListRow(todoItem: todoItem)
}
}
}
}
// 列表内容
struct ToDoListRow: View {
@ObservedObject var todoItem: ToDoItem
var body: some View {
Toggle(isOn: self.$todoItem.isCompleted) {
HStack {
Text(self.todoItem.name)
.strikethrough(self.todoItem.isCompleted, color: .black)
.bold()
.animation(.default)
Spacer()
Circle()
.frame(width: 20, height: 20)
.foregroundColor(self.color(for: self.todoItem.priority))
}
}.toggleStyle(CheckboxStyle())
}
// 依据优先级显示不同色彩
private func color(for priority: Priority) -> Color {
switch priority {
case .high:
return .red
case .normal:
return .orange
case .low:
return .green
}
}
}
// checkbox复选框样式
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .purple : .gray)
.font(.system(size: 20, weight: .bold, design: .default))
.onTapGesture {
configuration.isOn.toggle()
}
configuration.label
}
}
}
//NewToDoView.swift
import SwiftUI
struct NewToDoView: View {
@State var name: String
@State var isEditing = false
@State var priority: Priority
@Binding var showNewTask: Bool
@Binding var todoItems: [ToDoItem]
var body: some View {
VStack {
Spacer()
VStack {
TopNavBar(showNewTask: $showNewTask)
InputNameView(name: $name, isEditing: $isEditing)
PrioritySelectView(priority: $priority)
SaveButton(name: $name, showNewTask: $showNewTask, todoItems: $todoItems, priority: $priority)
}
.padding()
.background(Color.white)
.cornerRadius(10, antialiased: true)
.offset(y: isEditing ? -320 : 0)
}.edgesIgnoringSafeArea(.bottom)
}
}
struct NewToDoView_Previews: PreviewProvider {
static var previews: some View {
NewToDoView(name: "", priority: .normal, showNewTask: .constant(true), todoItems: .constant([]))
}
}
// 顶部导航栏
struct TopNavBar: View {
@Binding var showNewTask: Bool
var body: some View {
HStack {
Text("新建事项")
.font(.system(.title))
.bold()
Spacer()
Button(action: {
//封闭弹窗
self.showNewTask = false
}) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray)
.font(.title)
}
}
}
}
//输入框
struct InputNameView: View {
@Binding var name: String
@Binding var isEditing: Bool
var body: some View {
TextField("请输入", text: $name, onEditingChanged: { (editingChanged) in
self.isEditing = editingChanged
})
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.bottom)
}
}
// 挑选优先级
struct PrioritySelectView: View {
@Binding var priority: Priority
var body: some View {
HStack {
PrioritySelectRow(name: "高", color: priority == .high ? Color.red : Color(.systemGray4))
.onTapGesture { self.priority = .high }
PrioritySelectRow(name: "中", color: priority == .normal ? Color.orange : Color(.systemGray4))
.onTapGesture { self.priority = .normal }
PrioritySelectRow(name: "低", color: priority == .low ? Color.green : Color(.systemGray4))
.onTapGesture { self.priority = .low }
}
}
}
// 挑选优先级
struct PrioritySelectRow: View {
var name: String
var color:Color
var body: some View {
Text(name)
.frame(width: 80)
.font(.system(.headline))
.padding(10)
.background(color)
.foregroundColor(.white)
.cornerRadius(8)
}
}
// 保存按钮
struct SaveButton: View {
@Binding var name:String
@Binding var showNewTask: Bool
@Binding var todoItems: [ToDoItem]
@Binding var priority:Priority
var body: some View {
Button(action: {
//判别输入框是否为空
if self.name.trimmingCharacters(in: .whitespaces) == "" {
return
}
//增加一条新数据
self.addTask(name: self.name, priority: self.priority)
//封闭弹窗
self.showNewTask = false
}) {
Text("保存")
.font(.system(.headline))
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(8)
}
.padding([.top,.bottom])
}
//增加新事项办法
private func addTask(name: String, priority: Priority, isCompleted: Bool = false) {
let task = ToDoItem(name: name, priority: priority, isCompleted: isCompleted)
todoItems.append(task)
}
}
快来动手试试吧!
假如本专栏对你有协助,无妨点赞、评论、关注~