Passing Data Back from a Modal View in Watchkit

Passing data back from a modal view in WatchKit

I wrote a full example that uses Delegation in WatchKit, passing the delegate instance in the context, and calling delegate function from the modal : Here is the full project example on GitHub

Here is the principale classes of the example :

InterfaceController.swift

This is the main Controller, there are a label and a button on his view. When you press the button, the presentItemChooser get called and it present the ModalView (ModalInterfaceController). I pass the instance of InterfaceController in the context to the modal. Important this controller implements `ModalItemChooserDelegate' functions (the protocol definition is in the modal file)

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

This is the class of my modal controller. I hold the reference of my previous controller (self.delegate = context as? InterfaceController). When a row is selected, I call my delegate function didSelectItem(selectedItem) before dismissing it.

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

}

This is how I pass data back to my previous Controller. If is a better way let me know, I'll take it. :)

Passing data between interface controller in WatchKit

The issue is that you are passing a gameStruct instance using self.pushController(withName: "showDetails", context: gameWatchArray[rowIndex]) from your first interface controller to your second one, but then you are trying to cast gameStruct to String, which will obviously fail.

You should modify awake(withContext:) in your second interface controller to conditionally cast context to gameStruct and then access the gameName property of that struct when assigning the String name to the label's text.

In the future you should always handle the cases when a conditional casting fails so that you can find issues more easily by printing a message in case of a failed cast. Or if you are 100% sure that context will always be of a certain type, you can even do force casting, which will enable you to catch programming errors early on in the development stage.

override func awake(withContext context: Any?) {
super.awake(withContext: context)

if let gameDetails = context as? gameStruct {
gameNameLabel.setText(gameDetails.gameName)
} else {
print("Passed context is not a gameStruct: \(context)")
}
}

You should also conform to the Swift naming convention, which is UpperCamelCase for type names, so change gameStruct to GameStruct.

Swift - WatchKit: How to get data back to the root view controller?

So the way I solved this issue was to create a singleton class which held all my data throughout the different controllers. Then when I reach the last controller and send my data to the singleton, I pop back to the root controller.

When I'm back in the root controller, in the awake method, I call a function I made called loadTableData() which automatically takes information from the singleton when it awakes and loads it in the table. Seems to work perfectly for now but I don't know any potential issues that can come up with this method.

How to send data back to the previous interface controller?

You cannot change a UI element in the first interface controller when it is not active.

Here is one possible way:

  • Send self of the first interface controller to the second one (using pushControllerWithName("secondController", context: ... )).
  • Update a property in the first interface controller while the second interface controller is active.
  • You may programmatically go back to the first interface controller by calling the second interface controller's popControllermethod.
  • When the first interface controller is activated, read out the property and update the button accordingly (in the method willActivate).

Pass array data from one interface to another in watchkit using pagebased navigation?

The WatchKit method for passing objects to a page-based WKInterfaceController is different than in iOS. While you will see a relationship segue on the Storyboard, when you click on it there is no option to name this segue (which is the first step to using the prepareForSegue: method in iOS).

Rather, what you do is pass one array that contains "context" objects, one of which will be provided to each WKViewController that is managing a page. In Objective-C:

+ (void)reloadRootControllersWithNames:(NSArray *)names
contexts:(NSArray *)contexts

In Swift:

class func reloadRootControllersWithNames(_ names: [AnyObject]!,
contexts contexts: [AnyObject]!)

If you have a object you want to pass from one to the other, you set that object as the context for each page:

NSArray * namesArray = @[@"Page 1", @"Page 2", @"Page 3"];
NSArray * contextsArray = @[myObject, myObject, myObject];
[self reloadRootControllersWithNames:namesArray contexts:contextsArray];

Somewhat counter-intuitively from the method name, while this method is called reloadRootControllersWithNames:, the WatchKit documentation on Managing Page-Based Navigation indicates that this same method should be used to seed these values at launch time, as well as any time you want to reload this data at runtime.

How can I change the label Cancel from modal segue in Apple Watch

The label Cancel is the default 'title' of a modally presented WKInterfaceController, which appears on the Apple Watch status bar.

Replacing the title with an image

It is not possible to hide the status bar, nor is it possible to display an image in the status bar, neither as part of this link nor to replace this link.

Options to set modal view title

You can however set the title to a new string value. For instance, you might well want to replace Cancel with Close. There are four ways that you can set this title, which are outlined below. Ensure you read the Note at the bottom as likely only Option 1 will be acceptable in most circumstances.

  1. You can set the title of the modally presented WKInterfaceController in Interface Builder. Simply set the Title attribute in the Attributes Inspector. Only a single static title can be set this way for each WKInterfaceController, of course, although it can be changed dynamically at runtime using any of the mechanisms outlined above.

  2. You can set the title in the init method for the modally presented WKInterfaceController:

    override init () {
    super.init ()
    self.setTitle("Close")
    }
  3. You can set the title directly in the awakeWithContext method of the modally presented WKInterfaceController:

    override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    self.setTitle("Close")
    }
  4. You can pass the title to the modally presented WKInterfaceController using the context variable. In interface builder, set the identifier in the Attributes Inspector of the controller to be presented modally. (In this example, it was set to "modalController".) You then present the controller by passing the desired title as the Context:

    self.presentControllerWithName("modalController", context: "Close")

    Then, in the modally presented controller:

    override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    self.setTitle(context as? String)
    }

Note:

The current 'intended behaviour' of WatchKit almost certainly means that only the first option will be seen as acceptable in most use cases. This is because currently, for the other three options you will initially see the default title for the view as it loads, which will then be replaced with the text you set using setTitle. awakeWithContext runs by design after the view has loaded, but even using setTitle in init does not avoid the initial display of the default title.

The first option outlined above replaces Cancel with a new default title for the view. If you combine a custom Title in Interface builder with any of Options 2-4 below, you see exactly the same symptom (initial title then being replaced with your setTitle), just with a different initial title.

WatchKit Modal Sheet top right Done button

That's correct, Apple hasn't made a public API for that, and the Done button in the question article's screenshot is unique to Apple's own presentTextInputController(withSuggestions:allowedInputMode:completion:). I'd file a suggestion with your developer account in Bug Reporter → watchOS + SDK → WatchKit.

If you only need a Done button and not a Cancel button as well, what you could do is present a modal and change the top left title text from away from the default "Cancel" to "Done".



Related Topics



Leave a reply



Submit