1464

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

我有一个充当数据输入表单的视图,我想为用户提供选择多个产品的选项。产品在另一个视图中以 a 列出,UITableViewController并且我已启用多项选择。

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

我四处浏览,看到有些人在应用程序委托中声明了一个数组。我读了一些关于singletons的东西,但我不明白这些是什么,我读了一些关于创建数据模型的东西。

执行此操作的正确方法是什么,我将如何去做?

4

46 回答 46

1734

这个问题在 Stack Overflow 上似乎很受欢迎,所以我想我会尝试给出一个更好的答案来帮助像我这样从 iOS 世界开始的人。

转发数据

将数据从另一个视图控制器转发到视图控制器。如果你想将一个对象/值从一个视图控制器传递到另一个视图控制器,你可能会使用这个方法,你可能会推送到导航堆栈。

对于这个例子,我们将ViewControllerAViewControllerB

要将BOOL值从ViewControllerAto传递,ViewControllerB我们将执行以下操作。

  1. ViewControllerB.hBOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
    
  2. ViewControllerA你需要告诉它ViewControllerB所以使用

     #import "ViewControllerB.h"
    

然后,您想要加载视图的位置,例如,didSelectRowAtIndex或者 some IBAction,您需要在ViewControllerB将其推送到导航堆栈之前设置属性。

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];

这将设置isSomethingEnabled为value 。ViewControllerBBOOLYES

使用 Segue 转发数据

如果您使用 Storyboard,您很可能会使用 segue,并且需要此过程来向前传递数据。这与上面类似,但不是在推送视图控制器之前传递数据,而是使用一个名为的方法

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

因此,要将 from 传递给BOOLto ViewControllerAViewControllerB我们将执行以下操作:

  1. ViewControllerB.hBOOL

     @property (nonatomic, assign) BOOL isSomethingEnabled;
    
  2. ViewControllerA你需要告诉它ViewControllerB,所以使用

     #import "ViewControllerB.h"
    
  3. 在情节提要上创建从ViewControllerAto的 segueViewControllerB并给它一个标识符。在这个例子中,我们将调用它"showDetailSegue"

  4. 接下来,我们需要将方法添加到ViewControllerA执行任何 segue 时调用的方法。因此,我们需要检测调用了哪个 segue,然后做一些事情。在我们的示例中,我们将检查"showDetailSegue",如果执行,我们将把我们的BOOL值传递给ViewControllerB

     -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
         if([segue.identifier isEqualToString:@"showDetailSegue"]){
             ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
             controller.isSomethingEnabled = YES;
         }
     }
    

如果您将视图嵌入到导航控制器中,则需要将上面的方法稍微更改为以下

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }

这将设置isSomethingEnabled为value 。ViewControllerBBOOLYES

传回数据

要将数据从您那里传回ViewControllerBViewControllerA您需要使用Protocols 和 DelegatesBlocks,后者可以用作回调的松散耦合机制。

为此,我们将ViewControllerA委托ViewControllerB. 这允许ViewControllerB发回消息以ViewControllerA使我们能够发回数据。

ViewControllerA成为它的代表,ViewControllerB必须符合ViewControllerB我们必须指定的协议。这告诉ViewControllerA它必须实现哪些方法。

  1. ViewControllerB.h中,在 下方#import,但在上方@interface您指定协议。

     @class ViewControllerB;
    
     @protocol ViewControllerBDelegate <NSObject>
     - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
     @end
    
  2. 接下来还是在中ViewControllerB.h,需要设置一个delegate属性并在中合成ViewControllerB.m

     @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
    
  3. 当我们弹出视图控制器时,我们会ViewControllerB调用一条消息。delegate

     NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
     [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
    
  4. 就是这样ViewControllerB。现在在 中ViewControllerA.h,告诉ViewControllerA导入ViewControllerB并遵守其协议。

     #import "ViewControllerB.h"
    
     @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
    
  5. ViewControllerA.m我们的协议中实现以下方法

     - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
     {
         NSLog(@"This was returned from ViewControllerB %@", item);
     }
    
  6. 在推viewControllerB送到导航堆栈之前,我们需要告诉 ViewControllerBViewControllerA是它的委托,否则我们会得到一个错误。

     ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
     viewControllerB.delegate = self
     [[self navigationController] pushViewController:viewControllerB animated:YES];
    

参考

  1. 视图控制器编程指南中的使用委托与其他视图控制器通信
  2. 委托模式

NS通知中心

这是传递数据的另一种方式。

// Add an observer in controller(s) where you want to receive data
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeepLinking:) name:@"handleDeepLinking" object:nil];

-(void) handleDeepLinking:(NSNotification *) notification {
    id someObject = notification.object // Some custom object that was passed with notification fire.
}

// Post notification
id someObject;
[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

将数据从一个类传回另一个类(一个类可以是任何控制器、网络/会话管理器、UIView 子类或任何其他类)

块是匿名函数。

此示例将数据从控制器 B传递到控制器 A

定义块

@property void(^selectedVoucherBlock)(NSString *); // in ContollerA.h

添加块处理程序(侦听器)

在哪里需要值(例如,您需要 ControllerA 中的 API 响应,或者 A 上需要 ContorllerB 数据)

// In ContollerA.m

- (void)viewDidLoad {
    [super viewDidLoad];
    __unsafe_unretained typeof(self) weakSelf = self;
    self.selectedVoucherBlock = ^(NSString *voucher) {
        weakSelf->someLabel.text = voucher;
    };
}

转到控制器 B

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ControllerB *vc = [storyboard instantiateViewControllerWithIdentifier:@"ControllerB"];
vc.sourceVC = self;
    [self.navigationController pushViewController:vc animated:NO];

火块

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
    NSString *voucher = vouchersArray[indexPath.row];
    if (sourceVC.selectVoucherBlock) {
        sourceVC.selectVoucherBlock(voucher);
    }
    [self.navigationController popToViewController:sourceVC animated:YES];
}

块的另一个工作示例

于 2012-03-16T11:39:33.740 回答
217

迅速

这里和 Stack Overflow 周围有大量的解释,但如果您是初学者,只是想获得一些基本的工作,请尝试观看这个 YouTube 教程(它帮助我最终理解了如何去做)。

将数据转发到下一个 View Controller

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

在此处输入图像描述

在 Interface Builder 中创建故事板布局。要进行转场,您只需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中将第一个和第二个 View Controller 设置为适当的 Swift 文件。

将数据传回之前的 View Controller

要将数据从第二个视图控制器传递回第一个视图控制器,您需要使用协议和委托。该视频非常清楚地介绍了该过程:

以下是基于视频的示例(稍作修改)。

在此处输入图像描述

在 Interface Builder 中创建故事板布局。同样,要进行转场,您只需Control从按钮拖动到第二个视图控制器。将 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)
    }
}

请注意,它protocol位于 View Controller 类之外。

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

于 2015-08-11T06:35:25.237 回答
141

MVC 中的 M 代表“模型”,在 MVC 范例中,模型类的作用是管理程序的数据。模型与视图相反——视图知道如何显示数据,但它对如何处理数据一无所知,而模型知道如何处理数据的一切,但对如何显示数据一无所知。模型可以很复杂,但不一定非要如此——您的应用程序的模型可能与字符串或字典数组一样简单。

控制器的作用是在视图和模型之间进行调解。因此,它们需要一个或多个视图对象和一个或多个模型对象的引用。假设您的模型是一个字典数组,每个字典代表表中的一行。您的应用程序的根视图显示该表,它可能负责从文件加载数组。当用户决定向表中添加新行时,他们点击某个按钮,您的控制器会创建一个新的(可变)字典并将其添加到数组中。为了填写行,控制器创建一个详细视图控制器并为其提供新字典。详细视图控制器填写字典并返回。字典已经是模型的一部分,所以不需要发生任何其他事情。

于 2011-03-06T13:49:13.553 回答
103

iOS 中的不同类可以通过多种方式接收数据。例如 -

  1. 分配另一个类后直接初始化。
  2. 委托 - 用于传回数据
  3. 通知 - 一次将数据广播到多个类
  4. 保存NSUserDefaults- 以便以后访问
  5. 单例类
  6. 数据库和其他存储机制,如p-list 文件等。

但是对于将值传递给在当前类中完成分配的不同类的简单场景,最常见和首选的方法是在分配后直接设置值。这是按如下方式完成的:

我们可以使用两个控制器来理解它 - Controller1 和 Controller2

假设在 Controller1 类中,您要创建 Controller2 对象并使用传递的 String 值推送它。这可以这样做:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj passValue:@"String"];
    [self pushViewController:obj animated:YES];
}

在Controller2类的实现中会有这个函数:

@interface Controller2  : NSObject

@property (nonatomic, strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

- (void) passValue:(NSString *)value {

    _stringPassed = value; // Or self.stringPassed = value
}

@end

你也可以直接设置Controller2类的属性,类似如下:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj setStringPassed:@"String"];
    [self pushViewController:obj animated:YES];
}

要传递多个值,您可以使用多个参数,例如:

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1” andValues:objArray withDate:date];

或者,如果您需要传递三个以上与共同特征相关的参数,您可以将值存储在模型类中并将该模型对象传递给下一个类

ModelClass *modelObject = [[ModelClass alloc] init];
modelObject.property1 = _property1;
modelObject.property2 = _property2;
modelObject.property3 = _property3;

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passmodel: modelObject];

简而言之,如果你想——

  1. 设置第二个类的私有变量通过调用自定义函数并传递值来初始化值。
  2. setProperties 通过使用 setter 方法直接初始化它来完成。
  3. 以某种方式传递超过 3-4 个彼此相关的值,然后创建一个模型类并为其对象设置值并使用上述任何过程传递对象。
于 2014-04-08T10:24:29.583 回答
88

经过更多研究后,似乎协议委托是正确的/Apple 首选的方法。

我最终使用了这个例子(在 iPhone 开发 SDK 中):

在视图控制器和其他对象之间共享数据

它工作得很好,允许我在我的视图之间前后传递一个字符串和一个数组。

于 2011-03-13T21:20:09.367 回答
72

我发现带有传递块的最简单和最优雅的版本。让我们将等待返回数据的视图控制器命名为“A”,将返回视图控制器命名为“B”。在这个例子中,我们想要获得 2 个值:Type1 的第一个和 Type2 的第二个。

假设我们使用 Storyboard,首先控制器设置回调块,例如在 segue 准备期间:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[BViewController class]])
    {
        BViewController *viewController = segue.destinationViewController;

        viewController.callback = ^(Type1 *value1, Type2 *value2) {
            // optionally, close B
            //[self.navigationController popViewControllerAnimated:YES];

            // let's do some action after with returned values
            action1(value1);
            action2(value2);
        };

    }
}

和“B”视图控制器应该声明回调属性,BViewController.h:

// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);

比在实现文件 BViewController.m 中我们需要返回我们的回调的值之后应该调用:

if (self.callback)
    self.callback(value1, value2);

要记住的一件事是,使用块通常需要管理强引用和 __weak 引用,如此处所述

于 2013-10-14T18:11:42.133 回答
60

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

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

原始海报还询问了SingletonsAppDelegate的使用。这些问题需要回答。

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

应用场景

与其进行高度假设的抽象讨论,不如将具体应用牢记在心。为了帮助定义两个视图控制器的情况和多于两个视图控制器的情况,我将定义两个具体的应用场景。

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

见图一。

原问题示意图

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

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

见图二。

家庭库存应用图

应用程序中有四个视图控制器。它是一个基于标签的应用程序,用于管理家庭库存。三个视图控制器呈现相同数据的不同过滤视图:

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

每当创建或编辑单个项目时,它还必须与其他视图控制器同步。例如,如果我们在 ViewControllerD 中添加了一条船,但它还没有投保,那么当用户去 ViewControllerA(Luxury Items)和 ViewControllerC(Entire Home Inventory)时,船肯定会出现,但用户去的时候不会出现ViewControllerB(非保险项目)。我们不仅需要关注添加新项目,还需要关注删除项目(可能从四个视图控制器中的任何一个中允许),或编辑现有项目(可能从“添加新项目表单”中允许,重新调整用途用于编辑)。

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

解决方案:委托和观察者模式,以及单例

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

  • 转场
  • 代表
  • 直接在视图控制器上设置属性
  • NSUserDefaults(实际上是一个糟糕的选择)

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

  • 观察者模式
  • 单身人士

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

+ (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)
  • NS通知中心。

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

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

每当您的应用程序委托开始变得臃肿时,就开始将功能删除到单例中。例如,Core Data Stack 不应该留在 AppDelegate 中,而应该放在它自己的类中,即 coreDataManager 类中。

参考

于 2015-04-05T22:04:57.677 回答
45

将数据从 ViewController 2(目标)传递回 viewController 1(源)是更有趣的事情。假设您使用storyBoard,这些都是我发现的方法:

  • 代表
  • 通知
  • 用户默认值
  • 辛格尔顿

这些已经在这里讨论过了。

我发现还有更多方法:

使用块回调:

prepareForSegue在VC1中的方法中使用

NextViewController *destinationVC = (NextViewController *) segue.destinationViewController;
[destinationVC setDidFinishUsingBlockCallback:^(NextViewController *destinationVC)
{
    self.blockLabel.text = destination.blockTextField.text;
}];

使用情节提要展开(退出)

在 VC 1 中实现一个带有 UIStoryboardSegue 参数的方法,如下所示:

-(IBAction)UnWindDone:(UIStoryboardSegue *)segue { }

在storyBoard中,将“return”按钮钩到vc的绿色Exit按钮(Unwind)上。现在您有了一个“返回”的 segue,因此您可以在 VC2 的 prepareForSegue 中使用 destinationViewController 属性,并在 VC1 返回之前更改它的任何属性。

  • 使用storyboards Undwind (Exit)的另一种选择——你可以使用你在VC1中写的方法

     -(IBAction)UnWindDone:(UIStoryboardSegue *)segue {
         NextViewController *nextViewController = segue.sourceViewController;
         self.unwindLabel.text = nextViewController.unwindPropertyPass;
     }
    

在 VC1 的 prepareForSegue 中,您可以更改任何您想要共享的属性。

在这两个展开选项中,您都可以设置按钮的 tag 属性并在 prepareForSegue 中检查它。

于 2014-04-11T00:33:01.130 回答
42

OP 没有提到视图控制器,但很多答案都提到了,我想加入 LLVM 的一些新特性,以便在想要将数据从一个视图控制器传递到另一个视图控制器时更容易做到这一点得到一些结果。

Storyboard segues、ARC 和 LLVM 块使这对我来说比以往任何时候都容易。上面的一些答案已经提到了故事板和转场,但仍然依赖于授权。定义委托当然可以,但有些人可能会发现传递指针或代码块更容易。

使用 UINavigators 和 segues,有一些简单的方法可以将信息传递给从属控制器并取回信息。ARC 使传递指向从 NSObjects 派生的事物的指针变得简单,因此如果您希望从属控制器为您添加/更改/修改某些数据,请将其传递给可变实例的指针。块使传递动作变得容易,因此如果您希望从属控制器调用更高级别控制器上的动作,请将其传递给块。您定义块以接受对您有意义的任意数量的参数。如果更适合的话,您还可以设计 API 以使用多个块。

以下是 segue 胶水的两个简单示例。第一个直接显示一个参数传递给输入,第二个参数传递给输出。

// Prepare the destination view controller by passing it the input we want it to work on
// and the results we will look at when the user has navigated back to this controller's view.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results
     // by virtue of both controllers having a pointer to the same object.
     andResults:self.resultsFromNextController];
}

第二个示例显示为第二个参数传递回调块。我喜欢使用块,因为它使相关细节在源中紧密结合 - 更高级别的源。

// Prepare the destination view controller by passing it the input we want it to work on
// and the callback when it has done its work.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [[segue destinationViewController]

     // This parameter gives the next controller the data it works on.
     segueHandoffWithInput:self.dataForNextController

     // This parameter allows the next controller to pass back results.
     resultsBlock:^(id results) {
         // This callback could be as involved as you like.
         // It can use Grand Central Dispatch to have work done on another thread for example.
        [self setResultsFromNextController:results];
    }];
}
于 2013-02-21T22:26:12.650 回答
42

有多种共享数据的方法。

  1. 您始终可以使用NSUserDefaults. 设置您想要与您选择的键共享的值,NSUserDefault并在下一个视图控制器中从关联到该键的值中获取值。

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
    
  2. 您可以只在viewcontrollerA. 创建一个viewcontrollerAin对象viewcontrollerB并将所需的值分配给该属性。

  3. 您还可以为此创建自定义委托。

于 2013-09-27T10:38:36.390 回答
32

如果您想将数据从一个控制器传递到另一个控制器,请尝试以下代码:

文件FirstViewController.h

@property (nonatomic, retain) NSString *str;

SecondViewController.h

@property (nonatomic, retain) NSString *str1;

文件FirstViewController.m

- (void)viewDidLoad
   {
     // Message for the second SecondViewController
     self.str = @"text message";

     [super viewDidLoad];
   }

-(IBAction)ButtonClicked
 {
   SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
   secondViewController.str1 = str;
  [self.navigationController pushViewController:secondViewController animated:YES];
 }
于 2013-11-18T12:48:24.273 回答
31

这是一个非常古老的答案,这是反模式。请使用代表。不要使用这种方法!!

1.在第二个视图控制器中创建第一个视图控制器的实例,并为其设置属性@property (nonatomic,assign)

2.分配SecondviewController这个视图控制器的实例。

2.完成选择操作后,将数组复制到第一个 View Controller。当您卸载第二个视图时,第一个视图将保存数组数据。

于 2012-05-23T13:08:30.760 回答
30

这个解决方案我找了很久,终于找到了。首先,在 SecondViewController.h 文件中声明所有对象,例如

@interface SecondViewController: UIviewController
{
    NSMutableArray *myAray;
    CustomObject *object;
}

现在在您的实现文件中,为这些对象分配内存,如下所示:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self)
     {
         // Custom initialization
         myAray=[[NSMutableArray alloc] init];
         object=[[CustomObject alloc] init];
     }
     return self;
}

Array现在您已经为对象分配了内存。现在您可以在推送 this 之前填充该内存ViewController

转到您的 SecondViewController.h 并编写两个方法:

-(void)setMyArray:(NSArray *)_myArray;
-(void)setMyObject:(CustomObject *)_myObject;

在实现文件中,可以实现函数:

-(void)setMyArray:(NSArray *)_myArray
{
     [myArra addObjectsFromArray:_myArray];
}

-(void)setMyObject:(CustomObject *)_myObject
{
     [object setCustomObject:_myObject];
}

期望你CustomObject必须有一个 setter 函数。

现在您的基本工作已经完成。转到您要推送的地方SecondViewController并执行以下操作:

SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]] ;
[secondView setMyArray:ArrayToPass];
[secondView setMyObject:objectToPass];
[self.navigationController pushViewController:secondView animated:YES ];

注意拼写错误。

于 2013-07-27T08:52:34.863 回答
30

斯威夫特 5

好吧, Matt Price 的答案非常适合传递数据,但我将在最新的Swift 版本中重写它,因为我相信新程序员会因为新的语法和方法/框架而发现它不再具有挑战性,因为原始帖子是在 Objective-C 中.

在视图控制器之间传递数据有多种选择。

  1. 使用导航控制器推送
  2. 使用转场
  3. 使用委托
  4. 使用通知观察者
  5. 使用块

我将用最新的 iOS 框架在 Swift 中重写他的逻辑


通过导航控制器推送传递数据从 ViewControllerA 到 ViewControllerB

步骤 1.在 ViewControllerB 中声明变量

var isSomethingEnabled = false

步骤 2.在 ViewControllerB' ViewDidLoad 方法中打印变量

override func viewDidLoad() {
    super.viewDidLoad()
    // Print value received through segue, navigation push
    print("Value of 'isSomethingEnabled' from ViewControllerA: ", isSomethingEnabled)
}

步骤 3.在 ViewControllerA 中传递数据,同时通过导航控制器推送

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
    viewControllerB.isSomethingEnabled = true
    if let navigator = navigationController {
        navigator.pushViewController(viewControllerB, animated: true)
    }
}

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: Passing data through navigation PushViewController
    @IBAction func goToViewControllerB(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.isSomethingEnabled = true
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

    // MARK:  - Variable for Passing Data through Navigation push
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Print value received through navigation push
        print("Value of 'isSomethingEnabled' from ViewControllerA: ", isSomethingEnabled)
    }
}

通过 Segue 传递数据从 ViewControllerA 到 ViewControllerB

步骤 1.创建从 ViewControllerA 到 ViewControllerB 的 Segue,并在 Storyboard 中提供 Identifier = showDetailSegue,如下所示

在此处输入图像描述

步骤 2.在 ViewControllerB 中声明一个名为isSomethingEnabled的可行对象并打印其值。

步骤 3.在 ViewControllerA 中传递 isSomethingEnabled 的值,同时传递 Segue

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK:  - - Passing Data through Segue  - -
    @IBAction func goToViewControllerBUsingSegue(_ sender: Any) {
        performSegue(withIdentifier: "showDetailSegue", sender: nil)
    }

    // Segue Delegate Method
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "showDetailSegue") {
            let controller = segue.destination as? ViewControllerB
            controller?.isSomethingEnabled = true//passing data
        }
    }
}

视图控制器B

import UIKit

class ViewControllerB: UIViewController {
    var isSomethingEnabled = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Print value received through segue
        print("Value of 'isSomethingEnabled' from ViewControllerA: ", isSomethingEnabled)
    }
}

通过 Delegate 传递数据从 ViewControllerB 到 ViewControllerA

步骤 1.在 ViewControllerB 文件中,但在类之外声明协议ViewControllerBDelegate

protocol ViewControllerBDelegate: NSObjectProtocol {

    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

步骤 2.在 ViewControllerB 中声明 Delegate 变量实例

var delegate: ViewControllerBDelegate?

步骤 3.在 ViewControllerB 的 viewDidLoad 方法中为委托发送数据

delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")

步骤 4.在 ViewControllerA 中确认 ViewControllerBDelegate

class ViewControllerA: UIViewController, ViewControllerBDelegate  {
// to do
}

步骤 5.确认您将在 ViewControllerA 中实现委托

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
    viewControllerB.delegate = self//confirming delegate
    if let navigator = navigationController {
        navigator.pushViewController(viewControllerB, animated: true)
    }
}

步骤 6.在 ViewControllerA 中实现接收数据的委托方法

func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
    print("Value from ViewControllerB's Delegate", item!)
}

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController, ViewControllerBDelegate  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // Delegate method
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?) {
        print("Value from ViewControllerB's Delegate", item!)
    }

    @IBAction func goToViewControllerForDelegate(_ sender: Any) {

        if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {
            viewControllerB.delegate = self
            if let navigator = navigationController {
                navigator.pushViewController(viewControllerB, animated: true)
            }
        }
    }
}

视图控制器B

import UIKit

//Protocol decleare
protocol ViewControllerBDelegate: NSObjectProtocol {
    // Classes that adopt this protocol MUST define
    // this method -- and hopefully do something in
    // that definition.
    func addItemViewController(_ controller: ViewControllerB?, didFinishEnteringItem item: String?)
}

class ViewControllerB: UIViewController {
    var delegate: ViewControllerBDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        // MARK:  - - - -  Set Data for Passing Data through Delegate  - - - - - -
        delegate?.addItemViewController(self, didFinishEnteringItem: "Data for ViewControllerA")
    }
}

通过 Notification Observer 传递数据从 ViewControllerB 到 ViewControllerA

Step 1. 在 ViewControllerB 的通知观察者中设置和发布数据

let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)

Step 2. 在 ViewControllerA 中添加 Notification Observer

NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)

Step 3. 在 ViewControllerA 中接收 Notification 数据值

@objc func methodOfReceivedNotification(notification: Notification) {
    print("Value of notification: ", notification.object ?? "")
}

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController{

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add observer in controller(s) where you want to receive data
        NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil)
    }

    // MARK: Method for receiving Data through Post Notification
    @objc func methodOfReceivedNotification(notification: Notification) {
        print("Value of notification: ", notification.object ?? "")
    }
}

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // MARK:Set data for Passing Data through Post Notification
        let objToBeSent = "Test Message from Notification"
        NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)
    }
}

通过块传递数据从 ViewControllerB 到 ViewControllerA

步骤 1. 在 ViewControllerB 中声明块

var authorizationCompletionBlock:((Bool)->())? = {_ 在}

Step 2. 在 ViewControllerB 中的 block 中设置数据

if authorizationCompletionBlock != nil
{
    authorizationCompletionBlock!(true)
}

Step 3. 在 ViewControllerA 中接收块数据

// Receiver Block
controller!.authorizationCompletionBlock = { isGranted in
    print("Data received from Block is: ", isGranted)
}

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK:Method for receiving Data through Block
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "showDetailSegue") {
            let controller = segue.destination as? ViewControllerB
            controller?.isSomethingEnabled = true

            // Receiver Block
            controller!.authorizationCompletionBlock = { isGranted in
                print("Data received from Block is: ", isGranted)
            }
        }
    }
}

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

    // MARK: Variable for Passing Data through Block
    var authorizationCompletionBlock:((Bool)->())? = {_ in}

    override func viewDidLoad() {
        super.viewDidLoad()

        // MARK: Set data for Passing Data through Block
        if authorizationCompletionBlock != nil
        {
            authorizationCompletionBlock!(true)
        }
    }
}

您可以在我的 GitHub 上找到完整的示例应用程序,如果您对此有任何疑问,请告诉我。

于 2019-05-19T14:24:47.347 回答
27

这不是这样做的方法。您应该使用委托。

我假设我们有两个视图控制器,ViewController1 和 ViewController2,这个检查是在第一个,当它的状态改变时,你想在 ViewController2 中做一些事情。为了以正确的方式实现这一点,您应该执行以下操作:

将新文件添加到您的项目(Objective-C 协议)菜单FileNew。现在将其命名为 ViewController1Delegate 或任何您想要的名称,并在 @interface 和 @end 指令之间编写它们:

@optional

- (void)checkStateDidChange:(BOOL)checked;

现在转到 ViewController2.h 并添加:

#import "ViewController1Delegate.h"

然后将其定义更改为:

@interface ViewController2: UIViewController<ViewController1Delegate>

现在转到 ViewController2.m 并在实现中添加:

- (void)checkStateDidChange:(BOOL)checked {
     if (checked) {
           // Do whatever you want here
           NSLog(@"Checked");
     }
     else {
           // Also do whatever you want here
           NSLog(@"Not checked");
     }
}

现在转到 ViewController1.h 并添加以下属性:

@property (weak, nonatomic) id<ViewController1Delegate> delegate;

现在,如果您在某些事件之后在 ViewController2 中创建 ViewController1,那么您应该使用 NIB 文件以这种方式执行此操作:

ViewController1* controller = [[NSBundle mainBundle] loadNibNamed:@"ViewController1" owner:self options:nil][0];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:nil];

现在你们都准备好了。每当您检测到 ViewController1 中的检查更改事件时,您只需执行以下操作:

[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control
于 2014-09-01T19:46:45.523 回答
25

如果您想将数据从一个视图控制器发送到另一个视图控制器,可以使用以下方法:

假设我们有 viewControllers:viewControllerA 和 viewControllerB

现在在文件viewControllerB.h

@interface viewControllerB : UIViewController {

  NSString *string;
  NSArray *array;

}

- (id)initWithArray:(NSArray)a andString:(NSString)s;

在文件viewControllerB.m 中

#import "viewControllerB.h"

@implementation viewControllerB

- (id)initWithArray:(NSArray)a andString:(NSString)s {

   array = [[NSArray alloc] init];
   array = a;

   string = [[NSString alloc] init];
   string = s;

}

在文件viewControllerA.m 中

#import "viewControllerA.h"
#import "viewControllerB.h"

@implementation viewControllerA

- (void)someMethod {

  someArray = [NSArray arrayWithObjects:@"One", @"Two", @"Three", nil];
  someString = [NSString stringWithFormat:@"Hahahahaha"];

  viewControllerB *vc = [[viewControllerB alloc] initWithArray:someArray andString:someString];

  [self.navigationController pushViewController:vc animated:YES];
  [vc release];
}

因此,这就是您无需设置任何委托即可将数据从 viewControllerA 传递到 viewControllerB 的方式。;)

于 2012-07-29T10:28:43.177 回答
23

使用Swift倾斜并想要一个简单的示例,如果您使用 segue 来解决问题,这是我传递数据的首选方法。

它与上面类似,但没有按钮、标签等。只需简单地将数据从一个视图传递到下一个视图。

设置故事板

有三个部分。

  1. 寄件人
  2. 赛格
  3. 收件人

这是一个非常简单的视图布局,它们之间有一个segue。


非常简单的视图布局。 注意:没有导航控制器


这是发件人的设置


寄件人


这是接收器的设置。


收件人


最后是segue的设置。


转场标识符


视图控制器

我们保持这个简单,所以没有按钮,也没有动作。我们只是在应用程序加载时将数据从发送方移动到接收方,然后将传输的值输出到控制台。

此页面采用最初加载的值并将其传递。

import UIKit


class ViewControllerSender: UIViewController {

    // THE STUFF - put some information into a variable
    let favoriteMovie = "Ghost Busters"

    override func viewDidAppear(animated: Bool) {
        // PASS IDENTIFIER - go to the receiving view controller.
        self.performSegueWithIdentifier("goToReciever", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // GET REFERENCE - ...to the receiver view.
        var viewControllerReceiver = segue.destinationViewController as? ViewControllerReceiver

        // PASS STUFF - pass the variable along to the target.
        viewControllerReceiver!.yourFavMovie = self.favoriteMovie

    }
}

此页面仅在加载时将变量的值发送到控制台。至此,我们最喜欢的电影应该在那个变量中。

import UIKit

class ViewControllerReceiver: UIViewController {

    // Basic empty variable waiting for you to pass in your fantastic favorite movie.
    var yourFavMovie = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // And now we can view it in the console.
        println("The Movie is \(self.yourFavMovie)")

    }
}

如果您想使用 segue 并且您的页面没有位于导航控制器下,这就是您可以解决的方法。

一旦运行,它应该自动切换到接收者视图并将值从发送者传递给接收者,在控制台中显示该值。

Ghost Busters 是一个经典的人。

于 2015-08-25T15:28:28.837 回答
20

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

首先是构建一个单例类。请参阅页面我的 Objective-C 单例应该是什么样子?.

为了使对象全局可访问,我只是将它导入appName_Prefix.pch其中,以便在每个类中应用 import 语句。

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

于 2011-03-06T14:03:19.477 回答
20

在 FirstViewController 到 SecondViewController 之间传递数据,如下所示

例如:

FirstViewController 字符串值为

StrFirstValue = @"first";

所以我们可以使用以下步骤在第二个类中传递这个值:

  1. 我们需要在SecondViewController.h文件中创建一个字符串对象

     NSString *strValue;
    
  2. 需要在.h文件中将属性声明为以下声明

     @property (strong, nonatomic)  NSString *strSecondValue;
    
  3. 需要在头声明下方的FirstViewController.m文件中合成该值

     @synthesize strValue;
    

    在文件FirstViewController.h中:

     @property (strong, nonatomic)  NSString *strValue;
    
  4. 在 FirstViewController 中,我们从哪个方法导航到第二个视图,请在该方法中编写以下代码。

     SecondViewController *secondView= [[SecondViewController alloc]
     initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]];
    
     [secondView setStrSecondValue:StrFirstValue];
    
     [self.navigationController pushViewController:secondView animated:YES ];
    
于 2013-10-23T10:58:59.457 回答
19

我目前正在通过一个名为 MCViewFactory 的项目为这个问题的开源解决方案做出贡献,可以在这里找到:

Manticore iOS 视图工厂

这个想法是模仿 Android 的意图范式,使用全局工厂来管理您正在查看的视图并使用“意图”在视图之间切换和传递数据。所有文档都在 GitHub 页面上,但这里有一些亮点:

您在 .XIB 文件中设置所有视图并在应用程序委托中注册它们,同时初始化工厂。

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

// The following two lines are optional.
[factory registerView:@"YourSectionViewController"];

现在,在您的视图控制器 (VC) 中,无论何时您想要移动到一个新的 VC 并传递数据,您都可以创建一个新的意图并将数据添加到它的字典 (savedInstanceState)。然后,只需设置工厂的当前意图:

MCIntent* intent = [MCIntent intentWithSectionName:@"YourSectionViewController"];
[intent setAnimationStyle:UIViewAnimationOptionTransitionFlipFromLeft];
[[intent savedInstanceState] setObject:@"someValue" forKey:@"yourKey"];
[[intent savedInstanceState] setObject:@"anotherValue" forKey:@"anotherKey"];
// ...
[[MCViewModel sharedModel] setCurrentSection:intent];

你所有符合这个的视图都需要是 MCViewController 的子类,它允许你覆盖新的 onResume: 方法,允许你访问你传入的数据。

-(void)onResume:(MCIntent *)intent {
    NSObject* someValue = [intent.savedInstanceState objectForKey:@"yourKey"];
    NSObject* anotherValue = [intent.savedInstanceState objectForKey:@"anotherKey"];

    // ...

    // Ensure the following line is called, especially for MCSectionViewController
    [super onResume:intent];
}
于 2014-09-16T17:18:29.983 回答
15

在下一个view controller .h文件中创建属性并定义 getter 和 setter。

property在 nextVC 上的 NextVC.h 中添加这个:

@property (strong, nonatomic) NSString *indexNumber;

添加

@synthesize indexNumber; 在 NextVC.m

最后

NextVC *vc = [[NextVC alloc]init];

vc.indexNumber = @"123";

[self.navigationController vc animated:YES];
于 2014-01-02T12:29:49.203 回答
11

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

不久前我写了一篇关于此的博客文章:共享模型代码。这是一个简短的摘要:

共享数据

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

  • 视图控制器(在导航或标签栏控制器中)的暴力迭代以设置数据
  • 在 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通知
  • 核心数据
  • NSFetchedResultsController
  • 数据源

核心数据

Core Data 的好处在于它具有反向关系。因此,如果您只想为 NotesViewController 提供 notes 对象,您可以这样做,因为它与笔记本等其他东西有反比关系。如果您需要 NotesViewController 中笔记本上的数据,您可以通过执行以下操作返回对象图:

let notebookName = note.notebook.name

在我的博客文章中阅读更多相关信息:共享模型代码

于 2015-01-29T17:35:29.710 回答
11

如果您想将数据从 ViewControlerOne 传递到 ViewControllerTwo,请尝试这些...

在 ViewControlerOne.h 中执行这些操作:

 @property (nonatomic, strong) NSString *str1;

在 ViewControllerTwo.h 中执行这些操作:

 @property (nonatomic, strong) NSString *str2;

在 ViewControllerTwo.m 中合成 str2:

@interface ViewControllerTwo ()
@end
@implementation ViewControllerTwo
@synthesize str2;

在 ViewControlerOne.m 中执行这些操作:

 - (void)viewDidLoad
 {
   [super viewDidLoad];

   // Data or string you wants to pass in ViewControllerTwo...
   self.str1 = @"hello world";
 }

O 按钮单击事件,执行以下操作:

-(IBAction)ButtonClicked
{
  // Navigation on buttons click event from ViewControlerOne to ViewControlerTwo with transferring data or string..
  ViewControllerTwo *objViewTwo = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerTwo"];
  obj.str2 = str1;
  [self.navigationController pushViewController: objViewTwo animated:YES];
}

在 ViewControllerTwo.m 中执行这些操作:

- (void)viewDidLoad
{
  [super viewDidLoad];
  NSLog(@"%@", str2);
}
于 2015-05-27T14:11:49.103 回答
11

您可以将数据保存在 App 委托中,以便在您的应用程序中跨视图控制器访问它。您所要做的就是创建一个应用程序委托的共享实例:

AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

例如

如果您声明 a NSArray object *arrayXYZ,那么您可以在任何视图控制器中通过appDelegate.arrayXYZ.

于 2015-05-29T12:34:45.160 回答
10

当您使用 .xib 文件时,委派是执行此类操作的唯一一种解决方案。但是,所有以前的答案都是storyboard针对 .xibs 文件的。您需要使用委托。这是您可以使用的唯一解决方案。

另一种解决方案是使用单例类模式。初始化一次并在整个应用程序中使用它。

于 2013-09-17T09:10:20.677 回答
10

新闻视图控制器

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tbl_View deselectRowAtIndexPath:indexPath animated:YES];
  News *newsObj = [newstitleArr objectAtIndex:indexPath.row];
  NewsDetailViewController *newsDetailView = [[NewsDetailViewController alloc] initWithNibName:@"NewsDetailViewController" bundle:nil];

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

  [self.navigationController pushViewController:newsDetailView animated:YES];
}

NewsDetailViewController.h

@interface NewsDetailViewController : UIViewController
@property(nonatomic,retain) NSString *newsHeadlineStr;
@end

新闻DetailViewController.m

@synthesize newsHeadlineStr;
于 2013-10-24T12:07:54.967 回答
8

如果可以取消用户选择的内容,我喜欢基于 NSProxy 的模型对象和模拟对象来提交或丢弃数据的想法。

传递数据很容易,因为它是单个对象或几个对象,如果你有,比如说,一个 UINavigationController 控制器,你可以保留对模型的引用,所有推送的视图控制器都可以直接从导航控制器访问它。

于 2014-05-08T16:09:38.433 回答
8

我见过很多人使用这种didSelectRowAtPath方法使事情复杂化。我在示例中使用了Core Data

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    // This solution is for using Core Data
    YourCDEntityName * value = (YourCDEntityName *)[[self fetchedResultsController] objectAtIndexPath: indexPath];

    YourSecondViewController * details = [self.storyboard instantiateViewControllerWithIdentifier:@"nameOfYourSecondVC"]; // Make sure in storyboards you give your second VC an identifier

    // Make sure you declare your value in the second view controller
    details.selectedValue = value;

    // Now that you have said to pass value all you need to do is change views
    [self.navigationController pushViewController: details animated:YES];

}

方法中的四行代码就完成了。

于 2015-01-16T03:18:43.510 回答
7

将数据从一个 ViewController 传递到另一个 ViewController 有 3 种类型。

  1. 以编程方式
  2. 转场
  3. 用户默认值

演示项目链接在这里- https://github.com/kamanijasmin13/Swift-Pass-data-between-viewcontrollers

以编程方式 在此处输入图像描述

转场 在此处输入图像描述

用户默认值 在此处输入图像描述

演示项目链接在这里- https://github.com/kamanijasmin13/Swift-Pass-data-between-viewcontrollers

于 2018-03-23T10:30:42.627 回答
7

对于 SwiftUI

将其视为在大量视图@EnvironmentObject上使用的更智能、更简单的方式。@ObservedObject与其在视图 A 中创建一些数据,然后将其传递给视图 B,然后是视图 C,然后是视图 D,然后最终使用它,您可以在视图中创建它并将其放入环境中,以便视图 B、C 和 D 将自动访问它。

注意:环境对象必须由祖先视图提供——如果SwiftUI找不到正确类型的环境对象,您将遇到崩溃。这也适用于预览,所以要小心。

例如,这是一个存储用户设置的可观察对象:

class UserSettings: ObservableObject {
     @Published var score = 0
}
于 2019-12-10T10:45:22.883 回答
6

这个问题有很多答案,提供了许多不同的方法来执行视图控制器通信,这些方法确实有效,但我没有看到任何地方提到哪种方法实际上最好使用,哪些方法可以避免。

在实践中,我认为只推荐几个解决方案:

  • 向前传递数据:
    • 覆盖使用情节提要和转场时的prepare(for:sender:)方法UIViewController
    • 在通过代码执行视图控制器转换时通过初始化程序或属性传递数据
  • 向后传递数据
    • 更新应用程序共享状态(您可以使用上述任一方法在视图控制器之间传递)
    • 使用委托
    • 使用 unwind segue

我建议不要使用的解决方案:

  • 直接引用前一个控制器而不是使用委托
  • 通过单例共享数据
  • 通过应用程序委托传递数据
  • 通过用户默认值共享数据
  • 通过通知传递数据

这些解决方案虽然在短期内有效,但会引入过多的依赖关系,这会扰乱应用程序的架构并在以后产生更多问题。

对于那些感兴趣的人,我写了一些文章,更深入地解决了这些问题并突出了各种缺点:

于 2016-12-03T15:27:48.387 回答
4

使用通知中心

对于斯威夫特 3

let imageDataDict:[String: UIImage] = ["image": image]

// Post a notification
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: imageDataDict)
// `default` is now a property, not a method call

// Register to receive notification in your class
NotificationCenter.default.addObserver(self, selector: #selector(self.showSpinningWheel(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)

// Handle notification
func showSpinningWheel(_ notification: NSNotification) {
    print(notification.userInfo ?? "")
    if let dict = notification.userInfo as NSDictionary? {
        if let id = dict["image"] as? UIImage {
            // Do something with your image
        }
    }
}

对于斯威夫特 4

let imageDataDict:[String: UIImage] = ["image": image]

// Post a notification
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notificationName"), object: nil, userInfo: imageDataDict)
// `default` is now a property, not a method call

// Register to receive notification in your class
NotificationCenter.default.addObserver(self, selector: #selector(self.showSpinningWheel(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)

// Handle notification
@objc func showSpinningWheel(_ notification: NSNotification) {
    print(notification.userInfo ?? "")
    if let dict = notification.userInfo as NSDictionary? {
        if let id = dict["image"] as? UIImage {
            // Do something with your image
        }
    }
}
于 2018-11-13T10:28:59.133 回答
4

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

  1. 委托协议(向后方向)。
  2. NSNotification 中心(双向)。
  3. UserDefault(双向)。
  4. 直接属性(前进方向)。
  5. 闭合(向后方向)。
  6. Segue(前进方向)。
于 2021-04-17T17:01:56.877 回答
3

对于任何想要的人来说,这都是一个非常棒的教程。这是示例代码:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"myIdentifer]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        myViewController *destViewController = segue.destinationViewController;
        destViewController.name = [object objectAtIndex:indexPath.row];
    }
}
于 2015-09-28T09:00:00.900 回答
3

您可以创建从源视图控制器到目标视图控制器的推送 segue,并提供如下所示的标识符名称。

在此处输入图像描述

您必须像这样从 didselectRowAt 执行 segue。

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    performSegue(withIdentifier: "segue", sender: self)
}

您可以从以下函数传递所选项目的数组。

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let index = CategorytableView.indexPathForSelectedRow
    let indexNumber = index?.row
    print(indexNumber!)
    let VC = segue.destination as! AddTransactionVC
    VC.val = CategoryData[indexNumber!] . // Here you can pass the entire array instead of an array element.
}

您必须检查目标视图控制器的 viewdidload 中的值,然后将其存储到数据库中。

override func viewDidLoad{
    if val != ""{
        btnSelectCategory.setTitle(val, for: .normal)
    }
}
于 2018-04-12T10:17:20.157 回答
3

好吧,我们有几种方法可以使用委托系统或使用 storyboardSegue:

  1. 与使用 setter 和 getter 方法一样,例如在 viewController.h 中

     @property (retain, nonatomic) NSString *str;
    

    现在,在 viewController.m

    @synthesize str;
    

    在这里,我有一个 PDF URL 和一个像这样的另一个 viewController 的 segue,而 pdfObject 是我的 pdfModel。它基本上是一个 NSOBJECT 类。

     str = [NSString stringWithFormat:@"%@", pdfObject.objPath];
     NSLog(@"pdfUrl :***: %@ :***:", pdfUrl);
    
     [self performSegueWithIdentifier:@"programPDFViewController_segue" sender:self];
    
     #pragma mark - Navigation
    
     // In a storyboard-based application, you will often want to do a little preparation before navigation
    
     - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
          if ([[segue identifier] isEqualToString:@"programPDFViewController_segue"]) {
              programPDFViewController *pdfVC = [segue destinationViewController];
              [pdfVC setRecievedPdfUrl:str];
          }
     }
    

    现在我成功地收到了我的 PDF URL 字符串和其他 ViewController 并在 webview 中使用该字符串...

  2. 与这样的代表一起工作时,我有一个 NSObject 类的实用程序,其中包含我在整个应用程序中使用的 dateFormatter、sharedInstance、EscapeWhiteSpaceCharacters、convertImageToGrayScale 和更多方法,所以现在在文件 utility.h

    在这种情况下,您无需每次将数据从一个视图控制器解析到另一个视图控制器时都创建变量。有一次,您在文件utility.h中创建了一个字符串变量。

    只需制作nil并再次使用它。

     @interface Utilities : NSObject
    

    文件实用程序.h

     +(Utilities*)sharedInstance;
    
     @property(nonatomic, retain)NSString* strUrl;
    

    现在在文件utility.m中:

     @implementation utilities
    
     +(utilities*)sharedInstance
     {
         static utilities* sharedObj = nil;
         if (sharedObj == nil) {
             sharedObj = [[utilities alloc] init];
         }
         return sharedObj;
     }
    

    现在完成了,来到你的文件firstViewController.m并调用委托

    NSString*str = [NSString stringWithFormat:@"%@", pdfObject.objPath];

    [连接共享实例].strUrl = nil; [连接共享实例].strUrl = str;

    现在直接转到您的文件secondViewController.m,并在不创建变量的情况下使用它

    在viewwillapear我做了什么:

     -(void)viewWillAppear:(BOOL)animated {
         [super viewWillAppear:YES];
    
         [self webViewMethod:[Connection sharedInstance].strUrl];
     }
    
    
     -(void)WebViewMethod:(NSString)Url {
    
         // Working with webview. Enjoy coding :D
     }
    

这个委托工作在内存管理方面是可靠的。

于 2018-05-07T15:32:11.080 回答
3

CombineUIKit 和 AppKit 的纯解决方案

让我们举一个在 ViewController 之间传递Int值的简单示例。count

ViewController (VC) 有一个名为 count 的变量,ViewController 可以让用户更改 count 的值。一旦用户完成更改值,他们将关闭子控制器,然后父 VC 应该具有更新的值。


父视图控制器

ParentVC从 ChildVC获取更新后的计数值

class ParentVC: UIViewController {
    var count = 1
    var countObserver: AnyCancellable! // 1

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let childVC = segue.destination as! ChildVC
        childVC.sliderValue = count // 2
        countObserver = childVC.$sliderValue // 3
            .assign(to: \.count, on: self)
    }
}
  1. countObserver将保留观察 ChildVC 所做更改的观察者

  2. 当child出现时,我们将当前count值从parent赋值给ChildVC中的一个控件(这里是UISlider),这成为修改的值的起点count

  3. 我们观察到sliderValue(这是一个发布者)会发出用户通过拖动滑块来更改的计数值。请注意,我们使用$sliderValue的不仅仅是 sliderValue。


子视图控制器

ChildVC 是emit重视 ParentVC 感兴趣的一个:

class ChildVC: UIViewController {
    @Published var sliderValue: Int = 0 // 1

    @IBOutlet var sliderOutlet: UISlider!

    @IBAction func slided(_ sender: UISlider) {
        sliderValue = Int(sender.value)
    }
}
  1. @Published是存储值并在值更改时发出信号的 Publisher。它的行为与常规 var 类似,但可以发布可以通过在其前面添加$符号来访问的值。

CurrentValueSubject 和 PassthroughSubject 与 @Published

  1. CurrentValueSubject也可以用来代替@Published。唯一的区别是,您必须手动发出信号。它在您想要控制何时发射的情况下很有用。例如,您可以发出该值,但前提是它位于特定范围内。

  2. PassthroughSubject也可以用来代替@Published 或CurrentValueSubject。在这里,不同之处在于 PassthroughSubject 不能保存一个值,它只能发出一个信号。当值无法在变量中具体表示时,这可能很有用。

于 2020-05-19T05:14:34.280 回答
2

要将数据从一个视图控制器 (VC) 发送到另一个,请使用以下简单方法:

YourNextVC *nxtScr = (YourNextVC*)[self.storyboard  instantiateViewControllerWithIdentifier:@"YourNextVC"];//Set this identifier from your storyboard

nxtScr.comingFrom = @"PreviousScreen"l
[self.navigationController nxtScr animated:YES];
于 2015-11-28T06:55:29.190 回答
2

在Swift中传递数据有很多解决方案。

向前传递数据

我最喜欢的两种向前传递数据的方法是依赖注入(DI) 和 Property Observers

依赖注入

class CustomView : UIView {
    init(_ with model : Model) {
        // Do what you want with data
    }
}

物业观察员

class CustomView : UIView {
    var model : Model? {
        didSet {
            // Do what you want with data after assign data to model
        }
        willSet {
            // Do what you want with data before assign data to model
        }
    }
}

向后传递数据

也最喜欢将数据传递到以前的 VC/视图的方法:

协议和委托

protocol CustomViewDelegate : class {
    func addItemViewController(_ with data: Model?)
}

weak var delegate : CustomViewDelegate?

class AnotherCustomView: UIView {

     let customView = AnotherCustomView()

     init() {
         customView.delegate = self
     }
}

extention AnotherCustomView : CustomViewDelegate {
    func addItemViewController(_ with data: Model?) {
        // Do what you want with data
    }
}

关闭

class AnotherCustomView : UIView {
     init(addItem: @escaping (_ value : Model?) -> ()) {
        // Do what you want with data
     }
}

class CustomView : UIView {

    init() {
        let customView = AnotherCustomView { [weak self] model in
            // Do what you want with data
        }
    }
}
于 2020-08-28T09:19:48.803 回答
1

Apple 的一种方法是使用 Segues。您需要使用 prepareForSegue() 函数。

周围有很多很棒的教程,这里有一个: 释放你的内在应用程序开发人员第 21 部分:在控制器之间传递数据

另外,请阅读有关使用 segues 的 Apple 文档: Using Segues

于 2015-12-22T03:13:41.093 回答
1

我推荐块/闭包和自定义构造函数。

假设您必须将字符串从 FirstViewController 传递给 SecondViewController。

你的第一个视图控制器。

class FirstViewController : UIViewController {

    func moveToViewControllerB() {

        let second_screen = SecondViewController.screen(string: "DATA TO PASS", call_back: {
            [weak self] (updated_data) in
            ///This closure will be called by second view controller when it updates something
        })
        self.navigationController?.pushViewController(second_screen, animated: true)
    }


}

你的第二个视图控制器

class SecondViewController : UIViewController {

    var incoming_string : String?
    var call_back : ((String) -> Void)?

    class func screen(string: String?, call_back : ((String) -> Void)?) -> SecondViewController {

        let me = SecondViewController(nibName: String(describing: self), bundle: Bundle.main);
        me.incoming_string = string
        me.call_back = call_back
        return me
    }

    // Suppose its called when you have to update FirstViewController with new data.
    func updatedSomething() {

        //Executing block that is implemented/assigned by the FirstViewController.
        self.call_back?("UPDATED DATA")
    }

}
于 2017-05-08T22:54:50.123 回答
1

在为 iOS 创建应用程序时,您必须始终遵循 MVC 概念。

在两种情况下,您可能希望将数据从 ViewController 传递到另一个:

  1. 当层次结构中有一个“A”ViewContoller 并且您想将一些数据发送到“B”,它是下一个视图控制器。在这种情况下,您必须使用 Segue。只需为 segue 设置一个标识符,然后在“A”VC 中,编写以下代码:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "A to B segue identifier" {
            let bViewController = segue.destination as! UIDocumentBrowserViewController
            bViewController.data = someData
        }
    }
    
  2. 当有一个它作为模态(或嵌入)A打开时。B现在B视图控制器应该对其父级视而不见。因此,将数据发回的最佳方法A是使用Delegation.

    B在视图控制器和delegate属性中创建委托协议。因此B将向其代表报告(发回数据)。在Aviewcontroller 中,我们实现了Bviewcontroller 的委托协议,并将在方法中设置self为 viewcontroller 的delegate属性。Bprepare(forSegue:)

这就是它应该正确实施的方式。

于 2018-03-26T15:49:44.967 回答
1

我更喜欢在没有代表和 segues 的情况下完成它。它可以通过自定义初始化或设置可选值来完成。

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?()
  }
}
于 2019-02-06T09:08:07.677 回答
0

如果您尝试直接 setTitle 和 Image 那么它不会像使用 segue 传递那样在我的情况下不起作用。所以只需在第一个视图控制器中分配第二个视图控制器变量的值

第一视图控制器

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let newViewController = storyBoard.instantiateViewController(withIdentifier: "individualchat") as! firstViewController
        newViewController.imageLink = (updatedUserserInfo?.results?[indexPath.section].picture?.large!)!
        newViewController.username = (updatedUserserInfo?.results?[indexPath.section].name?.first)!

        navigationController?.pushViewController(newViewController, animated: true)
    }

第二视图控制器

@IBOutlet var userName: UIButton!
@IBOutlet var userProfile: UIImageView!

var imageLink: String = "http://logok.org/wp-content/uploads/2014/05/Total-logo-earth-880x660.png"
var username: String = "hello"

override func viewDidLoad() {
    super.viewDidLoad()
    userProfile.imageFromURL(urlString: imageLink)
    userName.setTitle(username, for: .normal)
    print(imageLink)
    print(username)
}
于 2022-02-07T11:37:00.873 回答
-1

这里有一个更简单的方法。

只需使用全局变量。声明需要传递给下一个类的对象或变量。

例如,我们有两个类 -classAclassB分别。

classA中,通常它包含:

#import "classA.h"

@interface classA()

@end

@implementation classA

-(void)viewDidLoad
{
    ...
}
-(void)didReceiveMemoryWarning
{
    ...
}

classB包含:

#import "classB.h"

@interface classB()

@end

@implementation classB

-(void)viewWillLoad
{
    ...
}
-(void)didReceiveMemoryWarning
{
    ...
}

现在,将第二个类classB导入classA

#import "classA.h"
#import "classB.h"  // --- Import classB to classA.
@interface classA()

@end

@implementation classA

-(void)viewDidLoad
{
    ...
}
-(void)didReceiveMemoryWarning
{
    ...
}

现在我们有一座桥可以上二等舱classB。现在,为了将变量或对象声明为全局,请在第一个类的 .m 文件中声明它,如下所示:

classA.h

#import "classA.h"
#import "classB.h"
@interface classA()

@end
NSString *temp;  // ---- Declare any object/variable as global.
@implementation classA

-(void)viewDidLoad
{
    ...
    temp=@"Hello";
    ...
}
-(void)didReceiveMemoryWarning
{
    ...
}

这里的对象temp是 class 的全局对象NSString。要访问任何类中的全局对象或变量,只需在第二个类中重新声明对象或变量即可。例如,如下所示:

classB.m

#import "classB.h"

@interface classB()

@end
extern NSString *temp;  //----use `extern` keyword for using the global object/variable in classB that was declared in classA.
@implementation classB

-(void)viewDidLoad
{
    ...
    LabeL.text=temp;
    ...
}
-(void)didReceiveMemoryWarning
{
    ...
}

现在第二个类可以访问该值。很简单!... 这个方法可以用于任意数量的类。

笔记:

您应该将第二类的.h文件导入第一类。但是没有必要将第一类的.h文件导入第二类。

记住那座桥。有桥的话,两边应该都能走。

于 2016-07-07T12:59:33.447 回答
-7

使用通知中心将数据从一个视图传递到另一个视图。

观察者侦听器模式效果最好。另一种解决方法是在两个类中创建相同的对象。

在类 1 中创建类 2 对象。访问要传递的数据对象,设置它们,然后推送视图控制器。

于 2013-07-17T10:41:21.293 回答