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

问题描述 投票:28回答:6

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

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

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

我的问题是,我们怎么能这样做呢?

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

ios watchkit apple-watch
6个回答
28
投票

我写了一个完整的例子,它在WatchKit中使用Delegation,在上下文中传递委托实例,并从模态调用委托函数:Here is the full project example on 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()
        }


    }

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


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
投票

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


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];
}

1
投票

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

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

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

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

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

我们来看看如何:

首先让我们在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:如果你有更多的segue,使用segueIdentifier将块或任何其他数据传输到目标interfaceController。

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

因此,让我们在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中触发该块,例如在带有按钮的action方法中:

- (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,无论是push还是modal,你可以使用pushControllerWithName:context:来触发segue,你可以用同样的方式使用这个方法的上下文。


-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..."];
}
© www.soinside.com 2019 - 2024. All rights reserved.