我对iOS和Objective-C以及整个MVC范式都是新手,我一直坚持以下几点:
我有一个充当数据输入表单的视图,我想让用户选择多个产品。产品列在另一个具有UITableViewController的视图中,我已启用多个选项。
如何将数据从一个视图传输到另一个视图?我会将UITableView上的选择保存在一个数组中,但如何将其传递回上一个数据输入表单视图,以便在提交表单时将其与其他数据一起保存到核心数据?
我在网上冲浪,看到一些人在应用程序委托中声明了一个数组。我读了一些关于单态的东西,但我不明白这些是什么,我读了关于创建数据模型的东西。
正确的执行方式是什么?我将如何执行?
用于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不能保存值,它只能发出信号。当无法在变量中具体表示值时,这可能很有用。
这个问题有很多答案,提供了许多不同的方法来执行视图控制器通信,这些方法确实有效,但我没有看到任何地方提到哪种方法实际上最好使用,哪种方法最好避免。
在实践中,我认为只建议几个解决方案:
要向前传递数据:使用情节提要和片段时,重写UIViewController的prepare(for:sender:)方法执行视图控制器转换时通过初始化器或财产传递数据困难代码向后传递数据更新应用程序共享状态(您可以使用上述任一方法在视图控制器之间传递该状态)使用委托使用放松段
我建议不要使用的解决方案:
直接引用上一个控制器,而不是使用委派通过单例共享数据通过应用程序代理传递数据通过用户默认值共享数据通过通知传递数据
这些解决方案虽然在短期内有效,但引入了太多的依赖性,这将扰乱应用程序的架构,并在以后产生更多问题。
对于感兴趣的人,我写了一些文章,更深入地讨论了这些问题,并强调了各种缺点:
iOS视图控制器如何相互通信如何构建iOS应用程序的代码通过一个实例了解iOS开发的核心架构原则
如果要将数据从一个控制器传递到另一个控制器,请尝试以下代码:
文件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];
}
我目前正在通过一个名为MCViewFactory的项目为这个问题的开源解决方案做出贡献,这个项目可以在这里找到:
Manticore iOS视图工厂
其想法是模仿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];
}