Pregunta Pasar datos entre los controladores de vista


Soy nuevo en iOS y Objective-C y en todo el paradigma MVC y estoy atrapado con lo siguiente:

Tengo una vista que actúa como un formulario de entrada de datos y quiero darle al usuario la opción de seleccionar múltiples productos. Los productos se enumeran en otra vista con un UITableViewController y he habilitado múltiples selecciones.

Mi pregunta es, ¿cómo transfiero los datos de una vista a otra? Voy a tener las selecciones en el UITableView en una matriz, pero ¿cómo puedo pasar eso a la vista de formulario de entrada de datos anterior para que pueda guardarse junto con los demás datos en Core Data en el momento del envío del formulario?

He navegado y he visto a algunas personas declarar una matriz en el delegado de la aplicación. Leí algo sobre Singletons pero no entiendo cuáles son estos y leo algo sobre cómo crear un modelo de datos.

¿Cuál sería la forma correcta de realizar esto y cómo lo haría?


1205
2018-03-06 12:43


origen


Respuestas:


Esta pregunta parece ser muy popular aquí en stackoverflow, así que pensé en intentar dar una mejor respuesta para ayudar a las personas que comienzan en el mundo de iOS como yo.

Espero que esta respuesta sea lo suficientemente clara como para que la gente entienda y que no me haya perdido nada.

Pasar datos adelante

Pasar datos hacia adelante a un controlador de vista desde otro controlador de vista. Utilizaría este método si quisiera pasar un objeto / valor de un controlador de vista a otro controlador de vista que puede estar presionando en una pila de navegación.

Para este ejemplo, tendremos ViewControllerA y ViewControllerB

Pasar un BOOL valor de ViewControllerA a ViewControllerB haríamos lo siguiente.

  1. en ViewControllerB.h crear una propiedad para el BOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
    
  2. en ViewControllerA tienes que contarlo ViewControllerB así que usa un

    #import "ViewControllerB.h"
    

    Entonces, donde quieres cargar la vista, por ej. didSelectRowAtIndex o algunos IBAction necesita establecer la propiedad en ViewControllerB antes de empujarlo en la pila de navegación.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.isSomethingEnabled = YES;
    [self pushViewController:viewControllerB animated:YES];
    

    Esto establecerá isSomethingEnabled en ViewControllerB a BOOL valor YES.

Pasar datos adelante usando Segues

Si está utilizando Storyboards, lo más probable es que esté utilizando segues y necesitará este procedimiento para pasar los datos hacia adelante. Esto es similar al anterior, pero en lugar de pasar los datos antes de presionar el controlador de vista, utiliza un método llamado

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

Entonces para pasar un BOOL de ViewControllerA a ViewControllerB haríamos lo siguiente:

  1. en ViewControllerB.h crear una propiedad para el BOOL

    @property (nonatomic, assign) BOOL isSomethingEnabled;
    
  2. en ViewControllerA tienes que contarlo ViewControllerB así que usa un

    #import "ViewControllerB.h"
    
  3. Crea un segue de ViewControllerA a ViewControllerB en el guión gráfico y darle un identificador, en este ejemplo lo llamaremos "showDetailSegue"

  4. Luego tenemos que agregar el método a ViewControllerA se llama cuando se realiza una segue, debido a esto necesitamos detectar qué segue fue llamado y luego hacer algo. En nuestro ejemplo vamos a verificar "showDetailSegue" y si eso se lleva a cabo vamos a pasar nuestra BOOL valor para ViewControllerB

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            ViewControllerB *controller = (ViewControllerB *)segue.destinationViewController;
            controller.isSomethingEnabled = YES;
        }
    }
    

    Si tiene sus vistas incrustadas en un controlador de navegación, debe cambiar ligeramente el método anterior a la siguiente

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
        if([segue.identifier isEqualToString:@"showDetailSegue"]){
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            ViewControllerB *controller = (ViewControllerB *)navController.topViewController;
            controller.isSomethingEnabled = YES;
        }
    }
    

    Esto establecerá isSomethingEnabled en ViewControllerB a BOOL valor YES.

Pasando datos de vuelta

Para pasar datos de ViewControllerB a ViewControllerA necesitas usar Protocolos y delegados o Bloques, este último se puede usar como un mecanismo débilmente acoplado para callbacks.

Para hacer esto haremos ViewControllerA un delegado de ViewControllerB. Esto permite ViewControllerB para enviar un mensaje de vuelta a ViewControllerA lo que nos permite enviar datos de vuelta.

por ViewControllerA ser delegado de ViewControllerB debe ajustarse a ViewControllerBEl protocolo que tenemos que especificar. Esto dice ViewControllerA qué métodos debe implementar.

  1. En ViewControllerB.h, bajo el #import, pero arriba @interface usted especifica el protocolo

    @class ViewControllerB;
    
    @protocol ViewControllerBDelegate <NSObject>
    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item;
    @end
    
  2. siguiente aún en el ViewControllerB.h necesitas configurar un delegate propiedad y sintetizar en ViewControllerB.m

    @property (nonatomic, weak) id <ViewControllerBDelegate> delegate;
    
  3. En ViewControllerB llamamos un mensaje en el delegate cuando sacamos el controlador de vista.

    NSString *itemToPassBack = @"Pass this value back to ViewControllerA";
    [self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
    
  4. Eso es todo por ViewControllerB. Ahora en ViewControllerA.h, contar ViewControllerA importar ViewControllerB y cumplir con su protocolo.

    #import "ViewControllerB.h"
    
    @interface ViewControllerA : UIViewController <ViewControllerBDelegate>
    
  5. En ViewControllerA.m implementar el siguiente método de nuestro protocolo

    - (void)addItemViewController:(ViewControllerB *)controller didFinishEnteringItem:(NSString *)item
    {
        NSLog(@"This was returned from ViewControllerB %@",item);
    }
    
  6. Antes de empujar viewControllerB a la pila de navegación que tenemos que contar ViewControllerB ese ViewControllerA es su delegado, de lo contrario, obtendremos un error.

    ViewControllerB *viewControllerB = [[ViewControllerB alloc] initWithNib:@"ViewControllerB" bundle:nil];
    viewControllerB.delegate = self
    [[self navigationController] pushViewController:viewControllerB animated:YES];
    

Referencias


1557
2018-03-16 11:39



Rápido

Hay toneladas y toneladas de explicaciones aquí y en todo StackOverflow, pero si eres un principiante y solo intentas hacer funcionar algo básico, prueba mirar este tutorial de YouTube (fue lo que finalmente me ayudó a entender cómo hacerlo).

Pasar los datos a la siguiente Controladora de Vista

El siguiente es un ejemplo basado en el video. La idea es pasar una cadena del campo de texto en el Controlador de primera vista a la etiqueta en el Controlador de segunda vista.

enter image description here

Cree el diseño del guión gráfico en Interface Builder. Para hacer el cambio, solo Controlar haga clic en el botón y arrastre hacia el Controlador de segunda vista.

Primer controlador de vista

El código para el First View Controller es

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    // This function is called before the segue
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // get a reference to the second view controller
        let secondViewController = segue.destination as! SecondViewController

        // set a variable in the second view controller with the String to pass
        secondViewController.receivedString = textField.text!
    }

}

Controlador de segunda vista

Y el código para el segundo controlador de vista es

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    // This variable will hold the data being passed from the First View Controller
    var receivedString = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Used the text from the First View Controller to set the label
        label.text = receivedString
    }

}

No lo olvides

  • Conecte las salidas para el UITextField y el UILabel.
  • Establezca el primer y segundo controlador de vista en los archivos Swift apropiados en IB.

Pasar datos de vuelta al controlador de vista anterior

Para pasar datos desde el segundo controlador de vista al primer controlador de vista, usa un protocolo y un delegado. Este video es una caminata muy clara de ese proceso:

El siguiente es un ejemplo basado en el video (con algunas modificaciones).

enter image description here

Cree el diseño del guión gráfico en Interface Builder. De nuevo, para hacer la transición, solo Controlar arrastre desde el botón al Controlador de segunda vista. Establezca el identificador de segue a showSecondViewController. Además, no se olvide de conectar los puntos de venta y las acciones usando los nombres en el siguiente código.

Primer controlador de vista

El código para el First View Controller es

import UIKit

class FirstViewController: UIViewController, DataEnteredDelegate {

    @IBOutlet weak var label: UILabel!

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showSecondViewController" {
            let secondViewController = segue.destination as! SecondViewController
            secondViewController.delegate = self
        }
    }

    func userDidEnterInformation(info: String) {
        label.text = info
    }
}

Tenga en cuenta el uso de nuestra costumbre DataEnteredDelegate protocolo.

Controlador y protocolo de segunda vista

El código para el segundo controlador de vista es

import UIKit

// protocol used for sending data back
protocol DataEnteredDelegate: class {
    func userDidEnterInformation(info: String)
}

class SecondViewController: UIViewController {

    // making this a weak variable so that it won't create a strong reference cycle
    weak var delegate: DataEnteredDelegate? = nil

    @IBOutlet weak var textField: UITextField!

    @IBAction func sendTextBackButton(sender: AnyObject) {

        // call this method on whichever class implements our delegate protocol
        delegate?.userDidEnterInformation(info: textField.text!)

        // go back to the previous view controller
        _ = self.navigationController?.popViewController(animated: true)
    }
}

Tenga en cuenta que protocol está fuera de la clase View Controller.

Eso es. Al ejecutar la aplicación ahora, debería poder enviar datos desde el segundo controlador de vista al primero.


131
2017-08-11 06:35



El M en MVC es para "Modelo" y en el paradigma MVC el rol de las clases modelo es administrar los datos de un programa. Un modelo es lo opuesto a una vista: una vista sabe cómo mostrar datos, pero no sabe nada sobre qué hacer con los datos, mientras que un modelo sabe todo sobre cómo trabajar con los datos, pero nada sobre cómo mostrarlos. Los modelos pueden ser complicados, pero no tienen que serlo; el modelo para su aplicación podría ser tan simple como una matriz de cadenas o diccionarios.

El papel de un controlador es mediar entre la vista y el modelo. Por lo tanto, necesitan una referencia a uno o más objetos de visualización y uno o más objetos de modelo. Digamos que su modelo es una matriz de diccionarios, con cada diccionario representando una fila en su tabla. La vista raíz de su aplicación muestra esa tabla, y puede ser responsable de cargar la matriz desde un archivo. Cuando el usuario decide agregar una nueva fila a la tabla, toca un botón y el controlador crea un nuevo diccionario (mutable) y lo agrega a la matriz. Para completar la fila, el controlador crea un controlador de vista detallada y le da el nuevo diccionario. El controlador de vista detallada rellena el diccionario y lo devuelve. El diccionario ya es parte del modelo, por lo que no es necesario que ocurra nada más.


118
2018-03-06 13:49



Hay varias maneras en que los datos se pueden recibir en una clase diferente en iOS. Por ejemplo -

  1. Inicialización directa después de la asignación de otra clase.
  2. Delegación - para pasar datos de vuelta
  3. Notificación: para transmitir datos a múltiples clases a la vez
  4. Guardando en NSUserDefaults - para acceder más tarde
  5. Clases Singleton
  6. Bases de datos y otros mecanismos de almacenamiento como plist, etc.

Pero para el escenario simple de pasar un valor a una clase diferente cuya asignación se realiza en la clase actual, el método más común y preferido sería la configuración directa de los valores después de la asignación. Esto se hace de la siguiente manera:

Podemos entenderlo usando dos controladores: Controller1 y Controller2 

Supongamos que en la clase Controller1 desea crear el objeto Controller2 y presionarlo con un valor String que se pasa. Esto se puede hacer así:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj passValue:@"String"];
    [self pushViewController:obj animated:YES];
}

En la implementación de la clase Controller2 habrá esta función como-

@interface Controller2  : NSObject

@property (nonatomic , strong) NSString* stringPassed;

@end

@implementation Controller2

@synthesize stringPassed = _stringPassed;

- (void) passValue:(NSString *)value {

    _stringPassed = value; //or self.stringPassed = value
}

@end

También puede establecer directamente las propiedades de la clase Controller2 de la misma manera que esta:

- (void)pushToController2 {

    Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
    [obj setStringPassed:@"String"];  
    [self pushViewController:obj animated:YES];
}

Para pasar múltiples valores puede usar los parámetros múltiples como:

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passValue:@“String1” andValues:objArray withDate:date]; 

O si necesita pasar más de 3 parámetros que están relacionados con una característica común, puede almacenar los valores en una clase Model y pasar ese modelObject a la siguiente clase

ModelClass *modelObject = [[ModelClass alloc] init]; 
modelObject.property1 = _property1;
modelObject.property2 = _property2;
modelObject.property3 = _property3;

Controller2 *obj = [[Controller2 alloc] initWithNib:@"Controller2" bundle:nil];
[obj passmodel: modelObject];

En fin, si quieres,

1) set the private variables of the second class initialise the values by calling a custom function and passing the values.
2) setProperties do it by directlyInitialising it using the setter method.
3) pass more that 3-4 values related to each other in some manner , then create a model class and set values to its object and pass the object using any of the above process.

Espero que esto ayude


85
2018-04-08 10:24



Después de más investigaciones, parece que Protocolos y delegados es la forma correcta / preferida de Apple de hacer esto.

Terminé usando este ejemplo

Compartir datos entre los controladores de vista y otros objetos @ iPhone Dev SDK

Funcionó bien y me permitió pasar una cadena y una matriz hacia adelante y hacia atrás entre mis puntos de vista.

Gracias por toda tu ayuda


74
2018-03-13 21:20



Encuentro la versión más simple y elegante con bloques pasantes. Vamos a nombrar controlador de vista que espera datos devueltos como "A" y devuelve controlador de vista como "B". En este ejemplo, queremos obtener 2 valores: primero de Type1 y segundo de Type2.

Suponiendo que usemos Storyboard, el primer controlador establece el bloqueo de devolución de llamada, por ejemplo, durante la preparación de segue:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.destinationViewController isKindOfClass:[BViewController class]])
    {
        BViewController *viewController = segue.destinationViewController;

        viewController.callback = ^(Type1 *value1, Type2 *value2) {
            // optionally, close B
            //[self.navigationController popViewControllerAnimated:YES];

            // let's do some action after with returned values
            action1(value1);
            action2(value2);
        };

    }
}

y el controlador de vista "B" debería declarar la propiedad de devolución de llamada, BViewController.h:

// it is important to use "copy"
@property (copy) void(^callback)(Type1 *value1, Type2 *value2);

Que en el archivo de implementación BViewController.m después de que tengamos los valores deseados para devolver nuestra devolución de llamada debe llamarse:

if (self.callback)
    self.callback(value1, value2);

Una cosa para recordar es que usar bloque a menudo necesita administrar referencias fuertes y __weak como se explica aquí


59
2017-10-14 18:11



Hay buena información en muchas de las respuestas dadas, pero ninguna aborda la pregunta completamente.

La pregunta pregunta sobre pasar información entre los controladores de vista. El ejemplo específico que se da pregunta acerca de la transmisión de información entre vistas, pero dado el carácter autodenominado de iOS, el póster original probablemente significó entre viewControllers, no entre vistas (sin la participación de ViewControllers). Parece que todas las respuestas se centran en dos controladores de vista, pero ¿qué ocurre si la aplicación evoluciona para necesitar la participación de más de dos controladores de visualización en el intercambio de información?

El cartel original también preguntó acerca de Singletons y el uso de la AppDelegate. Estas preguntas deben ser respondidas.

Para ayudar a cualquier otra persona que vea esta pregunta, que quiera una respuesta completa, intentaré brindarla.

Escenarios de aplicación

En lugar de tener una discusión abstracta muy hipotética, ayuda tener aplicaciones concretas en mente. Para ayudar a definir una situación de controlador de dos vistas y una situación de controlador de más de dos vistas, voy a definir dos escenarios de aplicación concretos.

Escenario uno: un máximo de dos controladores de vista alguna vez necesita compartir información. Ver diagrama uno.

diagram of original problem

Hay dos controladores de vista en la aplicación. Hay un ViewControllerA (Formulario de entrada de datos) y un Controlador de vista B (Lista de productos). Los elementos seleccionados en la lista de productos deben coincidir con los elementos que se muestran en el cuadro de texto en el formulario de entrada de datos. En este escenario, ViewControllerA y ViewControllerB deben comunicarse directamente entre sí y sin otros controladores de vista.

Escenario dos: más de dos controladores de vista necesitan compartir la misma información. Vea el diagrama dos.

home inventory application diagram

Hay cuatro controladores de vista en la aplicación. Es una aplicación basada en pestañas para administrar el inventario de la casa. Tres controladores de vista presentan vistas filtradas de manera diferente de los mismos datos:

  • ViewControllerA - Artículos de lujo
  • ViewControllerB - Artículos no asegurados
  • ViewControllerC - Inventario completo de la casa
  • ViewControllerD - Agregar nuevo formulario de elemento

Cada vez que se crea o edita un elemento individual, también debe sincronizarse con los demás controladores de vista. Por ejemplo, si agregamos un bote en ViewControllerD, pero aún no está asegurado, entonces el bote debe aparecer cuando el usuario vaya a ViewControllerA (Artículos de lujo), y también a ViewControllerC (Inventario completo de la vivienda), pero no cuando el usuario vaya a ViewControllerB (elementos no asegurados). Debemos preocuparnos no solo de agregar elementos nuevos, sino también de eliminar elementos (que pueden permitirse desde cualquiera de los cuatro controladores de vista) o editar elementos existentes (que pueden permitirse desde el "Formulario de Agregar nuevo elemento", reutilizando el mismo para editar).

Como todos los controladores de vista necesitan compartir los mismos datos, los cuatro controladores de vista deben permanecer sincronizados y, por lo tanto, debe haber algún tipo de comunicación con todos los demás controladores de vista, siempre que cualquier controlador de vista individual cambie los datos subyacentes. Debería ser bastante obvio que no queremos que cada controlador de vista se comunique directamente con cada controlador de vista en este escenario. En caso de que no sea obvio, considere si teníamos 20 controladores de vista diferentes (en lugar de solo 4). ¿Qué tan difícil y propenso a errores sería notificar a cada uno de los otros 19 controladores de vista cada vez que un controlador de vista realizara un cambio?

Las soluciones: delegados y el patrón de observador, y Singletons

En el escenario uno, tenemos varias soluciones viables, como otras respuestas han dado

  • segues
  • delegados
  • establecer propiedades en los controladores de vista directamente
  • NSUserDefaults (en realidad, una mala elección)

En el escenario dos, tenemos otras soluciones viables:

  • Patrón de observador
  • Singletons

UN semifallo es una instancia de una clase, siendo esa instancia la única instancia que existe durante su tiempo de vida. Un singleton recibe su nombre del hecho de que es la única instancia. Normalmente, los desarrolladores que usan singleton tienen métodos de clases especiales para acceder a ellos.

+ (HouseholdInventoryManager*) sharedManager; {
    static dispatch_once_t onceQueue;
    static HouseholdInventoryManager* _sharedInstance;

    // dispatch_once is guaranteed to only be executed once in the
    // lifetime of the application
    dispatch_once(&onceQueue, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}

Ahora que entendemos qué es un singleton, veamos cómo encaja un singleton en el patrón del observador. El patrón de observador se usa para que un objeto responda a los cambios de otro objeto. En el segundo escenario, tenemos cuatro controladores de vista diferentes, que todos quieren saber sobre los cambios en los datos subyacentes. Los "datos subyacentes" deberían pertenecer a una sola instancia, un singleton. El "conocimiento sobre los cambios" se logra al observar los cambios realizados en el singleton.

La aplicación de inventario de la casa tendría una sola instancia de una clase que está diseñada para administrar una lista de artículos de inventario. El gerente administraría una colección de artículos para el hogar. La siguiente es una definición de clase para el administrador de datos:

#import <Foundation/Foundation.h>

@class JGCHouseholdInventoryItem;

@interface HouseholdInventoryManager : NSObject
/*!
 The global singleton for accessing application data
 */
+ (HouseholdInventoryManager*) sharedManager;


- (NSArray *) entireHouseholdInventory;
- (NSArray *) luxuryItems;
- (NSArray *) nonInsuredItems;

- (void) addHouseholdItemToHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) editHouseholdItemInHomeInventory:(JGCHouseholdInventoryItem*)item;
- (void) deleteHoueholdItemFromHomeInventory:(JGCHouseholdInventoryItem*)item;
@end

Cuando cambia la recopilación de elementos del inventario de la vivienda, los controladores de visualización deben conocer este cambio. La definición de clase anterior no hace obvio cómo sucederá esto. Necesitamos seguir el patrón del observador. Los controladores de vista deben observar formalmente el sharedManager. Hay dos formas de observar otro objeto:

  • Key-Value-Observing (KVO)
  • NSNotificationCenter.

En el escenario dos, no tenemos una sola propiedad del HouseholdInventoryManager que se pueda observar utilizando KVO. Debido a que no tenemos una sola propiedad que sea fácilmente observable, el patrón del observador, en este caso, debe implementarse utilizando NSNotificationCenter. Cada uno de los cuatro controladores de vista se suscribiría a notificaciones, y sharedManager enviaría notificaciones al centro de notificaciones cuando corresponda. El administrador de inventario no necesita saber nada sobre los controladores de vista o las instancias de cualquier otra clase que pueda estar interesada en saber cuándo cambia la colección de elementos de inventario; NSNotificationCenter se ocupa de estos detalles de implementación. Los controladores de vista simplemente se suscriben a las notificaciones, y el administrador de datos simplemente publica las notificaciones.

Muchos programadores principiantes aprovechan el hecho de que siempre hay exactamente uno Delegado de la aplicación durante la vida de la aplicación, que es accesible a nivel mundial. Los programadores principiantes usan este hecho para incluir objetos y funcionalidades en la aplicaciónDelegate como una conveniencia para acceder desde cualquier otro lugar en la aplicación. El hecho de que AppDelegate sea singleton no significa que deba reemplazar a todos los otros singletons. Esta es una práctica deficiente ya que impone demasiada carga a una clase, rompiendo buenas prácticas orientadas a objetos. Cada clase debe tener un rol claro que se explique fácilmente, a menudo solo por el nombre de la clase.

Cada vez que su delegado de aplicación comienza a hincharse, comience a eliminar la funcionalidad en singletons. Por ejemplo, Core Data Stack no debe dejarse en AppDelegate, sino que debe colocarse en su propia clase, una clase coreDataManager.

Referencias


46
2018-04-05 22:04



Hay varios métodos para compartir datos.

  1. Siempre puedes compartir datos usando NSUserDefaults. Establezca el valor que desea compartir con respecto a una tecla de su elección y obtenga el valor de NSUserDefault asociado a esa clave en el siguiente controlador de vista.

    [[NSUserDefaults standardUserDefaults] setValue:value forKey:key]
    [[NSUserDefaults standardUserDefaults] objectForKey:key]
    
  2. Solo puedes crear una propiedad en viewcontrollerA. Crea un objeto de viewcontrollerA en viewcontrollerB y asigne el valor deseado a esa propiedad.

  3. También puedes crear delegados personalizados para esto.


37
2017-09-27 10:38