IOS开发----列表行删除,滑动和MVC

1,040 阅读7分钟

专业完整版请参考(www.appcoda.com/learnswift/)

上面网址的教程文档为试用版,需要购买。(支持正版从我做起💪)

各位看官注意啦,本文章不是教程,只是我的一个分享而已,有错误还请指出。

本人也只是一个刚学习的小白而已,写文章是想起到回顾和记录作用,同时也给大家分享一下。

如果哪里有错误,还请大家指正。

注意本文是基于iOS 12和Xcode 10
本章是在(juejin.cn/post/684490…)的代码上完成的

开头


在我之前的文章已经给大家讲过,如何实现tableView的点击和选中(多选)等效果。

这边我们要讲的是如何删除列表行。 此外我们还要添加一些新的功能在我们这个app中:

  1. 添加一个自定义动作按钮当我们水平滑动列表行时
  2. 添加一个分享功能在app里。

好! 我们这就开始

简单介绍下MVC


在开始敲代码之前,我觉得理应介绍一下Model-View-Controller(MVC)

MVC是界面编程用到最多的设计模式之一,如果你想编写一个优秀的应用程序和成为优秀的iOS开发者,你是无法避免它的。

MVC不是仅适用于iOS编程,如果你学过其他编程语言你也可能听说过它。 它是设计应用程序的强大设计模式,无论是移动应用程序还是Web应用程序

了解MVC


(不要嫌我啰嗦,这正是体现了MVC的重要性)

无论学习哪个编程语言,都需要知道 Separation of Concerns(关注点分离,简称Soc)这个概念。其中C也就是Concerns,指的是软件功能的不同方面。

理解这个概念非常的简单,就是鼓励开发人员将一个复杂的特征或程序分解为几个被关注的领域,使每个领域都有自己的责任。 值得一提的是iOS开发中的委托模式是Soc的一个例子

MVC也就是Soc的另一个列子。在MVC背后的代码思想就是将用户界面分成三个区域(或者对象组),每个区域负责特定功能。接下里我们要说的就是这三个区域。

  • Model -负责保护数据和对数据进行操作,就像一个储存数据的数组对象一样简单。添加,更新还有删除数据这些操作都在这其中完成。
  • View - 这个就很容易理解了,就是一某种视图来管理信息的可视化显示。举个例子,UITableView就是以列表显示数据。
  • Controller -是链接Model和View之间的桥梁,将用户交互从视图转换为模型中执行相应的操作。一个十分简单的列子就是用户在视图中点击来删除按钮,Controller就会在模型中触发删除操作。当操作完成后,Model就会请求视图刷新自身,用来反映数据模型的更新。

删除列表行


如果你没有完成或者看过我之前的文章,请先下载这个项目(github.com/x1700209131…

在删除列表行中,我们大致要有三个步骤要完成:

  1. 启用“swipe-to-delete”(滑动删除)功能在表视图,以便用户可以选择删除选项

  2. 从数据模型中删除相应的表数据

  3. 重新加载表视图以反映表数据的更改

接下来我们一步一步完成

启用滑动删除功能

在iOSapp中,人们普遍采用在列表行水平滑动去显示删除按钮。

如果阅读过文档,你会发现有个方法叫做tableView(_ : commit :forRowAt)。这就是处理删除特定行的方法。

如果实现这个方法,我们就有在用户滑动列表行时显示删除代码。

添加下列代码在你的RestaurantTableVIewController.swift文件中的RestaurantTableView类中

   override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
       
    }

这时候打开模拟器。通过这个还没实现的方法,我们可以看到当我们想左滑动时出现了一个红色的删除按钮。 这时候你可以尝试向左滑动删除这个按钮或者点击删除按钮。

在Model中删除列表行

接下来我们要实现这个方法来删除列表行。 在这个方法里,参数IndexPath代表列表行,我们就要利用这个参数选中我们想要删除的列表行,运用remove(at:)方法删除。

所以我们接着更新代码:

 override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            restaurantNames.remove(at: indexPath.row)
            restaurantLocations.remove(at: indexPath.row)
            restaurantTypes.remove(at: indexPath.row)
            restaurantImages.remove(at: indexPath.row)
            restaurantIsVisited.remove(at: indexPath.row)
        }
    }

其实这个方法支持插入和删除两种编辑方式(editingStyle),所以我们要在删除前先检测这个编辑模式。

如果你现在就运行模拟器,你会发现当你点击删除按钮时app无法正常工作,无法删除列表行。

我们可能会好奇这串代码是否被执行,所以我们运用调试(debug)的方法来查看我们这串代码是否执行。

在这串代码的最后我们添加上:

print("Total item: \(restaurantNames.count)")
            for name in restaurantNames {
                print(name)
            }

我们再次运行虚拟机执行操作看看。

发现总数变少一个,也找不到第一个"Cafe Deadend"。 也就是代表这串代码确实是执行了,但模拟器中并没有删除原因可能就是视图并没有重新更新,所以我们接下来就是要想办法更新视图。

更新视图

要求视图重新加载的一个方法是reloadData,所以我们在代码添加一句:

   tableView.reloadData()

当reloadData方法被调用时,列表视图会清空并从重新加载数组里的数据再显示。

这时我们运行模拟器在执行操作,就会发现列表行被删除。

但这样删除会让人感觉突兀,我们可以给他加上一点动画。修改reloadData为deleteRows(at:with:)。这个方法是删除了这个列表行。

修改后代码如下:

   tableView.deleteRows(at: [indexPath], with: .fade)

with参数后面要填的是动画,这里的.fade代表了淡入淡出是一种常用的样式,当然还有大家还可以尝试下其他的样式。





滑动解锁更多功能


当然,在现实中滑动后不仅仅只有删除按钮还有其他许多按钮。如何添加这些按钮呢,我们用UIContextualAction这个类中的方法来实现。

首先我们要删除整个tableView(_ : commit :forRowAt),因为我们将不用到这个方法。iOS为我们提供了另外两种方法来实现需求:

  • tableView(_:leadingSwipeActionsConfigurationForRowAt:)
  • tableView(_:trailingSwipeActionsConfigurationForRowAt:)

我们要用到的是第二种方法从右到左的滑动,插入代码在RestaurantTableViewController:

 override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(action, sourceView, complertionHandler) in
            self.restaurantNames.remove(at: indexPath.row)
            self.restaurantLocations.remove(at: indexPath.row)
            self.restaurantTypes.remove(at: indexPath.row)
            self.restaurantImages.remove(at: indexPath.row)
            self.restaurantIsVisited.remove(at: indexPath.row)
            
            self.tableView.deleteRows(at: [indexPath], with: .fade)
            complertionHandler(true)
        }
        
        let shareAction = UIContextualAction(style: .normal, title: "Share"){
            (action,sourceView,completionHandler) in
            let defaultText = "Just checking in at" + self.restaurantNames[indexPath.row]
            let activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            self.present(activityController,animated: true,completion: nil)
            completionHandler(true)
        }
        let swipeConfiguration = UISwipeActionsConfiguration(actions: [deleteAction,shareAction])
        return swipeConfiguration
    }

关于deleteAction这里的代码,跟之前的代码是一样的,唯独多了一行CompletionHandler(true)。这指的是执行过删除操作,所以关闭按钮的意思。

而shareAction这里的代码则跟之前讲过的选中效果的代码大同小异。

最关键的是最后两行代码。它是返回了带有UIContextualAcion类(deleAction和shareAction)的UISwipeActionsConfiguration类,是为了告诉列表列表行被滑动。

当启动模拟器并点击Share按钮时会发现中仅仅只有Copy和Save to Files两个,理应会有更多的功能。 苹果公司也允许开发者在分享菜单中自定义服务。但可用的项目因设备而异,具体取决于安装的应用程序。

更新代码至如下:

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(action, sourceView, complertionHandler) in
            self.restaurantNames.remove(at: indexPath.row)
            self.restaurantLocations.remove(at: indexPath.row)
            self.restaurantTypes.remove(at: indexPath.row)
            self.restaurantImages.remove(at: indexPath.row)
            self.restaurantIsVisited.remove(at: indexPath.row)
            
            self.tableView.deleteRows(at: [indexPath], with: .fade)
            complertionHandler(true)
        }
        
        let shareAction = UIContextualAction(style: .normal, title: "Share"){
            (action,sourceView,completionHandler) in
            let defaultText = "Just checking in at" + self.restaurantNames[indexPath.row]
            
            let activityController: UIActivityViewController
            
            if let imageToShare = UIImage(named: self.restaurantImages[indexPath.row]){
                activityController = UIActivityViewController(activityItems: [defaultText,imageToShare], applicationActivities: nil)
            } else {
                activityController = UIActivityViewController(activityItems: [defaultText], applicationActivities: nil)
            }
            self.present(activityController,animated: true,completion: nil)
            completionHandler(true)
        }
        let swipeConfiguration = UISwipeActionsConfiguration(actions: [deleteAction,shareAction])
        return swipeConfiguration
    }

我们在这里添加了图片的分享功能。

我们使用了if let用来检测图片是否加载成功,如果成功,在初始化阶段就把defaultText和imageToShare发送给UIActivityViewController。 并且当选择复制到粘贴板时,UIActivityViewController将自动嵌入所选餐厅的图像。

其实还有许多东西值得大家探索,比如放置图标,修改长宽,在iPAd中运行,实现由左向右滑动功能等。