ios 你如何在 Swift 中的视图控制器和其他对象之间共享数据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29734954/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do you share data between view controllers and other objects in Swift?
提问by Duncan C
Say I have multiple view controllers in my Swift app and I want to be able to pass data between them. If I'm several levels down in a view controller stack, how do I pass data to another view controller? Or between tabs in a tab bar view controller?
假设我的 Swift 应用程序中有多个视图控制器,我希望能够在它们之间传递数据。如果我在视图控制器堆栈中向下几个级别,我如何将数据传递给另一个视图控制器?或者在选项卡栏视图控制器中的选项卡之间?
(Note, this question is a "ringer".) It gets asked so much that I decided to write a tutorial on the subject. See my answer below.
(注意,这个问题是一个“铃声”。)问得太多,我决定写一篇关于这个主题的教程。看我下面的回答。
回答by nhgrif
Your question is verybroad. To suggest there is one simple catch-all solution to every scenario is a little na?ve. So, let's go through some of these scenarios.
你的问题很广泛。建议对每个场景都有一个简单的包罗万象的解决方案有点天真。那么,让我们来看看其中的一些场景。
The most common scenario asked about on Stack Overflow in my experience is the simple passing information from one view controller to the next.
根据我的经验,在 Stack Overflow 上被问到的最常见的场景是从一个视图控制器到下一个视图控制器的简单传递信息。
If we're using storyboard, our first view controller can override prepareForSegue
, which is exactly what it's there for. A UIStoryboardSegue
object is passed in when this method is called, and it contains a reference to our destination view controller. Here, we can set the values we want to pass.
如果我们使用故事板,我们的第一个视图控制器可以覆盖prepareForSegue
,这正是它的用途。UIStoryboardSegue
调用此方法时传入一个对象,它包含对目标视图控制器的引用。在这里,我们可以设置我们想要传递的值。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegueID" {
if let destination = segue.destination as? SecondController {
destination.myInformation = self.myInformation
}
}
}
Alternatively, if we're not using storyboards, then we're loading our view controller from a nib. Our code is slightly simpler then.
或者,如果我们不使用故事板,那么我们将从笔尖加载我们的视图控制器。那么我们的代码稍微简单一些。
func showNextController() {
let destination = SecondController(nibName: "SecondController", bundle: nil)
destination.myInformation = self.myInformation
show(destination, sender: self)
}
In both cases, myInformation
is a property on each view controller holding whatever data needs to be passed from one view controller to the next. They obviously don't have to have the same name on each controller.
在这两种情况下,myInformation
是每个视图控制器上的一个属性,保存需要从一个视图控制器传递到下一个视图控制器的任何数据。它们显然不必在每个控制器上都具有相同的名称。
We might also want to share information between tabs in a UITabBarController
.
我们可能还想在UITabBarController
.
In this case, it's actually potentially even simpler.
在这种情况下,它实际上可能更简单。
First, let's create a subclass of UITabBarController
, and give it properties for whatever information we want to share between the various tabs:
首先,让我们创建一个 的子类UITabBarController
,并为它提供我们想要在各个选项卡之间共享的任何信息的属性:
class MyCustomTabController: UITabBarController {
var myInformation: [String: AnyObject]?
}
Now, if we're building our app from the storyboard, we simply change our tab bar controller's class from the default UITabBarController
to MyCustomTabController
. If we're not using a storyboard, we simply instantiate an instance of this custom class rather than the default UITabBarController
class and add our view controller to this.
现在,如果我们从故事板构建我们的应用程序,我们只需将标签栏控制器的类从默认更改UITabBarController
为MyCustomTabController
. 如果我们不使用故事板,我们只需实例化这个自定义类而不是默认UITabBarController
类的实例,然后将我们的视图控制器添加到它。
Now, all of our view controllers within the tab bar controller can access this property as such:
现在,标签栏控制器中的所有视图控制器都可以访问此属性:
if let tbc = self.tabBarController as? MyCustomTabController {
// do something with tbc.myInformation
}
And by subclassing UINavigationController
in the same way, we can take the same approach to share data across an entire navigation stack:
通过UINavigationController
以相同的方式进行子类化,我们可以采用相同的方法在整个导航堆栈中共享数据:
if let nc = self.navigationController as? MyCustomNavController {
// do something with nc.myInformation
}
There are several other scenarios. By no means does this answer cover all of them.
还有其他几种情况。这个答案绝不涵盖所有这些。
回答by Duncan C
This question comes up all the time.
这个问题一直出现。
One suggestion is to create a data container singleton: An object that gets created once and only once in the life of your application, and persists for the life of your app.
一个建议是创建一个数据容器单例:在应用程序的生命周期中创建一次且仅一次的对象,并在应用程序的生命周期中持续存在。
This approach is well suited for a situation when you have global app data that needs to be available/modifiable across different classes in your app.
这种方法非常适用于需要在应用程序中的不同类之间可用/可修改的全局应用程序数据的情况。
Other approaches like setting up one-way or 2-way links between view controllers are better suited to situations where you are passing information/messages directly between view controllers.
其他方法,例如在视图控制器之间设置单向或双向链接,更适合直接在视图控制器之间传递信息/消息的情况。
(See nhgrif's answer, below, for other alternatives.)
(有关其他替代方案,请参阅下面的 nhgrif 的回答。)
With a data container singleton, you add a property to your class that stores a reference to your singleton, and then use that property any time you need access.
使用数据容器单例,您可以向类添加一个属性,该属性存储对单例的引用,然后在需要访问时随时使用该属性。
You can set up your singleton so that it saves it's contents to disk so that your app state persists between launches.
您可以设置您的单例,以便将其内容保存到磁盘,以便您的应用程序状态在两次启动之间保持不变。
I created a demo project on GitHub demonstrating how you can do this. Here is the link:
我在 GitHub 上创建了一个演示项目来演示如何做到这一点。链接在这里:
SwiftDataContainerSingleton project on GitHubHere is the README from that project:
GitHub 上的 SwiftDataContainerSingleton 项目以下是该项目的 README:
SwiftDataContainerSingleton
SwiftDataContainerSingleton
A demonstration of using a data container singleton to save application state and share it between objects.
使用数据容器单例来保存应用程序状态并在对象之间共享它的演示。
The DataContainerSingleton
class is the actual singleton.
该DataContainerSingleton
班是实际单。
It uses a static constant sharedDataContainer
to save a reference to the singleton.
它使用静态常量sharedDataContainer
来保存对单例的引用。
To access the singleton, use the syntax
要访问单例,请使用语法
DataContainerSingleton.sharedDataContainer
The sample project defines 3 properties in the data container:
示例项目在数据容器中定义了 3 个属性:
var someString: String?
var someOtherString: String?
var someInt: Int?
To load the someInt
property from the data container, you'd use code like this:
要从someInt
数据容器加载属性,您可以使用如下代码:
let theInt = DataContainerSingleton.sharedDataContainer.someInt
To save a value to someInt, you'd use the syntax:
要将值保存到 someInt,您可以使用以下语法:
DataContainerSingleton.sharedDataContainer.someInt = 3
The DataContainerSingleton's init
method adds an observer for the UIApplicationDidEnterBackgroundNotification
. That code looks like this:
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()
}
In the observer code it saves the data container's properties to NSUserDefaults
. You can also use NSCoding
, Core Data, or various other methods for saving state data.
在观察者代码中,它将数据容器的属性保存到NSUserDefaults
. 您还可以使用NSCoding
、Core Data 或各种其他方法来保存状态数据。
The DataContainerSingleton's init
method also tries to load saved values for it's properties.
DataContainerSingleton 的init
方法还尝试为它的属性加载保存的值。
That portion of the init method looks like this:
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?
//-----------------------------------------------------------------------------
The keys for loading and saving values into NSUserDefaults are stored as string constants that are part of a struct DefaultsKeys
, defined like this:
将值加载和保存到 NSUserDefaults 的键存储为作为 struct 一部分的字符串常量DefaultsKeys
,定义如下:
struct DefaultsKeys
{
static let someString = "someString"
static let someOtherString = "someOtherString"
static let someInt = "someInt"
}
You reference one of these constants like this:
您可以像这样引用这些常量之一:
DefaultsKeys.someInt
Using the data container singleton:
使用数据容器单例:
This sample application makes trival use of the data container singleton.
这个示例应用程序简单地使用了数据容器单例。
There are two view controllers. The first is a custom subclass of UIViewController ViewController
, and the second one is a custom subclass of UIViewController SecondVC
.
有两个视图控制器。第一个是 UIViewController 的自定义子类,ViewController
第二个是 UIViewController 的自定义子类SecondVC
。
Both view controllers have a text field on them, and both load a value from the data container singlelton's someInt
property into the text field in their viewWillAppear
method, and both save the current value from the text field back into the `someInt' of the data container.
两个视图控制器上都有一个文本字段,并且都将数据容器单例someInt
属性中的值加载到其viewWillAppear
方法中的文本字段中,并且都将文本字段中的当前值保存回数据容器的“someInt”中。
The code to load the value into the text field is in the viewWillAppear:
method:
将值加载到文本字段中的代码在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)"
}
The code to save the user-edited value back to the data container is in the view controllers' textFieldShouldEndEditing
methods:
将用户编辑的值保存回数据容器的代码位于视图控制器的textFieldShouldEndEditing
方法中:
func textFieldShouldEndEditing(textField: UITextField) -> Bool
{
//Save the changed value back to our data container singleton
DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt()
return true
}
You should load values into your user interface in viewWillAppear rather than viewDidLoad so that your UI updates each time the view controller is displayed.
您应该在 viewWillAppear 而不是 viewDidLoad 中将值加载到您的用户界面中,以便您的 UI 在每次显示视图控制器时更新。
回答by iOS Team
Swift 4
斯威夫特 4
There are so many approaches?for data passing in swift. Here I am adding some of the best approaches of it.
有很多方法可以快速传递数据。在这里,我添加了一些最好的方法。
1) Using StoryBoard Segue
1) 使用 StoryBoard Segue
Storyboard segues are very much useful for passing data in between Source and Destination View Controllers and vice versa also.
故事板转场对于在源视图控制器和目标视图控制器之间传递数据非常有用,反之亦然。
// 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"
}
}
}
2) Using Delegate Methods
2) 使用委托方法
ViewControllerD
视图控制器D
//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)
}
}
ViewControllerC
视图控制器C
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
}
}
回答by Duncan C
Another alternative is to use the notification center (NSNotificationCenter) and post notifications. That is a very loose coupling. The sender of a notification doesn't need to know or care who's listening. It just posts a notification and forgets about it.
另一种选择是使用通知中心 (NSNotificationCenter) 并发布通知。这是一个非常松散的耦合。通知的发送者不需要知道或关心谁在听。它只是发布一个通知,然后忘记它。
Notifications are good for one-to-many message passing, since there can be an arbitrary number of observers listening for a given message.
通知适用于一对多的消息传递,因为可以有任意数量的观察者监听给定的消息。
回答by Kristiina
Instead of creating a data controller singelton I would suggest to create a data controller instance and pass it around. To support dependency injection I would first create a DataController
protocol:
我建议创建一个数据控制器实例并传递它,而不是创建一个数据控制器单例。为了支持依赖注入,我首先要创建一个DataController
协议:
protocol DataController {
var someInt : Int {get set}
var someString : String {get set}
}
Then I would create a SpecificDataController
(or whatever name would currently be appropriate) class:
然后我将创建一个SpecificDataController
(或任何当前合适的名称)类:
class SpecificDataController : DataController {
var someInt : Int = 5
var someString : String = "Hello data"
}
The ViewController
class should then have a field to hold the dataController
. Notice that the type of dataController
is the protocol DataController
. This way it's easy to switch out data controller implementations:
在ViewController
随后类应该有一个场举行dataController
。请注意,类型dataController
是协议DataController
。通过这种方式,可以轻松切换数据控制器实现:
class ViewController : UIViewController {
var dataController : DataController?
...
}
In AppDelegate
we can set the viewController's dataController
:
在AppDelegate
我们可以设置viewController的dataController
:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let viewController = self.window?.rootViewController as? ViewController {
viewController.dataController = SpecificDataController()
}
return true
}
When we move to a different viewController we can pass the dataController
on in:
当我们移动到不同的 viewController 时,我们可以传入dataController
:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
}
Now when we wish to switch out the data controller for a different task we can do this in the AppDelegate
and do not have to change any other code that uses the data controller.
现在,当我们希望为不同的任务切换数据控制器时,我们可以在 中执行此操作,AppDelegate
而不必更改任何其他使用数据控制器的代码。
This is of course overkill if we simply want to pass around a single value. In this case it's best to go with nhgrif's answer.
如果我们只想传递单个值,这当然是矫枉过正。在这种情况下,最好使用 nhgrif 的答案。
With this approach we can separate view form the logic part.
通过这种方法,我们可以将视图与逻辑部分分开。
回答by Maxim
SWIFT 3:
快速 3:
If you have a storyboard with identified segues use:
如果您有一个带有已确定的 segue 的故事板,请使用:
func prepare(for segue: UIStoryboardSegue, sender: Any?)
Although if you do everything programmatically including navigation between different UIViewControllers then use the method:
尽管如果您以编程方式执行所有操作,包括在不同 UIViewController 之间进行导航,请使用以下方法:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
Note: to use the second way you need to make your UINavigationController, you are pushing UIViewControllers on, a delegate and it needs to conform to the protocol UINavigationControllerDelegate:
注意:要使用第二种方式,您需要制作 UINavigationController,您将 UIViewControllers 推到一个委托上,并且它需要符合 UINavigationControllerDelegate 协议:
class MyNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
// do what ever you need before going to the next UIViewController or back
//this method will be always called when you are pushing or popping the ViewController
}
}
回答by Yusuf Demirci
It depends when you want to get data.
这取决于您想要获取数据的时间。
If you want to get data whenever you want, can use a singleton pattern. The pattern class is active during the app runtime. Here is an example of the singleton pattern.
如果您想随时获取数据,可以使用单例模式。模式类在应用程序运行时处于活动状态。这是单例模式的示例。
class AppSession: NSObject {
static let shared = SessionManager()
var username = "Duncan"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(AppSession.shared.username)
}
}
If you want to get data after any action, can use NotificationCenter.
如果您想在任何操作后获取数据,可以使用 NotificationCenter。
extension Notification.Name {
static let loggedOut = Notification.Name("loggedOut")
}
@IBAction func logoutAction(_ sender: Any) {
NotificationCenter.default.post(name: .loggedOut, object: nil)
}
NotificationCenter.default.addObserver(forName: .loggedOut, object: nil, queue: OperationQueue.main) { (notify) in
print("User logged out")
}
回答by Duncan C
As @nhgrif pointed out in his excellent answer, there are lots of different ways that VCs (view controllers) and other objects can communicate with each other.
正如@nhgrif 在他出色的回答中指出的那样,VC(视图控制器)和其他对象可以通过多种不同的方式相互通信。
The data singleton I outlined in my first answer is really more about sharing and saving global state than about communicating directly.
我在第一个答案中概述的数据单例实际上更多是关于共享和保存全局状态,而不是关于直接通信。
nhrif's answer lets you send information directly from the source to the destination VC. As I mentioned in reply, it's also possible to send messages back from the destination to the source.
nhrif 的回答使您可以将信息直接从源发送到目标 VC。正如我在回复中提到的,也可以将消息从目标发送回源。
In fact, you can set up an active one-way or 2-way channel between different view controllers. If the view controllers are linked via a storyboard segue, the time to set up the links is in the prepareFor Segue method.
实际上,您可以在不同的视图控制器之间设置一个活动的单向或 2 向通道。如果视图控制器通过 storyboard segue 链接,则设置链接的时间在 prepareFor Segue 方法中。
I have a sample project on Github that uses a parent view controller to host 2 different table views as children. The child view controllers are linked using embed segues, and the parent view controller wires up 2-way links with each view controller in the prepareForSegue method.
我在 Github 上有一个示例项目,它使用父视图控制器将 2 个不同的表视图作为子视图托管。子视图控制器使用嵌入转场链接,父视图控制器在 prepareForSegue 方法中与每个视图控制器连接 2 路链接。
You can find that project on github(link). I wrote it in Objective-C, however, and haven't converted it to Swift, so if you're not comfortable in Objective-C it might be a little hard to follow
您可以在 github 上找到该项目(链接)。然而,我是用 Objective-C 编写的,并没有将它转换为 Swift,所以如果你不习惯使用 Objective-C,可能会有点难以理解