I am trying to send a notification from my iPhone to the Watch device using
CFNotificationCenterAddObserver on the watch and CFNotificationCenterPostNotification.(I am NOT testing on the Xcode simulator).
This is my code in the iOS app:
#include <CoreFoundation/CoreFoundation.h>
...
- (void)sendLogOutNotificationToWatch{
dispatch_async(dispatch_get_main_queue(), ^{
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("NOTIFICATION_TO_WATCH"), (__bridge const void *)(self), nil, TRUE);
});
}
And this is how I use it on the apple watch extension app:
#implementation InterfaceController
.....
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
....
[self registerToNotification];
}
- (void)registerToNotification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:#com.test.app" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH" ), NULL );
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(userLoggedOut ) name:#"com.test.app" object:nil];
CFNotificationCenterAddObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), didReceivedDarwinNotification, CFSTR( "NOTIFICATION_TO_WATCH" ), NULL, CFNotificationSuspensionBehaviorDrop );
}
void didReceivedDarwinNotification()
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.test.app" object:nil];
}
- (void)didDeactivate {
[super didDeactivate];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"com.test.app" object:nil];
CFNotificationCenterRemoveObserver( CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)( self ), CFSTR( "NOTIFICATION_TO_WATCH" ), NULL );
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)userLoggedOut{
[self showAlertViewwithTitle:#"Notice" andMessage:#"User logged out on iPhone device!"];
}
You should use WatchConnectivity to send a message from the iPhone to the Apple Watch.
The API's are almost identical on both the Watch and iPhone. If your watch app is not running, or the screen is off you should use transferUserInfo. If your watch app is running and the screen is on you can use sendMessage. I normally wrap these calls, attempting to use sendMessage first and if it fails use transferUserInfo:
// On the iPhone
func trySendMessage(message: [String : AnyObject]) {
if self.session != nil && self.session.paired && self.session.watchAppInstalled {
self.session.sendMessage(message, replyHandler: nil) { (error) -> Void in
// If the message failed to send, queue it up for future transfer
self.session.transferUserInfo(message)
}
}
}
On the watch you will need to implement both session:DidReceiveMessage and session:didReceiveUserInfo. Note that I don't bother checking if the watch is reachable, because if it is not (or if it starts reachable and moves out of range after the check but before the transfer has finished) then the data will still get sent when it is back in range from transferUserInfo.
Is this on watchOS 2? As others suggested, you need to use WatchConnectivity. Darwin notification won't work since the watchOS 2 process no longer sits on the phone as it used to in watchOS 1. Besides, WatchConnectivity is so much more convenient.
Related
I'm streaming video using AVPlayer and i want to pause the video when GSM call comes and resume when the call will end and control is back to my app. How can i achieve this?
I figured it out, i did it by using AVAudioSessionInterruptionNotification. Below is code snippet
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(routeInterrypt:) name:AVAudioSessionInterruptionNotification object:nil];
-(void)routeInterrypt:(NSNotification *)notification {
NSDictionary *dic = notification.userInfo;
int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
if (changeReason == AVAudioSessionRouteChangeReasonUnknown) {
if (_state == TYVideoPlayerStateContentPlaying || _state == TYVideoPlayerStateBuffering) {
[self pauseContent];
return;
}
} }
I am facing memory leaks when we play a video and return back to the parent window. See the screenshot below of readings from allocations tool. Every time, when I pop the view controller (showing video) there are some objects related to AVFoundation holding the memory.
Interestingly the responsible library of all these objects is AVFoundation. None of the increase in memory is due to objects created in the APP. It is very highly unlikely that there is some problem with such a popular framework. I saw a few examples of AVPlayerViewController over the web but they seem to have the same problem.
Does anyone have any idea what/where is the problem? If anyone wants to replicate this then he can download any of the 2 projects given above. You have to make minor changes in the storyboard for creating root view controller with the navigation controller.
http://www.modejong.com/blog/post13_iOS8_SunSpot/index.html
https://github.com/coolioxlr/PageView-AVPlayer
This is how I am clearing the memory:
-(void) dealloc{
[self clearCurrentVideo];
}
-(void)clearCurrentVideo {
[_playerItem removeObserver:self forKeyPath:#"status"];
[_currentVideoPlayerViewController.player removeObserver:self forKeyPath:#"rate"];
[_currentVideoPlayerViewController.player pause];
_currentVideoPlayerViewController.delegate=nil;
_currentVideoPlayerViewController.player=nil;
_playerItem=nil;
_currentVideoPlayerViewController = nil;
}
This is how I load the asset for videos:
-(void)playtheAsset:(AVAsset *)asset{
[asset loadValuesAsynchronouslyForKeys:#[#"playable"] completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
[self loadTheAsset:asset withKeys:#[#"playable"]];
});
}];
}
- (void)loadTheAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys{
/* Make sure that the value of each key has loaded successfully. */
for (NSString *thisKey in requestedKeys)
{
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed)
{
//[self assetFailedToPrepareForPlayback:error];
if([thisKey isEqualToString:#"playable"]){
[self showNetworkErrorLabel];
}
return;
} else if ((keyStatus == AVKeyValueStatusLoaded) || ( keyStatus == AVKeyValueStatusLoading )){
[self removeNetworkLabel ];
}
}
/* Use the AVAsset playable property to detect whether the asset can be played. */
if (!asset.playable)
{
/* Generate an error describing the failure. */
NSString *localizedDescription = NSLocalizedString(#"Item cannot be played", #"Item cannot be played description");
NSString *localizedFailureReason = NSLocalizedString(#"The assets tracks were loaded, but could not be made playable.", #"Item cannot be played failure reason");
NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
localizedDescription, NSLocalizedDescriptionKey,
localizedFailureReason, NSLocalizedFailureReasonErrorKey,
nil];
NSError *assetCannotBePlayedError = [NSError errorWithDomain:#"StitchedStreamPlayer" code:0 userInfo:errorDict];
NSLog(#"%#",assetCannotBePlayedError);
[self showNetworkErrorLabel];
/* Display the error to the user. */
[self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
return;
}
/* At this point we're ready to set up for playback of the asset. */
/* Stop observing our prior AVPlayerItem, if we have one. */
if (_playerItem)
{
/* Remove existing player item key value observers and notifications. */
[_playerItem removeObserver:self forKeyPath:#"status"];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:_playerItem];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemPlaybackStalledNotification
object:_playerItem];
}
/* Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. */
_playerItem = [AVPlayerItem playerItemWithAsset:asset];
/* Observe the player item "status" key to determine when it is ready to play. */
[_playerItem addObserver:self
forKeyPath:#"status"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:AVPlayerDemoPlaybackViewControllerStatusObservationContext];
/* When the player item has played to its end time we'll toggle
the movie controller Pause button to be the Play button */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:_playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemFailedToPlayToEndTime:) name:AVPlayerItemPlaybackStalledNotification object:_playerItem];
// Remove the movie player view controller from the "playback did finish" notification observers
// Observe ourselves so we can get it to use the crossfade transition
[[NSNotificationCenter defaultCenter] removeObserver:_currentVideoPlayerViewController
name:kPlayerViewDismissedNotification
object:_currentVideoPlayerViewController.player];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(videoFinishedCallback:)
name:kPlayerViewDismissedNotification
object:_currentVideoPlayerViewController.player];
/* Create new player, if we don't already have one. */
if (!_currentVideoPlayerViewController.player)
{
/* Get a new AVPlayer initialized to play the specified player item. */
_currentVideoPlayerViewController.player=[AVPlayer playerWithPlayerItem:self->_playerItem];
[_currentVideoPlayerViewController.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew
context:AVPlayerDemoPlaybackViewControllerRateObservationContext];
}
}
I couldn't figure out the reason behind this. I tried using AVPlayer instead and created my own UI using (reference Apple AVPlayer Demo app.) and I couldn't find any leak there. It just worked.
If someone gets stuck with similar problem just give a try to reference code from AVPlayer Demo app.
And if someone knows the answer for the issue I on this thread. Please let me know.
Prior iOS 8 all works fine. The problem is:
I have two observers in different classes:
class1:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishParseUser:)
name:USERS_LOADED_NOTIFICATION_ID object:nil];
class2:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishParseUser:)
name:USERS_LOADED_NOTIFICATION_ID object:nil];
and notification is posted in some other place:
[FBRequestConnection startWithGraphPath:#"me/friends?fields=id,first_name,last_name,picture.type(small)" completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:USERS_LOADED_NOTIFICATION_ID object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys: currentUser, #"user", friends, #"friends", nil]];
} else {
// An error occurred, we need to handle the error
// See: https://developers.facebook.com/docs/ios/errors
}
}];
addObserver method is called for both of mentioned classes, however notification is being delivered just to one observer. If I delete this observer(which receives the notification), then another one receives the notification.
Prior to iOS 8 both observers receive the notification.
Can you, please, help me with this issue?
Found answer.
There is another way in iOS 8 to register for receiving remote notifications. I get nil for device token and app break on line:
NSDictionary *item = #{UID_ID : sCurrentUserId, #"deviceToken": appDelegate.deviceToken, #"handle": #"", #"friends": friends};
and second observer never receives notification.
You need to register your NSNotifications to work on iOS 8 and later but don't need to register if your iOS version is less than iOS 8.
Use Following Code
NSArray *vComp = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:#"."];
if ([[vComp objectAtIndex:0] intValue] >= 8) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationActivationModeBackground categories:nil]];
}
happy Coding
I'm trying to implement device discovery using bluetooth in IOS 5.0.1 iPhone 4S.
I'm using the private framework BluetoothManager.
My code is:
- (IBAction)searchForDevices:(id)sender
{
[self.indicator setHidden:NO];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(bluetoothAvailabilityChanged:) name:#"BluetoothAvailabilityChangedNotification" object:nil];
btCont = [BluetoothManager sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(deviceDiscovered:) name:#"BluetoothDeviceDiscoveredNotification" object:nil];
}
- (void)bluetoothAvailabilityChanged:(NSNotification *)notification
{
self.label.text = #"Availability changed!";
[btCont setDeviceScanningEnabled:YES];
}
- (void)deviceDiscovered:(BluetoothDevice *)device
{
[self.indicator setHidden:YES];
self.label.text = device.address;
My bluetooth headset is discovered.
deviceDiscovered callback function is called,
but device.address does NOT contain the MAC address of the bluetooth device. The app is crashing.
Also, device.name return the name of the notification (BluetoothDeviceDiscoveredNotification) instead of the name of the device discovered.
Any suggestions how can I retrieve the MAC address of my bluetooth headset this way?
use this code:
- (void)deviceDiscovered:(NSNotification *) notification {
BluetoothDevice *bt = [notification object];
NSLog(#"name: %# address: %#",bt.name, bt.address);
If this is a jailbreak app, you can use the key kLockdownBluetoothAddressKey via liblockdown.dylib
I've been trying to get this to work for a while now.
I've done everything they say in the documentation and still got nothing.
This is the code in my app delegate that registers for local notifications:
- (void) registerForLocalNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_accessoryConnected:)
name:EAAccessoryDidConnectNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_accessoryDisconnected:)
name:EAAccessoryDidDisconnectNotification
object:nil];
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications]; }
The above is called from applicationDidFinishLaunching.
Here is the code of the connect/disconnect methods:
- (void) _accessoryConnected:(NSNotification *)notification {
NSLog(#"_accessoryConnected"); }
- (void) _accessoryDisconnected:(NSNotification*)notification {
NSLog(#"_accessoryDisconnected"); }
-(void) accessoryDidDisconnect:(EAAccessory *) accessory {
NSLog(#"accessoryDidDisconnect"); }
Tried connecting the headphones that come with the iPhone and got nothing, same for my external accessory I want to integrate with the app.
Please help,
Thanks,
Shaul.
You should use AudioSessionPropertyListener for this.
EAAccessory notifications are for hardware that connects to the 30 pin port.
Add this listener in viewDidLoad and remove it in ViewDidUnLoad
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, audioSessionPropertyListener, nil);
Add the following methods in the view controller.
BOOL isHeadsetPluggedIn() {
UInt32 routeSize = sizeof (CFStringRef);
CFStringRef route;
OSStatus error = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute,
&routeSize,
&route
);
NSLog(#"%#", route);
return (!error && (route != NULL) && ([(NSString*)route rangeOfString:#"Head"].location != NSNotFound));
}
void audioSessionPropertyListener(void* inClientData, AudioSessionPropertyID inID,
UInt32 inDataSize, const void* inData) {
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
// Determines the reason for the route change, to ensure that it is not
// because of a category change.
CFDictionaryRef routeChangeDictionary = inData;
CFNumberRef routeChangeReasonRef = CFDictionaryGetValue (routeChangeDictionary,CFSTR (kAudioSession_AudioRouteChangeKey_Reason));
SInt32 routeChangeReason;
CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason);
// "Old device unavailable" indicates that a headset was unplugged, or that the
// device was removed from a dock connector that supports audio output.
if (routeChangeReason != kAudioSessionRouteChangeReason_OldDeviceUnavailable)
return;
if (!isHeadsetPluggedIn())
{
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,sizeof (audioRouteOverride),&audioRouteOverride);
}
else
{
UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker;
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, sizeof(audioRouteOverride), &audioRouteOverride);
}
}
Note, I got this code long ago from somewhere and it worked for me. Cannot attribute the source now as I don't know where I got it from.