【问题标题】:Passing data between view controllers在视图控制器之间传递数据
【发布时间】:2011-07-09 19:02:16
【问题描述】:

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

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

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

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

执行此操作的正确方法是什么?我该怎么做?

【问题讨论】:

    标签: ios objective-c swift model-view-controller uiviewcontroller


    【解决方案1】:

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

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

    • 向前传递数据:
      • 在使用情节提要和转场时覆盖 UIViewControllerprepare(for:sender:) 方法
      • 在通过代码执行视图控制器转换时通过初始化程序或属性传递数据
    • 向后传递数据
      • 更新应用共享状态(您可以使用上述任一方法在视图控制器之间传递)
      • 使用委托
      • 使用展开转场

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

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

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

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

    【讨论】:

      【解决方案2】:

      使用通知中心

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

      【讨论】:

      • 你应该删除 NSNotification 和大多数类型的 NS 前缀,并且从 Swift 3 开始。同样适用于使用 Swift 本机 Dictionary [AnyHashable: Any] 而不是 NSDictionary。顺便说一句stackoverflow.com/questions/30328452/…
      【解决方案3】:

      这是a really great tutorial,适合任何想要的人。下面是示例代码:

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

      【讨论】:

        【解决方案4】:

        您可以创建从源视图控制器到目标视图控制器的推送 segue,并提供如下标识符名称。

        你必须像这样从 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.indexPathForSelectedRow
            let indexNumber = index?.row
            print(indexNumber!)
            let VC = segue.destination as! AddTransactionVC
            VC.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)
            }
        }
        

        【讨论】:

          【解决方案5】:

          好吧,我们有几种方法可以使用代理系统或使用 storyboardSegue:

          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 类实用程序,其中包含我在整个应用程序中使用的 dateFormatter、sharedInstance、EscapeWhiteSpaceCharacters、convertImageToGrayScale 和更多方法,所以现在在文件 utilities.h 中.

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

            只需将其设为nil 并再次使用它。

             @interface Utilities : NSObject
            

            文件Utilities.h

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

            现在在文件 utilities.m 中:

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

            现在已经完成了,来到你的文件 firstViewController.m 并调用代理

            NSString*str = [NSString stringWithFormat:@"%@", pdfObject.objPath];

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

            现在直接转到你的文件 secondViewController.m,并在不创建变量的情况下使用它

            看看我做了什么:

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

          这个委托工作在内存管理上是可靠的。

          【讨论】:

            【解决方案6】:

            Combine UIKit 和 AppKit 解决方案

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

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


            父视图控制器

            ParentVC 从 ChildVC 获取 更新的 count 值

            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)
                }
            }
            
            1. countObserver 将保留观察 ChildVC 所做更改的观察者

            2. 当child出现时,我们将当前count值从parent赋值给ChildVC中的一个控件(这里是UISlider),这成为修改count值的起点。

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


            子视图控制器

            ChildVC 是 emit ParentVC 感兴趣的值:

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

            CurrentValueSubject 和 PassthroughSubject 与 @Published

            1. CurrentValueSubject 也可以用来代替@Published。唯一的区别是,您必须手动发出信号。它在您想要控制何时发射的情况下很有用。例如,您可以发出值,但前提是它在特定范围内。

            2. PassthroughSubject 也可以用来代替@Published 或 CurrentValueSubject。在这里,不同之处在于 PassthroughSubject 不能保存一个值,它只能发出一个信号。当值无法在变量中具体表示时,这可能很有用。

            【讨论】:

            • 你能告诉我在两个独立的 VC 之间我必须使用哪种方法吗? (非父母-非儿童VC)因为segue(前进方向)对我不起作用!我有 VC A,>B,C B 是我想直接将数据从 C 传递到 A 的初始 VC。如何做到这一点,我应该使用哪种传递数据方法??
            • 您必须将日期从 C 传递到 B,然后从 B 传递到 A。我建议您发布一个单独的问题,提供更多详细信息
            【解决方案7】:

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

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

            【讨论】:

            • 我必须在 YourNextVC ViewController 上创建comingFrom 属性吗?
            • @Ashok 是的,您必须在 YourNextVC.h 中创建comingFrom,例如@property (strong,nonatomic) NSString *comingFrom;
            • 这么简单,为什么前面有40个答案?
            • @PeterMortensen 这个答案适用于那些不关心推理,只想解决问题或完成工作的人。
            【解决方案8】:

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

            向前传递数据

            我最喜欢的两种向前传递数据的方式是dependency injection (DI) 和 Property Observers

            依赖注入

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

            【讨论】:

              【解决方案9】:

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

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

              【讨论】:

              • 非常直截了当的答案...谢谢@smit 你给了我一个非常重要的关于passig 方向的信息,以前没有人提到过关于它们的任何事情!你能告诉我在两个独立的 VC 之间我必须使用哪种方法吗?因为 seguea(前进方向)对我不起作用!我有 VC A,>B,C B 是我想直接将数据从 C 传递到 A 的初始 VC。如何做到这一点,我应该使用哪种传递数据方法??
              【解决方案10】:

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

              假设您必须将字符串从 FirstViewController 传递给 SecondViewController。

              您的第一个视图控制器。

              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 = string
                      me.call_back = call_back
                      return 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")
                  }
              
              }
              

              【讨论】:

                【解决方案11】:

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

                1.自定义初始化

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

                2。可选变量

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

                【讨论】:

                  【解决方案12】:

                  Apple 的一种方法是使用 Segues。您需要使用 prepareForSegue() 函数。

                  周围有很多很棒的教程,这里有一个: Unleash Your Inner App Developer Part 21: Passing Data Between Controllers

                  另外,阅读有关使用 segues 的 Apple 文档: Using Segues

                  【讨论】:

                    【解决方案13】:

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

                    在两种情况下,您可能希望将数据从 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! UIDocumentBrowserViewController
                              bViewController.data = someData
                          }
                      }
                      
                    2. 当有一个 A 以模态(或嵌入)形式打开 B 时。现在B viewcontroller 应该对其父级视而不见。因此,将数据发送回A 的最佳方式是使用Delegation

                      B 视图控制器和delegate 属性中创建一个委托协议。所以B 将向其代表报告(发回数据)。在A viewcontroller 中,我们实现了B viewcontroller 的委托协议,并将self 设置为B viewcontroller 在prepare(forSegue:) 方法中的delegate 属性。

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

                    【讨论】:

                      【解决方案14】:

                      这里有一个更简单的方法。

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

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

                      现在,将第二个类classB 导入到classA

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

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

                      classB.m

                      #import "classB.h"
                      
                      @interface classB()
                      
                      @end
                      extern 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 准备方法,以便在您的视图控制器中注入信息
                      • 但我仍然没有遇到这种方法的任何问题或任何类型的数据丢失
                      • 我相信你。有时不是工作与不工作的问题,而是让您的开发尽可能安全。您构建它的方式是,只要您的应用程序存在,您就会在内存中保留一个变量,而不是使用它,然后在不再需要时将其丢弃。
                      • 全局变量很危险,因为你可能使用了另一个同名的局部变量,它会搞砸项目。
                      • @GeneCode 都是关于编码标准的。优秀的程序员永远不会为不同的变量(无论是全局变量还是局部变量)提供相同的名称。
                      【解决方案15】:

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

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

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

                      【讨论】:

                      • 我相信这种模式会比使用下面我的回答中描述的方法增加更多开销。
                      • 当然会,但如果以适当的方式执行,这将保持 d 代码的清洁和可维护性。
                      • 我没有投反对票,但我不会为此使用通知。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-07-27
                      相关资源
                      最近更新 更多