我对iOS和Objective-C以及整个MVC范式都是新手,我一直坚持以下几点:

我有一个充当数据输入表单的视图,我想让用户选择多个产品。产品列在另一个具有UITableViewController的视图中,我已启用多个选项。

如何将数据从一个视图传输到另一个视图?我会将UITableView上的选择保存在一个数组中,但如何将其传递回上一个数据输入表单视图,以便在提交表单时将其与其他数据一起保存到核心数据?

我在网上冲浪,看到一些人在应用程序委托中声明了一个数组。我读了一些关于单态的东西,但我不明白这些是什么,我读了关于创建数据模型的东西。

正确的执行方式是什么?我将如何执行?


当前回答

敏捷的

这里和Stack Overflow周围有很多解释,但如果你是一个初学者,只是想做一些基本的工作,那么试试看YouTube教程(它帮助我最终了解了如何做)。

YouTube教程:如何通过segue发送数据(Swift)

将数据转发给下一个视图控制器

以下是基于视频的示例。其思想是将一个字符串从第一视图控制器中的文本字段传递到第二视图控制器的标签。

在界面生成器中创建情节提要布局。要制作segue,只需Control单击按钮并拖动到“第二视图控制器”。

第一视图控制器

第一视图控制器的代码为

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // Get a reference to the second view controller
        let secondViewController = segue.destination as! SecondViewController

        // Set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

第二视图控制器

第二视图控制器的代码是

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

别忘了

连接UITextField和UILabel的插座。在Interface Builder中将第一个和第二个视图控制器设置为相应的Swift文件。

将数据传回上一个视图控制器

要将数据从第二个视图控制器传递回第一个视图控制器,请使用协议和代理。本视频非常清晰地介绍了这一过程:

YouTube教程:iOS Swift基础教程:协议和委托但也要阅读这篇文章,以确保您不会陷入强大的参考循环。

以下是基于视频的示例(经过一些修改)。

在界面生成器中创建情节提要布局。同样,要制作segue,只需按住Ctrl键将其从按钮拖动到“第二视图控制器”。将segue标识符设置为showSecondViewController。此外,不要忘记使用以下代码中的名称连接出口和操作。

第一视图控制器

第一视图控制器的代码为

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destination as! SecondViewController
            secondViewController.delegate = self
        }
    }

    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

请注意自定义DataEnteredDelegate协议的使用。

第二视图控制器和协议

第二个视图控制器的代码为

import UIKit

// Protocol used for sending data back
protocol DataEnteredDelegate: AnyObject {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {

    // Making this a weak variable, so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

        // Call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(info: textField.text!)

        // Go back to the previous view controller
        _ = self.navigationController?.popViewController(animated: true)
    }
}

请注意,该协议在View Controller类之外。

就是这样。现在运行应用程序,您应该能够将数据从第二个视图控制器发送回第一个视图控制器。

其他回答

在我的例子中,我使用了一个单例类,它可以作为一个全局对象,允许访问应用程序中几乎所有地方的数据。

第一件事是构建一个单例类。请参考页面我的Objective-C单例应该是什么样子?。

为了使对象全局可访问,我只需将其导入appName_Prefix.pch中,该程序用于在每个类中应用import语句。

为了访问和使用这个对象,我简单地实现了一个类方法来返回共享实例,该实例包含自己的变量。

有几种方法可以在视图控制器之间传递数据。

代表协议(向后)。NSNotification中心(双向)。UserDefault(双向)。直接属性(正向)。关闭(向后)。Segue(前进方向)。

有很多方法可以做到这一点,选择正确的方法很重要。可能最大的架构决策之一在于如何在整个应用程序中共享或访问模型代码。

不久前,我写了一篇关于这一点的博客文章:共享模型代码。下面是一个简短的总结:

共享数据

一种方法是在视图控制器之间共享指向模型对象的指针。

在视图控制器(在导航或选项卡栏控制器中)上强制迭代以设置数据在prepareForSegue(如果是故事板)或init(如果是编程的)中设置数据

因为准备segue是最常见的,这里有一个例子:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    var next = segue.destinationViewController as NextViewController
    next.dataSource = dataSource
}

独立访问

另一种方法是一次处理满屏数据,而不是将视图控制器彼此耦合,将每个视图控制器耦合到它们可以独立访问的单个数据源。

我看到的最常见的方法是单例实例。因此,如果单例对象是DataAccess,则可以在UIViewController的viewDidLoad方法中执行以下操作:

func viewDidLoad() {
    super.viewDidLoad()
    var data = dataAccess.requestData()
}

还有其他工具也有助于传递数据:

关键价值观察NS通知核心数据NSFetchedResults控制器数据源

核心数据

核心数据的优点在于它具有相反的关系。因此,如果你只想给NotesViewController一个notes对象,你可以这样做,因为它与笔记本等其他东西有相反的关系。如果需要NotesViewController中笔记本上的数据,可以通过执行以下操作返回对象图:

let notebookName = note.notebook.name

在我的博客文章:共享模型代码中了解更多信息

在给出的许多答案中都有一些很好的信息,但没有一个完全解决这个问题。

该问题询问如何在视图控制器之间传递信息。给出的具体示例要求在视图之间传递信息,但考虑到iOS自称的新颖性,原始海报可能是指视图控制器之间,而不是视图之间(没有viewControllers的任何参与)。似乎所有的答案都集中在两个视图控制器上,但如果应用程序发展到需要在信息交换中包含两个以上的视图控制器,该怎么办?

最初的海报还询问了Singleton和AppDelegate的使用。这些问题需要回答。

为了帮助其他人看这个问题,谁想要一个完整的答案,我将尝试提供它。

应用场景

与其进行高度假设、抽象的讨论,不如考虑具体的应用。为了帮助定义两个视图控制器情形和两个以上视图控制器情形,我将定义两个具体的应用场景。

场景一:最多需要两个视图控制器共享信息。

见图一。

应用程序中有两个视图控制器。有一个ViewControllerA(数据输入表单)和一个ViewController B(产品列表)。产品列表中选择的项目必须与数据输入表单中文本框中显示的项目相匹配。在这种情况下,ViewControllerA和ViewControllerB必须彼此直接通信,而不能与其他视图控制器通信。

场景二:两个以上的视图控制器需要共享相同的信息。

见图二。

应用程序中有四个视图控制器。这是一个基于选项卡的应用程序,用于管理家庭库存。三个视图控制器显示相同数据的不同过滤视图:

ViewControllerA-奢侈品ViewControllerB-非保险项目ViewControllerC-整个住宅库存ViewControllerD-添加新项目表单

无论何时创建或编辑单个项目,它也必须与其他视图控制器同步。例如,如果我们在ViewControllerD中添加了一条船,但它还没有投保,那么当用户转到ViewControllerA(豪华物品)和ViewControllerC(整个住宅库存)时,船必须出现,但当用户转到ViewControllerB(非投保物品)时则不会出现。我们不仅需要关注添加新项目,还需要关注删除项目(可以从四个视图控制器中的任何一个中删除),或编辑现有项目(可以在“添加新项目表单”中允许,将其重新用于编辑)。

由于所有视图控制器都需要共享相同的数据,所以所有四个视图控制器都必须保持同步,因此,每当任何单个视图控制器更改基础数据时,都需要与所有其他视图控制器进行某种通信。很明显,在这种情况下,我们不希望每个视图控制器直接与其他视图控制器通信。如果不明显,考虑我们是否有20个不同的视图控制器(而不是4个)。每当一个视图控制器发生更改时,通知其他19个视图控制器中的每一个会有多困难和容易出错?

解决方案:代表和观察者模式,以及Singleton

在场景一中,我们有几个可行的解决方案,正如其他答案所给出的

赛格牌手表代表直接设置视图控制器上的财产NSUserDefaults(实际上是一个糟糕的选择)

在场景2中,我们还有其他可行的解决方案:

观察者模式单身者

单例是类的一个实例,该实例是其生命周期中唯一存在的实例。单例的名字来源于它是一个实例。通常,使用singleton的开发人员有特殊的类方法来访问它们。

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed
    // once in the lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

现在我们了解了单例是什么,让我们讨论单例如何适合观察者模式。观察者模式用于一个对象响应另一个对象的变化。在第二个场景中,我们有四个不同的视图控制器,他们都想知道底层数据的更改。“底层数据”应该属于单个实例,即单例。“了解更改”是通过观察对单例所做的更改来实现的。

家庭库存应用程序将具有一个类的单个实例,该类旨在管理库存项目列表。经理将管理一系列家庭用品。以下是数据管理器的类定义:

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

当主库存项的集合发生更改时,视图控制器需要了解此更改。上面的类定义并没有说明这将如何发生。我们需要遵循观察者模式。视图控制器必须正式遵守sharedManager。有两种方法可以观察另一个对象:

关键值观察(KVO)NSNotificationCenter。

在场景2中,我们没有使用KVO可以观察到的HouseholdInventoryManager的单个属性。因为我们没有一个易于观察的属性,所以在这种情况下,必须使用NSNotificationCenter实现观察者模式。四个视图控制器中的每一个都将订阅通知,sharedManager将在适当的时候向通知中心发送通知。库存管理器不需要知道任何关于视图控制器或任何其他类的实例的信息,这些类可能有兴趣知道库存项集合何时发生变化;NSNotificationCenter负责这些实施细节。视图控制器只需订阅通知,数据管理器只需发布通知。

许多初学者程序员利用了这样一个事实,即在应用程序的生命周期中始终只有一个应用程序委托,它是全局可访问的。初级程序员利用这一事实将对象和功能填充到appDelegate中,以方便从应用程序中的任何其他位置进行访问。仅仅因为AppDelegate是单例并不意味着它应该替换所有其他单例。这是一个糟糕的实践,因为它给一个类带来了太多负担,打破了良好的面向对象实践。每个类都应该有一个易于解释的明确角色,通常只需通过类的名称。

每当您的应用程序代理开始变得臃肿时,就开始将功能删除到单例中。例如,核心数据堆栈不应该留在AppDelegate中,而是应该放在它自己的类coreDataManager类中。

工具书类

管理视图控制器之间的数据流在视图控制器之间传递数据Objective-C中的异步JSON请求

我更喜欢在没有代表和片段的情况下进行。它可以通过自定义init或设置可选值来完成。

1.自定义初始化

class ViewControllerA: UIViewController {
  func openViewControllerB() {
    let viewController = ViewControllerB(string: "Blabla", completionClosure: { success in
      print(success)
    })
    navigationController?.pushViewController(animated: true)
  }
}

class ViewControllerB: UIViewController {
  private let completionClosure: ((Bool) -> Void)
  init(string: String, completionClosure: ((Bool) -> Void)) {
    self.completionClosure = completionClosure
    super.init(nibName: nil, bundle: nil)
    title = string
  }

  func finishWork() {
    completionClosure()
  }
}

2.可选变量

class ViewControllerA: UIViewController {
  func openViewControllerB() {
    let viewController = ViewControllerB()
    viewController.string = "Blabla"
    viewController.completionClosure = { success in
      print(success)
    }
    navigationController?.pushViewController(animated: true)
  }
}

class ViewControllerB: UIViewController {
  var string: String? {
    didSet {
      title = string
    }
  }
  var completionClosure: ((Bool) -> Void)?

  func finishWork() {
    completionClosure?()
  }
}