33

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

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

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

我的问题是,我们怎么能反过来呢?

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

4

6 回答 6

31

我写了一个在 WatchKit 中使用委托的完整示例,在上下文中传递委托实例,并从模态调用委托函数:这是 GitHub 上的完整项目示例

这是示例的主要类:

接口控制器.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()
        }


    }

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

于 2014-11-19T16:25:36.537 回答
13

您可以通过在上下文中传递来通过协议传回信息:self

接口控制器.m

// don't forget to conform to the protocol!
@interface InterfaceController() <PictureSelectionControllerDelegate>

//...

// in some method
[self pushControllerWithName:@"PictureSelectionController" 
                     context:@{@"delegate" : self}];

并像这样设置委托:

图片选择控制器.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"];
    }
}

不要忘记声明您的协议:

图片选择控制器.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);
}
于 2015-03-24T10:03:01.423 回答
2

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

于 2014-11-24T08:07:32.500 回答
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];
}
于 2015-03-20T20:43:01.057 回答
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。

用于 Apple Watch 故事板的 InterfaceBuilder

然后在源 interfaceController 的 .h 文件中,我们将其命名为SourceInterfaceController.h为该块声明一个属性:

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

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

这个 Apple 方法实际上使用 (id)context 作为返回对象,它可以是任何对象,并且目标 interfaceController 的awakeWithContext:(id)context方法将在 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中只需触发块,例如在带有按钮的操作方法中:

- (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,你也可以以同样的方式使用这个方法的 context。

于 2017-09-26T08:12:01.320 回答
-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..."];
}
于 2015-05-12T09:17:24.927 回答