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

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

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

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

我四处冲浪,看到一些人在应用委托中声明了一个数组。我读了一些关于单例的文章,但我不明白这些是什么,我读了一些关于创建数据模型的文章。

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

512325 次浏览

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

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

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

第一件事是构建一个单例类。请参考第0页。

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

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

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

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

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

它工作得很好,允许我在视图之间来回传递字符串和数组。

这个问题似乎在Stack Overflow上非常流行,所以我想我会尝试给出一个更好的答案来帮助像我这样的iOS世界的人。

向前传递数据

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

对于这个例子,我们将有ViewControllerAViewControllerB

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

  1. ViewControllerB.h中为BOOL创建一个属性

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

这会将ViewControllerB中的isSomethingEnabled设置为BOOLYES

使用Segue转发数据

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

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

因此,要将BOOLViewControllerA传递到ViewControllerB,我们将执行以下操作:

  1. ViewControllerB.h中为BOOL创建一个属性

     @property (nonatomic, assign) BOOL isSomethingEnabled;
  2. ViewControllerA中,您需要告诉它关于ViewControllerB的信息,因此使用

     #import "ViewControllerB.h"
  3. 在故事板上创建从ViewControllerAViewControllerB的segue并给它一个标识符。在这个例子中,我们称之为"showDetailSegue"

  4. 接下来,我们需要将方法添加到执行任何segue时调用的ViewControllerA。因此,我们需要检测调用了哪个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;}}

这会将ViewControllerB中的isSomethingEnabled设置为BOOLYES

传回数据

要将数据从ViewControllerB传递回ViewControllerA,您需要使用协议和代表,后者可以用作回调的松散耦合机制。

为此,我们将使ViewControllerA成为ViewControllerB的委托。这允许ViewControllerBViewControllerA发送消息,使我们能够发送数据。

要使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. 委托模式

通知中心

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

// 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 notificationid someObject;[NSNotificationCenter.defaultCenter postNotificationName:@"handleDeepLinking" object:someObject];

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

块是匿名函数。

此示例将数据从控制器b传递到控制器a

定义一个块

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

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

需要值的地方(例如,您需要在ControlllerA中的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];}

块的另一个工作示例

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

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

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

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

如果你想将数据从一个ViewController发送到另一个ViewController,这里有一种方法:

假设我们有viewController:viewControlllerA和viewControlllerB

现在在文件参数名ViewControlllerB. h

@interface viewControllerB : UIViewController {
NSString *string;NSArray *array;
}
- (id)initWithArray:(NSArray)a andString:(NSString)s;

在文件视图类型中:

#import "viewControllerB.h"
@implementation viewControllerB
- (id)initWithArray:(NSArray)a andString:(NSString)s {
array = [[NSArray alloc] init];array = a;
string = [[NSString alloc] init];string = s;
}

在文件视图控制器中:

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

所以这就是你如何在不设置任何委托的情况下将数据从viewControlllerA传递到viewControlllerB。;)

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

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

使用UINavigators和segue,有一些简单的方法可以将信息传递给从属控制器并将信息获取回来。ARC使传递指针到从NSObject派生的东西变得简单,因此如果您希望从属控制器为您添加/更改/修改一些数据,请将指针传递给可变实例。块使传递操作变得容易,因此如果您希望从属控制器调用更高级别控制器上的操作,请传递一个块。您定义块以接受对您有意义的任何数量的参数。如果更适合的话,您还可以设计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];}];}

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

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

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

我在这个解决方案中搜索了很长时间,终于找到了它。首先,像这样声明您的秒视图控制器. h文件中的所有对象

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

现在在你的实现文件中,像这样为这些对象分配内存:

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

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

转到您的第二个ViewController. 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 ];

注意拼写错误。

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

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

有多种方法可以共享数据。

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

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key][[NSUserDefaults standardUserDefaults] objectForKey:key]
  2. You can just create a property in viewcontrollerA. Create an object of viewcontrollerA in viewcontrollerB and assign the desired value to that property.

  3. You can also create custom delegates for this.

我发现最简单,最优雅的版本与传递块。让我们将等待返回数据的视图控制器命名为“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 valuesaction1(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的引用,如解释这里

在FirstViewController之间传递数据到第二个ViewController,如下所示

例如:

FirstViewController字符串值为

StrFirstValue = @"first";

因此,我们可以使用以下步骤在第二个类中传递此值:

  1. 我们需要在第二视图控制器. h文件中创建一个字符串对象

     NSString *strValue;
  2. 需要在. h文件中声明一个属性如下

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

     @synthesize strValue;

    在文件First View Controller. 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 ];

是否必选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];}

新闻详情View Controller. h

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

新闻详情View Controller. m

@synthesize newsHeadlineStr;

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

文件First View Controller. h

@property (nonatomic, retain) NSString *str;

第二视图控制器. h

@property (nonatomic, retain) NSString *str1;

文件First View Controller. m

- (void)viewDidLoad{// Message for the second SecondViewControllerself.str = @"text message";
[super viewDidLoad];}
-(IBAction)ButtonClicked{SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];secondViewController.str1 = str;[self.navigationController pushViewController:secondViewController animated:YES];}

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

在nextVC上的NextVC. h中添加此property

@property (strong, nonatomic) NSString *indexNumber;

添加

@synthesize indexNumber;在NextVC. m

最后一次

NextVC *vc = [[NextVC alloc]init];
vc.indexNumber = @"123";
[self.navigationController vc animated:YES];

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

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

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

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

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

- (void)pushToController2 {
Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];[obj passValue:@"String"];[self pushViewController:obj animated:YES];}

在Controlller2类的实现中,将有以下函数:

@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

您也可以直接设置Controlller2类的属性,类似于以下方式:

- (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个相互关联的值,然后创建一个模型类并将值设置为其对象,并使用上述任何过程传递对象。

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

  • 代表
  • 通知
  • 用户默认
  • Singleton

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

我发现有更多的方法:

使用块回调:

在VC1的prepareForSegue方法中使用它

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

使用故事板展开(退出)

在VC 1中使用UIStoryboardSegue参数实现一个方法,如下所示:

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

在StoryBoard中,将“返回”按钮挂钩到vc的绿色退出按钮(展开)。现在你有一个“返回”的segue,这样你就可以在VC2和在返回之前更改VC1的任何属性。

  • 使用故事板的另一种选择取消(退出)-您可以使用您在VC1中编写的方法

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

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

在这两个展开选项中,您可以设置按钮的标签属性并在准备ForSegue中选中它。

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

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

这不是解决问题的方法。您应该使用委托。

我假设我们有两个视图控制器,ViewControlller1和ViewControlller2,并且这个检查在第一个中,当它的状态发生变化时,你想在ViewControlller2中做些什么。要以正确的方式实现这一点,你应该执行以下操作:

将一个新文件添加到您的项目(Objective-C协议)菜单文件。现在将其命名为ViewControlller1代理或您想要的任何名称,并在@接口和@end指令之间写入这些:

@optional
- (void)checkStateDidChange:(BOOL)checked;

现在转到ViewControlller2. h并添加:

#import "ViewController1Delegate.h"

然后将其定义更改为:

@interface ViewController2: UIViewController<ViewController1Delegate>

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

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

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

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

现在,如果您在某个事件后在ViewControlller2中创建ViewControlller1,那么您应该使用NIB文件以这种方式执行:

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

现在你一切都准备好了。每当你检测到ViewControlller1中检查更改的事件时,你所要做的就是以下操作:

[delegate checkStateDidChange:checked]; // You pass here YES or NO based on the check state of your control

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

ManticoreiOS查看工厂

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

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

// Register activities
MCViewFactory *factory = [MCViewFactory sharedFactory];
// The following two lines are optional.[factory registerView:@"YourSectionViewController"];

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

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

我见过很多人使用didSelectRowAtPath方法将其复杂化。我在我的示例中使用核心数据

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// This solution is for using Core DataYourCDEntityName * 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 controllerdetails.selectedValue = value;
// Now that you have said to pass value all you need to do is change views[self.navigationController pushViewController: details animated:YES];
}

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

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

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

共享数据

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

  • 对视图控制器(在导航或选项卡控制器中)进行蛮力迭代以设置数据
  • 设置数据准备ForSegue(如果故事板)或init(如果编程)

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

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

独立接入

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

我见过的最常见的完成方式是单例实例。因此,如果您的单例对象是DataAccess,您可以在UIViewController的viewTitLoad方法中执行以下操作:

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

还有一些附加工具也有助于传递数据:

  • 键值观察
  • NSNotify
  • 核心数据
  • NS获取结果控制器
  • 数据源详细信息

核心数据

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

let notebookName = note.notebook.name

阅读更多关于我的博客文章:共享模型代码

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

这个问题问的是在视图控制器之间传递信息。给出的具体示例询问了在视图之间传递信息,但考虑到iOS的新鲜感,最初的海报可能意味着在viewController之间,而不是在视图之间(没有ViewController的任何参与)。似乎所有的答案都集中在两个视图控制器上,但是如果应用程序发展到需要在信息交换中涉及两个以上的视图控制器怎么办?

最初的海报还询问了Singletons请求代理的使用情况。这些问题需要回答。

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

应用场景:

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

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

见图一。

原始问题示意图

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

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

见图二。

家居盘点应用图

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

  • ViewControlllerA-奢侈品
  • ViewControlllerB-非保险物品
  • ViewControlllerC-整个家庭库存
  • ViewControlllerD-添加新项目表单

每当创建或编辑单个项目时,它还必须与其他视图控制器同步。例如,如果我们在ViewControlllerD中添加了一个船,但它还没有保险,那么当用户转到ViewControlllerA(豪华项目)和ViewControlllerC(整个家庭库存)时,船必须出现,但当用户转到ViewControlllerB(非保险项目)时,不需要出现船。我们不仅需要添加新项目,还需要删除项目(可能被四个视图控制器中的任何一个允许),或编辑现有项目(可能被“添加新项目表单”允许,重新利用相同的项目进行编辑)。

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

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

在第一种情况下,我们有几个可行的解决方案,正如其他答案所给出的那样

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

在第二种情况下,我们有其他可行的解决方案:

  • 观察者模式
  • Singletons

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

+ (HouseholdInventoryManager*) sharedManager; {static dispatch_once_t onceQueue;static HouseholdInventoryManager* _sharedInstance;
// dispatch_once is guaranteed to only be executed// once in the lifetime of the applicationdispatch_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观察到的属性。因为我们没有一个容易观察到的属性,在这种情况下,观察者模式必须使用NSNotificationCenter来实现。四个视图控制器中的每一个都将订阅通知,SharedManager将在适当的时候将通知发送到通知中心。库存管理器不需要知道任何关于视图控制器或任何其他类的实例的信息,这些类可能有兴趣知道库存项的集合何时变化;NSNotificationCenter负责这些实现细节。视图控制器只需订阅通知,数据管理器只需发布通知。

许多初学者利用了这样一个事实:在应用程序的生命周期内总是只有一个应用程序代理,它是全局可访问的。初学者利用这一事实将对象和功能填充到app代理中,作为从应用程序中任何其他地方访问的便利。仅仅因为App代理是单例并不意味着它应该取代所有其他单例。这是一种糟糕的做法,因为它给一个类带来了太多负担,破坏了良好的面向对象实践。每个类都应该有一个清晰的角色,易于解释,通常只是通过类名。

每当你的应用程序委托开始膨胀时,开始将功能移除到单例中。例如,核心数据堆栈不应该留在App委托中,而应该放在它自己的类中,一个coreDataManager类。

参考文献

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

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

 @property (nonatomic, strong) NSString *str1;

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

 @property (nonatomic, strong) NSString *str2;

在ViewControlllerTwo. 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];}

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

- (void)viewDidLoad{[super viewDidLoad];NSLog(@"%@", str2);}

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

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

举个例子

如果你声明了NSArray object *arrayXYZ,那么你可以通过appDelegate.arrayXYZ在任何视图控制器中访问它。

Swift

这里和堆栈溢出周围有大量的解释,但是如果你是一个初学者,只是想得到一些基本的工作,试着看这个YouTube教程(它帮助我最终理解如何做到这一点)。

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

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

在此处输入图片描述

在Interface Builder中创建故事板布局。要制作segue,您只需控制单击按钮并拖到第二个视图控制器。

第一视图控制器

第一个视图控制器的代码是

import UIKit
class FirstViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
// This function is called before the segueoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get a reference to the second view controllerlet secondViewController = segue.destination as! SecondViewController
// Set a variable in the second view controller with the String to passsecondViewController.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 Controllervar receivedString = ""
override func viewDidLoad() {super.viewDidLoad()
// Used the text from the First View Controller to set the labellabel.text = receivedString}
}

别忘了

  • UITextFieldUILabel连接插座。
  • 将第一个和第二个视图控制器设置为界面生成器中的适当Swift文件。

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

要将数据从第二个视图控制器传递回第一个视图控制器,你可以使用一个协议和一个代表。这段视频非常清楚地了解了该过程:

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

在此处输入图片描述

在Interface Builder中创建故事板布局。同样,要制作segue,您只需控制从按钮拖动到第二视图控制器。将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! SecondViewControllersecondViewController.delegate = self}}
func userDidEnterInformation(info: String) {label.text = info}}

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

第二视图控制器和协议

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

import UIKit
// Protocol used for sending data backprotocol DataEnteredDelegate: AnyObject {func userDidEnterInformation(info: String)}
class SecondViewController: UIViewController {
// Making this a weak variable, so that it won't create a strong reference cycleweak var delegate: DataEnteredDelegate? = nil
@IBOutlet weak var textField: UITextField!
@IBAction func sendTextBackButton(sender: AnyObject) {
// Call this method on whichever class implements our delegate protocoldelegate?.userDidEnterInformation(info: textField.text!)
// Go back to the previous view controller_ = self.navigationController?.popViewController(animated: true)}}

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

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

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

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

设置故事板

有三个部分。

  1. 发件人
  2. The Segue
  3. 接收者

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


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


这是发送者的设置


发件人


这是接收器的设置。


接收者


最后是segue的设置。


序列标识符


视图控制器

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

此页面获取最初加载的值并将其传递。

import UIKit

class ViewControllerSender: UIViewController {
// THE STUFF - put some information into a variablelet 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并且您的页面不在导航控制器下,那么您可以通过这种方式来处理它。

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

幽灵克星是一个经典的人。

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

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

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

YourNextVC *nxtScr = (YourNextVC*)[self.storyboard  instantiateViewControllerWithIdentifier:@"YourNextVC"];//Set this identifier from your storyboard
nxtScr.comingFrom = @"PreviousScreen"l[self.navigationController nxtScr animated:YES];

Apple的一种方法是使用Segue。您需要使用准备ForSegue()函数。

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

此外,阅读Apple关于使用segue的留档:使用Segue

更简单的方法在这里。

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

例如,我们有两个类-分别为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{...}

现在,导入第二个类classBclassA

#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()
@endNSString *temp;  // ---- Declare any object/variable as global.@implementation classA
-(void)viewDidLoad{...temp=@"Hello";...}-(void)didReceiveMemoryWarning{...}

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

classB.m

#import "classB.h"
@interface classB()
@endextern 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文件到第二类。

记住这座桥。如果有一座桥,它应该可以在两边通行。

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

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

  • 向前传递数据:
    • 使用故事板和segue时覆盖UIViewControllerprepare(for:sender:)方法
    • 在执行视图控制器转换时通过初始化器或属性传递数据
  • 向后传递数据
    • 更新应用程序共享状态(你可以使用上述方法之一在视图控制器之间转发)
    • 使用委托
    • 使用一个展开的序列

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

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

这些解决方案虽然在短期内有效,但引入了太多的依赖项,这些依赖项会混淆应用程序的架构并在以后产生更多问题。

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

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

假设您必须将字符串从FirstViewController传递给第二个ViewController。

您的第一个视图控制器。

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 = stringme.call_back = call_backreturn 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")}
}

有3种类型用于将数据从一个ViewController传递到另一个ViewController查看控制器。

  1. 编程
  2. Segue
  3. 用户名

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

计划一个抽搐的盟友输入图片描述

Segue输入图片描述

用户默认值输入图片描述

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

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

您可能希望将数据从ViewController传递到另一个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! UIDocumentBrowserViewControllerbViewController.data = someData}}
  2. 当有一个A以模态(或嵌入)的形式打开B时。现在B视图控制器应该对其父对象不可见。因此将数据发送回A的最佳方法是使用Delegation

    B视图控制器中创建委托协议和delegate属性。因此B将向它的委托报告(发送数据)。在A视图控制器中,我们实现了B视图控制器的委托协议,并将self设置为prepare(forSegue:)方法中B视图控制器的delegate属性。

这就是它应该如何正确实施。

您可以创建从源ViewControler到目标ViewControler的推送序列,并提供如下所示的标识符名称。

在此输入图片描述

你必须像这样执行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.indexPathForSelectedRowlet indexNumber = index?.rowprint(indexNumber!)let VC = segue.destination as! AddTransactionVCVC.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)}}

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

  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类的实用程序,其中包含我在整个应用程序中使用的dateFor的方法,SharedInstance,Escape eWhiteSpaceCharacters,转换ImageToGrayScale和更多方法,所以现在在文件设施. h中。

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

    nil,然后再用一次。

     @interface Utilities : NSObject

    文件h

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

    现在在文件设施中:

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

    现在完成了,来到您的文件First View Controller. m并调用代表

    NSString*str=[@"%@",pdfObject.obj路径];

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

    现在直接转到文件第二个视图Controller. m,并在不创建变量的情况下使用它

    关于我所做的:

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

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

使用通知中心

对于Swift 3

let imageDataDict:[String: UIImage] = ["image": image]
// Post a notificationNotificationCenter.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 classNotificationCenter.default.addObserver(self, selector: #selector(self.showSpinningWheel(_:)), name: NSNotification.Name(rawValue: "notificationName"), object: nil)
// Handle notificationfunc 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}}}

对于Swift 4

let imageDataDict:[String: UIImage] = ["image": image]
// Post a notificationNotificationCenter.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 classNotificationCenter.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}}}

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

1.自定义初始化

class ViewControllerA: UIViewController {func openViewControllerB() {let viewController = ViewControllerB(string: "Blabla", completionClosure: { success inprint(success)})navigationController?.pushViewController(animated: true)}}
class ViewControllerB: UIViewController {private let completionClosure: ((Bool) -> Void)init(string: String, completionClosure: ((Bool) -> Void)) {self.completionClosure = completionClosuresuper.init(nibName: nil, bundle: nil)title = string}
func finishWork() {completionClosure()}}

2.可选vars

class ViewControllerA: UIViewController {func openViewControllerB() {let viewController = ViewControllerB()viewController.string = "Blabla"viewController.completionClosure = { success inprint(success)}navigationController?.pushViewController(animated: true)}}
class ViewControllerB: UIViewController {var string: String? {didSet {title = string}}var completionClosure: ((Bool) -> Void)?
func finishWork() {completionClosure?()}}

Swift5

好吧,Matt Price的回答对于传递数据来说是非常好的,但是我我要重写它,在最新 Swift版本中,因为我相信新的程序员发现由于新的语法和方法/框架,因为原始帖子在Objective-C中。

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

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

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


通过导航控制器推送传递数据从ViewControlllerA到ViewControlllerB

步骤1。在ViewControlllerB中声明变量

var isSomethingEnabled = false

步骤2。在ViewControlllerB'ViewdicLoad方法中打印变量

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

步骤3。在ViewControlllerA通过导航控制器时传递数据

if let viewControllerB = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewControllerB") as? ViewControllerB {viewControllerB.isSomethingEnabled = trueif 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 = trueif let navigator = navigationController {navigator.pushViewController(viewControllerB, animated: true)}}}}

视图控制项B

import UIKit
class ViewControllerB: UIViewController {
// MARK:  - Variable for Passing Data through Navigation pushvar isSomethingEnabled = false
override func viewDidLoad() {super.viewDidLoad()// Print value received through navigation pushprint("Value of 'isSomethingEnabled' from ViewControllerA: ", isSomethingEnabled)}}

通过Segue传递数据从ViewControlllerA到ViewControlllerB

步骤1。从ViewControlllerA创建Segue到ViewControlllerB并在故事板中给出标识符=showDetailSegue,如下所示

在此处输入图片描述

步骤2。在ViewControlllerB中声明一个名为是否启用的可行并打印其值。

步骤3。在ViewControlllerA在传递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 Methodoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {if (segue.identifier == "showDetailSegue") {let controller = segue.destination as? ViewControllerBcontroller?.isSomethingEnabled = true//passing data}}}

视图控制项B

import UIKit
class ViewControllerB: UIViewController {var isSomethingEnabled = false
override func viewDidLoad() {super.viewDidLoad()// Print value received through segueprint("Value of 'isSomethingEnabled' from ViewControllerA: ", isSomethingEnabled)}}

通过委托传递数据从ViewControlllerB到ViewControlllerA

步骤1。在ViewControlllerB文件中声明协议视图控制器B代表,但在类之外

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。在ViewControlllerB中声明委托变量实例

var delegate: ViewControllerBDelegate?

步骤3。在ViewControlllerB的viewDi Load方法中发送委托数据

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

步骤4。在ViewControlllerA中确认ViewControlllerB代表

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

步骤5。确认您将在ViewControlllerA中实现委托

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

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

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 methodfunc 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 = selfif let navigator = navigationController {navigator.pushViewController(viewControllerB, animated: true)}}}}

视图控制项B

import UIKit
//Protocol decleareprotocol 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")}}

通过通知观察者传递数据从ViewControlllerB到ViewControlllerA

步骤1.在ViewControlllerB的通知观察者中设置和发布数据

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

步骤2.在ViewControlllerA中添加通知观察者

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

步骤3.在ViewControlllerA中接收通知数据值

@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 dataNotificationCenter.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 Notificationlet objToBeSent = "Test Message from Notification"NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: objToBeSent)}}

通过块传递数据从ViewControlllerB到ViewControlllerA

步骤1.在ViewControlllerB中声明块

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

步骤2.在ViewControlllerB中按块设置数据

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

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

// Receiver Blockcontroller!.authorizationCompletionBlock = { isGranted inprint("Data received from Block is: ", isGranted)}

所以这里是完整的代码:

视图控制项A

import UIKit
class ViewControllerA: UIViewController  {
override func viewDidLoad() {super.viewDidLoad()}
// MARK:Method for receiving Data through Blockoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {if (segue.identifier == "showDetailSegue") {let controller = segue.destination as? ViewControllerBcontroller?.isSomethingEnabled = true
// Receiver Blockcontroller!.authorizationCompletionBlock = { isGranted inprint("Data received from Block is: ", isGranted)}}}}

视图控制项B

import UIKit
class ViewControllerB: UIViewController {
// MARK: Variable for Passing Data through Blockvar authorizationCompletionBlock:((Bool)->())? = {_ in}
override func viewDidLoad() {super.viewDidLoad()
// MARK: Set data for Passing Data through Blockif authorizationCompletionBlock != nil{authorizationCompletionBlock!(true)}}}

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

对于SwiftUI

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

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

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

class UserSettings: ObservableObject {@Published var score = 0}

UIKit和AppKit的纯Combine解决方案

让我们举一个简单的例子,在ViewController之间传递Intcount

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


父视图控制器

家长VC从ChildVC获取计数的更新

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

  2. 当子级出现时,我们将父级的当前计数值分配给ChildVC中的控件(这里是UISlider),这将成为修改count值的起点。

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


子视图控制器

ChildVC是一个会emit值的人,家长VC感兴趣:

class ChildVC: UIViewController {@Published var sliderValue: Int = 0 // 1
@IBOutlet var sliderOutlet: UISlider!
@IBAction func slided(_ sender: UISlider) {sliderValue = Int(sender.value)}}
  1. @已发布是存储值并在值更改时发出信号的发布者。它的行为就像普通的var,但可以发布可以通过在其前面添加$符号来访问的值。

当前值主体和密码主体与@已发布

  1. 当前对象的值也可以用来代替@发布。唯一的区别是,你必须手动发出信号。它在你想控制何时发出的情况下很有用。例如,你可以发出值,但只有当它落在特定范围内时。

  2. 密码主题也可以用来代替@Publ的或货币价值主题。在这里,区别是密码主题不能保存一个值,它可以只发出一个信号。当值不能具体表示在变量中时,这可能很有用。

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

向前传递数据

我最喜欢的两种向前传递数据的方法是依赖注入(DI)和属性观察者

依赖注入

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

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

  1. 委托协议(向后方向)。
  2. NSNotify中心(双向)。
  3. UserDefault(两个方向)。
  4. 直接属性(前进方向)。
  5. 关闭(向后方向)。
  6. Segue(前进方向)。

我用同一个例子实现了MVC、MVVM和MVP。请检查实现以清楚地了解如何修改模式。

https://github.com/Rajib-Sarwar/MVC-MVVM-Combine