ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

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


A
Anye

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

转发数据

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

对于此示例,我们将有 ViewControllerAViewControllerB

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

在 ViewControllerB.h 中为 BOOL @property (nonatomic, assign) BOOL isSomethingEnabled 创建一个属性;在 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,我们将执行以下操作:

在 ViewControllerB.h 中为 BOOL @property (nonatomic, assign) BOOL isSomethingEnabled 创建一个属性;在 ViewControllerA 中,您需要告诉它有关 ViewControllerB 的信息,因此使用 #import "ViewControllerB.h" 在情节提要上创建从 ViewControllerA 到 ViewControllerB 的 segue 并给它一个标识符。在本例中,我们将其命名为“showDetailSegue”。接下来,我们需要将该方法添加到 ViewControllerA 中,当执行任何 segue 时都会调用该方法。因此,我们需要检测调用了哪个 segue,然后做一些事情。在我们的示例中,我们将检查“showDetailSegue”,如果执行了,我们会将我们的 BOOL 值传递给 ViewControllerB -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ if([segue.identifier isEqualToString: @"showDetailSegue"]){ ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;控制器.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,您需要使用 Protocols and DelegatesBlocks,后者可用作回调的松散耦合机制。

为此,我们将使 ViewControllerA 成为 ViewControllerB 的代表。这允许 ViewControllerBViewControllerA 发回消息,使我们能够发回数据。

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

在 ViewControllerB.h 中,在 #import 下方但在 @interface 上方指定协议。 @class ViewControllerB; @protocol ViewControllerBDelegate - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; @end 接下来还是在ViewControllerB.h中,需要设置一个delegate属性,在ViewControllerB.m中合成 @property (nonatomic, weak) id delegate;在 ViewControllerB 中,当我们弹出视图控制器时,我们会在委托上调用一条消息。 NSString *itemToPassBack = @"将此值传回 ViewControllerA"; [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];这就是 ViewControllerB。现在在 ViewControllerA.h 中,告诉 ViewControllerA 导入 ViewControllerB 并遵守其协议。 #import "ViewControllerB.h" @interface ViewControllerA : UIViewController 在 ViewControllerA.m 中从我们的协议中实现以下方法 - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item { NSLog(@"This从 ViewControllerB 返回 %@", item);在将 viewControllerB 推送到导航堆栈之前,我们需要告诉 ViewControllerB ViewControllerA 是它的委托,否则我们会得到一个错误。 ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil]; viewControllerB.delegate = self [[self navigationController] pushViewController:viewControllerB animated:YES];

参考

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

NS通知中心

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

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

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

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

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

块是匿名函数。

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

定义块

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

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

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

// In ContollerA.m

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

转到控制器 B

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

火块

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

Another Working Example for Blocks


我们是否还必须在 @protocol 定义上方放置一个 @class ViewControllerB;?没有它,我在 @protocol 声明中的 - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item; 行中的 ViewControllerB 上收到“预期类型”错误
这很好用。正如 alan-p 所说,不要忘记写 @class ViewControllerB;在协议之上,否则您将收到“预期类型”错误。
您不需要代表传回,只需使用 unwind。
当我把“viewControllerB.delegate = self;”在 ViewControllerB 中出现错误。从不兼容的类型“ViewControllerB *const __strong”分配给“id”,我不确定我做错了什么。任何人都可以帮忙吗?另外我必须更改:initWithNib --> initWithNibName。
如果您使用 NavigationController,则必须使用 [self.navigationController pushViewController:viewController animated:YES]; 而不是 [self pushViewController:viewControllerB animated:YES];
P
Peter Mortensen

迅速

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

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

将数据转发到下一个 View Controller

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

https://i.stack.imgur.com/tpcnS.png

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

第一个视图控制器

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

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

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

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

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

}

第二个视图控制器

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

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

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

    override func viewDidLoad() {
        super.viewDidLoad()

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

}

不要忘记

连接 UITextField 和 UILabel 的插座。

在 Interface Builder 中将第一个和第二个 View Controller 设置为适当的 Swift 文件。

将数据传回之前的 View Controller

要将数据从第二个视图控制器传回第一个视图控制器,请使用 a protocol and a delegate。该视频非常清楚地介绍了该过程:

YouTube 教程:iOS Swift 基础教程:协议和委托 同时阅读这篇文章以确保您不会陷入强引用循环。

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

https://i.stack.imgur.com/ypAga.png

在 Interface Builder 中创建故事板布局。同样,要进行转场,您只需将 Control 从按钮拖到第二个视图控制器。将 segue 标识符设置为 showSecondViewController。此外,不要忘记使用以下代码中的名称连接插座和操作。

第一个视图控制器

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

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

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

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

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

第二视图控制器和协议

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

import UIKit

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

class SecondViewController: UIViewController {

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

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

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

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

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

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


鉴于一些最新的 Swift 更新,这仍然是一种常见的实现模式吗?
我见过的大多数 Swift 更新都是相对较小的语法变化,而不是视图控制器之间数据传递方式的变化。如果我确实了解到任何类似的重大变化,我会更新我的答案。
offtopic - iOS 有一种丑陋的方式将参数传递给新的视图控制器,令人难以置信 - 你必须在调用时设置参数,而不是在其他地方。 Android 在这方面有一个更好的方法——当你启动一个 Activity 时,你可以通过它的启动 Intent 传递任何数据(嗯,几乎)。简单的。不需要投什么的。将返回值传回给调用者也是必不可少的,无需委托。当然也可以使用丑陋的方法,没有问题))
@Himanshu,首先获取对第二个视图控制器的引用。然后更新它包含的公共变量。
@蜂蜜。我认为“代表”一词令人困惑。让我用“工人”这个词。 “工人”(第一个视图控制器)做任何“老板”(第二个视图控制器)告诉它做的事情。 “老板”不知道它的“工人”是谁;可以是任何人。所以在第一个视图控制器(“工人”类)中,它说,我将成为你的“工人”。你告诉我在标签上写什么,我会为你做的。因此,secondViewController.delegate = self 表示“我同意成为老板的工人”。有关另一个示例和更多说明,请参见 this answer
C
Caleb

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

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


P
Peter Mortensen

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

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

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

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

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

- (void)pushToController2 {

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

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

@interface Controller2  : NSObject

@property (nonatomic, strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

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

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

@end

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

- (void)pushToController2 {

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

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

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

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

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

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

简而言之,如果你想——

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


P
Peter Mortensen

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

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

Sharing data between view controllers and other objects

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


不要使用协议和委托,只需使用 unwind。
@malhal 如果您不使用情节提要怎么办?
我也讨厌无用的协议和委托。 @malhal
@EvanR您可以在代码中创建和执行segues。这都一样。
本质上,此页面上的整个 QA 都是“来自容器视图之前的旧时代”。一百万年后,您现在永远不会为协议或委托而烦恼。无论如何,您在任何屏幕上所做的每一件小事都是一个容器视图,因此,问题真的不再存在 - 您已经拥有所有容器视图中的所有“上下”引用。
L
Leszek Zarna

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

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

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

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

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

    }
}

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

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

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

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

要记住的一件事是,使用块通常需要管理强引用和 __weak 引用,如解释 here


为什么不 value 是回调块的参数而不是单独的属性?
P
Peter Mortensen

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

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

原发帖人还询问了 Singletons 和 AppDelegate 的使用。这些问题需要回答。

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

应用场景

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

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

见图一。

https://i.stack.imgur.com/jtpRP.jpg

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

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

见图二。

https://i.stack.imgur.com/o0Pw4.jpg

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

ViewControllerA - 奢侈品

ViewControllerB - 非保险项目

ViewControllerC - 整个家庭库存

ViewControllerD - 添加新项目表单

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

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

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

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

转场

代表

直接在视图控制器上设置属性

NSUserDefaults(实际上是一个糟糕的选择)

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

观察者模式

单身人士

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

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

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

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

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

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

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


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

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

当家庭库存项目的集合发生变化时,需要让视图控制器意识到这种变化。上面的类定义并没有说明这将如何发生。我们需要遵循观察者模式。视图控制器必须正式观察 sharedManager。观察另一个物体有两种方法:

键值观察 (KVO)

NS通知中心。

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

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

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

参考

管理视图控制器之间的数据流

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

Objective-C 中的异步 JSON 请求


P
Peter Mortensen

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

代表

通知

用户默认值

辛格尔顿

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

我发现还有更多方法:

使用块回调:

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

使用故事板的另一个选项 Undwind (Exit) - 你可以使用你在 VC1 中编写的方法 -(IBAction)UnWindDone:(UIStoryboardSegue *)segue { NextViewController *nextViewController = segue.sourceViewController; self.unwindLabel.text = nextViewController.unwindPropertyPass; }

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

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


W
WeakPointer

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

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

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

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

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

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

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

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

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

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

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

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

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

"LLVM""ARC" 是什么?例如,LLVM 是 the compiler infrastructure project 吗?或者是其他东西?
B
Bhavin Ramani

有多种共享数据的方法。

您始终可以使用 NSUserDefaults 共享数据。根据您选择的键设置您要共享的值,并从与下一个视图控制器中的该键关联的 NSUserDefault 获取值。 [[NSUserDefaults standardUserDefaults] setValue:value forKey:key] [[NSUserDefaults standardUserDefaults] objectForKey:key] 你可以在 viewcontrollerA 中创建一个属性。在 viewcontrollerB 中创建 viewcontrollerA 的对象并将所需的值分配给该属性。您还可以为此创建自定义委托。


NSUserDefaults 的典型目的是存储在应用程序执行之间持续存在的用户偏好,因此除非明确删除,否则存储在这里的任何内容都将保留在这里。使用它在应用程序中的视图控制器(或任何其他对象)之间传递信息是一个非常糟糕的主意。
P
Peter Mortensen

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

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

J
Jav Solo

Swift 5 Well Matt Price 的答案非常适合传递数据,但我将在最新的 Swift 版本中重写它,因为我相信新程序员会因为新的语法和方法/框架而放弃挑战,因为原始帖子在 Objective -C。

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

使用 Navigation Controller Push 使用 Segue 使用 Delegate 使用 Notification Observer 使用 Block

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

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

步骤 1. 在 ViewControllerB 中声明变量

var isSomethingEnabled = false

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

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

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

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

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

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

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

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

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

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

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

通过 Segue 传递数据:从 ViewControllerA 到 ViewControllerB

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

https://i.stack.imgur.com/fWzeW.png

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

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

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

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

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

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

视图控制器B

import UIKit

class ViewControllerB: UIViewController {
    var isSomethingEnabled = false

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

通过 Delegate 传递数据:从 ViewControllerB 到 ViewControllerA

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

protocol ViewControllerBDelegate: NSObjectProtocol {

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

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

var delegate: ViewControllerBDelegate?

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

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

步骤 4. 在 ViewControllerA 中确认 ViewControllerBDelegate

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

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

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

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

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

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController, ViewControllerBDelegate  {

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

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

    @IBAction func goToViewControllerForDelegate(_ sender: Any) {

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

视图控制器B

import UIKit

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

class ViewControllerB: UIViewController {
    var delegate: ViewControllerBDelegate?

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

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

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

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

Step 2. 在 ViewControllerA 中添加 Notification Observer

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

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

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

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController{

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

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

通过 Block 传递数据:从 ViewControllerB 到 ViewControllerA

步骤 1. 在 ViewControllerB 中声明块

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

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

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

Step 3. 在 ViewControllerA 中接收块数据

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

所以这里是完整的代码:

视图控制器A

import UIKit

class ViewControllerA: UIViewController  {

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

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

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

视图控制器B

import UIKit

class ViewControllerB: UIViewController {

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

    override func viewDidLoad() {
        super.viewDidLoad()

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

You can find complete sample Application at my GitHub如果您对此有任何疑问,请告诉我。


P
Peter Mortensen

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

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

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

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


我不相信这是正确的方法,因为它在视图控制器之间创建了一个非常复杂的链接。并没有真正坚持 MVC。
如果您想严格遵循 MVC,请使用 NSNotificationCenter 方法可以从 ViewControllerA 调用到 ViewControllerB ,检查 this 它可能对您有帮助
P
Peter Mortensen

这个解决方案我找了很久,终于找到了。首先,在 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 ];

注意拼写错误。


P
Peter Mortensen

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

我假设我们有两个视图控制器,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

P
Peter Mortensen

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

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


我尝试在我的项目中使用您的代码,但无法获取 viewcontrollerB 中的值。你能告诉我可能是什么问题吗?
@Ajitthala 您可以将代码粘贴到新问题中吗?我会尽力解决你的问题。 :)
不使用 init 方法是错误的,而只是从 viewcontroller A 执行类似 vcB.string = @"asdf" 的操作吗?
@khanh.tran.vinh 取决于您是否使用 ARC。
P
Peter Mortensen

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

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

设置故事板

有三个部分。

发送者 Segue 接收者

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

https://i.stack.imgur.com/N6G2p.jpg

这是发件人的设置

https://i.stack.imgur.com/dGH1N.jpg

这是接收器的设置。

https://i.stack.imgur.com/nWIib.jpg

最后是segue的设置。

https://i.stack.imgur.com/cvYVj.jpg

视图控制器

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

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

import UIKit


class ViewControllerSender: UIViewController {

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

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

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

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

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

    }
}

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

import UIKit

class ViewControllerReceiver: UIViewController {

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

    override func viewDidLoad() {
        super.viewDidLoad()

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

    }
}

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

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

https://i.stack.imgur.com/edubX.jpg


P
Peter Mortensen

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

首先是构建一个单例类。请参阅页面 What should my Objective-C singleton look like?

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

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


这是正确的答案。只需使用单例作为“模型”。请注意,正如 Caleb 所说,“您的应用程序的模型可能与字符串数组一样简单”。需要注意的是,在 Swift 中做一个单例是非常简单的。 (如此简单,在这里甚至不值得一提——只是谷歌。)对于新程序员来说,值得理解的是,制作单例曾经是一件非常痛苦的事情。然而,单例绝对是 iOS 编程的核心——Apple 所做的一切都是单例。这就是为什么 Apple 最终使(在 Swift 中)能够正确地制作单例。
但是请注意,这些天(2016+)“一切都是 iOS 中的容器视图”。你在屏幕上做的每一件事都会产生一个小的容器视图。获取容器视图的“上下”引用链是相当简单的(尽管 Apple 将来会让这变得更容易),而且你几乎对每个容器视图都这样做。所以,如果你已经这样做了 - 你有答案;不需要单身人士。容器视图介绍... stackoverflow.com/a/23403979/294884
P
Peter Mortensen

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

例如:

FirstViewController 字符串值为

StrFirstValue = @"first";

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

我们需要在 SecondViewController.h 文件中创建一个字符串对象 NSString *strValue;需要在 .h 文件中声明一个属性,如下声明 @property (strong, nonatomic) NSString *strSecondValue;需要在头声明@synthesize strValue 下方的 FirstViewController.m 文件中合成该值;在文件 FirstViewController.h 中:@property (strong, nonatomic) NSString *strValue;在 FirstViewController 中,我们从哪个方法导航到第二个视图,请在该方法中编写以下代码。 SecondViewController *secondView= [[SecondViewController alloc] initWithNibName:@"SecondViewController " bundle:[NSBundle MainBundle]]; [secondView setStrSecondValue:StrFirstValue]; [self.navigationController pushViewController:secondView 动画:YES ];


进入 SecondViewController 后,如何将数据传回 FirstViewController?
P
Peter Mortensen

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

Manticore iOS View Factory

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

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

// Register activities

MCViewFactory *factory = [MCViewFactory sharedFactory];

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

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

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

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

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

    // ...

    // Ensure the following line is called, especially for MCSectionViewController
    [super onResume:intent];
}

那么所有控制器对象都可以在任何范围内获取/设置所有已注册的字典吗?对此投反对票。
P
Peter Mortensen

在下一个 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];

P
Peter Mortensen

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

我不久前写了一篇关于此的博客文章:Sharing Model Code。这是一个简短的摘要:

共享数据

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

视图控制器(在导航或标签栏控制器中)的暴力迭代以设置数据

在 prepareForSegue (如果是故事板)或 init (如果是程序化的)中设置数据

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

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

独立访问

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

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

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

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

键值观察

NS通知

核心数据

NSFetchedResultsController

数据源

核心数据

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

let notebookName = note.notebook.name

在我的博文中了解更多信息:Sharing Model Code


P
Peter Mortensen

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

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

 @property (nonatomic, strong) NSString *str1;

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

 @property (nonatomic, strong) NSString *str2;

在 ViewControllerTwo.m 中合成 str2:

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

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

 - (void)viewDidLoad
 {
   [super viewDidLoad];

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

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

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

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

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

P
Peter Mortensen

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

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

例如

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


F
Florent

新闻视图控制器

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

  newsDetailView.newsHeadlineStr = newsObj.newsHeadline;

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

NewsDetailViewController.h

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

新闻DetailViewController.m

@synthesize newsHeadlineStr;

P
Peter Mortensen

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

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


M
Mohamed Shaban

对于 SwiftUI

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

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

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

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

P
Peter Mortensen

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

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


P
Peter Mortensen

我看到很多人使用 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];

}

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


K
Kamani Jasmin

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

以编程方式 Segue UserDefaults

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

https://i.stack.imgur.com/2gFjo.jpg

https://i.stack.imgur.com/3jweJ.jpg

https://i.stack.imgur.com/pBk3v.jpg

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