Digamos que tengo varios controladores de vista en mi aplicación Swift y quiero poder pasar datos entre ellos. Si estoy varios niveles hacia abajo en una pila de controladores de vista, ¿cómo paso los datos a otro controlador de vista? ¿O entre pestañas en un controlador de vista de barra de pestañas?
(Tenga en cuenta que esta pregunta es un "timbre".) Se pregunta tanto que decidí escribir un tutorial sobre el tema. Vea mi respuesta a continuación.
Respuestas:
Tu pregunta es muy amplia. Sugerir que hay una solución simple para todos los escenarios es un poco ingenuo. Entonces, repasemos algunos de estos escenarios.
El escenario más común sobre el que se pregunta en Stack Overflow en mi experiencia es el simple paso de información de un controlador de vista al siguiente.
Si estamos usando un guión gráfico, nuestro primer controlador de vista puede anular
prepareForSegue
, que es exactamente para lo que está ahí. SeUIStoryboardSegue
pasa un objeto cuando se llama a este método y contiene una referencia a nuestro controlador de vista de destino. Aquí, podemos establecer los valores que queremos pasar.override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "MySegueID" { if let destination = segue.destination as? SecondController { destination.myInformation = self.myInformation } } }
Alternativamente, si no estamos usando guiones gráficos, entonces cargamos nuestro controlador de vista desde una plumilla. Nuestro código es un poco más simple entonces.
func showNextController() { let destination = SecondController(nibName: "SecondController", bundle: nil) destination.myInformation = self.myInformation show(destination, sender: self) }
En ambos casos,
myInformation
hay una propiedad en cada controlador de vista que contiene los datos que se deben pasar de un controlador de vista al siguiente. Obviamente, no es necesario que tengan el mismo nombre en cada controlador.También es posible que deseemos compartir información entre pestañas en un
UITabBarController
.En este caso, es potencialmente incluso más simple.
Primero,
UITabBarController
creemos una subclase de y le asignamos propiedades para cualquier información que queramos compartir entre las distintas pestañas:class MyCustomTabController: UITabBarController { var myInformation: [String: AnyObject]? }
Ahora, si estamos creando nuestra aplicación desde el guión gráfico, simplemente cambiamos la clase del controlador de la barra de pestañas de la predeterminada
UITabBarController
aMyCustomTabController
. Si no estamos usando un guión gráfico, simplemente creamos una instancia de esta clase personalizada en lugar de laUITabBarController
clase predeterminada y agregamos nuestro controlador de vista a esto.Ahora, todos nuestros controladores de vista dentro del controlador de la barra de pestañas pueden acceder a esta propiedad como tal:
if let tbc = self.tabBarController as? MyCustomTabController { // do something with tbc.myInformation }
Y al crear subclases
UINavigationController
de la misma manera, podemos adoptar el mismo enfoque para compartir datos en toda una pila de navegación:if let nc = self.navigationController as? MyCustomNavController { // do something with nc.myInformation }
Hay varios otros escenarios. De ninguna manera esta respuesta los cubre a todos.
fuente
prepareForSegue
. Es una lástima que esta simple observación se pierda entre las otras respuestas y digresiones aquí.prepareForSegue
u otras transferencias directas de información en casi todos los escenarios y luego simplemente estar de acuerdo con los novatos cuando se presentan con el escenario para el que estas situaciones no funcionan y luego tenemos que enseñarles sobre estos enfoques más globales.Esta pregunta surge todo el tiempo.
Una sugerencia es crear un contenedor de datos singleton: un objeto que se crea una y solo una vez en la vida de su aplicación, y persiste durante la vida de su aplicación.
Este enfoque es adecuado para una situación en la que tiene datos de aplicaciones globales que deben estar disponibles / modificables en diferentes clases de su aplicación.
Otros enfoques, como la configuración de enlaces unidireccionales o bidireccionales entre controladores de vista, se adaptan mejor a situaciones en las que está pasando información / mensajes directamente entre controladores de vista.
(Consulte la respuesta de nhgrif, a continuación, para ver otras alternativas).
Con un contenedor de datos singleton, agrega una propiedad a su clase que almacena una referencia a su singleton y luego usa esa propiedad cada vez que necesite acceso.
Puede configurar su singleton para que guarde su contenido en el disco para que el estado de su aplicación persista entre lanzamientos.
Creé un proyecto de demostración en GitHub que demuestra cómo puede hacer esto. Aqui esta el link:
Proyecto SwiftDataContainerSingleton en GitHub Aquí está el archivo README de ese proyecto:
SwiftDataContainerSingleton
Una demostración del uso de un contenedor de datos singleton para guardar el estado de la aplicación y compartirlo entre objetos.
La
DataContainerSingleton
clase es el singleton real.Utiliza una constante estática
sharedDataContainer
para guardar una referencia al singleton.Para acceder al singleton, use la sintaxis
DataContainerSingleton.sharedDataContainer
El proyecto de muestra define 3 propiedades en el contenedor de datos:
var someString: String? var someOtherString: String? var someInt: Int?
Para cargar la
someInt
propiedad desde el contenedor de datos, usaría un código como este:let theInt = DataContainerSingleton.sharedDataContainer.someInt
Para guardar un valor en someInt, usaría la sintaxis:
DataContainerSingleton.sharedDataContainer.someInt = 3
El
init
método de DataContainerSingleton agrega un observador paraUIApplicationDidEnterBackgroundNotification
. Ese código se ve así: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() }
En el código del observador, guarda las propiedades del contenedor de datos en
NSUserDefaults
. También puede utilizarNSCoding
Core Data o varios otros métodos para guardar datos estatales.El
init
método de DataContainerSingleton también intenta cargar valores guardados para sus propiedades.Esa parte del método init se ve así:
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? //-----------------------------------------------------------------------------
Las claves para cargar y guardar valores en NSUserDefaults se almacenan como constantes de cadena que forman parte de una estructura
DefaultsKeys
, definida así:struct DefaultsKeys { static let someString = "someString" static let someOtherString = "someOtherString" static let someInt = "someInt" }
Hace referencia a una de estas constantes de esta manera:
DefaultsKeys.someInt
Usando el contenedor de datos singleton:
Esta aplicación de muestra hace un uso trival del contenedor de datos singleton.
Hay dos controladores de vista. La primera es una subclase personalizada de UIViewController
ViewController
y la segunda es una subclase personalizada de UIViewControllerSecondVC
.Ambos controladores de vista tienen un campo de texto, y ambos cargan un valor de la
someInt
propiedad singlelton del contenedor de datos en el campo de texto de suviewWillAppear
método, y ambos guardan el valor actual del campo de texto en el 'someInt' del contenedor de datos.El código para cargar el valor en el campo de texto está en el
viewWillAppear:
método: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)" }
El código para guardar el valor editado por el usuario en el contenedor de datos está en los
textFieldShouldEndEditing
métodos de los controladores de vista :func textFieldShouldEndEditing(textField: UITextField) -> Bool { //Save the changed value back to our data container singleton DataContainerSingleton.sharedDataContainer.someInt = textField.text!.toInt() return true }
Debe cargar valores en su interfaz de usuario en viewWillAppear en lugar de viewDidLoad para que su interfaz de usuario se actualice cada vez que se muestre el controlador de vista.
fuente
Rápido 4
Hay muchos enfoques para la transmisión rápida de datos. Aquí estoy agregando algunos de los mejores enfoques.
1) Uso de StoryBoard Segue
Las secuencias de guiones gráficos son muy útiles para pasar datos entre los controladores de vista de origen y destino y viceversa también.
// 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) Usar métodos delegados
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) } }
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 } }
fuente
ViewControllerA
aViewControllerB
. Acabo de pegar el fragmento de código en la parte inferior de miViewControllerA.swift
(dondeViewControllerA.swift
está realmente el nombre de su archivo, por supuesto) justo antes de la última llave. "prepare
" es en realidad una función preexistente incorporada especial en una Clase determinada [que no hace nada], por lo que tienes que "override
" hacerloOtra alternativa es utilizar el centro de notificaciones (NSNotificationCenter) y publicar notificaciones. Ese es un acoplamiento muy flojo. El remitente de una notificación no necesita saber ni preocuparse por quién está escuchando. Simplemente publica una notificación y se olvida de ella.
Las notificaciones son buenas para el paso de mensajes de uno a varios, ya que puede haber un número arbitrario de observadores escuchando un mensaje determinado.
fuente
En lugar de crear un singelton de controlador de datos, sugeriría crear una instancia de controlador de datos y pasarla. Para admitir la inyección de dependencia, primero crearía un
DataController
protocolo:protocol DataController { var someInt : Int {get set} var someString : String {get set} }
Entonces crearía una
SpecificDataController
clase (o el nombre que sea apropiado actualmente):class SpecificDataController : DataController { var someInt : Int = 5 var someString : String = "Hello data" }
La
ViewController
clase debería tener un campo para contener eldataController
. Observe que el tipo dedataController
es el protocoloDataController
. De esta manera, es fácil cambiar las implementaciones del controlador de datos:class ViewController : UIViewController { var dataController : DataController? ... }
En
AppDelegate
podemos configurar el viewController'sdataController
:func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { if let viewController = self.window?.rootViewController as? ViewController { viewController.dataController = SpecificDataController() } return true }
Cuando nos movemos a un viewController diferente podemos pasar el
dataController
en:override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { ... }
Ahora, cuando deseamos cambiar el controlador de datos para una tarea diferente, podemos hacerlo en el
AppDelegate
y no tenemos que cambiar ningún otro código que utilice el controlador de datos.Por supuesto, esto es una exageración si simplemente queremos pasar un solo valor. En este caso, es mejor ir con la respuesta de nhgrif.
Con este enfoque podemos separar la vista de la parte lógica.
fuente
Como señaló @nhgrif en su excelente respuesta, hay muchas formas diferentes en que los VC (controladores de vista) y otros objetos pueden comunicarse entre sí.
El singleton de datos que describí en mi primera respuesta es realmente más sobre compartir y guardar el estado global que sobre comunicarse directamente.
La respuesta de nhrif le permite enviar información directamente desde la fuente al VC de destino. Como mencioné en la respuesta, también es posible enviar mensajes desde el destino al origen.
De hecho, puede configurar un canal activo unidireccional o bidireccional entre diferentes controladores de vista. Si los controladores de vista están vinculados a través de una secuencia de guión gráfico, el momento de configurar los vínculos es en el método prepareFor Segue.
Tengo un proyecto de muestra en Github que usa un controlador de vista principal para alojar 2 vistas de tabla diferentes como elementos secundarios. Los controladores de vista secundaria están vinculados mediante incrustaciones de segues, y el controlador de vista principal conecta enlaces de 2 vías con cada controlador de vista en el método prepareForSegue.
Puede encontrar ese proyecto en github (enlace). Sin embargo, lo escribí en Objective-C y no lo he convertido a Swift, por lo que si no se siente cómodo con Objective-C, puede ser un poco difícil de seguir.
fuente
SWIFT 3:
Si tiene un guión gráfico con segues identificadas, use:
func prepare(for segue: UIStoryboardSegue, sender: Any?)
Aunque si hace todo mediante programación, incluida la navegación entre diferentes UIViewControllers, use el método:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
Nota: para usar la segunda forma que necesita para hacer su UINavigationController, está presionando UIViewControllers, un delegado y debe cumplir con el protocolo 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 } }
fuente
Depende de cuándo quieras obtener datos.
Si desea obtener datos cuando lo desee, puede utilizar un patrón singleton. La clase de patrón está activa durante el tiempo de ejecución de la aplicación. A continuación se muestra un ejemplo del patrón singleton.
class AppSession: NSObject { static let shared = SessionManager() var username = "Duncan" } class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() print(AppSession.shared.username) } }
Si desea obtener datos después de cualquier acción, puede utilizar 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") }
fuente