待办清单-iOS项目说明

这是一个关于iOS开发的待办清单项目说明,详细介绍了功能介绍、数据结构、页面转场、数据持久化和本地通知等内容。项目包括查看、编辑待办分类和事项,支持提醒和数据存储。数据结构涉及待办事项、分类及其顶层数据结构。页面转场通过Storyboard和手动转场实现。数据持久化使用Plist存储。本地通知确保用户在设定时间接收到提醒。
摘要由CSDN通过智能技术生成

- 项目链接:Checklists

1.功能与ViewController介绍

1. 查看已有待办分类,并可以新增、编辑待办分类,并为该分类选择图标

  • 每个分类显示其中还未完成的项目,这是AllListsViewController

在这里插入图片描述

  • 新增、编辑待办分类,这是ListDetailViewController

在这里插入图片描述

  • 为分类选择图标,这是IconPickerViewController

在这里插入图片描述

2. 每个分类中有它的待办清单,可以新增、编辑待办事项,并可选择提醒时间、是否提醒

  • 可以点击行来标记是否完成该事项,这是ChecklistsViewController

在这里插入图片描述

  • 可以新增、编辑待办事项并选择提醒时间,这是ItemDetailViewController

在这里插入图片描述

3. 使用plist保存待办数据

实体机
模拟器

在这里插入图片描述

4. 使用UserDefault和导航控制器代理方法实现保存和恢复上次的浏览位置

在这里插入图片描述

5. 用户可以为每条待办事项选择提醒时间,并会按时收到本地通知

在这里插入图片描述

2.数据结构

2.1 对于待办事项

  • 应包含的变量:
    事项ID、事项名、是否完成、提醒时间、是否提醒;其中事项ID是用于设置本地提醒时的identifier

  • 应包含的方法:
    重写init(方便新增待办事项)、重写deinit(删除待办事项时本地通知也要移除)、切换是否完成、设置本地通知、移除本地通知

代码如下,方法的具体实现省略,见源文件

class ChecklistClass: NSObject, Codable{
   
	//成员变量
	var itemID = 0
    var text = ""
    var flag = false
    var shouldRemind = false
    var dueDate = Date()
    //方法
    init(text: String, flag: Bool) {
   
    }
    deinit {
   
    }
    func changeFlag() {
   
    }
    func setNotification() {
   
    }
    func removeNotification() {
   
    }
 }

2.2 对于待办分类

  • 应包含的变量:
    分类名、分类图标名、事项数组

  • 应包含的方法:
    重写init(方便新增待办分类)、计算未完成的待办事项数

class ChecklistKindClass: NSObject, Codable {
   
    var name = ""
    var iconName = "No Icon"
    var items = [ChecklistClass]()
    init(name: String, iconName: String = "No Icon") {
   
    }
    func cntNoflag() -> Int {
   
    }
}

2.3 顶层数据结构

  • 顶层数据用来存放待办分类数组、实现数据的存储和读取,并封装一些方法(如存取用户上次点击的待办分类序号、计算下一条事项ID)供外部方法调用。
  • AppDelegate是整个App中最顶层的对象,因此让AppDelegate持有DataModel是最合理的,它可以向任何需要DataModel的视图控制器传递这一对象
class DataModel {
   
    var lists = [ChecklistKindClass]()
    //数据持久化
    func documentsDirectory() -> URL {
   
    }
    func dataFilePath() -> URL {
   
    }
    func loadLists(){
   
    }
    func saveLists(){
   
    } 
    //初始化时加载plist数据
    init() {
   
        loadLists()
    }
    //存取上一次访问的分类序号
    var indexOfLastList : Int {
   
        get {
   
            return UserDefaults.standard.integer(forKey: "listIndex")
        }
        set{
   
            UserDefaults.standard.setValue(newValue, forKey: "listIndex")
        }
    }
    //类方法直接用类名调用,ChecklistClass的初始化时调用生成不重复的事项ID
    class func nextItemID() -> Int {
   
        let userDefaults = UserDefaults.standard
        //一开始没有ItemID,返回0;
        let itemID = userDefaults.integer(forKey: "ItemID")
        userDefaults.set(itemID + 1, forKey: "ItemID")
        userDefaults.synchronize()
        return itemID
    }
}

3.ViewController关系图与转场

3.1 ViewController关系图简介

  • 以下“xxViewController”简称“xxVC

在这里插入图片描述

  • AllListsVCChecklistsVC在同一个导航控制器中,在AllListsVC中点击某分类行进入ChecklistsVCChecklistVC点击左上角可返回上一级

  • ChecklistsVC界面,点击右上角“+”或点击某行的Detail Disclosure,可进入ItemDetailVC进行添加/编辑该项目;ChecklistsVC通过ItemDetailVC的导航控制器找到它

  • AllListsVC界面,点击右上角“+”或点击某行的Detail Disclosure,可进入listDetailVC进行添加/编辑该分类;AllListsVC通过listDetailVC的导航控制器找到它。

关于为什么要将ItemDetailVC嵌入一个新的导航控制器中:

  • 这里使用present modally转场方式,是一个弹出的页面效果。如果不嵌入导航控制器,无法显示该页的title(添加还是编辑)、也无法在顶部添加done、cancel的Label
  • 如果不给ItemDetailVC加导航控制器,也可以使用show转场,不过这种的话它与ChecklistsVC处于同一个导航控制器下,就不是页面弹出的效果而是页面层级的关系
  • 我个人觉得添加、编辑待办事项用弹出页面的方式更好一些,而不像待办分类AllListsVC和待办事项ChecklistsVC那两个层级的页面关系;同理,添加、编辑待办分类的listDetailVC也嵌入到导航控制器中。
  • 此外,如果是present modally转场,应该在done、cancel(完成编辑、取消编辑)的代理方法中用dismiss;如果是show转场,它们处于同一个导航控制器,应该用navigationControllerpopViewController方法,因为导航控制器是一个类似栈的结构,是ViewController的容器,栈顶放的是当前显示的ViewController

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

3.2 通过Storyboard转场

  • 以添加、编辑待办事项为例(ChecklistsVC转场至ItemDetailVC
  1. 按住ctrl,连接“+”的 LabelItemDetailVC的导航控制器,选择“Action Segue”中的present modally,并在属性器中填入转场ID:addItemSegue
    在这里插入图片描述
  2. 按住ctrl,连接 Table View CellItemDetailVC的导航控制器,选择“Accessory Action”中的present modally,并在属性器中填入转场ID:editItemSegue。
  • 注意要选中cell与导航控制器相连(而不是cell中的Label),因为那个Detail Disclosure的accessory是cell的属性
    在这里插入图片描述
  1. 代码及注释如下,将ChecklistVC设为ItemDetailVCdelegate是为了反向传值
override func prepare(for segue:UIStoryboardSegue, sender: Any?) {
   
    if segue.identifier == "addItemSegue" {
   
        //ItemViewVC是它所在的导航控制器的第一个VC,通过找到导航控制器找到ItemDetailVC
        let nvController = segue.destination as! UINavigationController
        let controller = nvController.topViewController as! ItemDetailViewController
        controller.delegate = self
    }
    //正向传值,让ItemDetailVC知道是add还是edit
    else if segue.identifier == "editItemSegue" {
   
        let nvController = segue.destination as! UINavigationController
        let controller = nvController.topViewController as! ItemDetailViewController
        controller.delegate = self
        if let indexPath = tableView.indexPath(for: sender as! UITableViewCell){
   
            controller.itemToEdit = checklistKind.items[indexPath.row]
        }
    }
}

3.3 通过Table View Delegate手动转场

  • 以进入某个待办分类为例(AllListsVC转场至ChecklistsVC
  1. 按住ctrl,连接AllListsVCChecklistsVC,选择“Manual Segue”中的show,并在属性器中填入转场ID:showItemSegue在这里插入图片描述
  2. 重写AllListsVC Table View的代理方法,在点击某一行时手动触发转场,并正向传值告诉ChecklistsVC选择的是哪个待办分类
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath:IndexPath) {
   
    let checklistKind = dataModel.lists[indexPath.row]
    //向ChecklistViewController传checklistKind对象
    performSegue(withIdentifier: "showItemSegue", sender: checklistKind)
}

3.4 通过Table View Delegatepresent方法

  • 以编辑某个待办分类为例,从AllListsVCListDetailVC
  1. 在Storyboad中,给ListDetailVC的导航控制器填入ID在这里插入图片描述
  2. 重写AllListsVC Table View的代理方法,当点击某一行的Accessory时被调用:Storyboard根据上面的ID实例化一个视图控制器(就是ListDetailVC的导航控制器
    )并展现在屏幕上,效果和转场类似。
//从AllListsVC到ListDetailVC
//ListDetailVC需要向AlllistVC反向传值,因此需要设置代理;edit也需要正向传值
//ListDetailVC通过其导航控制器找到
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
   
    let nvController = storyboard!.instantiateViewController(withIdentifier: "ListDetailNavigationController") as! UINavigationController
    let controller = nvController.topViewController as! ListDetailViewController
    controller.delegate = self
    let checklistKind = dataModel.lists[indexPath.row]
    controller.listToEdit = checklistKind
    present(nvController, animated: true, completion: nil)
}
  • 这里将ListDetailVC嵌入一个单独导航控制器的原因上面已经提到了,在使用present时是一个弹出的效果,如果不嵌入导航控制器无法显示title的顶部Label
  • 如果不用present,则不用将ListDetailVC嵌入一个单独的导航控制器:用导航控制器的pushViewController方法(相当于转场中的show),相应的要将实例化视图控制器的ID填入ListDetailVC中,代码如下:
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
   
    let controller = storyboard!.instantiateViewController(withIdentifier: "ListDetailNavigatio
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值