JohnnyDark / IOS_Checklist

0 stars 0 forks source link

app设计 #1

Open JohnnyDark opened 4 years ago

JohnnyDark commented 4 years ago

整体界面设置

app整体设计

AllChecklists->ChecklistDetail->AddItem

AllChecklist_Checklist_AddItem

Navigation controller

navigation_controller

JohnnyDark commented 4 years ago

1. CheckMark设计:cell上添加一个label,选中时给label添加 √ 字符,uncheck时使用 “” 空字符串

2. textField在屏幕显示后立即获取焦点

   override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  textField.becomeFirstResponder()   
  textField.resignFirstResponder() //失去焦点
}

3. 不允许textfield空输入

1. storyboard中, 设置键盘:Auto-enable Return Key
2. textField的代理方法
    // MARK:- Text Field Delegates
func textField(_ textField: UITextField, 
               shouldChangeCharactersIn range: NSRange, 
               replacementString string: String) -> Bool {

  let oldText = textField.text!    
  let stringRange = Range(range, in:oldText)!
  let newText = oldText.replacingCharacters(in: stringRange, 
                                          with: string)
  if newText.isEmpty {
    doneBarButton.isEnabled = false
  } else {
    doneBarButton.isEnabled = true
  }
  return true
}

4. 使用clear button后,不能disable done button的处理

    func textFieldShouldClear(_ textField: UITextField) -> Bool {
       doneBarButton.isEnabled = false
       return true
}

5. 代理模式实现将当前VC数据返回到上一个VC中 (weak var delegate: AddItemViewControllerDelegate?)

6. 依赖注入将数据从当前VC注入到操作的目的VC的变量中

7. 使用performSegue(withIdentifier:sender) 和prepare(for: sender:): preform传递数据,prepare设置目的地数据

segue流程

8. 使用代码加载View Controller, 代码执行 accessory 的segue

override func tableView(_ tableView: UITableView, 
   accessoryButtonTappedForRowWith indexPath: IndexPath) {

  let controller = storyboard!.instantiateViewController(
                   withIdentifier: "ListDetailViewController")
                   as! ListDetailViewController
  controller.delegate = self

  let checklist = lists[indexPath.row]
  controller.checklistToEdit = checklist

  navigationController?.pushViewController(controller, 
                                 animated: true)
}
JohnnyDark commented 4 years ago

数据保存于数据加载,数据能被保存到文件,数据类型需要实现 Codable 协议

数据保存时机, 相关方法: AppDelegate.swift中

  1. app后台运行时:func applicationDidEnterBackground(_ application: UIApplication){saveData()}
  2. app退出时:func applicationWillTerminate(_ application: UIApplication){saveData()}

AppDelegate中

let navigationController = window?.rootViewController let controller = navigationController.viewControllers[0] as! AllListsViewController

Document目录获取

func documentsDirectory() -> URL {
  let paths = FileManager.default.urls(for: .documentDirectory, 
                                        in: .userDomainMask)
  return paths[0]
}

func dataFilePath() -> URL {
  return documentsDirectory().appendingPathComponent(
                                  "Checklists.plist")
}

获取data.plist文件目录

func dataFilePath() -> URL {
  return documentsDirectory().appendingPathComponent(
                                  "Checklists.plist")
}

数据保存

func saveChecklistItems() {
  // 1
  let encoder = PropertyListEncoder()
  // 2
  do {
    // 3
    let data = try encoder.encode(items)
    // 4
    try data.write(to: dataFilePath(), 
              options: Data.WritingOptions.atomic)
    // 5
  } catch {
    // 6
    print("Error encoding item array: \(error.localizedDescription)")
  }
}

数据加载

func loadChecklistItems() {
  // 1
  let path = dataFilePath()
  // 2
  if let data = try? Data(contentsOf: path) {
    // 3
    let decoder = PropertyListDecoder()
    do {
      // 4
      items = try decoder.decode([ChecklistItem].self, 
                                 from: data)
    } catch {
      print("Error decoding item array: \(error.localizedDescription)")
    }
  }
}
JohnnyDark commented 4 years ago

DataModel类设计与使用

  1. App启动时,AppDelegate.swift的didFinishLaunchWithOptions方法中进行数据加载,与变量赋值
    
    let navigationController = window!.rootViewController 
                             as! UINavigationController
    let controller = navigationController.viewControllers[0] 
                   as! AllListsViewController
    controller.dataModel = dataModel
JohnnyDark commented 4 years ago

用代码添加并使用table view cell

添加identifier实例变量 let cellIdentifier = "ChecklistCell" 给table view注册cell tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) 使用cell

override func tableView(_ tableView: UITableView,
         cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
                withIdentifier: cellIdentifier, for: indexPath)
  cell.textLabel!.text = "List \(indexPath.row)"
  return cell
}
JohnnyDark commented 4 years ago

UserDefault: 使用

基本应用:

//保存值 UserDefaults.standard.set(indexPath.row, forKey: "ChecklistIndex")

//取值: UserDefaults.standard.integer( forKey: "ChecklistIndex")

//设置初始值

let dictionary = [ "ChecklistIndex": -1 ]
UserDefaults.standard.register(defaults: dictionary)

//结合计算属性使用:


var indexOfSelectedChecklist: Int {
  get {
    return UserDefaults.standard.integer(
                              forKey: "ChecklistIndex")
  }
  set {
    UserDefaults.standard.set(newValue, 
                              forKey: "ChecklistIndex")
  }
}

//userDefaults设置值后,将值立即写入进行保存 UserDefaults.standard.synchronize()

使用场景一:保持显示退出前,最后一次停留的界面

使用场景二:App启动时,在userdefaults中为某些变量设置初始值

应用:判断app是否首次启动,预先在userdefaults中添加该 bool类型变量,实现 场景2
应用:userdefaults中获取key的整数值,如果没有,会返回0,此时需要在userdefaults中提前设置默认值实现 场景2
应用:将最后一次的checklist的数据在 datamodel中的索引进行保存实现 场景1

问题处理:

app闪退后,新添加的checklist对象没有被保存到datamodel,但是这个checklist对应的index确被保存了,造成启动app时,查询index对应的checklist对象没有,app crash, 添加判断:if index >= 0 && index < dataModel.lists.count {}

实现记录上一次界面

  1. 实现navigation controller delegate

    // MARK:- Navigation Controller Delegates
    func navigationController(
                _ navigationController: UINavigationController, 
               willShow viewController: UIViewController, 
                              animated: Bool) {
    
    // Was the back button tapped?
    if viewController === self {
    UserDefaults.standard.set(-1, forKey: "ChecklistIndex")
    }
    }
  2. 生命周期方法中为navigation controller设置代理对象

    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    navigationController?.delegate = self
    
    let index = UserDefaults.standard.integer(
                                     forKey: "ChecklistIndex")
    if index != -1 {
    let checklist = dataModel.lists[index]
    performSegue(withIdentifier: "ShowChecklist", 
                         sender: checklist)
    }
    }

    ps: 该方法中设置代理的分析:

  3. func navigationController( _:willShow:animated:): 该方法在stack中任意vc显示时都会调用,前提是给它设置了代理对象

  4. app启动时不应该先去设置代理对象,这样会导致每次启动都将userdefaults中保存的index设置为-1,不能实现切换到指定的checklist对象的界面

  5. 在viewDidLoad中设置代理对象,则只有在main screen显示出来后,其后续的操作中可以使用navigation view controller的代理方法,这样才能正确实现该场景

JohnnyDark commented 4 years ago

app前台运行,收到local notification时,执行指定任务

  1. AppDelegate.swift中 didFinishLaunching方法中执行如下代码

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        return true
    }
  2. AppDelegate实现代理方法:

//MARK:- app前台运行时收到local Notification,可以进行相关的任务处理,在如下方法中
extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        print("收到 通知")
    }
}
JohnnyDark commented 4 years ago

为checklist Item添加对应的local notification

  1. 为checklistItem指定一个itemID
  2. 使用该 itemId来创建一个notification

处理各种情况下需要进行notification添加, 修改,删除的问题

每个item创建时,同时判断是否需要创建notification,如果是true则不管是是否已有notification,统一先移除原有的notification,按当前item详情界面的数据,创建新的notification并保存,如果为否,则不执行notification的创建

notification的删除

只需要在 checklistItem 的 deinit方法中进行就可以,这样无论是直接删除item,还是删除item所属的checklist,都会执行deinit方法,将notification移除