Lors de la présentation modale ou de la poussée d'un contrôleur d'interface, nous pouvons spécifier le paramètre context
pour transmettre certaines données au nouveau contrôleur comme suit.
// Push
[self pushControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
// Modal
[self presentControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
Ma question est, comment pouvons-nous faire l'inverse?
Supposons que nous présentions un contrôleur modal pour que l'utilisateur sélectionne un élément dans une liste et que nous revenions au contrôleur principal. Comment pouvons-nous obtenir l'élément qui a été sélectionné?
J'ai écrit un exemple complet qui utilise la délégation dans WatchKit, en passant l'instance de délégué dans le contexte et en appelant la fonction de délégué depuis le modal: Voici l'exemple de projet complet sur GitHub
Voici les classes principales de l'exemple:
InterfaceController.Swift
C'est le contrôleur principal, il y a une étiquette et un bouton sur sa vue. Lorsque vous appuyez sur le bouton, la presentItemChooser
est appelée et présente le ModalView (ModalInterfaceController). Je passe l'instance de InterfaceController
dans le contexte au modal. Il est important que ce contrôleur implémente les fonctions `ModalItemChooserDelegate '(la définition du protocole est dans le fichier 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
C'est la classe de mon contrôleur modal. Je tiens la référence de mon ancien contrôleur (self.delegate = context as? InterfaceController
). Lorsqu'une ligne est sélectionnée, j'appelle ma fonction de délégué didSelectItem(selectedItem)
avant de la fermer.
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()
}
}
C’est ainsi que je renvoie les données à mon ancien contrôleur. Si c'est un meilleur moyen de me le faire savoir, je le prendrai. :)
Vous pouvez transférer des informations en retour via Protocoles en transmettant self
dans le contexte:
InterfaceController.m
// don't forget to conform to the protocol!
@interface InterfaceController() <PictureSelectionControllerDelegate>
//...
// in some method
[self pushControllerWithName:@"PictureSelectionController"
context:@{@"delegate" : self}];
Et en configurant le délégué comme suit:
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"];
}
}
N'oubliez pas de déclarer votre protocole:
PictureSelectionController.h
@protocol PictureSelectionControllerDelegate <NSObject>
- (void)selectedPicture:(UIImage *)picture;
@end
Ensuite, vous pouvez appeler cette méthode depuis PictureSelectionController.m
:
- (IBAction)buttonTapped {
// get image
UIImage *someCrazyKatPicture = //...
[self.delegate seletedPicture:someCrazyKatPicture];
}
Et recevez-le dans la méthode delegate dans InterfaceController.m
:
- (void)selectedPicture:(UIImage *)picture {
NSLog(@"Got me a cat picture! %@", picture);
}
Comme le dit ghr, cela nécessite un peu plus d'explications. Le moyen le plus simple (si difficile) consiste à faire en sorte que le contrôleur qui présente le disque fasse partie du contexte que vous passez dans le contrôleur présenté. De cette façon, vous pouvez rappeler le contrôleur de présentation lorsque vous en avez besoin. Une façon de procéder consiste à utiliser un NSDictionary en tant que contexte et à stocker une clé spéciale avec une référence au contrôleur de présentation. J'espère que cela t'aides.
Transmettre les données depuis watchOS interfaceController à l'aide de blocs et segue
Le transfert de données entre interfaceControllers n’est pas si simple. WatchKit est en cours, mais le premier problème est qu’il n’ya pas de prepareForSegue et que vous ne pouvez pas atteindre le contrôle ViewView de Segue, vous ne pouvez donc pas injecter facilement de contenus dans le nouveau contrôleur (WatchOS 3 - 4) . direction il n’y a pas de sortie donc vous ne pouviez pas atteindre la zone de détente.
Un autre problème est que ces solutions tentent de mettre à jour les données et l'interface utilisateur du premier interfaceController dans la méthode willActivate qui est déclenchée chaque fois que l'écran de veille s'active - de manière assez fréquente -, ce qui peut poser problème et compliquer les choses.
La pratique de la programmation consiste principalement à utiliser delegate et à injecter self en utilisant le contexte de la séquence, comme indiqué dans les réponses ci-dessus.
Mais utiliser delegate est un peu compliqué, alors j’utilise des blocs qui sont plus contemporains et qui me semblent meilleurs et plus élégants.
Voyons comment:
Commençons par préparer la séquence dans le Générateur d'interface du storyboard de l'Apple Watch. Il suffit de connecter un bouton à un autre interfaceController en appuyant sur le bouton Ctrl et de nommer la séquence.
puis dans le fichier .h du sourceController source nommons-le SourceInterfaceController.h déclarons une propriété pour le bloc:
@property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
utilisez ensuite contextForSegueWithIdentifier: pour transférer le bloc ou toute autre donnée vers le contrôleur d'interface de destination à l'aide de segueIdentifier si vous avez plus de segments.
Cette méthode Apple utilise en réalité un contexte (id) en tant qu'objet de retour, qui peut être n'importe quel objet. La méthode awakeWithContext: (id) context de l'interfaceController de destination l'utilisera au lancement de l'interfaceController.
Donc déclarons le bloc dans SourceInterfaceController.m puis passons-le au contexte:
- (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 vous souhaitez transférer plus de données que le bloc avec le contexte dans l'interfaceController de destination, enveloppez-les simplement dans un NSDictionary.
Dans le nom interfaceController de destination, il DestinationInterfaceController.h déclarons une autre propriété pour stocker le bloc en utilisant un nom autre que la même déclaration de variable
@property (copy) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
puis récupérez le bloc du contexte dans DestinationInterfaceController.m:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.initNewSessionBlock = context;
}
Plus tard, dans DestinationInterfaceController.m, déclenchez simplement le blocage, par exemple dans une méthode d'action avec un bouton:
- (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];
}
}
Le bloc sera exécuté par n’importe quelle méthode du contrôleur source source utilisant les données de l’étendue du contrôleur interface de destination, afin que vous puissiez renvoyer des données au source sourceController . Vous pouvez afficher le contrôleur interface avec popController si tout est ok et le bloc retourne oui en tant que BOOL.
Remarque: Bien sûr, vous pouvez utiliser n'importe quel type de séquence, qu'il s'agisse d'un Push ou d'un modal et vous pouvez utiliser pushControllerWithName: context: et to déclencher la transition, et vous pouvez utiliser le contexte de cette méthode de la même manière.
J'ai essayé de passer self
aux contrôleurs (modal ou non) et d'utiliser didDeactivate
comme moyen d'appeler les méthodes de délégation, mais le problème est qu'il est appelé chaque fois que l'écran est fermé ou lorsqu'un nouvel affichage est présenté. Je viens juste de commencer à utiliser WatchKit pour que je puisse me tromper totalement ici.
Mon délégué
@class Item;
@class ItemController;
@protocol AddItemDelegate <NSObject>
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item;
Mon contrôleur racine
@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.");
}
Mon contrôleur d'enfant
@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];
}