ios - logo - watch kit
Devolviendo datos desde una vista modal en WatchKit (6)
Devolviendo datos desde watchOS interfaceController usando block y segue
Pasar datos de un lado a otro entre interfaceControllers no es tan simple. Hay un proceso de segue en WatchKit, pero el primer problema es que no hay prepareForSegue y no pudo alcanzar el control de control de destino de segue, por lo que no pudo inyectar fácilmente el nuevo controlador (WatchOS 3 - 4). En la dirección hacia atrás, no hay salida, por lo que no se pudo alcanzar el segmento de desenrollado.
Otro problema es que estas soluciones intentan actualizar los datos y la interfaz de usuario del primer interfaceController en el método willActivate que se activa cada vez que la pantalla del reloj se despierta, con mucha frecuencia, y esto podría causar problemas y complicar.
La práctica de programación consiste principalmente en usar delegado e inyectarse a sí mismo utilizando el contexto del segmento, como describen las respuestas anteriores.
Pero usar el delegado es un poco complicado, así que uso bloques que son más contemporáneos y creo que mejor y más elegante.
Veamos cómo:
Primero, preparemos el segmento en el Creador de Interfaz del guión gráfico de Apple Watch, simplemente conecte un botón con otro controlador de interfaz presionando el botón Ctrl y nombre el segmento.
luego, en el archivo .h del origen interfaceController, llamémosle SourceInterfaceController.h declare una propiedad para el bloque:
@property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
luego use contextForSegueWithIdentifier: para transferir el bloque o cualquier otro dato al interfaceController de destino usando el segueIdentifier si tiene más segues.
Este método de Apple realmente usa un contexto (id) como un objeto de retorno que podría ser cualquier objeto y el método de contexto awakeWithContext: (id) del controlador de interfaz de destino lo usará cuando se inicie el controlador de interfaz.
Así que declaremos el bloque en SourceInterfaceController.m y luego pasémoslo al contexto:
- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier {
__unsafe_unretained typeof(self) weakSelf = self;
if ([segueIdentifier isEqualToString:@"MySegue"]) {
self.initNewSessionBlock = ^BOOL (NSDictionary *mySegueDict, NSError *error)
{
[weakSelf initNewSession];
NSLog(@"message from destination IC: %@", realTimeDict[@"messageBack"]);
return YES;
};
return self.initNewSessionBlock;
}
else if ([segueIdentifier isEqualToString:@"MyOtherSegue"]) {
self.otherBlock = ^BOOL (NSString *myText, NSError *error)
{
//Do what you like
return YES;
};
return self.otherBlock;
}
else {
return nil;
}
}
Si desea transferir más datos que solo el bloque con el contexto al controlador de interfaz de destino, simplemente envuélvalos en un NSDictionary.
En el interface de control del controlador de destino el nombre DestinationInterfaceController.h declaremos otra propiedad para almacenar el bloque usando cualquier nombre, pero la misma declaración de variable
@property (copy) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
luego recupera el bloque del contexto en DestinationInterfaceController.m :
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.initNewSessionBlock = context;
}
Más adelante, en DestinationInterfaceController.m, solo se activa el bloque, por ejemplo, en un método de acción con un botón:
- (IBAction)initNewSessionAction:(id)sender {
NSError *error = nil;
NSDictionary *realTimeDict = @{@"messageBack" : @"Greetings from the destination interfaceController"};
BOOL success = self.initNewSessionBlock(realTimeDict, error);
if (success) {
[self popController];
}
}
El bloque se ejecutará con cualquier método del controlador de interfaz de origen utilizando los datos en el ámbito del controlador de interfaz de destino, por lo que puede enviar datos de vuelta al controlador de origen de destino. Puede abrir el interfaceController utilizando popController si todo está bien y el bloqueo devuelve yes como BOOL.
Nota: por supuesto, puede usar cualquier tipo de segue, ya sea un push o modal y puede usar pushControllerWithName: context: también : para activar el segue, y puede usar el contexto de este método de la misma manera.
Cuando presentamos o presionamos modalmente un controlador de interfaz, podemos especificar el parámetro de context
para pasar algunos datos al nuevo controlador de la siguiente manera.
// Push
[self pushControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
// Modal
[self presentControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
Mi pregunta es, ¿cómo podemos hacer lo contrario?
Supongamos que presentamos un controlador de manera modal para que el usuario elija un elemento de una lista y volvamos al controlador principal. ¿Cómo podemos obtener el elemento seleccionado?
Como dice ghr, esto requiere un poco más de explicación. La manera fácil (si no es fácil) es hacer que el controlador de presentación forme parte del contexto que está pasando al controlador presentado. De esa manera, puede volver a llamar al controlador de presentación cuando lo necesite. Una forma de hacer esto es usar un NSDictionary como su contexto y almacenar una clave especial con una referencia al controlador de presentación. Espero que esto ayude.
Escribí un ejemplo completo que usa Delegación en WatchKit, pasando la instancia de delegado en el contexto y llamando a la función de delegado desde el modal: Aquí está el ejemplo completo del proyecto en GitHub
Aquí están las clases principales del ejemplo:
InterfaceController.swift
Este es el controlador principal, hay una etiqueta y un botón en su vista. Cuando presiona el botón, se presenta el presentItemChooser y presenta el ModalView (ModalInterfaceController). Paso la instancia de InterfaceController
en el contexto al modal. Importante: este controlador implementa funciones `ModalItemChooserDelegate ''(la definición del protocolo está en el archivo modal)
class InterfaceController: WKInterfaceController, ModalItemChooserDelegate {
@IBOutlet weak var itemSelected: WKInterfaceLabel!
var item = "No Item"
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
itemSelected.setText(item)
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func didSelectItem(itemSelected: String) {
self.item = itemSelected
}
@IBAction func presentItemChooser() {
self.presentControllerWithName("ModalInterfaceController", context: self)
}
}
ModalInterfaceController.swift
Esta es la clase de mi controlador modal. Tengo la referencia de mi controlador anterior ( self.delegate = context as? InterfaceController
). Cuando se selecciona una fila, llamo a mi función de delegado didSelectItem(selectedItem)
antes de descartarla.
protocol ModalItemChooserDelegate {
func didSelectItem(itemSelected:String)
}
class ModalInterfaceController: WKInterfaceController {
let rowId = "CustomTableRowController"
let items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
var delegate: InterfaceController?
@IBOutlet weak var customTable: WKInterfaceTable!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
self.delegate = context as? InterfaceController
// Configure interface objects here.
println(delegate)
loadTableData()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
private func loadTableData(){
customTable.setNumberOfRows(items.count, withRowType: rowId)
for(i, itemName) in enumerate(items){
let row = customTable.rowControllerAtIndex(i) as! TableRowController
row.fillRow(itemName)
}
}
override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
let selectedItem = items[rowIndex]
self.delegate?.didSelectItem(selectedItem)
self.dismissController()
}
}
Así es como le paso los datos a mi controlador anterior. Si es una manera mejor házmelo saber, lo tomaré. :)
He estado probando pasar self
a los controladores (modal o no) y utilizando didDeactivate
como una forma de invocar los métodos de delegado, pero el problema es que se llama cada vez que se descarta la pantalla o cuando se presenta una nueva vista. Estoy empezando con WatchKit por lo que podría estar totalmente equivocado aquí.
Mi delegado
@class Item;
@class ItemController;
@protocol AddItemDelegate <NSObject>
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item;
Mi controlador raíz
@interface ListController() <AddItemDelegate>
...
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex {
// TODO: How do we pass data back? Delegates? Something else?
if ([self.items[rowIndex] isEqualToString:@"Item 1"]) {
// TODO: Do I really want to pass along a single object here?
[self pushControllerWithName:@"Item" context:self];
}
}
...
#pragma mark - AddItemDelegate
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item {
NSLog(@"didAddItem:withItem: delegate called.");
}
Mi hijo controlador
@property (nonatomic, strong) Item *item;
@property (nonatomic, weak) id<AddItemDelegate> delegate;
...
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// TODO: Check that this conforms to the protocol first.
self.delegate = context;
}
...
- (void)didDeactivate {
[super didDeactivate];
[self.delegate didAddItem:self withItem:self.item];
}
Puede transferir información a través de Protocolos pasando self
en el contexto:
InterfaceController.m
// don''t forget to conform to the protocol!
@interface InterfaceController() <PictureSelectionControllerDelegate>
//...
// in some method
[self pushControllerWithName:@"PictureSelectionController"
context:@{@"delegate" : self}];
Y poniendo al delegado de esta manera:
PictureSelectionController.m
@property (nonatomic, unsafe_unretained) id<PictureSelectionControllerDelegate> delegate;
// ...
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
if ([context isKindOfClass:[NSDictionary class]]) {
self.delegate = [context objectForKey:@"delegate"];
}
}
No olvides declarar tu protocolo:
PictureSelectionController.h
@protocol PictureSelectionControllerDelegate <NSObject>
- (void)selectedPicture:(UIImage *)picture;
@end
Entonces puedes llamar a ese método desde PictureSelectionController.m
:
- (IBAction)buttonTapped {
// get image
UIImage *someCrazyKatPicture = //...
[self.delegate seletedPicture:someCrazyKatPicture];
}
Y recibalo en el método delegado dentro de InterfaceController.m
:
- (void)selectedPicture:(UIImage *)picture {
NSLog(@"Got me a cat picture! %@", picture);
}
quizás haya otras formas, pero prefiero usar el método pushControllerWithName :.
Controlador de raíz:
- (IBAction)GoToChildControllerButton {
[self pushControllerWithName:@"TableInterfaceController" context:@"pass some data to child controller here..."];
}
Controlador infantil:
- (IBAction)BackToRootControllerButton {
[self pushControllerWithName:@"TableInterfaceController" context:@"pass some data back to root controller here..."];
}