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

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

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

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

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


当前回答

这个问题在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];
}

块的另一个工作示例

其他回答

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

在分配另一个类后直接初始化。委派-用于传回数据通知-用于在同一时间向多个类广播数据保存在NSUserDefaults中-用于以后访问Singleton类数据库和其他存储机制,如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];

或者,如果需要传递三个以上与公共特征相关的参数,则可以将这些值存储在模型类中,然后将该modelObject传递给下一个类

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

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

总之,如果你想-

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

用于UIKit和AppKit的Pure Combine解决方案

让我们举一个简单的例子,在ViewControllers之间传递计数的Int值。

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


父视图控制器

ParentVC从ChildVC获取计数的更新值

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

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let childVC = segue.destination as! ChildVC
        childVC.sliderValue = count // 2
        countObserver = childVC.$sliderValue // 3
            .assign(to: \.count, on: self)
    }
}

countObserver将保留观察ChildVC所做更改的观察者当子级出现时,我们将当前的计数值从父级分配给ChildVC中的一个控件(这里是UISlider),这成为修改计数值的起点。我们观察到sliderValue(它是一个发布者),它会发出用户通过拖动滑块更改的计数值。请注意,我们使用了$sliderValue而不仅仅是sliderValue。


子视图控制器

ChildVC是发出ParentVC感兴趣的值的一个:

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

    @IBOutlet var sliderOutlet: UISlider!

    @IBAction func slided(_ sender: UISlider) {
        sliderValue = Int(sender.value)
    }
}

@Published是存储值并在值更改时发出信号的Publisher。它的行为类似于常规变量,但可以发布可以通过在其前面加上$符号来访问的值。


CurrentValueSubject和PassthroughSubject与@Published

还可以使用CurrentValueSubject代替@Published。唯一的区别是,你必须手动发出信号。在希望控制何时发射的情况下,它非常有用。例如,您可以发射该值,但前提是该值在特定范围内。PassthroughSubject也可以代替@Published或CurrentValueSubject。这里的区别是PassthroughSubject不能保存值,它只能发出信号。当无法在变量中具体表示值时,这可能很有用。

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

在NextVC上的NextVC.h中添加此属性:

@property (strong, nonatomic) NSString *indexNumber;

Add

@综合索引编号;在NextVC.m中

最后一个

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

vc.indexNumber = @"123";

[self.navigationController vc animated:YES];

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

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

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

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

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

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

当层次结构中有一个“A”ViewContoller,并且您希望将一些数据发送到“B”,这是下一个视图控制器。在这种情况下,您必须使用Segue。只需为segue设置一个标识符,然后在“A”VC中编写以下代码:override func prepare(segue:UIStoryboardSegue,sender:Any?){如果segue.identifier==“A到B segue-identifier”{让bViewController=segue.destination为!UIDocumentBrowserViewControllerbViewController.data=某些数据}}当有一个A将B作为模态(或嵌入)打开时。现在,B视图控制器应该对其父对象视而不见。因此,将数据发送回A的最佳方法是使用委派。在B视图控制器和委托属性中创建委托协议。因此,B将向其代表报告(发送数据)。在A视图控制器中,我们实现了B视图控制器的委托协议,并将在prepare(forSegue:)方法中将self设置为B视图控制器中的委托属性。

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