如何在Swift中的视图控制器和其他对象之间共享数据?

Dun*_*n C 86 ios swift

假设我的Swift应用程序中有多个视图控制器,我希望能够在它们之间传递数据.如果我在视图控制器堆栈中有几个级别,我如何将数据传递给另一个视图控制器?或者在标签栏视图控制器中的标签之间?

(注意,这个问题是一个"铃声".)它被问到这么多,我决定写一个关于这个主题的教程.请参阅下面的答案.

nhg*_*rif 90

你的问题广泛.建议对每种情况都有一个简单的全能解决方案是有点幼稚.那么,让我们来看看其中一些场景.


在我的经验中,关于Stack Overflow的最常见场景是从一个视图控制器到下一个视图控制器的简单传递信息.

如果我们使用故事板,我们的第一个视图控制器可以覆盖prepareForSegue,这正是它的用途.UIStoryboardSegue调用此方法时传入一个对象,它包含对目标视图控制器的引用.在这里,我们可以设置我们想要传递的值.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "MySegueID" {
        if let destination = segue.destinationViewController as? SecondController {
            destination.myInformation = self.myInformation
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,如果我们不使用故事板,那么我们将从笔尖加载视图控制器.我们的代码稍微简单一些.

func showNextController() {
    let destination = SecondController(nibName: "SecondController", bundle: NSBundle.mainBundle())
    destination.myInformation = self.myInformation
    self.showViewController(destination, sender: self)
}
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,myInformation每个视图控制器上的属性是保存需要从一个视图控制器传递到下一个视图控制器的任何数据.它们显然不必在每个控制器上具有相同的名称.


我们可能还想在一个标签之间共享信息UITabBarController.

在这种情况下,它实际上可能更简单.

首先,让我们创建一个子类UITabBarController,并为我们想要在各个标签之间共享的任何信息提供属性:

class MyCustomTabController: UITabBarController {
    var myInformation: [String: AnyObject]?
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们从故事板构建我们的应用程序,我们只需将标签栏控制器的类从默认值更改UITabBarControllerMyCustomTabController.如果我们不使用故事板,我们只是实例化此自定义类的实例而不是默认UITabBarController类,并将我们的视图控制器添加到此.

现在,选项卡栏控制器中的所有视图控制器都可以访问此属性:

if let tbc = self.tabBarController as? MyCustomTabController {
    // do something with tbc.myInformation
}
Run Code Online (Sandbox Code Playgroud)

通过UINavigationController以相同的方式进行子类化,我们可以采用相同的方法在整个导航堆栈中共享数据:

if let nc = self.navigationController as? MyCustomNavController {
    // do something with nc.myInformation
}
Run Code Online (Sandbox Code Playgroud)

还有其他几种情况.这个答案绝不会涵盖所有这些答案.

  • @Rob Yup.单身人士和通知应该是最后的选择.我们应该更喜欢几乎在每个场景中的"prepareForSegue"或其他**直接**信息传输,然后当他们出现这些情况不起作用的场景时我们就可以接受新手了,然后我们必须教他们关于这些更全球化的方法. (2认同)
  • @nhgrif.谢谢你的回答.如果您希望数据在4或5个视图控制器之间传递,那该怎么办?如果我说4-5视图控制器管理客户端登录和密码等,我想在这些视图控制器之间传递用户的电子邮件,有没有比在每个viewcontroller中声明var然后在prepareforsegue中传递var更方便的方法.有没有办法我可以声明一次,每个viewcontroller都可以访问它,但这也是一种很好的编码实践方式? (2认同)

Dun*_*n C 43

这个问题一直存在.

一个建议是创建一个数据容器单例:一个在应用程序的生命周期中只创建一次的对象,并在应用程序的生命周期中持续存在.

当您拥有需要在应用程序中的不同类中可用/可修改的全局应用程序数据时,此方法非常适合.

其他方法(如在视图控制器之间设置单向或双向链接)更适合于在视图控制器之间直接传递信息/消息的情况.

(有关其他替代方案,请参阅下面的nhgrif的答案.)

使用数据容器单例,您可以向类中添加一个属性,该属性存储对单例的引用,然后在您需要访问时随时使用该属性.

您可以设置单例,以便将其内容保存到磁盘,以便在启动之间保持应用程序状态.

我在GitHub上创建了一个演示项目,演示了如何做到这一点.链接在这里:

GitHub上的SwiftDataContainerSingleton项目 以下是该项目的README:

SwiftDataContainerSingleton

演示使用数据容器单例来保存应用程序状态并在对象之间共享它.

这个DataContainerSingleton班级是实际的单身人士.

它使用静态常量sharedDataContainer来保存对单例的引用.

要访问单例,请使用语法

DataContainerSingleton.sharedDataContainer
Run Code Online (Sandbox Code Playgroud)

示例项目在数据容器中定义了3个属性:

  var someString: String?
  var someOtherString: String?
  var someInt: Int?
Run Code Online (Sandbox Code Playgroud)

要从someInt数据容器加载属性,您可以使用如下代码:

let theInt = DataContainerSingleton.sharedDataContainer.someInt
Run Code Online (Sandbox Code Playgroud)

要将值保存到someInt,您将使用以下语法:

DataContainerSingleton.sharedDataContainer.someInt = 3
Run Code Online (Sandbox Code Playgroud)

DataContainerSingleton的init方法添加了一个观察者UIApplicationDidEnterBackgroundNotification.该代码如下所示:

goToBackgroundObserver = NSNotificationCenter.defaultCenter().addObserverForName(
  UIApplicationDidEnterBackgroundNotification,
  object: nil,
  queue: nil)
  {
    (note: NSNotification!) -> Void in
    let defaults = NSUserDefaults.standardUserDefaults()
    //-----------------------------------------------------------------------------
    //This code saves the singleton's properties to NSUserDefaults.
    //edit this code to save your custom properties
    defaults.setObject( self.someString, forKey: DefaultsKeys.someString)
    defaults.setObject( self.someOtherString, forKey: DefaultsKeys.someOtherString)
    defaults.setObject( self.someInt, forKey: DefaultsKeys.someInt)
    //-----------------------------------------------------------------------------

    //Tell NSUserDefaults to save to disk now.
    defaults.synchronize()
}
Run Code Online (Sandbox Code Playgroud)

在观察者代码中,它将数据容器的属性保存到NSUserDefaults.您还可以使用NSCodingCore Data或其他各种方法来保存状态数据.

DataContainerSingleton的init方法还尝试为其属性加载已保存的值.

init方法的那部分看起来像这样:

let defaults = NSUserDefaults.standardUserDefaults()
//-----------------------------------------------------------------------------
//This code reads the singleton's properties from NSUserDefaults.
//edit this code to load your custom properties
someString = defaults.objectForKey(DefaultsKeys.someString) as! String?
someOtherString = defaults.objectForKey(DefaultsKeys.someOtherString) as! String?
someInt = defaults.objectForKey(DefaultsKeys.someInt) as! Int?
//-----------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

用于将值加载和保存到NSUserDefaults的键存储为字符串常量,这些常量是结构的一部分DefaultsKeys,定义如下:

struct DefaultsKeys
{
  static let someString  = "someString"
  static let someOtherString  = "someOtherString"
  static let someInt  = "someInt"
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样引用其中一个常量:

DefaultsKeys.someInt
Run Code Online (Sandbox Code Playgroud)

使用数据容器单例:

此示例应用程序使用数据容器单例.

有两个视图控制器.第一个是UIViewController的自定义子类,ViewController第二个是UIViewController的自定义子类SecondVC.

两个视图控制器都有一个文本字段,并且都将数据容器singlelton someInt属性中的值加载到其viewWillAppear方法的文本字段中,并将当前值从文本字段保存回数据容器的"someInt".

将值加载到文本字段的代码位于以下viewWillAppear:方法中:

override func viewWillAppear(animated: Bool)
{
  //Load the value "someInt" from our shared ata container singleton
  let value = DataContainerSingleton.sharedDataContainer.someInt ?? 0

  //Install the value into the text field.
  textField.text =  "\(value)"
}
Run Code Online (Sandbox Code Playgroud)

将用户编辑的值保存回数据容器的代码位于视图控制器的textFieldShouldEndEditing方法中:

 func textFieldShouldEndEditing(textField: UITextField) -> Bool
 {
   //Save the changed value back to our data container singleton
   DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
   return true
 }
Run Code Online (Sandbox Code Playgroud)

您应该在viewWillAppear而不是viewDidLoad中将值加载到用户界面中,以便每次显示视图控制器时UI都会更新.

  • 我不想对此进行投票,因为我认为您花时间创建问题和答案作为资源是非常好的.谢谢.尽管如此,我认为我们对新开发人员提出了很大的不利,他们提倡模型对象的单例.我不是在"单身人士是邪恶的"阵营(虽然新闻应该谷歌这句话以更好地理解这些问题),但我确实认为模型数据是单身人士的可疑/有争议的使用. (8认同)

iOS*_*eam 9

斯威夫特4

有很多方法可以快速传递数据.在这里,我将添加一些最好的方法.

1)使用StoryBoard Segue

故事板segue对于在源视图控制器和目标视图控制器之间传递数据非常有用,反之亦然.

// If you want to pass data from ViewControllerB to ViewControllerA while user tap on back button of ViewControllerB.
        @IBAction func unWindSeague (_ sender : UIStoryboardSegue) {
            if sender.source is ViewControllerB  {
                if let _ = sender.source as? ViewControllerB {
                    self.textLabel.text = "Came from B = B->A , B exited"
                }
            }
        }

// If you want to send data from ViewControllerA to ViewControllerB
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if  segue.destination is ViewControllerB {
                if let vc = segue.destination as? ViewControllerB {
                    vc.dataStr = "Comming from A View Controller"
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

2)使用委托方法

ViewControllerD

//Make the Delegate protocol in Child View Controller (Make the protocol in Class from You want to Send Data)
    protocol  SendDataFromDelegate {
        func sendData(data : String)
    }

    import UIKit

    class ViewControllerD: UIViewController {

        @IBOutlet weak var textLabelD: UILabel!

        var delegate : SendDataFromDelegate?  //Create Delegate Variable for Registering it to pass the data

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            textLabelD.text = "Child View Controller"
        }

        @IBAction func btnDismissTapped (_ sender : UIButton) {
            textLabelD.text = "Data Sent Successfully to View Controller C using Delegate Approach"
            self.delegate?.sendData(data:textLabelD.text! )
            _ = self.dismiss(animated: true, completion:nil)
        }
    }
Run Code Online (Sandbox Code Playgroud)

ViewControllerC

    import UIKit

    class ViewControllerC: UIViewController , SendDataFromDelegate {

        @IBOutlet weak var textLabelC: UILabel!

        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }

        @IBAction func btnPushToViewControllerDTapped( _ sender : UIButton) {
            if let vcD = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerD") as?  ViewControllerD  {
                vcD.delegate = self // Registring Delegate (When View Conteoller D gets Dismiss It can call sendData method
    //            vcD.textLabelD.text = "This is Data Passing by Referenceing View Controller D Text Label." //Data Passing Between View Controllers using Data Passing
                self.present(vcD, animated: true, completion: nil)
            }
        }

        //This Method will called when when viewcontrollerD will dismiss. (You can also say it is a implementation of Protocol Method)
        func sendData(data: String) {
            self.textLabelC.text = data
        }

    }
Run Code Online (Sandbox Code Playgroud)


Dun*_*n C 8

另一种方法是使用通知中心(NSNotificationCenter)并发布通知.这是一个非常松散的耦合.通知的发送者不需要知道或关心谁在听.它只是发布通知并忘记它.

通知适用于一对多消息传递,因为可以有任意数量的观察者监听给定消息.

  • 请注意,使用通知中心会导致耦合太松散。它会使跟踪程序流变得非常困难,因此应谨慎使用。 (2认同)