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

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

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

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

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


当前回答

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

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

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

其他回答

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

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

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

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

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

转发数据

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

对于本例,我们将使用ViewControllerA和ViewControllerB

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

在ViewControllerB.h中为BOOL创建一个属性@属性(非原子,赋值)BOOL是SomethingEnabled;在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设置为BOOL值YES。

使用Segues转发数据

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

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

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

在ViewControllerB.h中为BOOL创建一个属性@属性(非原子,赋值)BOOL是SomethingEnabled;在ViewControllerA中,您需要告诉它ViewControllerB,因此使用#import“ViewControllerB.h”在情节提要上创建从ViewControllerA到ViewControllerB的片段,并为其提供标识符。在本例中,我们将其称为“showDetailSegue”接下来,我们需要将该方法添加到ViewControllerA,在执行任何segue时调用该方法。因此,我们需要检测调用了哪个segue,然后采取措施。在我们的示例中,我们将检查“showDetailSegue”,如果执行了,我们将把BOOL值传递给ViewControllerB-(void)准备segue:(UIStoryboardSegue*)segue发件人:(id)发件人{if([segue.identifier isEqualToString:@“showDetailSegue”]){ViewControllerB*控制器=(ViewControllerB*)segue.destinationViewController;controller.isSomethingEnabled=是;}}

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

    -(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设置为BOOL值YES。

传回数据

要将数据从ViewControllerB传递回ViewControllerA,需要使用协议和委托或块,后者可以用作回调的松耦合机制。

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

要使ViewControllerA成为ViewControllerB的委托,它必须符合我们必须指定的ViewControllerC协议。这告诉ViewControllerA它必须实现哪些方法。

在ViewControllerB.h中,在#import下方,但在@interface上方,指定协议。@类ViewControllerB;@协议ViewControllerBDLegate<NSObject>-(void)addItemViewController:(ViewControllerB*)控制器didFinishEnteringItem:(NSString*)项;@完接下来仍然在ViewControllerB.h中,您需要设置委托属性并在ViewController B.m中进行合成@属性(非原子,弱)id<ViewControllerBDLegate>委托;在ViewControllerB中,当我们弹出视图控制器时,我们调用代理上的消息。NSString*itemToPassBack=@“将此值传递回ViewControllerA”;[self.delegate addItemViewController:self-didFinishEnteringItem:itemToPassBack];这就是ViewControllerB。现在在ViewControllerA.h中,告诉ViewControllerA导入ViewControllerB并遵守其协议。#import“ViewControllerB.h”@界面ViewControllerA:UIViewController<ViewControllerBDLegate>在ViewControllerA.m中,根据我们的协议实现以下方法-(void)addItemViewController:(ViewControllerB*)控制器didFinishEnteringItem:(NSString*)项{NSLog(@“这是从ViewControllerB%@返回的”,项);}在将viewControllerB推送到导航堆栈之前,我们需要告诉viewControllerB ViewControllerA是它的委托,否则我们将得到一个错误。ViewControllerB*ViewControllerB=[[ViewControllerB alloc]initWithNib:@“ViewControllerA”捆绑包:nil];viewControllerB.delegate=自身[[self-navigationController]pushViewController:viewControllerB动画:是];


工具书类

在《视图控制器编程指南》中使用委派与其他视图控制器通信代理模式

NS通知中心

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

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

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

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

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

块是匿名函数。

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

定义块

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

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

需要值的地方(例如,需要ControllerA中的API响应,或者需要a上的ContorlerB数据)

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

块的另一个工作示例

使用通知中心

对于Swift 3

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

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

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

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

对于Swift 4

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

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

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

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

有三种类型用于将数据从一个ViewController传递到另一个ViewControlViewController。

程序性地赛格牌手表用户默认值

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

程序性地

赛格牌手表

用户默认值

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

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

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

要向前传递数据:使用情节提要和片段时,重写UIViewController的prepare(for:sender:)方法执行视图控制器转换时通过初始化器或财产传递数据困难代码向后传递数据更新应用程序共享状态(您可以使用上述任一方法在视图控制器之间传递该状态)使用委托使用放松段

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

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

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

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

iOS视图控制器如何相互通信如何构建iOS应用程序的代码通过一个实例了解iOS开发的核心架构原则