iOS开发-加载视图控制器和在它们之间传递值的技术—第 2 部分

161 阅读16分钟

在这个两步教程的第一部分中,我介绍了加载和呈现视图控制器的技术。这些技术包括图形和编程方法,因此当新的视图控制器即将出现时,每个人都可以选择最适合他们的方法。然而,在那篇文章中,我根本没有展示如何在两个视图控制器之间来回发送数据,所以我接下来会这样做。

在接下来的部分中,我将首先展示如何将数据从已经呈现的视图控制器发送到新的(呈现)控制器,无论是在使用 segue 还是代码进行呈现时。之后,我将专注于如何将数据从新视图控制器发送回前一个视图控制器,我将通过三种不同的方式来执行这样的操作。

你会在这篇文章中读到什么......

1. 从源到目的:使用 Segue 时发送数据

2. 从源到目的:在代码中加载视图控制器时传递数据

3. 将 数据从目标发送到源视图控制器:委托模式

4. 从目标发送数据到源视图控制器:通知

5. 将 数据从目标发送到源视图控制器:动作处理程序

6. 总结

从源到目标:使用 Segue 时发送数据

就我们的示例而言,我们将 在加载但尚未呈现后从FirstViewController 到发送一个随机生成的数字(整数)SecondViewController。当我们使用带有标识符idSegueSecondVC的 segue 呈现后者时,我们将这样做 。为了刷新您的记忆,在本教程的前一部分中,我们已将标题为Second View Controller的按钮的 segue 连接 到 Main storyboard 中具有该标识符的 Second View Controller 场景:

image.png

我们的目标是使用该标识符来指定我们想要的转场,然后将随机值传递给第二个视图控制器。然而,在我们到达那里之前,我们必须从其他事情开始:每个视图控制器在呈现时必须提供值,它必须包含可以实际保存传递给它的值的属性,否则无法从源到目标视图控制器。所以,我们的第一个任务是打开 SecondViewController.swift文件并添加下一个属性,因此可以保留一个 Int 随机值:

var  randomNumber:  Int!

接下来,我们将在视图控制器的根视图出现后将该随机数打印到 Xcode 控制台:

override  func  viewDidAppear(_  animated:  Bool)  {

    super.viewDidAppear(animated)

    if  let  random  =  randomNumber  {

        print("Random number received from FirstViewController:",  random)

    }

}

通过上述两个操作,我们设法:

  1. 在属性中保留一些将传递给此视图控制器的数据(一个 Int 值),
  2. 通过将其打印到控制台来验证此数据是否已成功传递。

以上就是我们在SecondViewController 类中需要做的所有事情,它包括将数据从源传递到目标视图控制器所需的一半工作。另一半工作是准备和发送FirstViewController. 我们将继续打开 FirstViewController.swift文件,并覆盖以下方法:

override  func  prepare(for  segue:  UIStoryboardSegue,  sender:  Any?)  {

}

当您将新的视图控制器添加到项目中时,Xcode 会自动添加此方法以及其余默认代码,但已将其注释掉。您所要做的就是取消注释。如果它不存在,只需将上面的代码片段复制并粘贴到您的 FirstViewController.swift文件中。

使用 segue 呈现视图控制器时,每次在呈现视图控制器之前都会调用上述方法。这是您可以将任何您想要的数据传递给将要呈现的视图控制器的地方,显然这也是我们将在这里使用的地方。由于此方法是通用的,并且会为所有连接到源视图控制器的 segue 调用,因此我们必须使用 segue 的标识符手动区分我们引用的 segue。因此,这就是为什么在创建 segue 时为它们设置标识符很重要的原因。

一旦我们区分了segue,我们必须访问即将呈现的视图控制器,并最终为它的各种属性分配我们想要的值。这可以通过 参数值的destination 属性来实现segue。为了演示所有内容,以下是我们示例的上述方法的实现:

override  func  prepare(for  segue:  UIStoryboardSegue,  sender:  Any?)  {

    if  let  identifier  =  segue.identifier  {

        if  identifier  ==  "idSegueSecondVC"  {

            if  let  secondVC  =  segue.destination  as?  SecondViewController  {

                secondVC.randomNumber  =  self.generateRandomNumber()

            }

        }

    }

让我们一步一步地看看上面代码中发生了什么:

  • 最初我们确保 segue 有一个标识符,并且只有在它已经设置后才继续。
  • 我们设置了一个条件,如果只有标识符被称为idSegueSecondVC,我们就继续 。
  • 我们SecondViewController 使用destination segue 对象的属性获取类的实例。注意上面的转换,因为该destination 属性返回一个通用UIViewController 对象,我们将无法访问视图控制器的自定义属性。
  • 如果实例化SecondViewController 成功,那么我们为randomNumber 之前创建的属性分配一个随机数。

将数据传递给新的视图控制器时,上述步骤始终相同。所有改变的是从一个视图控制器传递到另一个视图控制器的数据类型。

在上面的代码中,我们引用了一个名为 的方法generateRandomNumber(),而该方法尚不存在。定义它的时间:

func  generateRandomNumber()  ->  Int  {

    return  Int(arc4random_uniform(100))

}

此方法返回 0 到 99 之间的随机数。

这就是我们需要做的所有事情。如果我们现在测试应用程序并点击标题为Second View Controller的红色按钮 ,则在第二个视图控制器出现后,随机数将打印到控制台:

image.png

(这些值在连续显示第二个视图控制器五次后打印)。

在我们结束本节之前,还有一件更重要的事情必须提及。当您使用该perform(withIdentifier:sender:) 方法以编程方式触发 segue 时,我之前介绍的内容也适用。我们已经在本教程的前一部分,handleDoubleTap() 即FirstViewController.swift文件中的方法中 调用了该方法:

@objc func  handleDoubleTap()  {

    self.performSegue(withIdentifier:  "idSegueSecondVC_2",  sender:  self)

}

当我们双击第一个视图控制器的视图时,上面显示了第二个视图控制器。但是,在这种情况下不会传递随机数,因为执行的 segue 具有 idSegueSecondVC_2标识符。如果你也想让它工作,只需更新prepare(for segue:sender:) 方法:

override  func  prepare(for  segue:  UIStoryboardSegue,  sender:  Any?)  {

    if  let  identifier  =  segue.identifier  {

        if  identifier  ==  "idSegueSecondVC"  ||  identifier  ==  "idSegueSecondVC_2"  {

            if  let  secondVC  =  segue.destination  as?  SecondViewController  {

                secondVC.randomNumber  =  self.generateRandomNumber()

            }

        }

    }

}

从源到目标:在代码中加载视图控制器时传递数据

如果你不使用 segues 但你喜欢以编程方式加载和呈现视图控制器,那么事情会比以前的所有东西都更容易。您所需要的只是在加载目标视图控制器之后和呈现它之前,将您想要的值分配给目标视图控制器的正确属性。

要查看它的实际效果,让我们打开包含实现的 ThirdViewController.swift文件ThirdViewController (该文件已在代码中完整呈现)。与我们之前所做的类似,让我们为将在此视图控制器中传递的随机值声明一个 Int 属性。在类的开头,添加以下内容:

var  randomNumber:  Int!

当然,下一个代码可以在 Xcode 控制台中查看随机数:

override  func  viewDidAppear(_  animated:  Bool)  {

    super.viewDidAppear(animated)

    if  let  random  =  randomNumber  {

        print("Random number received from FirstViewController:",  random)

    }

}

现在是有趣的部分。打开 FirstViewController.swift文件并找到presentThirdVC() 我们加载和呈现ThirdViewController. 在其中添加以下突出显示的行,在视图控制器加载之后,但在呈现之前:

@IBAction func  presentThirdVC()  {

    let  thirdVC  =  ThirdViewController()

    thirdVC.randomNumber  =  self.generateRandomNumber()

    thirdVC.modalTransitionStyle  =  .partialCurl

    self.present(thirdVC,  animated:  true,  completion:  nil)

}

如果我们现在测试演示应用程序,那么通过点击标题为Third View Controller的红色按钮,  类似于下一个输出的内容将被打印到 Xcode 控制台:

image.png

这个简单的代码清楚地展示了当您完全在代码中工作时如何将数据从源控制器传递到目标控制器。

将数据从目标发送到源视图控制器:委托模式

从本节开始,我们将关注各种可能的方式,这些方式允许我们将数据从呈现的视图控制器发送回触发其呈现的视图控制器。我将一共向您展示三种方法,我们将从最常见的一种开始:委托模式。

就您理解的概念和必须遵循的步骤而言,使用委托非常简单。请在网上搜索有关它工作的详细信息,因为它不是我在这里广泛引用它的计划的一部分;相反,我将通过演示展示它是如何工作的。为了这个例子,我们将实现以下简单的场景:我们将更新我们的应用程序,所以每次SecondViewController 在当前时间戳上点击关闭按钮时都会被发送FirstViewController 到 Xcode 控制台,然后打印到 Xcode 控制台。

委托模式包括两个视图控制器中必须交换数据的操作。通常,我们从应该发送数据的视图控制器开始,并通过 实现一个新的协议。该协议包含必须发送数据时应调用的方法的签名,但它们是在目标视图控制器中实现的。当然,沿途还有更多细节,但我们也会看到它们。

回到我们的项目,打开 SecondViewController.swift文件并SecondViewController 在文件顶部的类实现开始之前移动。添加以下协议以及仅一个方法签名:

protocol  SecondViewControllerDelegate  {

    func  secondVCWillDismiss(withTimestamp timestamp:  TimeInterval)

}

我们真正想要的是在用户点击关闭按钮时调用上面的方法,但要访问它,我们必须在类中声明该协议的对象SecondViewController (当然稍后会使用它):

class  SecondViewController: UIViewController  {

    var  randomNumber:  Int!

    var  delegate:  SecondViewControllerDelegate!

    ...

}

现在让我们来看看dismissMe() IBAction 方法的定义。目前,有一行代码使视图控制器被解散。这将会改变,因为我们将在方法的开头添加更多行,我们将在其中做两件事:

  1. 我们将确保delegate 我们刚刚声明为属性的对象不是nil (因此我们可以使用它),
  2. 我们将调用secondVCWillDismiss(withTimestamp:) 委托方法。

接下来突出显示所需的更改:

@IBAction func  dismissMe()  {

    if  let  delegate  =  self.delegate  {

        delegate.secondVCWillDismiss(withTimestamp:  Date.timeIntervalSinceReferenceDate)

    }

    self.dismiss(animated:  true,  completion:  nil)

}

让我们在这里喘口气,让我们再次看看到目前为止我们做了什么:

  • 我们首先实现了一个包含方法签名的协议,当数据必须发送到FirstViewController. 这里的目的是 采用 中SecondViewControllerDelegate 协议FirstViewController 并实现我们只有我们想要应用的逻辑的一种方法。
  • 我们创建了一个delegate 属性(实际上你可以随意调用它)让我们可以访问该secondVCWillDismiss(withTimestamp:) 方法。
  • 我们在想要将一些数据发送回FirstViewController.

现在,让我们跳到另一边,打开 FirstViewController.swift文件。我们将首先采用我们的自定义协议,如下所示:

class  FirstViewController:  UIViewController,  SecondViewControllerDelegate  {

    ...

}

下一个非常重要的步骤是将 设置FirstViewController 为 的委托SecondViewController (换句话说,告诉应用程序FirstViewController 实际上正在SecondViewController 通过SecondViewControllerDelegate 协议侦听传入消息)。prepare(_:sender:) 在执行任何idSegueSecondVC或 idSegueSecondVC_2转场之前,转到我们可以访问第二个视图控制器的方法覆盖,  并添加突出显示的行:

override  func  prepare(for  segue:  UIStoryboardSegue,  sender:  Any?)  {

    if  let  identifier  =  segue.identifier  {

        if  identifier  ==  "idSegueSecondVC"  ||  identifier  ==  "idSegueSecondVC_2"  {

            if  let  secondVC  =  segue.destination  as?  SecondViewController  {

                secondVC.randomNumber  =  self.generateRandomNumber()

                secondVC.delegate  =  self

            }

        }

    }

}

最后,我们必须在类中实现唯一的协议方法FirstViewController :

func  secondVCWillDismiss(withTimestamp timestamp:  TimeInterval)  {

    print("Second View Controller dismissed at:",  timestamp)

}

就是这样!如果我们现在运行应用程序, 它会在被解除之前FirstViewController 接收来自 的消息SecondViewController以及时间戳数据,并打印如下内容:

image.png

委托模式是一种非常常见的技术,它被认为是每个 iOS 开发人员必须知道的事情之一。尽管我们刚刚看到的示例非常简单,但通过在现实生活中的大型项目中添加不同数量的方法,它很容易扩展。通过使用委托,我们可以将消息从一个视图控制器发送到另一个视图控制器,即使没有实际数据要传递。最后,委托可以在任何类型的类之间用作通信方法,而不仅仅是在视图控制器之间。

将数据从目标发送到源视图控制器:通知

在视图控制器(以及通常任何类型的类)之间发送数据的另一种方法是使用 通知。一个类可以向应用程序内部发布通知(或换句话说,一条消息),该通知 可以包含或不包含数据,而另一个(或更多)侦听该通知的类会接收该消息并采取适当的行动。发布通知并观察它们始终是相同的标准技术,我们接下来将看到。

对于我们的演示,我们将在SecondViewController 出现时发布通知。随着通知,我们将发送视图控制器出现的时间戳。该FirstViewController 会遵守该通知,并在收到它,我们就会显示会说当时的视图控制器出现的消息。很简单,但这就是我们需要切入正题的全部内容。

首先,打开 SecondViewController.swift文件。第一步是通过NotificationCenter 类发布通知。此外,我们需要为我们的通知提供一个 唯一的名称,以便以后容易区分。

在该viewDidAppear(_:) 方法中,添加以下突出显示的行:

override  func  viewDidAppear(_  animated:  Bool)  {

    ...

    NotificationCenter.default.post(name:  NSNotification.Name("secondVCDidAppearNotification"),  object:  Date.timeIntervalSinceReferenceDate)

}

这就是发布通知所需的全部内容!请注意:

  1. secondVCDidAppearNotification 是我们的通知的唯一名称。
  2. 我们将当前时间戳作为一个对象传递给将要发送的通知。

下一步是转到 FirstViewController.swift文件,并开始观察该通知。到目前为止,只是发布上述通知根本没有任何结果。

在 类的viewDidLoad() 方法中FirstViewController,添加下一行:

override  func  viewDidLoad()  {

    ...

    NotificationCenter.default.addObserver(self,  selector:  #selector(self.handleSecondVCDidAppearNotification(notification:)),  name:  NSNotification.Name("secondVCDidAppearNotification"),  object:  nil)

}

有了这个,我们FirstViewController 每次加载时都会开始观察我们的自定义通知。如您所见,我们对要观察的通知使用完全相同的唯一名称,这就是我们必须向系统说明我们感兴趣的通知的方式。因此,请谨慎使用并提供正确的名称关于您想在课堂上捕捉的通知。

一旦收到通知,就必须采取行动。此操作始终在#selector 参数值中调用的方法中实现,在上面的代码片段中,您可以看到我们将handleSecondVCDidAppearNotification(notification:) 方法指定为每次收到通知时都会调用的方法。让我们定义该方法:

@objc func  handleSecondVCDidAppearNotification(notification:  Notification)  {

    if  let  timestamp  =  notification.object  as?  TimeInterval  {

        print("Second View Controller appeared at:",  timestamp)

    }

}

请记住,我们要显示的时间戳是作为通知对象出现的,因此我们需要访问它并确保它在那里。然后,我们只需将其打印到 Xcode 控制台。

立即运行应用程序;您会注意到,当SecondViewController 出现时,控制台会打印一条消息。再一次,我们设法通过几个简单的步骤将数据从目标发送到源视图控制器。我提醒你,当你想在类之间发送消息时,可以使用本节中介绍的技术,而不仅仅是视图控制器。

image.png

将数据从目标发送到源视图控制器:动作处理程序

将数据从目标传递回源视图控制器的最后一种方法依赖于动作处理程序的使用 。该技术的详细信息在示例的旁边显示,但只是为了概述总体思路,我想说的是,如果您曾经创建或使用过完成处理程序在函数中,您将看到的内容看起来很熟悉。值得一提的是,这种方法在视图控制器之间并不像在一般类之间那样普遍,因为委托模式或通知是最优选的方法。但是,如果在某些情况下实现委托模式或发送和接收通知很重要,那么这种方式是从目标视图控制器接收数据的最合适的解决方案,因为它是一种快速实现。

就示例而言,这次我们将使用ThirdViewController 和dismiss 操作,让我们关闭该视图控制器。我们将定义一个动作处理程序,并在解除时调用它,这反过来将使FirstViewController 显示成为通常的现在时间戳。

进入正,打开 ThirdViewController.swift文件并在类的开头添加以下声明:

class  ThirdViewController: UIViewController  {

    var  actionHandler:  ((_  timestamp:  TimeInterval)  ->  Void)!

    ...

}

上面我们声明了一个回调处理程序,当我们关闭视图控制器时我们将调用它。稍后,我们将 在初始化之后和呈现之前为该处理程序分配一个 闭包,并且在该闭包的主体中,我们将在控制台上打印时间戳。FirstViewController``ThirdViewController

dismissMe() 现在我们关闭视图控制器的方法中,让我们添加突出显示的行,如下所示:

@objc func  dismissMe()  {

    if  let  actionHandler  =  self.actionHandler  {

        actionHandler(Date.timeIntervalSinceReferenceDate)

    }

    self.dismiss(animated:  true,  completion:  nil)

}

确保该actionHandler 属性不是很重要nil。万一它是nil 并且我们没有考虑到它,当我们点击关闭按钮时,我们将享受一个非常漂亮的应用程序崩溃。除此之外,请注意我们将当前时间戳作为动作处理程序中的参数传递,这是根据前面的声明它期望的值类型。

现在让我们打开 FirstViewController.swift,让我们前往该presentThirdVC() 方法。到目前为止,这个方法加载ThirdViewController,它创建并传递一个随机值给它,它指定一个过渡样式并最终呈现视图控制器。是时候进行添加,并为actionHandler 我们在上面声明的属性分配一个闭包,每次在ThirdViewController. 下面的代码段突出显示了所需的更改:

@IBAction func  presentThirdVC()  {

    let  thirdVC  =  ThirdViewController()

    thirdVC.randomNumber  =  self.generateRandomNumber()

    thirdVC.actionHandler  =  {  timestamp in

        print("Third View Controller dismissed at:",  timestamp)

    }

    thirdVC.modalTransitionStyle  =  .partialCurl

    self.present(thirdVC,  animated:  true,  completion:  nil)

}

通过运行应用程序并打开ThirdViewController,这是我们通过点击关闭按钮在 Xcode 的控制台中获得的内容:

image.png

结论

在这一点上,我们总结了这个由两部分组成的教程的第二部分,关于允许我们加载和呈现视图控制器,并在它们之间来回传递数据的现有技术。在这一部分中,我们通过研究三种不同的方法,重点讨论如何将数据从源视图控制器传递到目标控制器,以及如何将数据发送回源。通过尽量保持简单并切中要点,我有意避免了此处介绍的所有概念背后的所有理论,但如果您愿意,可以轻松地在网络上找到更多信息。无论如何,如果您坚持使用本文中演示的示例,您将很容易在视图控制器之间交换数据。

为方便起见,您可以下载演示项目,其中包含我们从此链接经历的所有示例。

文末推荐:iOS热门文集

iOS面试基础知识 (一)

iOS面试基础知识 (二)

iOS面试基础知识 (三)

iOS面试基础知识 (四)

iOS面试基础知识 (五)