从WatchKit中的模态视图传回数据

时间:2014-11-19 15:54:30

标签: ios watchkit apple-watch

当模态呈现或推送接口控制器时,我们可以指定context参数以将一些数据传递给新控制器,如下所示。

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

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

我的问题是,我们怎样才能做到相反?

假设我们以模态方式呈现一个控制器,以便用户从列表中选择一个项目并返回主控制器,我们如何才能获得已被选中的项目?

6 个答案:

答案 0 :(得分:27)

我写了一个完整的例子,它在WatchKit中使用Delegation,在上下文中传递委托实例,并从模态调用委托函数:Here is the full project example on GitHub

以下是示例的原则类:

<强> InterfaceController.swift

这是主控制器,他的视图上有一个标签和一个按钮。当您按下按钮时,presentItemChooser被调用并呈现ModalView(ModalInterfaceController)。我将上下文中的InterfaceController实例传递给模态。重要的是这个控制器实现了`ModalItemChooserDelegate&#39;函数(协议定义在模态文件中)

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()
        }


    }

这是我将数据传回我之前的Controller的方式。如果有更好的方式让我知道,我会接受它。 :)

答案 1 :(得分:11)

您可以在上下文中传递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);
}

答案 2 :(得分:2)

正如格尔所说,这需要更多解释。简单(如果hacky)方式是使呈现控制器成为您传递到所呈现的控制器的上下文的一部分。这样,您可以在需要时回拨演示控制器。一种方法是使用NSDictionary作为上下文,并存储一个特殊的键,其中包含对呈现控制器的引用。希望这会有所帮助。

答案 3 :(得分:1)

我一直在测试将self传递给控制器​​(模态与否)以及使用didDeactivate作为调用委托方法的方法,但问题是每当屏幕被解除时都会调用它或者在呈现新视图时。我刚刚开始使用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];
}

答案 4 :(得分:1)

  

使用block和segue从watchOS interfaceController传回数据

在interfaceControllers之间来回传递数据并不是那么简单。 WatchKit中有segue进程,但第一个问题是没有prepareForSegue,你无法访问segue的destinationViewController,所以你不能轻易地将东西注入新控制器(WatchOS 3 - 4)。 在向后方向没有出口,所以你无法到达展开区域。

另一个问题是这些解决方案尝试更新willActivate方法中第一个interfaceController的数据和用户界面,该方法在手表屏幕唤醒的任何时候都会被唤醒 - 这种情况非常频繁 - 这可能会导致问题并且复杂化

编程实践主要是使用委托并使用segue的上下文注入 self ,如上面的答案所述。

但是使用委托有点复杂,所以我使用更现代的块,我觉得更好,更优雅。

让我们看看如何:

首先让我们在Apple Watch的故事板的Interface Builder中准备segue,只需将按钮与另一个interfaceController连接,按住Ctrl键并命名segue。

InterfaceBuilder for Apple Watch storyboard

然后在源interfaceController的.h文件中,让它命名为 SourceInterfaceController.h 声明块的属性:

@property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);

然后使用 contextForSegueWithIdentifier:使用 segueIdentifier 将块或任何其他数据传输到目标interfaceController(如果您有更多segue)。

这个Apple方法实际上使用(id)上下文作为返回对象,可以是任何对象,并且目标interfaceController的 awakeWithContext:(id)上下文方法将在interfaceController启动时使用它。 / p>

因此,让我们在 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;
    }

}

如果您想将除上下文的块之外的任何数据传输到目标interfaceController,只需将它们包装在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];
    }

}

该块将使用目标interfaceController范围内的数据执行源interfaceController的任何方法,因此您可以将数据发送回目标sourceController。 你可以使用 popController 弹出interfaceController,如果一切正常,那么块返回yes作为BOOL。

  

注意:当然你可以使用任何类型的segue,无论是推送还是模式,你也可以使用 pushControllerWithName:context:至   触发segue,你可以用同样的方式使用这个方法的上下文。

答案 5 :(得分:-4)

也许有其他一些方法,但我更喜欢使用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..."];
}