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 (usingpushControllerWithName("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
popController
method. - 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.
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.
You can set the title in the init method for the modally presented WKInterfaceController:
override init () {
super.init ()
self.setTitle("Close")
}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")
}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
How to Deserialize an Escaped JSON String with Nsjsonserialization
How to Use Http Live Streaming Protocol in iPhone Sdk 3.0
How to Disable 4 Finger Gestures on iPad
iOS - Is Motion Activity Enabled in Settings > Privacy > Motion Activity
Uicolor Colorwithred:Green:Blue:Alpha: Always Returns White Unless One Argument Is 0
iOS Web Page Errors Over Cellular Data But Not Over Wifi? Recent Change to At&T Cellular Network
Swift: Determine iOS Screen Size
iOS - Corelocation and Geofencing While App Is Closed
Change Tab Bar Tint Color on iOS 7
Open Links in Safari Instead of Uiwebview
How to Create Different User Groups in Firebase
Ios: One Ibaction for Multiple Buttons
Corebluetooth: What Is the Lifetime of Unique Uuids
iOS 7 - Restrict Landscape Orientation Only in One View Controller
Urlresponse Is Not Retrieved After Storing in Cache Using Storecachedresponse