在视图控制器之间传递数据


1372

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

我有一个充当数据输入表单的视图,我想给用户选择多个产品的选项。产品在另一个视图上以列出,UITableViewController并且我启用了多个选择。

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

我到处逛逛,看到有人在应用程序委托中声明了一个数组。我读了一些有关Singletons的内容,但不了解它们是什么,并且读了一些有关创建数据模型的知识。

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

Answers:


1683

这个问题在stackoverflow上似乎很受欢迎,所以我想我会尝试给出一个更好的答案来帮助像我这样的iOS初学者。

我希望这个答案足够清晰,让人们理解,并且我没有错过任何东西。

向前传递数据

将数据从另一个视图控制器传递到视图控制器。如果要将对象/值从一个视图控制器传递到可能要推送到导航堆栈的另一个视图控制器,则可以使用此方法。

在这个例子中,我们将ViewControllerAViewControllerB

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

  1. ViewControllerB.hBOOL

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

    #import "ViewControllerB.h"

    然后在你想加载视图的地方。didSelectRowAtIndex或一些IBAction您需要设置的属性,ViewControllerB然后再将其推入导航堆栈。

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

    这将设置isSomethingEnabledViewControllerBBOOL价值YES

使用Segues转发数据

如果使用情节提要,则很有可能使用segues,并且需要此过程将数据转发。这与上面的类似,但是不是在推送视图控制器之前传递数据,而是使用一种称为

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

因此,要将BOOLfrom 传递ViewControllerAViewControllerB我们,请执行以下操作:

  1. ViewControllerB.hBOOL

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

    #import "ViewControllerB.h"
  3. 从创建的SEGUE ViewControllerAViewControllerB的故事板,并给它一个标识符,在这个例子中,我们叫它"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;
        }
    }
    

    这将设置isSomethingEnabledViewControllerBBOOL价值YES

传回数据

为了将数据传递回去ViewControllerBViewControllerA您需要使用Protocols and 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. View Controller编程指南》中的使用委派与其他View Controller通信
  2. 代表图案

NSNotification中心 这是传递数据的另一种方法。

// add 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];
}

块的另一个工作示例


24
我们还必须@class ViewControllerB;在@protocol定义上方放一个吗?没有它,我会在以下行的ViewControllerB上收到“预期类型”错误:- (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;@protocol声明中
alan-p

4
这很棒。正如alan-p所说的,不要忘记编写@class ViewControllerB。否则,您会收到“预期类型”错误。
安德鲁·戴维斯

6
您不需要委托传回,只需使用unwind。
malhal 2013年

4
当我放入“ viewControllerB.delegate = self;”时 在ViewControllerB中,我得到一个错误。从不兼容类型'ViewControllerB * const __strong'分配给'id <ViewControllerBDelegate>',我不确定自己在做什么错。有人可以帮忙吗?另外,我不得不更改:initWithNib-> initWithNibName。
uplearnedu.com 2014年

4
如果你正在使用NavigationController你必须使用 [self.navigationController pushViewController:viewController animated:YES];代替[self pushViewController:viewControllerB animated:YES];
纳齐尔

192

迅速

此处和有关StackOverflow的解释不计其数,但是如果您是初学者,只是想尝试一些基本的知识,请尝试观看此YouTube教程(这是帮助我最终了解如何做的)。

将数据转发到下一个View Controller

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

在此处输入图片说明

在“界面生成器”中创建情节提要板布局。要进行设置,您只需Control单击按钮并拖动到Second View Controller。

第一视图控制器

First View Controller的代码是

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
  • 将第一个和第二个View Controller设置为IB中的相应Swift文件。

将数据传回上一个View Controller

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

以下是基于视频的示例(进行了一些修改)。

在此处输入图片说明

在“界面生成器”中创建情节提要板布局。同样,要进行设置,只需Control将其从按钮拖动到Second View Controller。将segue标识符设置为showSecondViewController。另外,不要忘记使用以下代码中的名称来连接插座和操作。

第一视图控制器

First View Controller的代码是

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类中。

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


考虑到一些最新的Swift更新,这仍然是实现的常见模式吗?
piofusco

4
我见过的大多数Swift更新都是相对较小的语法更改,而不是视图控制器之间的数据传递方式的更改。如果我确实了解到类似的重大变化,则将更新答案。
Suragch 2015年

2
offtopic-iOS具有一种将参数传递给新的视图控制器的丑陋方式,令人难以置信-您在进行调用时必须将参数设置在某个地方,而不是其他地方。Android在这方面有一个更好的方法-当您启动一个Activity时,您可以通过其启动Intent传递几乎所有数据。简单。无需投射等。将返回值传递回调用方也是必不可少的事情,无需委托。当然也可以使用丑陋的方法,没有问题))
Mixaz '16

1
@Himanshu,首先获取对第二个视图控制器的引用。然后更新它包含的公共变量。
Suragch '16

8
@蜜糖。我认为“委托”一词令人困惑。让我用“工人”一词。“工作人员”(第一个视图控制器)执行“老板”(第二个视图控制器)指示的操作。“老板”不知道谁是“工人”。可能是任何人。因此,在第一个视图控制器(“工人”类)中,我将成为您的“工人”。您告诉我标签上要写什么,我会帮您做。因此,secondViewController.delegate = self意思是“我同意成为老板的工人”。有关另一个示例和更多说明,请参见此答案
Suragch

136

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

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


95

有多种方法可以将数据接收到iOS中的不同类。例如 -

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

但是对于将值传递给在当前类中完成分配的另一个类的简单方案,最常见和首选的方法是分配后直接设置值。这样做如下:

我们可以使用两个控制器来理解它-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]; 

或者,如果您需要传递与通用功能相关的三个以上参数,则可以将值存储到Model类中,然后将该modelObject传递给下一个类

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) set the private variables of the second class initialise the values by calling a custom function and passing the values.
2) setProperties do it by directlyInitialising it using the setter method.
3) pass more that 3-4 values related to each other in some manner , then create a model class and set values to its object and pass the object using any of the above process.

希望这可以帮助


84

经过更多的研究,似乎协议和代表是正确的/ Apple偏爱的方式。

我最终使用了这个例子

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

工作正常,并允许我在视图之间来回传递字符串和数组。

感谢你的帮助


3
不使用协议和委托,仅使用展开。
malhal 2013年

1
@malhal如果不使用故事板怎么办?
Evan R

我也讨厌无用的协议和委托。@malhal
DawnSong 2015年

@EvanR您可以在代码中创建和执行序列。这都一样。
DawnSong 2015年

1
本质上,此页面上的整个质量检查是“从以前的容器查看开始”。一百万年来,您现在再也不会为协议或代表所困扰。无论如何,您在任何屏幕上所做的每件事都是容器视图,因此,这个问题实际上不再存在-您已经拥有所有容器视图中所有“上下”的引用。
Fattie

66

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

假设我们使用Storyboard,则第一个控制器设置回调块,例如在进行序列准备期间:

- (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引用类似解释这里


为什么不将value用作回调块的参数而不是单独的属性?
Timuçin

56

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

该问题询问有关在视图控制器之间传递信息的问题。给出的特定示例询问有关视图之间传递信息的问题,但是鉴于iOS的自我声明的新颖性,原始张贴者可能意味着在ViewController之间,而不是视图之间(不涉及ViewController)。似乎所有答案都集中在两个视图控制器上,但是如果应用程序演变为需要在信息交换中包含两个以上的视图控制器,该怎么办?

原始海报还询问了Singletons以及AppDelegate的用法。这些问题需要回答。

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

应用场景

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

方案1:最多需要两个视图控制器共享信息。参见图一。

原始问题图

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

方案二两个以上的视图控制器需要共享相同的信息。见图二。

房屋库存应用图

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

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

每当创建或编辑单个项目时,它还必须与其他视图控制器同步。例如,如果我们在ViewControllerD中添加了一条船,但尚未投保,则该船必须在用户转到ViewControllerA(豪华物品)和ViewControllerC(整个房屋库存)时出现,但在用户转到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)
  • NSNotificationCenter。

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

许多初学者都利用了这样一个事实,那就是在应用程序的生命周期中始终只有一个应用程序委托,该委托可以全局访问。入门程序员使用此事实将对象和功能填充到appDelegate中,以方便从应用程序中的任何其他位置访问。仅仅因为AppDelegate是一个单例并不意味着它应该替换所有其他单例。这是一种糟糕的做法,因为它将过多的负担放在一个类上,破坏了良好的面向对象的做法。每个类都应具有明确的角色,且通常仅通过类名即可轻松解释。

每当您的应用程序代表开始变得肿时,就开始将功能删除为单例。例如,不应将Core Data Stack保留在AppDelegate中,而应将其放在自己的类coreDataManager类中。

参考文献


41

OP没有提到视图控制器,但是有很多答案,我想了解一下LLVM的一些新功能,以便在要将数据从一个视图控制器传递到另一个视图控制器,然后再将其传递给其他人时,使此操作变得更加容易得到一些结果。

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

使用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];
    }];
}

41

将数据从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 { }

在故事板中,将“返回”按钮挂接到vc的绿色“退出”按钮(展开)。现在您有了一个“返回”的segue,因此您可以在VC2的prepareForSegue中使用destinationViewController属性,并在返回之前更改VC1的任何属性。

  • 使用情节提要板Undwind(退出)的另一种选择-您可以使用在VC1中编写的方法

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

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

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

希望我在讨论中添加了一些内容。

:)干杯。


40

有多种共享数据的方法。

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

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

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


30
NSUserDefaults的典型用途是存储在应用程序执行之间保持的用户首选项,因此,除非明确删除,否则此处存储的所有内容都将保留在此处。使用它在应用程序的视图控制器(或任何其他对象)之间传递信息是一个非常糟糕的主意。
何塞·冈萨雷斯

30

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

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];
 }

29

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

1.在第二个View Controller中创建第一个View Controller的实例,并设置其属性@property (nonatomic,assign)

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

2.完成选择操作后,将阵列复制到第一个View Controller,当您卸载SecondView时,FirstView将保存阵列数据。

希望这可以帮助。


2
我不认为这是正确的方法,因为它在视图控制器之间创建了非常棘手的链接。不真正坚持MVC。
马特·普赖斯

1
如果您想严格遵循MVC,请使用NSNotificationCenter可以将一个方法从ViewControllerA调用到ViewControllerB,检查可能会帮助您
kaar3k 2012年

28

我一直在寻找这个解决方案很长时间,Atlast我找到了。首先,在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和对象分配了内存。现在您可以在执行此操作之前填满该内存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 ];

注意拼写错误。


24

这不是做到这一点的方法,您应该使用委托,我假设我们有两个视图控制器ViewController1和ViewController2,并且此检查项位于第一个中,并且当其状态更改时,您想要在ViewController2中进行一些操作,以以正确的方式实现这一目标,您应该执行以下操作:

将新文件添加到您的项目(Objective-C协议)文件->新建,现在将其命名为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

如果我无法正确理解您的问题,请告诉我是否有任何不清楚的地方。


23

如果您想将数据从一个发送到另一个viewController,则可以采用以下方法:

假设我们有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的方法。;)


1
我尝试在项目中使用ur代码,但无法在viewcontrollerB中获取值。你能告诉我可能是什么问题吗?
X-Coder

1
@Ajitthala您可以将代码粘贴到新问题中吗?我会尽力解决您的问题。:)
Aniruddh Joshi 2013年

1
不使用init方法,只是从viewcontroller A中执行vcB.string = @“ asdf”之类的事情,这是错误的吗?
khanh.tran.vinh 2013年

1
@ khanh.tran.vinh取决于您是否使用ARC。
Aniruddh Joshi 2013年

21

我知道这是一个热门话题,但是对于那些希望通过SWIFT倾斜来回答这个问题并且想要一个简单的例子的人,如果您使用segue来解决问题,这里是我传递数据的首选方法。

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

设置情节提要

分为三个部分。

  1. 发件人
  2. 塞格
  3. 收件人

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


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


这是发件人的设置


发件人


这是接收机的设置。


收件人


最后,设置segue。


Segue标识符


视图控制器

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

该页面采用初始加载的值并将其传递。

import UIKit


class ViewControllerSender: UIViewController {

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

    override func viewDidAppear(animated: Bool) {
        // PASS IDENTIFIER - go to the recieving 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并且您的页面不在导航控制器下面,那么这就是解决该问题的方法。

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

幽灵克星是一个经典的乡亲。


19

以我为例,我使用了一个单例类,该类可以用作全局对象,从而允许从应用程序中的几乎所有位置访问数据。第一件事是建立一个单例类。请参阅页面,“ 我的Objective-C单例应该是什么样? ”而使对象可全局访问的我所做的只是简单地导入它,appName_Prefix.pch该对象用于在每个类中应用导入语句。为了访问和使用该对象,我仅实现了类方法以返回共享实例,该实例包含其自己的变量。


这是正确的答案。只需将单例用作“模型”。请注意,正如Caleb所说:“您的应用程序的模型可能像字符串数组一样简单”。必须指出的是,在Swift进行单例确实很简单。(是如此简单,在这里甚至都不值得一提-只是google。)对于新程序员来说,值得理解的是,使单身人士曾经是一个真正的麻烦。但是,单例绝对是iOS编程的核心-苹果所做的一切都是单例。这就是为什么苹果公司最终使其(在Swift中)正确地制作单身人士。
Fattie

1
但是请注意,这些天(2016年以上)“一切都是iOS中的容器视图”。您在屏幕上所做的每件事都会产生一个小的容器视图。获得容器视图的“上下”链接是相当琐碎的(尽管苹果将来会更容易做到),而且无论如何,您几乎都可以对每个容器视图进行操作。因此,如果您无论如何都已完成-您将获得答案;无需单身。容器视图介绍... stackoverflow.com/a/23403979/294884
Fattie

19

迅捷5

马特·普赖斯(Matt Price)的《答案》非常适合传递数据,但我将在最新的Swift版本中对其进行重写,因为我相信新程序员会由于新的语法和方法/框架而放弃具有挑战性的方法,正如原始文章在Objective-C中一样。

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

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

我将使用最新的iOS Framework在Swift中重写他的逻辑


通过导航控制器Push传递数据从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)
        }
    }

所以这是完整的代码:

ViewControllerA

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)
            }
        }
    }
}

ViewControllerB

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中,传递Segue时传递isSomethingEnabled的值

所以这是完整的代码:

ViewControllerA

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
        }
    }
}

ViewControllerB

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)
    }
}

通过委托传递数据从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中声明委托变量实例

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!)
    }

所以这是完整的代码:

ViewControllerA

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)
            }
        }
    }
}

ViewControllerB

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

第1步。在ViewControllerB的Notification viewer中设置和发布数据

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

步骤2.在ViewControllerA中添加Notification Observer

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

步骤3.在ViewControllerA中接收Notification数据值

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

所以这是完整的代码:

ViewControllerA

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 ?? "")
    }
}

ViewControllerB

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)
    }
}

通过Block传递数据从ViewControllerB到ViewControllerA

步骤1.在ViewControllerB中声明块

varauthorizationCompletionBlock:(((Bool)->())?= {_ in}

步骤2.在ViewControllerB中的块中设置数据

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

步骤3.在ViewControllerA中接收块数据

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

所以这是完整的代码:

ViewControllerA

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)
                }
            }
        }
}

ViewControllerB

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上找到完整的示例应用程序。如果对此有任何疑问,请告诉我。


18

如下所示在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 ];

进入SecondViewController之后,如何将数据传递回FirstViewController?
bruno 2015年

18

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

https://github.com/YetiHQ/manticore-iosviewfactory

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

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

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

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

现在,在您的VC中,只要您想移至新的VC并传递数据,就可以创建一个新的Intent并将数据添加到其字典(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];
}

希望你们中的一些人觉得这个解决方案有用/有趣。


然后,所有控制器对象都可以获取/设置任何范围内的所有已注册词典?对此投票。
Itachi

15

在next上创建属性,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];

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
  • 数据源

核心数据

核心数据的优点在于它具有逆向关系。因此,如果您只想给NotesViewController提供notes对象,则可以使用它,因为它与笔记本之类的东西具有相反的关系。如果您需要NotesNoteController中笔记本上的数据,则可以通过执行以下操作来回退对象图:

let notebookName = note.notebook.name

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


10

NewsViewController

- (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

NewsDetailViewController.m

@synthesize newsHeadlineStr;

10

当您使用.xib文件时,委派是执行此类操作的唯一解决方案,但是上述所有答案均适用storyboard于您需要使用委派的.xibs文件。那是您唯一的解决方案。

另一个解决方案是使用单例类模式对其进行一次初始化,然后在整个应用程序中使用它。


10

如果您想将数据从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";

 }

在按钮单击事件上执行此操作。

-(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);
}

10

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

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

例如

如果您声明a,NSArray object *arrayXYZ则可以通过以下方式在任何视图控制器中访问它appDelegate.arrayXYZ


这是黑客马拉松的最佳选择
Hai Feng Kao

9

如果您想将数据从一个发送到另一个viewController,则可以采用以下方法:

假设我们有viewControllers:ViewController和NewViewController。

在ViewController.h中

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
    IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;
}

@property (nonatomic,retain) IBOutlet UITextField *mytext1,*mytext2,*mytext3,*mytext4;

-(IBAction)goToNextScreen:(id)sender;

@end

在ViewController.m中

#import "ViewController.h"

#import "NewViewController.h"

@implementation ViewController
@synthesize mytext1,mytext2,mytext3,mytext4;

-(IBAction)goToNextScreen:(id)sender
{
    NSArray *arr = [NSArray arrayWithObjects:mytext1.text,mytext2.text,mytext3.text,mytext4.text, nil];


    NewViewController *newVc = [[NewViewController alloc] initWithNibName:@"NewViewController" bundle:nil];

    newVc.arrayList = arr;

    [self.navigationController pushViewController:newVc animated:YES];

}

在NewViewController.h中

#import <UIKit/UIKit.h>

@interface NewViewController : UITableViewController
{
    NSArray *arrayList;

    NSString *name,*age,*dob,*mobile;

}

@property(nonatomic, retain)NSArray *arrayList;

@end

在NewViewController.m中

#import "NewViewController.h"

#import "ViewController.h"

@implementation NewViewController
@synthesize arrayList;

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{

    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    // Return the number of rows in the section.
    return [arrayList count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];      
    }
    // Configure the cell...
    cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
    return cell;


}

@end

这样我们就可以将数据从一个ViewController传递到另一个View Controller ...


8

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

因为数据是单个对象或几个对象,所以传递数据很容易,如果您说UINavigationController控制器,则可以将对模型的引用保留在内部,并且所有推入的视图控制器都可以直接从导航控制器访问它。


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];

}

方法中的4行代码即可完成。


6

这个问题有很多答案,它们提供了执行视图控制器通信的多种方法,这些方法的确可行,但是我看不到任何地方提到哪个实际上最适合使用,哪个应该避免。

在我看来,实际上,仅建议使用几种解决方案:

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

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

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

这些解决方案虽然短期内起作用,但引入了过多的依赖关系,这些依赖关系会混淆应用程序的体系结构,并在以后产生更多问题。

对于感兴趣的人,我写了一些文章来更深入地解决这些问题,并强调各种弊端:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.