Подтвердить что ты не робот

Передача данных из модального вида в WatchKit

При модульном представлении или нажатии контроллера интерфейса мы можем указать параметр context для передачи некоторых данных новому контроллеру следующим образом.

// Push
[self pushControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]]; 

// Modal
[self presentControllerWithName:@"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, @"someKey", ..., nil]]; 

Мой вопрос: как мы можем сделать обратное?

Скажем, мы представляем контроллер для пользователя, чтобы выбрать элемент из списка, и мы возвращаемся к главному контроллеру, как мы можем получить элемент, который был выбран?

4b9b3361

Ответ 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..."];
}