I am developing an app that communicates between the watch and the iOS parent app. It sends data from the WatchKit extension to the parent application by opening it. I understand that openParentApplication:reply, when called, opens the iPhone application from the Apple Watch. After that, application:handleWatchKitExtension:reply is called in the app's delegate.
From there you can open a notification to the view controller with:
NSNotificationCenter.defaultCenter().postNotificationName(aName: String, object anObject: AnyObject?)
"aName" is what you can open up in the view conroller with:
NSNotificationCenter.defaultCenter().addObserver(self,
selector: Selector("handleWatchKitNotification:"),
name: "WatchKitSaysHello",
object: nil)
Here is my code for my interface controller in my WatchKit extension:
//
// InterfaceController.swift
// SendColors WatchKit Extension
//
// Created by Tommy on 12/30/14.
// Copyright (c) 2014 Tommy. All rights reserved.
//
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
var ypo = false
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
#IBOutlet weak var redButton: WKInterfaceButton!
#IBOutlet weak var greenButton: WKInterfaceButton!
#IBOutlet weak var blueButton: WKInterfaceButton!
#IBAction func onRedButtonClick() {
if ypo {
openParentAppWithColor("Yellow")
}
else {
openParentAppWithColor("Red")
}
}
#IBOutlet weak var moreButton: WKInterfaceButton!
#IBAction func moreButtonClick() {
if !ypo {
ypo = true
redButton.setTitle("Yellow")
redButton.setColor(UIColor.yellowColor())
greenButton.setTitle("Purple")
greenButton.setColor(UIColor.purpleColor())
blueButton.setTitle("Orange")
blueButton.setColor(UIColor.orangeColor())
moreButton.setTitle("Back")
}
else {
ypo = false
redButton.setTitle("Red")
redButton.setColor(UIColor.redColor())
greenButton.setTitle("Green")
greenButton.setColor(UIColor.greenColor())
blueButton.setTitle("Blue")
blueButton.setColor(UIColor.blueColor())
moreButton.setTitle("More...")
}
}
#IBAction func onGreenButtonClick() {
if ypo {
openParentAppWithColor("Purple")
}
else {
openParentAppWithColor("Green")
}
}
#IBAction func onBlueButtonClick() {
if ypo {
openParentAppWithColor("Orange")
}
else {
openParentAppWithColor("Blue")
}
}
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 openParentAppWithColor(color: String) {
if ["color": color] != nil {
if !WKInterfaceController.openParentApplication(["color": color], reply: { (reply, error) -> Void in
println(reply)
}) {
println("ERROR")
}
}
}
}
My problem is that say for instance I clicked the red button on the watch simulator. The action would be called and in that action it calls openParentApplicationWithColor("Red") which would call WKInterfaceController.openParentApplication(["color": color], reply: { (reply, error) -> Void in }) What this is supposed to do is open the parent app on the simulator. It opens it in the background. I, therefore open it manually. When I open the app manually, the background is completely black. I suspect the problem is that I did not enable App Groups in the Capabilities tab. In order to do that, you need to be in the developer program. Should I join it to do this, or is there another problem? I am using Xcode 6.2 Beta 3. Thank you all in advance!
I have tested and can confirm that openParentApplication:reply: does not require App Groups to be enabled.
I understand that openParentApplication:reply, when called, opens the iPhone application from the Apple Watch.
This method opens the iPhone app in the background. In previous betas of Xcode 6.2 the app did open in the foreground, but this was not the intended behaviour and is not how the WatchKit Extension-iPhone app communication will work in the shipping version.
You can still launch the iPhone app in the foreground manually, but this isn't necessary unless you want to test the use case of both being used at once. At least with the API currently available, the iPhone app it can't be launched programmatically by the watch app to the foreground, and the watch app can't be launched programmatically by the iPhone app.
Once the app launches, you've reported the screen is still black when you expect it to change colour based on the message you've sent it. Is application:handleWatchKitExtension:reply: being called in the iPhone app? You will know it is definitely being called if you are receiving the reply block back for execution in your WatchKit Extension, which should be outputting something from your println(reply). If so, then the issue is with the code in the iPhone app to do something with the message you've passed to it. It would be useful to see your implementation of that method in your AppDelegate of the iPhone app. (Note if that method failed to call the reply block, it might still be receiving the message and you wouldn't get the reply, but then you would be seeing an error message about the reply not being received.)
Note that you won't see NSLog messages or get breakpoints in Xcode when running the Watch extension initially, but you can select Debug > Attach to process > [select your iPhone app under 'Likely targets'] and then you will get the logs and breakpoints of the iPhone app instead of the Watch app, while still being able to use the Watch app in the watch simulator. Most useful for debugging.
No, you no need to make group enable to use ole parent application. you can make a request to open parent IOS application and waiting a reply from IOS App without setting the group app. You only need to setting the group app when you want to share data between watchkit app and IOS app using NSUserDefauts with suite name.
I am using Xcode 6.2 Beta 5. Select Debug > Attach to process > [select your iPhone app under likely targets is not working. I see the NSLogs for Watch extension but not in iPhone app delegate and view controller of iPhone app.
Related
The problem is that Apple rejects my app because when the button is clicked to purchase the "no ads" upgrade, it doesn't make the banner disappear until you close the app and re-open it. This is because I initialize all my RevMob code in the viewController.swift file. I have a boolean in place that turns to false as soon as the upgrade is purchase inside this viewController.swift file. So next time you open the app and the viewController loads, the boolean is set to false and it doesn't allow the ads to appear.
Anybody know if this is the wrong way to go about this? Or is there an easy way to make them disappear immediately upon press of the no Ads button without having to close the app and re-open it?
//BANNER AD =======================================
let bannerBlock: () -> Void = {
//Custom method defined below
if UserDefaults.standard.object(forKey: "adsBool") as! Bool == true
{
self.showBannerWithCustomFrame()
}
else
{
//don't show ads because user purchased
}
}
let bannerFailBlock: ((Error?) -> Void) = {error in
NSLog("[RevMob Sample App] Session failed to start with error: \(error!.localizedDescription)")
}
RevMobAds.startSession(withAppID: "00000000000000000000",
withSuccessHandler: bannerBlock,
andFailHandler: bannerFailBlock)
This is how my bannerView is set up in my GameViewController
class GameViewController: UIViewController, RevMobAdsDelegate {
var bannerView:RevMobBannerView?
override func viewDidLoad() {
super.viewDidLoad()
From RevMob's banner documentation, there's a method called hideAd. Calling that method on the IAP callback would solve your problem right?
To hide the banner:
banner!.hideAd()
Let me see if i got your problem correctly, you want to hide your banner as soon as the purchase is made.
First, you need to set the bannerView as a property of your viewController.
Then you have to add this code inside the callback from the purchase success:
viewController.bannerView.removeFromSuperview();
I fixed it! Thanks for your help. I had to move the code from the GameViewController to the GameScene for the bannerAd so that I could remove it as soon as the purchase was made. Works perfectly.
I am currently working on my game and I have decided to enable multiplayer via GameCenter in the Game to allow users to play their friend. I have followed a tutorial by RayWinderLinch, but ran into a problem.
My problem is that when I load up the GKMatchMakingViewController and hit the big Play Now button on both devices it will find each other (which is meant to happen) and under the set game center user name it will say Ready.
This means that GameCenter has found each player and is ready to start the match which it should, but in my case the match never begins. It is stuck on a loop that says Starting Game... and nothing happens. It appears that the
func matchmakerViewController(viewController: GKMatchmakerViewController!, didFindMatch theMatch: GKMatch!)
and the
func match(theMatch: GKMatch!, player playerID: String!, didChangeState state: GKPlayerConnectionState)
method's are never ran. I am completely lost on what is going on. I have tried this many times over and over to fix the problem but nothing worked. I will attach an image that show's the screen of the application where my problem persists and I will also attach the code I am using.
I am using a framework based of of the GameKitHelper.h In the
mentioned tutorial above. It is written in swift and is called
GCHelper
Code
The code for GCHelper can be found using the GitHub link mention earlier
I have cut out code that is unnecessary for this problem
class GameScene : SKScene, GameKitHelper, MultiplayerNetworkingProtocol {
override func didMoveToView () {
GCHelper().authenticateLocalUser() //Authenticate GameCenter User
println("\n \n \n Authenticating local user \n \n \n")
}
func startMultiplayer () {
var vc = self.view?.window?.rootViewController
GameKitHelper().findMatchWithMinPlayers(2, maxPlayers: 2, viewController: vc!, delegate: self); //Find match and load GKMatchMakerViewController
}
func matchStarted() {
//Delegate method
println("match started")
}
func matchEnded() {
//Delegate method
println("match ended")
}
func match(match: GKMatch, didReceiveData: NSData, fromPlayer: String){
//Delegate Method
println("Did receive data")
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == multiplayer //SKSpriteNode {
//User clicked on multiplayer button, launch multiplayer now!
println("Loading multiplayer")
startMultiplayer()
}
}
Image
UPDATE
I have noticed that when I test using my iPhone and the simulator, on the iPhone the status will go from Ready to Disconnected but on the simulator the status is still Ready and then I will get the following message in the console for the iPhone
Warning matchmakerViewController:didFindMatch: delegate method not implemented`
Even though it is implemented in the GCHelper.swift file. This does not happen when I test on my iPhone and iPad Mini it just keeps on saying Starting Game...
Any help will be appreciated.
Prerequisites
Both players must be in the same environment (Sandbox for testing)
The authenticationChanged in GCHelper.swift must not be private. You may have to remove that keyword.
There are a few delegates involved, and in your example, there are a few competing protocols. My recommendation is to create a new App using minimalistic code to track down the startMultiplayer issue.
Gamekit Multi Player Step by Step Tutorial using GCHelper
Create a new project (Xcode > File > New > Project... > Single View Application > ... > Create) using the very same Product Name & Organization Name as your game, so that it matches both App Bundle Identifier and iTunes Game Center parameters. This will allow you to run tests without overhead.
Use this Podfile:
platform :ios, '8.0'
use_frameworks!
target 'SO-31699439' do
pod 'GCHelper'
end
Use a GCHelperDelegate
Create a UIViewController with just the bare minimum (a Start Multiplayer button), and connect it to this action:
#IBAction func startMultiplayerAction(_ sender: AnyObject) {
GCHelper.sharedInstance.findMatchWithMinPlayers(
2,
maxPlayers: 2,
viewController: self,
delegate: self);
}
Here is the crux: the delegate you pass must adopt GCHelperDelegate. It does not have to be the same class, but in your example above it is, and the present rule was not respected. For this example, ViewController adopts GCHelperDelegate:
import UIKit
import GCHelper
import GameKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GCHelper.sharedInstance.authenticateLocalUser()
}
}
Implement required GCHelperDelegate methods in an extension
Since ViewController adopts GCHelperDelegate, the three methods below must be in that same class, and will be invoked:
extension ViewController: GCHelperDelegate {
func matchStarted() {
print("matchStarted")
}
func match(_ match: GKMatch, didReceiveData: Data, fromPlayer: String) {
print("match:\(match) didReceiveData: fromPlayer:\(fromPlayer)")
}
func matchEnded() {
print("matchEnded")
}
}
Execution
Tested: built, linked, ran, successful match.
Launch app, tap Start Multiplayer button, tap Play Now on both devices (or iPhone Simulator + real device).
Log:
Authenticating local user...
Authentication changed: player not authenticated
Ready to start match!
Found player: SandboxPlayer
matchStarted
► Find this solution on GitHub and additional details on Swift Recipes.
I am having a problem with the proximity sensor on the iPhone. It seems that when the idle timer is disabled and the proximity monitoring is enabled the screen (sometimes) will turn on when receiving a notification. After this the proximity state is reported incorrectly and the screen won't turn off again.
I provided a sample project to illustrate the problem. But the steps to reproduce are quite simple. I tested this on multiple phones (4S, 5, 6, 6+) with multiple iOS versions (7.0.3 and 8.3). It seems to occur most reliably when the phone is not connected to a power source or debugger.
The code in my only ViewController is (the view is created in a storyboard):
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var checkingToggleButton: UIButton!
#IBOutlet weak var debugLabel: UILabel!
#IBOutlet weak var screenDebugLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.checkingToggleButton.setTitle("Start checking", forState: UIControlState.Normal)
self.debugLabel.text = "Not checking"
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("proximityChanged:"), name: "UIDeviceProximityStateDidChangeNotification", object: nil)
}
#IBAction func handleCheckingToggle(sender: AnyObject) {
let enabled = !UIDevice.currentDevice().proximityMonitoringEnabled
if enabled {
self.debugLabel.text = "Checking"
self.checkingToggleButton.setTitle("Stop checking", forState: UIControlState.Normal)
} else {
self.debugLabel.text = "Not checking"
self.checkingToggleButton.setTitle("Start checking", forState: UIControlState.Normal)
}
UIApplication.sharedApplication().idleTimerDisabled = enabled
UIDevice.currentDevice().proximityMonitoringEnabled = enabled
}
func proximityChanged(notification:NSNotification)
{
if UIDevice.currentDevice().proximityState{
self.screenDebugLabel.text = "Proximity true"
println("Proximity true")
} else {
self.screenDebugLabel.text = "Proximity false"
println("Proximity false")
}
}
}
Steps to reproduce:
Build the provided sample project on a device and run the App on the the device disconnected from power and the debugger (this seems to make some difference).
Enable proximityMonitoring and disable the idle timer by pressing the "Start checking" button.
Cover the proximity sensor, this turns the screen off.
Wait approximately 2 minutes (this also makes a difference, doing it too soon won't reproduce the problem)
Send yourself a notification (for instance an iMessage)
The screen will turn on and won't turn off again. We also created a label that shows the current proximityState, the state is (incorrectly) reported as false.
Link to sample project on GitHub:
https://github.com/TimPelgrim/ProximityTest
I've tested a lot of things with the Notification method.
As Asi mentioned it, it doesn't work well. The solution is to observe the variable.
The issue is still there in iOS 11.4.1 (not tested on iOS 12 beta yet).
Swift 4:
// Prepare you observer variable
private var observer: NSKeyValueObservation?
// ...
// Start observing proximityState
self.observer = UIDevice.current.observe(\.proximityState) { [weak self] (device, _) in
print("State changed: \("device.proximityState")")
}
// ...
// Don't forget to call the `invalidate` method on your observer when you want to stop observing
self.observer?.invalidate()
I sent in a technical support issue to the apple engineers and they told me this is a bug in the operating system. No report on when it gets fixed but be advised that this issue seems to be occur in all versions of (at least) iOS 7 and 8 and that there does not seem to be a workaround. I will update here if I receive notice of a fix.
There is actually workaround..
UIDevice.current.proximityState is changing as it should change. but the problem is with the UIDeviceProximityStateDidChange notification.
So the workaround is to create timer that check every x time if UIDevice.current.proximityState changed and that it. just don't use UIDeviceProximityStateDidChange.
I have got an iPhone 8.2 app that communicates with a bluetooth accessory during background mode (I enabled it on the capabilities tab). Whenever I receive a message from the accessory (handled in the iPhone app bundle) I'd like to send a notification to the Apple Watch extension (so that the user can visualise the updated state of the accessory).
How can I do this?
Additional sub-questions:
Ideally I'd like the user to see the notification also if the Apple
Watch app extension is in background mode (question 2: can apple
watch extension go in background mode?).
I am also unsure if I can send a notification without turning that
Apple watch app on. question 3: Is this possible?
You can send that notification using MMWormhole.
You send it using:
[self.wormhole passMessageObject:#{#"titleString" : title}
identifier:#"messageIdentifier"];
and you receive it using:
[self.wormhole listenForMessageWithIdentifier:#"messageIdentifier"
listener:^(id messageObject) {
// Do Something
}];
Note that wormhole uses app groups to communicate, so you need to enable it.
What MMWormhole uses, under the hood, is CFNotificationCenterGetDarwinNotifyCenter and you have more info about that in this medium post.
As for the sub-questions I am afraid I don't have 100% certain, but I believe that yes, you the apple watch extension also works in background mode. As for the third question, I didn't understand it.
An update for iOS 9.0 and above.
While MMWormhole works on watchOS 2 as well, it would be preferable to use Apple's WatchConnectivity framework on watchOS 2.0 and above. MMWormhole requires the use of App Groups, while WatchConnectivity does not. WatchConnectivity does indeed require iOS 9.0 and above.
Below is a quick example of how to send a simple String from an iOS app to a WatchKit Extension. First let's set up a helper class.
class WatchConnectivityPhoneHelper : NSObject, WCSessionDelegate
{
static let sharedInstance:WatchConnectivityPhoneHelper = WatchConnectivityPhoneHelper()
let theSession:WCSession = WCSession.defaultSession()
override init()
{
super.init()
// Set the delegate of the WCSession
theSession.delegate = self
// Activate the session so that it will start receiving delegate callbacks
theSession.activateSession()
}
// Used solely to initialize the singleton for the class, which calls the constructor and activates the event listeners
func start()
{
}
// Both apps must be active to send a message
func sendStringToWatch(message:String, callback:[String : AnyObject] -> ())
{
// Send the message to the Watch
theSession.sendMessage(["testString" : message], replyHandler:
{ (reply: [String : AnyObject]) -> Void in
// Handle the reply here
// Send the reply in the callback
callback(reply)
},
errorHandler:
{ (error:NSError) -> Void in
// Handle the error here
})
}
// A message was sent by the Watch and received by the iOS app. This does NOT handle replies to messages sent from the iOS app
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
// Handle received message logic here
if (message["testString"] != nil)
{
replyHandler(["testString" : "Received Message!"])
}
}
}
This helper class would be almost identical for the WatchKit Extension. I've named the WatchKit Extension version of this class WatchConnectivityExtensionHelper. I won't paste it because, again, it is just about identical to the helper class above.
Usage
We need to start the iOS and WatchKit Extension message listeners by instantiating the singleton helper class. All we need to do is call some function or refer to some variable within the singleton to initialize it. Otherwise, iOS or the WatchKit Extension will be sending out messages but the other might not receive them.
iOS - AppDelegate.swift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
// Start the event listener for communications between the iOS app and the Apple Watch
WatchConnectivityPhoneHelper.sharedInstance().start()
...
}
...
}
WatchKit Extension - ExtensionDelegate.swift
class ExtensionDelegate: NSObject, WKExtensionDelegate
{
func applicationDidFinishLaunching()
{
// Start the event listener for communications between the iOS app and the Apple Watch
WatchConnectivityExtensionHelper.sharedInstance.start()
...
}
...
}
Then anywhere in your iOS app, call sendStringToWatch to send a String to the WatchKit Extension:
WatchConnectivityPhoneHelper.sharedInstance.sendStringToWatch("Test message!")
{ (reply:[String : AnyObject]) -> Void in
if (reply["testString"] != nil)
{
let receivedString:String = reply["testString"] as! String
print(receivedString)
}
}
I have an iPhone application and added a WatchKitExtension. From the iPhone App I want to pass a String to the WatchApp which is supposed to change an image on the Watch.
What I already did was to download the source files and import the MMWormhole.m & .h. They are written in Obj-C and so Xcode automatically bridged them for me.
I also added an app group and activated it for my WatchExtension & my iPhone target
In the tutorial on GitHub it says I have to initialize the wormhole with:
self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"group.com.mutualmobile.wormhole"
optionalDirectory:#"wormhole"];
...and send a message using:
[self.wormhole passMessageObject:#{#"titleString" : title}
identifier:#"messageIdentifier"];
But I have actually no idea where to put that, I am using Swift in my iPhone application and the WatchExtension.
Can anyone please help me there?
I suppose it depends on different applications, but for one application I put the listeners in the didFinishLaunchingWithOptions method of my app delegate in the main iOS app. This was because the user would be using the watch, and they would be relaying information off to the phone
There were multiple listeners...
var wormhole = MMWormhole(applicationGroupIdentifier: "group", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("identifier", listener: { (message) -> Void in
//do stuff
})
wormhole.listenForMessageWithIdentifier("identifier2", listener: { (message) -> Void in
//do stuff
})
wormhole.listenForMessageWithIdentifier("identifier3", listener: { (message) -> Void in
//do stuff
})
And then in a WKInterfaceController, I sent a message. Sometimes in an action, sometimes in the willActivate method. It really depends on the flow of your app
var wormhole = MMWormhole(applicationGroupIdentifier: "group", optionalDirectory: nil)
#IBAction func buttonPushed(){
wormhole.passMessageObject("object", identifier: "identifier1")
}
This can work both ways though, I could have very easily put a listener in my watch which would wait for messages initiated by some Interface Controller on the phone.
Here are my instructions. Hopefully they help you with the simplest use case and then you can expand from there. (Remember to structure your code so that it actually makes sense!)
Get MMWormhole (the .h and the .m) added to your project. If you know how to use Cocoapods, do that, but otherwise, just use git submodules. (I use git submmodules)
Because you need the .h to be visible from Swift, you need to use a bridging header.
Set up an App Group, which requires using the Developer Portal. Link is here
In your iPhone build target -> Capabilities -> App Groups and add your group. If all three checkboxes do not go perfectly, go back to the Developer Portal and make sure everything is right or start again.
MMWormhole, iPhone Side
Set up the wormhole somewhere you can reach it. NOTE: your group ID has to be the one from above!
let wormhole = MMWormhole(applicationGroupIdentifier: "group.testMe.now", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("wormholeMessageFromWatch", listener: { (message ) -> Void in
if let messageFromWatch = message as? String {
// do something with messageFromWatch
}
})
iPhone App Sends String
wormhole.passMessageObject("message from phone to watch", identifier: "wormholeMessageFromPhone")
iPhone app registers to receive and sends again in the callback via MMWormhole (asynchronous but cool)
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
universe.initWormhole(.phone, messageHandler: { (message) -> () in
universe.wormhole.passMessageObject("the phone got \(message)", identifier: "wormholeMessageFromPhone")
})
return true
}
MMWormhole, Apple Watch Side
Set up the wormhole somewhere you can reach it. NOTE: your group ID has to be the one from above!
let wormhole = MMWormhole(applicationGroupIdentifier: "group.testMe.now", optionalDirectory: nil)
wormhole.listenForMessageWithIdentifier("wormholeMessageFromPhone", listener: { (message ) -> Void in
if let messageFromPhone = message as? String {
// do something with messageFromPhone
}
})
MMWormhole, watch app registers to receive
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
universe.initWormhole(.watch, messageHandler: { (message) -> () in
println("MMWormhole Message Came to Watch: \(message)")
})
}
MMWormhole, watch app sends
// force open the parent application because otherwise the message goes nowhere until the app is opened
WKInterfaceController.openParentApplication(["":""], reply: nil)
universe.wormhole.passMessageObject("[from watch to phone]", identifier: "wormholeMessageFromWatch")