Передача данных из модального вида в WatchKit
При модульном представлении или нажатии контроллера интерфейса мы можем указать параметр context
для передачи некоторых данных новому контроллеру следующим образом.
// Push
[self pushControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
// Modal
[self presentControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]];
Мой вопрос: как мы можем сделать обратное?
Скажем, мы представляем контроллер для пользователя, чтобы выбрать элемент из списка, и мы возвращаемся к главному контроллеру, как мы можем получить элемент, который был выбран?
Ответы
Ответ 1
Я написал полный пример, который использует делегирование в WatchKit, передает экземпляр делегата в контексте и вызывает функцию делегата из модального: Вот полный проект пример на GitHub
Вот основные классы примера:
InterfaceController.swift
Это главный контроллер, на нем есть метка и кнопка. Когда вы нажмете кнопку, вызовите presentItemChooser
и представите ModalView (ModalInterfaceController). Я передаю экземпляр InterfaceController
в контексте модального. Важно, чтобы этот контроллер реализовал функции ModalItemChooserDelegate (определение протокола находится в модальном файле)
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
Это класс моего модального контроллера. Я держу ссылку на мой предыдущий контроллер (self.delegate = context as? InterfaceController
). Когда строка выбрана, я вызываю функцию делегата didSelectItem(selectedItem)
, прежде чем отклонять ее.
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()
}
}
Вот как я передаю данные обратно моему предыдущему контроллеру. Если это лучший способ, дайте мне знать, я возьму его.:)
Ответ 2
Вы можете передать обратно информацию через Протоколы, передав self
в контексте:
InterfaceController.m
// don't forget to conform to the protocol!
@interface InterfaceController() <PictureSelectionControllerDelegate>
//...
// in some method
[self pushControllerWithName:@"PictureSelectionController"
context:@{@"delegate" : self}];
И установив делегат следующим образом:
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"];
}
}
Не забудьте объявить свой протокол:
PictureSelectionController.h
@protocol PictureSelectionControllerDelegate <NSObject>
- (void)selectedPicture:(UIImage *)picture;
@end
Затем вы можете вызвать этот метод из PictureSelectionController.m
:
- (IBAction)buttonTapped {
// get image
UIImage *someCrazyKatPicture = //...
[self.delegate seletedPicture:someCrazyKatPicture];
}
И получите его в методе делегата в InterfaceController.m
:
- (void)selectedPicture:(UIImage *)picture {
NSLog(@"Got me a cat picture! %@", picture);
}
Ответ 3
Как говорит гхр, это требует немного большего объяснения. Легкий (если взломанный) способ состоит в том, чтобы представить контроллер в рамках контекста, который вы передаете в представленный контроллер. Таким образом, вы можете вернуться к контроллеру представления, когда вам нужно. Один из способов сделать это - использовать NSDictionary как ваш контекст и сохранить специальный ключ со ссылкой на представляющий контроллер. Надеюсь, это поможет.
Ответ 4
Я тестировал передачу self
на контроллеры (модальные или нет) и с помощью didDeactivate
в качестве способа вызова методов делегата, но catch - это то, что он вызывается всякий раз, когда экран отклоняется или когда новый представлен вид. Я только начинаю с WatchKit, поэтому я могу быть абсолютно неправ здесь.
Мой делегат
@class Item;
@class ItemController;
@protocol AddItemDelegate <NSObject>
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item;
Мой корневой контроллер
@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.");
}
Мой дочерний контроллер
@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];
}
Ответ 5
Передача данных обратно из интерфейса watchOS с помощью блока и segue
Передача данных назад и вперед между интерфейсом Controller не так проста. В WatchKit существует процесс segue, но первая проблема заключается в том, что нет готовой системы, и вы не смогли достигнуть segue destinationViewController, чтобы вы не могли легко вводить материал новому контроллеру (WatchOS 3 - 4).
В обратном направлении нет выхода, чтобы вы не смогли достичь разматывания.
Другая проблема заключается в том, что эти решения пытаются обновить данные и пользовательский интерфейс первого интерфейса ControlController в методе willActivate, который запускается в любое время, когда экран просмотра пробуждается - так довольно часто - и это может вызвать проблемы и усложнить.
Практика программирования в основном использует делегат и вводит сам, используя контекст segue, как описано выше.
Но использование делегата немного усложняется, поэтому я использую блоки, которые более современны, и я думаю, что лучше и элегантнее.
Посмотрим, как:
Сначала давайте подготовим segue в Interface Builder раскадровки Apple Watch, просто подключите кнопку с другим интерфейсом Controller, нажимая кнопку Ctrl и назовите segue.
![InterfaceBuilder для раскадровки Apple Watch]()
то в .h файле исходного интерфейсаController позволяет назвать его SourceInterfaceController.h объявить свойство для блока:
@property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
то используйте contextForSegueWithIdentifier: чтобы передать блок или любые другие данные в интерфейс назначения interfaceController, используя segueIdentifier, если у вас есть больше segues.
Этот метод Apple фактически использует контекст (id) как возвращаемый объект, который может быть любым объектом, а целевой метод interfaceController awakeWithContext: (id) будет использовать его при запуске интерфейсаController.
Итак, пусть объявляет блок в SourceInterfaceController.m, затем передайте его в контекст:
- (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;
}
}
Если вы хотите перенести больше данных, чем только блок с контекстом на целевой интерфейсController, просто оберните их в NSDictionary.
В имени интерфейса interfaceController DestinationInterfaceController.h пусть объявляет другое свойство для хранения блока с использованием любого имени, но с тем же объявлением переменной
@property (copy) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
затем извлеките блок из контекста в DestinationInterfaceController.m:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.initNewSessionBlock = context;
}
Позже в DestinationInterfaceController.m просто запустите блок, например, в методе действий с помощью кнопки:
- (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];
}
}
Блок будет выполнен любым методом исходного интерфейсаController, используя данные в области целевого интерфейса ControlController, чтобы вы могли отправить данные обратно в исходный контроллер.
Вы можете поместить интерфейсController с помощью popController, если все в порядке, и блок возвращает yes как BOOL.
Примечание. Конечно, вы можете использовать любой тип segue, будь то push или модальный, и вы можете использовать pushControllerWithName: context: тоже в триггер segue, и вы можете использовать этот контекст метода таким же образом.
Ответ 6
Возможно, есть и другие способы, но я предпочитаю использовать метод pushControllerWithName:.
Корневой контроллер:
- (IBAction)GoToChildControllerButton {
[self pushControllerWithName:@"TableInterfaceController" context:@"pass some data to child controller here..."];
}
Ребенок:
- (IBAction)BackToRootControllerButton {
[self pushControllerWithName:@"TableInterfaceController" context:@"pass some data back to root controller here..."];
}