Scripting Bridge does not work(KVO,nil values) - objective-c

I am currently writing an app extension for Spotify that allows me to control the playback. I am using the Spotify AppleScript API in combination with the Scripting Bridge in Objective-C. The first thing I would like to ask,does the Scripting API support Key Value Observing? Because when I add an observer I don't get any notifications from the API and when I try to get data manually from the Scripting API I always get nil values,why?
I have the following code:
-(id)init
{
self=[super init];
if(self)
{
spotify=[SBApplication applicationWithBundleIdentifier:#"com.spotify.client"];
//Not sure if KVO is implemented,so I use this to get data from the API
timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(observeValueForKeyPath) userInfo:nil repeats:YES];
if([self isSpotifyRunning])
{
//Useless?
[spotify addObserver:self forKeyPath:#"currentTrack" options:NSKeyValueObservingOptionNew context:nil];
[spotify addObserver:self forKeyPath:#"playerPosition" options:NSKeyValueObservingOptionNew context:nil];
[spotify addObserver:self forKeyPath:#"playerState" options:NSKeyValueObservingOptionNew context:nil];
[self.playBackSlider setTarget:self];
[self.playBackSlider setAction:#selector(sliderDidMove:)];
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Play"];
}
else
{
[self.playButton setStringValue:#"Stop"];
}
}
}
return self;
}
-(void)observeValueForKeyPath
{
[self.titleTextField setStringValue:spotify.currentTrack.name];
[self.artistTextField setStringValue:spotify.currentTrack.artist];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
[self.remainingTimeTextField setStringValue:[self formatTime:spotify.currentTrack.duration]];
[self.playBackSlider setMaxValue:spotify.currentTrack.duration];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Play"];
}
else
{
[self.playButton setStringValue:#"Stop"];
}
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if([keyPath isEqualToString:#"currentTrack"])
{
[self.titleTextField setStringValue:spotify.currentTrack.name];
[self.artistTextField setStringValue:spotify.currentTrack.artist];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
[self.remainingTimeTextField setStringValue:[self formatTime:spotify.currentTrack.duration]];
[self.playBackSlider setMaxValue:spotify.currentTrack.duration];
[self.playBackSlider setDoubleValue:spotify.playerPosition];
}
else if([keyPath isEqualToString:#"playerPosition"])
{
[self.playBackSlider setDoubleValue:spotify.playerPosition];
[self.currentPlayBackPositionTextField setStringValue:[self formatTime:spotify.playerPosition]];
}
else if([keyPath isEqualToString:#"playerState"])
{
if(spotify.playerState==SpotifyEPlSPaused||spotify.playerState==SpotifyEPlSStopped)
{
[self.playButton setStringValue:#"Stop"];
}
else
{
[self.playButton setStringValue:#"Play"];
}
}
}
EDIT:
I have set a delegate for the SBApplication Object and I get the following error:
Error Domain=NSOSStatusErrorDomain Code=-600 "procNotFound: no
eligible process with specified descriptor"
UserInfo={ErrorNumber=-600}
What exactly does that mean? Spotify starts when the SBApplication is created,so why is the SBApplication telling me that it didn't found the process? I also took a look at Info.plist in the Spotify Bundle and it is scriptable, so why is it not working?
Thank you in advance for any help!

The AppleScript scripting dictionary of any application – what you're calling AppleScript API - doesn't support KVO.
Regarding to use the scripting dictionary in Cocoa apps forget ScriptingBridge.
AppleScriptObjC (introduced in 10.6 Snow Leopard) provides a much easier way to interact with AppleScript. You can even write a Cocoa application completely in AppleScriptObjC using AppleScript and a Objective-C like terminology in the same file.

Related

Cordova - Implementing the Privacy Screen functionality with the ASWebAuthenticationSession usage

I'm adding the privacy screen functionality to the hybrid Cordova app via a plugin and following the approach adviced by apple.
Though it leads to unexpected issues when I open ASWebAuthenticationSession window I use for the OAuth authentication. What happens is, when system dialogue appears with a text "Your app wants to use xxx for Sign In", it makes the app to lose a focus and the privacy screen appears behind the overlay. After I choose "Yes", the app gains focus back and the code removing the privacy screen fires, the same code also closes the freshly opened ASWebAuthenticationSession window.
The code in PrivacyScreenPlugin.m:
UIViewController *blankViewController;
#interface PrivacyScreenPlugin ()
#end
#implementation PrivacyScreenPlugin
- (void)pluginInitialize
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onPageDidLoad) name:CDVPageDidLoadNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onAppWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
}
-(UIViewController *)createViewWithGradient {
UIViewController *viewController;
viewController = [UIViewController new];
viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
return viewController;
}
-(void) applyPrivacyScreen
{
if (blankViewController == NULL) {
blankViewController = [self createViewWithGradient];
}
blankViewController.view.window.hidden = NO;
[self.viewController.view.window.rootViewController presentViewController:blankViewController animated:NO completion:NULL];
}
#pragma mark - Explicit Commands
- (void) hidePrivacyScreen:(CDVInvokedUrlCommand*)command
{
[self removePrivacyScreen];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void) showPrivacyScreen:(CDVInvokedUrlCommand*)command
{
[self applyPrivacyScreen];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
#pragma mark - Triggered functions
- (void) onPageDidLoad
{
[self removePrivacyScreen];
}
- (void)onAppDidBecomeActive:(UIApplication *)application
{
[self removePrivacyScreen];
}
- (void)onAppWillResignActive:(UIApplication *)application
{
[self applyPrivacyScreen];
}
#pragma mark - Helper functions
-(void) removePrivacyScreen
{
[self.viewController dismissViewControllerAnimated:NO completion:nil];
}
#end
So far I got that issue is related to the way view is dismissed, namely dismissViewControllerAnimated which dismisses the stack of modal windows:
[self.viewController dismissViewControllerAnimated:NO completion:nil];
Can it be helped or worked around? Maybe instead of removing a security screen, it can be hidden? Or is there a different way to draw the overlay which is free of the issue?
P.S. I tried to listen to UIApplicationDidEnterBackgroundNotification event, but it's not what I want. I'd like the app screen to be covered as soon as it's sent to the list of apps (via a double tap on the home button or long swipe).
To accomplish it, it's needed to render the privacy screen in a subview, instead of a modal view. It enables to hide/show a privacy screen view instead of adding/removing it:
-(void) removePrivacyScreen
{
blankViewController.view.hidden=YES;
}
-(void) applyPrivacyScreen
{
if (blankViewController == NULL) {
blankViewController = [self createViewWithGradient];
}
blankViewController.view.hidden = NO;
[self.viewController.view.window addSubview:blankViewController.view];
}

App did not update the location when app is killed by User from iPhone app Switcher (Objective C)

I am working on location update.
When app is in foreground then app update the location using KVO,
self.mapView_locUpd.myLocationEnabled = YES;
[self.mapView_locUpd addObserver:self forKeyPath:#"myLocation" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:#"myLocation"]) {
NSLog(#"Kvo....location");
CLLocation *location = [object myLocation];
self.currentLatitude= [NSString stringWithFormat:#"%f",location.coordinate.latitude];
self.currentLongitude=[NSString stringWithFormat:#"%f",location.coordinate.longitude];
[self updateCurrentLocation];
}
}
When app is in Background (Not killed) then app update the location using CLLocationManager,
-(void)initLocationmManager
{
if(self.locationManagerCurrent == nil)
self.locationManagerCurrent = [[CLLocationManager alloc]init];
self.locationManagerCurrent.delegate=self;
self.locationManagerCurrent.desiredAccuracy= kCLLocationAccuracyBestForNavigation; //provide precise location but more battery consumer
self.locationManagerCurrent.activityType = CLActivityTypeOtherNavigation;
self.locationManagerCurrent.pausesLocationUpdatesAutomatically = YES;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8) {
[self.locationManagerCurrent requestAlwaysAuthorization];
}
if ([self.locationManagerCurrent respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManagerCurrent setAllowsBackgroundLocationUpdates:YES];
}
}
-(void)configureLocationManagerOnAppinBackground{
if(self.locationManagerCurrent == nil)
{
[self initLocationmManager];
}
[self.locationManagerCurrent startMonitoringSignificantLocationChanges];
if ( [[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground)
{
[self.locationManagerCurrent startUpdatingLocation];
}
}
#pragma mark -CllocationmanagerDelegate
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
NSLog(#"Loctaionmanager....DidUpadetLoc");
CLLocation *location =[locations objectAtIndex:0];
if (location.horizontalAccuracy < 0) {
return;
}
self.currentLatitude= [NSString stringWithFormat:#"%f",location.coordinate.latitude];
self.currentLongitude=[NSString stringWithFormat:#"%f",location.coordinate.longitude];
[self updateCurrentLocation];
}
Using CllocationManager the location updates very well when app in background. Suppose in any case, the iOS suspend the app then iOS will launch my app in background and slowly start location updates.
Now if user killed the app from iPhone app switcher then there is not location update arise.
So i want to update the location (continously or slowly) when app get killed by user.
I tried so many thing to launch the app on killed.
Pls help.
You should explore https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
You need UIBackgroundModes with location key.
Also you need to request Privacy - Location Always Usage and provide
"Privacy - Location Always Usage Description" key in your Info.plist
This will allow iOS to partial relaunch your app when user kills it. iOS will return a key UIApplicationLaunchOptionsLocationKey to the app delegate method didFinishLaunchingWithOptions.
Also this answer should help you: How to Get Location Updates for iOS 7 and 8 Even when the App is Suspended

UILabel KVO observing

I am going nuts, I looked everywhere on the web but I always found the same code for KVO observing. Yet my observeValueForKeyPath: is never called; this is the simple code I use to observe UILabel taximeterValue:
-(id) initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if (self){
[taximeterValue addObserver:self forKeyPath:#"text" options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld) context:NULL];
}
return self;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
if ([keyPath isEqual:#"text"]) {
if (myBookingAlert) myBookingAlert.taximeterValue=self.taximeterValue;
NSLog(#"the text changed");
// put your logic here
}
// be sure to call the super implementation
// if the superclass implements it
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
A few possible explanations:
taximeterValue is nil.
Nothing is changing taximeterValue.text.
Something is changing taximeterValue.text in a non-KVO-compliant way. Every change to taximeterValue.text must be done either by calling [taximeterValue setText:], or surrounded by appropriate willChangeValueForKey: and didChangeValueForKey: messages.

AVPlayer replaceCurrentItemWithPlayerItem not working on iOS 4.3.3+

I have an audio player that I'm building using AVPlayer.
Currently, I keep the player instance around and when I need to swap tracks (either from a manual selection or the track has reached the end) I create a new AVPlayerItem and I call replaceCurrentItemWithPlayerItem with the new item.
According to the docs, replaceCurrentItemWithPlayerItem is an asynchronous operation, so I also observe the currentItem key path of the player. When that gets called, I tell my player to play.
Here is the relevant code:
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerItemStatusChangedContext];
if (!_player) {
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerStatusChangedContext];
[_player addObserver:self forKeyPath:#"currentItem" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerCurrentItemChangedContext];
} else {
[_player replaceCurrentItemWithPlayerItem:playerItem];
}
And here is the key value observation callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == CHStreamingAudioPlayer_PlayerCurrentItemChangedContext) {
NSLog(#"Player's current item changed! Change dictionary: %#", change);
if (_player.currentItem) {
[self play]; //<---- doesn't get called
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
On iOS 4.3.3 (and iOS 5) my key observation method is called but _player.currentItem is always nil. On 4.2.1 and 4.3.2 this property contains the actual value. This method is never called again. So in essence, replacing seems to always fail.
This seems like a bug, but perhaps I'm doing something wrong.
I had this issue with iOS 5 (including 5.0.1). It used to work fine on iOS 4.x.
There are two ways to workaround this, release and recreate your AVPlayer with the desired AVPlayerItems each time you need to swap tracks. Or, simply call replaceCurrentItemWithPlayerItem: on the main thread.
I tried both options and they worked fine.
Credits to: Apple Developer Forums
I've been experiencing similar problems. You probably got started from AVPlayerDemoPlaybackViewController from Apple sample code like me. Maybe the problem why currentItem is nil is because it's not loaded yet or ready for playback (my problem was I couldn't get the duration of the new AVPlayerItem).
You could try starting the playback when observed status of the currentItem is ReadyToPlay.
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status) {
case AVPlayerStatusUnknown: {
NSLog(#"PLAYER StatusUnknown");
}
break;
case AVPlayerStatusReadyToPlay: {
NSLog(#"PLAYER ReadyToPlay");
[self play];
}
break;
case AVPlayerStatusFailed: {
AVPlayerItem *playerItem = (AVPlayerItem *)object;
[self handleError: [playerItem.error localizedDescription]];
}
break;
}
I don't know if this will wok for you, I didn't try this on lower or higher than 4.3.4 iPad, so I guess I'll run into complications soon.

MBProgresshud with tableview

I am making a application with a tableview in it. I would like to implement a loading screen, using MBProgressHUD such that it will display before data is read from internet. However the data's not shown using following code:
- (void)viewDidLoad
{
HUD = [[MBProgressHUDalloc] initWithView:self.view];
[self.viewaddSubview:HUD];
HUD.delegate = self;
[HUD showWhileExecuting:#selector(load_data) onTarget:self withObject:nil animated:YES];
}
the data can be shown in tableview using the function load_data alone (i.e [self load_data], but not with HUD.
In my experience, when using the HUD to display while loading or waiting for data to load, you should call the HUD in the -viewDidAppear method. I also noticed that you didn't include the [super viewDidLoad]; call in your code. If you are going to present your HUD, you will have to call it after you call on the super viewDidLoad if you want it to appear. Hopefully these help you out.
I like to present and hide the HUD with separate methods that only do that. e.g.
#pragma mark - The HUD
-(void)showHudWithText:(NSString *)text {
if (self.hud == nil) {
self.hud = [[[MBProgressHUD alloc] initWithWindow:self.window] autorelease];
[self.window addSubview:hud];
}
[self.hud setLabelText:text];
[self.hud setMode:MBProgressHUDModeIndeterminate];
[self.hud show:YES];
}
-(void)hideHud {
[self.hud hide:YES];
}
This allows the HUD to be controlled independently of the view life cycle, as well as from asynchronous methods, timers, etc. e.g:
-(void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showHudWithText:) name:kSomethingImportantStartedNotification object:#"Starting..."];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideHud) name:kSomethingImportantEndedNotification object:nil];
}
Or something like that.