Swift从头开始:访问控制和属性观察器

上一课中 ,我们添加了创建待办事项的功能。 尽管此添加使应用程序更有用,但添加将项目标记为已完成并删除项目的功能也将很方便。 这就是本课中要重点讨论的内容。

先决条件

如果您想跟我一起学习,请确保您的计算机上安装了Xcode 8.3.2或更高版本。 您可以从Apple的App Store下载Xcode 8.3.2。

1.删除项目

要删除项目,我们需要实现UITableViewDataSource协议的两个附加方法。 我们首先需要通过实现tableView(_:canEditRowAt:)方法来告诉表视图可以编辑哪些行。 正如您在下面的代码片段中看到的那样,实现非常简单。 我们通过返回true告诉表视图每行都是可编辑的。

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true
}

我们感兴趣的第二种方法是tableView(_:commit:forRowAt:) 。 实现有点复杂,但足够容易掌握。

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        // Update Items
        items.remove(at: indexPath.row)

        // Update Table View
        tableView.deleteRows(at: [indexPath], with: .right)
    }
}

我们首先检查editingStyle的值,该值是UITableViewCellEditingStyle类型的枚举。 仅当editingStyle的值等于UITableViewCellEditingStyle.delete才删除项目。

不过,Swift比这更聪明。 因为它知道editingStyleUITableViewCellEditingStyle类型,所以我们可以省略UITableViewCellEditingStyle (枚举的名称),并编写.delete (我们感兴趣的枚举的成员值)。如果您是Swift枚举的新手,那么我建议您阅读有关Swift中枚举的快速提示

接下来,我们通过在items属性上调用remove(at:)并传递正确的索引来更新表视图的数据源items 。 我们还通过在tableView上调用deleteRows(at:with:)来更新表视图,并传入带有indexPath.right的数组以指定动画类型。 如前所述,我们可以省略枚举的名称UITableViewRowAnimation ,因为Swift知道第二个参数的类型是UITableViewRowAnimation

用户现在应该能够从列表中删除项目。 生成并运行应用程序以对此进行测试。

2.检查项目

要将项目标记为完成,我们将在对应的行上添加一个选中标记。 这意味着我们需要跟踪用户已标记为完成的项目。 为此,我们将声明一个新属性来为我们管理此属性。 声明类型为[String]的变量属性checkedItems ,并使用一个空数组对其进行初始化。

var checkedItems: [String] = []

tableView(_:cellForRowAt:) ,我们通过调用contains(_:)方法并传入与当前行相对应的项目,来检查checkedItems是否包含相应的项目。 如果checkedItems包含item则该方法返回true

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Fetch Item
    let item = items[indexPath.row]

    // Dequeue Cell
    let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath)

    // Configure Cell
    cell.textLabel?.text = item

    if checkedItems.contains(item) {
        cell.accessoryType = .checkmark
    } else {
        cell.accessoryType = .none
    }

    return cell
}

如果item被发现checkedItems ,我们设置了电池的accessoryType属性.checkmark的成员值UITableViewCellAccessoryType枚举。 如果未找到item ,我们将退回到.none作为单元格的附件类型。

下一步是通过实现UITableViewDelegate协议的tableView(_:didSelectRowAt:)方法来添加将项目标记为完成的功能。 在此委托方法中,我们首先在tableView上调用deselectRow(at:animated:)以取消选择用户点击的行。

// MARK: - Table View Delegate Methods

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)

    // Fetch Item
    let item = items[indexPath.row]

    // Fetch Cell
    let cell = tableView.cellForRow(at: indexPath)

    // Find Index of Item
    let index = checkedItems.index(of: item)

    if let index = index {
        checkedItems.remove(at: index)
        cell?.accessoryType = .none
    } else {
        checkedItems.append(item)
        cell?.accessoryType = .checkmark
    }
}

然后,我们从items获取对应的项目,并获取与所点击的行相对应的单元格的引用。 我们通过调用index(of:)checkedItems询问相应项的index(of:) 。 此方法返回一个可选的Int 。 如果checkedItems包含item ,则将其从checkedItems删除,并将单元格的附件类型设置为.none 。 如果checkedItems不包含item ,则将其添加到checkedItems ,并将单元格的附件类型设置为.checkmark

通过这些添加,用户现在可以将项目标记为已完成。 生成并运行该应用程序,以确保一切正常。

3.保存状态

该应用程序当前不保存两次启动之间的状态。 为了解决这个问题,我们将把itemscheckedItems数组存储在应用程序的用户默认数据库中。

步骤1:载入状态

首先创建两个帮助器方法loadItems()loadCheckedItems() 。 请注意,在每个帮助程序方法之前加了private关键字。 private关键字告诉Swift,这些方法只能从ViewController类中访问。

// MARK: Private Helper Methods

private func loadItems() {
    let userDefaults = UserDefaults.standard

    if let items = userDefaults.object(forKey: "items") as? [String] {
        self.items = items
    }
}

private func loadCheckedItems() {
    let userDefaults = UserDefaults.standard

    if let checkedItems = userDefaults.object(forKey: "checkedItems") as? [String] {
        self.checkedItems = checkedItems
    }
}

private关键字是Swift的访问控制的一部分 。 顾名思义,访问控制定义了哪些代码可以访问哪些代码。 访问级别适用于方法,函数,类型等。Apple仅指实体 。 有五个访问级别:开放,公共,内部,文件专用和专用。

  • 开放/公开 标记为开放或公开的实体可由同一模块以及其他模块中定义的实体访问。 这是公开框架接口的理想选择。 开放访问级别和公共访问级别之间存在一些差异。 您可以在The Swift Programming Language中阅读有关这些差异的更多信息。
  • 内部:这是默认访问级别。 换句话说,如果未指定访问级别,则应用此访问级别。 内部访问级别的实体只能由同一模块中定义的实体访问。
  • 文件专用:声明为文件专用的实体只能由同一源文件中定义的实体访问。 例如,在ViewController类中定义的私有帮助器方法只能由ViewController类访问。
  • 私有:私有与文件私有非常相似。 唯一的区别是,声明为私有的实体只能从其包围的声明中进行访问。 例如,如果我们在ViewController.swift中ViewController类创建扩展,则在扩展中将无法访问任何标记为file-private的实体,但可以访问私有实体。

如果您熟悉UserDefaults类,则辅助方法的实现很简单。 为了易于使用,我们将对标准用户默认对象的引用存储在名为userDefaults的常量中。 对于loadItems() ,我们向userDefaults请求与键"items"关联的对象,并将其向下转换为可选的字符串数组。 我们安全地解开了可选项,这意味着如果可选项不是nil ,则将值存储在常量items ,并将该值分配给视图控制器的items属性。

如果if语句看起来令人困惑,则在下面的示例中查看一下loadItems()方法的简单版本。 结果是相同的; 唯一的区别是简洁。

private func loadItems() {
    let userDefaults = UserDefaults.standard
    let storedItems = userDefaults.object(forKey: "items") as? [String]

    if let items = storedItems {
        self.items = items
    }
}

除了用于加载存储在用户默认数据库中的对象的键之外, loadCheckedItems()的实现是相同的。 让我们通过更新viewDidLoad()方法来使用loadItems()loadCheckedItems()

override func viewDidLoad() {
    super.viewDidLoad()

    // Set Title
    title = "To Do"

    // Populate Items
    items = ["Buy Milk", "Finish Tutorial", "Play Minecraft"]

    // Load State
    loadItems()
    loadCheckedItems()

    // Register Class for Cell Reuse
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell")
}

步骤2:保存状态

为了保存状态,我们实现了另外两个私有帮助器方法saveItems()saveCheckedItems() 。 逻辑类似于loadItems()loadCheckedItems() 。 区别在于我们将数据存储在用户默认数据库中。 确保setObject(_:forKey:)调用中使用的键与loadItems()loadCheckedItems()使用的键匹配。

private func saveItems() {
    let userDefaults = UserDefaults.standard

    // Update User Defaults
    userDefaults.set(items, forKey: "items")
    userDefaults.synchronize()
}

private func saveCheckedItems() {
    let userDefaults = UserDefaults.standard

    // Update User Defaults
    userDefaults.set(checkedItems, forKey: "checkedItems")
    userDefaults.synchronize()
}

并非必须要调用synchronize() 操作系统将确保您在用户的默认数据库存储数据在某个时刻写入磁盘。 但是,通过调用synchronize() ,您可以明确地告诉操作系统将所有挂起的更改写入磁盘。 这在开发过程中很有用,因为如果您杀死应用程序,操作系统不会将您的更改写入磁盘。 然后,似乎有些事情无法正常工作。

我们需要在许多地方调用saveItems()saveCheckedItems() 首先,在将新项目添加到列表时调用saveItems() 我们在AddItemViewControllerDelegate协议的委托方法中执行此操作。

// MARK: Add Item View Controller Delegate Methods

func controller(_ controller: AddItemViewController, didAddItem: String) {
    // Update Data Source
    items.append(didAddItem)

    // Save State
    saveItems()

    // Reload Table View
    tableView.reloadData()

    // Dismiss Add Item View Controller
    dismiss(animated: true)
}

当项目的状态在tableView(_:didSelectRowAt:)更改时,我们将更新checkedItems 。 此时最好也调用saveCheckedItems()

// MARK: - Table View Delegate Methods

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)

    // Fetch Item
    let item = items[indexPath.row]

    // Fetch Cell
    let cell = tableView.cellForRow(at: indexPath)

    // Find Index of Item
    let index = checkedItems.index(of: item)

    if let index = index {
        checkedItems.remove(at: index)
        cell?.accessoryType = .none
    } else {
        checkedItems.append(item)
        cell?.accessoryType = .checkmark
    }

    // Save State
    saveCheckedItems()
}

删除itemsitemscheckedItems更新。 为了保存此更改,我们同时调用saveItems()saveCheckedItems()

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        // Fetch Item
        let item = items[indexPath.row]

        // Update Items
        items.remove(at: indexPath.row)

        if let index = checkedItems.index(of: item) {
            checkedItems.remove(at: index)
        }

        // Update Table View
        tableView.deleteRows(at: [indexPath], with: .right)

        // Save State
        saveItems()
        saveCheckedItems()
    }
}

而已。 生成并运行该应用程序以测试您的工作。 玩应用程序并强制退出它。 当您再次启动该应用程序时,最后一个已知状态应被加载并可见。

4.物业观察员

目前尚缺乏该应用程序的用户体验。 删除所有项目或首次启动应用程序时,用户将看到一个空的表视图。 不好 我们可以通过在没有物品时显示一条消息来解决此问题。 这也使我有机会向您展示Swift的另一个功能,即属性观察器

步骤1:添加标签

首先,向用户界面添加标签以显示消息。 在ViewController类中声明一个名为UILabel名为messageLabel的插座,打开Main.storyboard ,然后在视图控制器的视图中添加一个标签。

@IBOutlet var messageLabel: UILabel!

将必需的布局约束添加到标签,并将其与Connections Inspector中的视图控制器的messageLabel插座连接 。 将标签的文本设置为“ 您没有待办事项”。 并将标签的文本放在Attributes Inspector中

添加消息标签

步骤2:实现属性观察器

仅当items包含任何元素时,消息标签才应该可见。 发生这种情况时,我们还应该隐藏表格视图。 我们可以通过在ViewController类中添加各种检查来解决此问题,但是更方便,更优雅的方法是使用属性观察器。

顾名思义,属性观察者观察属性。 每当属性更改时,即使新值与旧值相同,也会调用属性观察器。 有两种类型的属性观察器。

  • willSet :在值更改之前调用
  • didSet :值更改后调用

为了我们的目的,我们将为items属性实现didSet观察器。 看一下下面的代码片段中的语法。

var items: [String] = [] {
    didSet {
        let hasItems = items.count > 0
        tableView.isHidden = !hasItems
        messageLabel.isHidden = hasItems
    }
}

起初该结构可能看起来有些奇怪,所以让我解释一下发生了什么。 调用didSet属性观察器时,更改items属性后,我们将检查items属性是否包含任何元素。 基于hasItems常量的值,我们更新用户界面。 就这么简单。

didSet观察器被传递了一个常数参数,该常数包含该属性的旧值的值。 上面的示例中省略了它,因为在实现中我们不需要它。 以下示例显示了如何使用它。

var items: [String] = [] {
    didSet(oldValue) {
        if oldValue != items {
            let hasItems = items.count > 0
            tableView.isHidden = !hasItems
            messageLabel.isHidden = hasItems
        }
    }
}

示例中的oldValue参数没有显式类型,因为Swift知道items属性的类型。 在该示例中,仅当旧值与新值不同时,我们才更新用户界面。

willSet观察者的工作方式与此类似。 主要区别在于传递给willSet观察器的参数是一个常数,用于保存属性的新值。 使用属性观察器时,请记住,实例初始化时不会调用它们。

生成并运行该应用程序,以确保所有内容均正确连接。 即使该应用程序不是完美的并且可以使用更多功能,您还是使用Swift创建了第一个iOS应用程序。

结论

在本系列的最后三课中,您使用Swift的面向对象功能创建了功能性iOS应用程序。 如果您有一些编程和开发应用程序的经验,那么您肯定已经注意到,当前的数据模型存在一些不足之处,因此请轻描淡写。

如果要构建适当的应用程序,将项目存储为字符串并创建一个单独的数组来存储项目的状态并不是一个好主意。 更好的方法是创建一个单独的ToDo类以对项目进行建模并将其存储在应用程序的沙箱中。 这将是本系列下一课的目标。

翻译自: https://code.tutsplus.com/tutorials/swift-from-scratch-access-control-and-property-observers--cms-23487

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值