Unsupported action method signature. Must return MPRemoteCommandHandlerStatus - react-native

I'm getting this error whenever I'm running simulator on iOS 13+. Everything works for iOS 12 and below so I'm not sure what to do here. Is there anything I can change/edit on my end to make react-native-music-control work for iOS 13?
Exception 'Unsupported action method signature. Must return MPRemoteCommandHandlerStatus or take a completion handler as the second argument.' was thrown while invoking enableControl on target MusicControlManager with params ( pause, 1, { } )

This react-native-music-control probably hasn't updated its iOS MediaPlayer methods.
One common method is MediaPlayer's addTarget. Starting iOS 13, this method must return a MPRemoteCommandHandlerStatus. It used to return nothing in previous iOS versions.
For example, if you have a play method that gets called when the Play button is tapped:
- (void) play {
/* start playing */
}
You are probably registering this play to be called when the Media Player's play command is trigged:
[[MPRemoteCommandCenter sharedCommandCenter].playCommand addTarget:self action:#selector(play)];
Then, all you need is to simply change your play method to return a MPRemoteCommandHandlerStatus like this:
- (MPRemoteCommandHandlerStatus) play
{
// if successfully played
return MPRemoteCommandHandlerStatusSuccess;
// else if there's an issue
// return MPRemoteCommandHandlerStatusCommandFailed;
}
This is in Objective-C. Changing the return value in Swift will be simple too.
Reference: https://developer.apple.com/documentation/mediaplayer/mpremotecommand/1622895-addtarget?language=objc

So this plugin is not being maintained anymore I don't know why, so for those using this plugin the solution is really Simple, based on what #CSawy. I was able to fix it.
1 - Change the method signature to MPRemoteCommandHandlerStatus
All the methods that interact with the control are defined as void so change them to MPRemoteCommandHandlerStatus. They are defined in the file MusicControls and I guess you would find it in your plugins folder. For me it was in myProject/plugins/MusiControls.h
These are the methods that require the change
playEvent
pauseEvent
nextTrackEvent
prevTrackEvent
skipForwardEvent
skipBackwardEvent
After changing all the methods signature u should have
(MPRemoteCommandHandlerStatus) playEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) pauseEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) nextTrackEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) prevTrackEvent:(MPRemoteCommandEvent *) event;
(MPRemoteCommandHandlerStatus) skipForwardEvent: (MPSkipIntervalCommandEvent *) event;
(MPRemoteCommandHandlerStatus) skipBackwardEvent: (MPSkipIntervalCommandEvent *) event;
2-Add return to the implementations
Now you should change the implementation of these methods otherwise your app would still be crushing.
Simply open the file MusicControls.m. If you are using Xcode it will highlight the methods for you ... If it's no highlighted then just search for them
For all these functions you will need to add a return ;
For your play event do:
-(MPRemoteCommandHandlerStatus) playEvent:(MPRemoteCommandEvent *)event {
NSString * action = #"music-controls-play";
NSString * jsonAction = [NSString stringWithFormat:#"{\"message\":\"%#\"}", action];
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:jsonAction];
[self.commandDelegate sendPluginResult:pluginResult callbackId:[self latestEventCallbackId]];
return MPRemoteCommandHandlerStatusSuccess;
}
Do this for all the methods and I hope it works.

Related

NSSpeechRecognizer and .delegate=self; Problems

I've run into an issue with this little Objective-C project I'm doing and it's proving to be a bit of a roadblock. I'm playing around with Apple's NSSpeechRecognizer software on El Capitan, and I'm trying to get this guy running properly so that when the riddle I give it is posed to the user, the user can respond with a word to "do something cool". As it stands right now, the delegate method:
-(void) speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command { ... }`
is never even called, even though it appears the recognition icon is correctly detecting the answer to the riddle.
The problem is that your main function has a loop that is continually checking whether the speech has been recognizing. You are not giving NSSpeechRecognizer a chance to actually deliver any messages to you.
Your app needs to let the main "run loop" run, so it can deliver messages. Normally, in an OS X app, your main would just call NSApplicationMain, which does this for you.
Your code is effectively this:
#interface RecognizerDelegate : NSObject <NSSpeechRecognizerDelegate>
#property (nonatomic) NSSpeechRecognizer *recognizer;
#property (nonatomic) BOOL didRecognize;
#end
#implementation RecognizerDelegate
- (id)init
{
if ((self = [super init])) {
self.didRecognize = NO;
self.recognizer = [[NSSpeechRecognizer alloc] init];
self.recognizer.listensInForegroundOnly = NO;
self.recognizer.blocksOtherRecognizers = YES;
self.recognizer.delegate = self;
self.recognizer.commands = #[ #"hello" ];
[self.recognizer startListening];
}
return self;
}
- (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(NSString *)command
{
self.didRecognize = YES;
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool
{
RecognizerDelegate *recognizerDelegate = [[RecognizerDelegate alloc] init];
while (recognizerDelegate.didRecognize == NO) {
// do nothing
}
NSLog(#"Recognized!");
}
return 0;
}
That while loop is doing nothing useful, just running your CPU in a loop and wasting time and energy. You are not letting any other code in NSSpeechSynthesizer, or any of the system frameworks like Foundation or AppKit, get the chance to do anything. So, nothing happens.
To fix this in the short term: you can let the main run loop run for a little while in each pass through the loop. This code would let the system run for a second, then would return to your code, so you could check again:
while (recognizerDelegate.didRecognize == NO) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
The longer-term fix would be to move your code out of main and to structure it like a real OS X app. Instead of using a loop to poll a condition like recognizerDelegate.didRecognize, you would just trigger the "next thing" directly from delegate methods like -speechRecognizer:didRecognizeCommand:, or you would use things like NSTimer to run code periodically.
For more details, see the Apple doc Cocoa Application Competencies for OS X, specifically the "Main Event Loop" section.
I had the same problem using NSSpeechRecognizer. The callback function:
func speechRecognizer(_ sender: NSSpeechRecognizer,
didRecognizeCommand command: String) {}
...was never called, even though everything appeared to be working.
There were three things I changed to get the code working.
1) I had to enable the entitlement in my "sandboxed" mode application to allow for microphone use.
... I also did these other two things, as well.
2) I added the "Privacy - Microphone Usage Description" in the info.pList, and set the string value to "I want to listen to you speak"
3) I added the "Privacy - Speech Recognition Usage Description" in the info.pList, and set the string value to "I want to write down what you say"

ReactiveCocoa: throttle never executes / not working

I try to subscribe to a signal with throttle, but it never executes.
I have a UISearchController (Attention: UISearchController from iOS8, not the older UISearchDisplayController, which works quiet better and has thousands of working tutorials and examples in the web) and want to make API-Requests while the user is typing.
To let the traffic being low, i don't want to start API-Requests with each key the user is pressing, but when the user stops for a while, say 500 ms after the last keypress.
Since we're unable to reference the TextField in the SearchBar of the UISearchController, we use the delegates from UISearchController:
To get the latest typed text of the Textfield in the Searchbar, I use this:
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchText = searchController.searchBar.text;
// strip out all the leading and trailing spaces
NSString *strippedString = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if([strippedString isEqualToString:self.currentFilter]) {
return;
}
self.currentFilter = strippedString;
}
The property currentFilter keeps the current search string.
Also, i have a RACObserve on the currentFilter-Property to react on every change which is made to this property:
[RACObserve(self, currentFilter) subscribeNext:^(NSString* x) {
NSLog(#"Current Filter: %#", x);
// do api calls and everything else
}];
Now i want to throttle this signal. But when i implement the call to throttle, nothing happens. SubscribeNext will never be called:
[[RACObserve(self, currentFilter) throttle:500] subscribeNext:^(NSString* x) {
NSLog(#"%#", x); // will never be called
}];
How to achieve to throttle inputs in a searchbar? And what is wrong here?
UPDATE
i found a workaround besides ReactiveCocoa thanks to #malcomhall. I moved the code within the updateSearchResultsForSearchController-delegate method into a separate method and schedule it with performSelector and cancel this scheduler with cancelPreviousPerformRequestsWithTarget.
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(useTextSearchQuery) object:nil];
[self performSelector:#selector(useTextSearchQuery) withObject:nil afterDelay:1.0];
}
Anyway, i want still understand how "throttle" from ReactiveCocoa is working and why not in this case :)
-throttle: accepts an NSTimeInterval, which is a floating-point specification of seconds, not milliseconds.
Given the code in the question, I expect you would see results after 500 seconds have elapsed.

When using KIF test framework UIButton is not working for UIControlEventTouchUpInside

I try to make some scenarios with the KIF framework. When I adding for example:
[scenario addStep:[KIFTestStep stepToTapViewWithAccessibilityLabel:#"MyUIButtonAccessibilityLabel"]];
the UIControlEventTouchUpInside is not fired for the UIButton with accessibilityLabel #"MyUIButtonAccessibilityLabel".
Is uigesturerecognizer interfering with the UIControlEventTouchUpInside in KIF? Is there a work-around for this in KIF?
I actually ran into this yesterday. stepToTapViewWithAccessibilityLabel has worked just fine for everything in my app, but I encountered a button that it just absolutely won't work for.
To work around it, I ended up using stepToTapScreenAtPoint (which I don't especially like), but it seems to work fine. I also added a void initialize method to my category so that I can determine which device is executing the tests at run-time and based on that, figure out where exactly I need to tap:
#implementation KIFTestStep (SLAdditions)
static CGPoint kButtonLocation;
+ (void)initialize
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
kButtonLocation = CGPointMake(498.0, 622.0);
else
kButtonLocation = CGPointMake(247.0, 316.0);
}
+ (NSArray *)stepsToDoSomething
{
NSMutableArray *steps = [NSMutableArray array];
[steps addObject:[KIFTestStep stepToWaitForTappableViewWithAccessibilityLabel:#"The Button Label"]];
[steps addObject:[KIFTestStep stepToTapScreenAtPoint:kButtonLocation]];
// Verify that tapping succeeded
[steps addObject:[KIFTestStep stepToWaitForAbsenceOfViewWithAccessibilityLabel:#"The Button Label"]];
return steps;
}

Where does the main-loop go when creating an iOS app?

I am writing an iOS app for the iPhone in Xcode and I have created some classes as well as their methods inside their respective .h and .m files (that's two classes so basically I have two pairs of .h & .m files)
I now I want to start writing my main loop that will be executed whenever the user hits on the play button, but where exactly do I do that ?
Do I do that in ViewController.m ? e.g. inside this method :
- (IBAction)playPressed:(UIButton *)sender
{
// main loop executed in here ?
// or simply message to the main loop to start executing is *sent* from here ?
}
I read about a similar question in here and someone was suggesting AppDelegate. Now would that be AppDelegate.m or AppDelegate.h ? And if that's the case do I just start writing code or do I include everything inside something like :
int main(int argc, char **argv)
{
....
}
in the Appdelegate file?
I tried to simply start instantiating classes and declaring generic methods (not belonging to any particular class that is..) in a game.m file I created and I get a initializer element is not a compile-time constant warning as soon as I try instantiating anything
Any help? Coming from c++ it would really help me to clarify once and for all in which file exactly to write my main loop and whether I should wrap it in some kind of an int main() function..
thanks!
PS :
Just in case it makes any difference, my ViewController will only consist of a play button that would start the execution of my main loop whenever its pressed, and a stop button that would terminate the execution of the main loop
I have created their respective methods in ViewController.m :
- (IBAction)playPressed:(UIButton *)sender
{
//
}
- (IBAction)stopPressed:(UIButton *)sender
{
// ??
}
which are for the time being empty :)
The programming methodoly on iOS is different from the C++ methodoly.
In C++ , indeed , you would have to make an infinite loop and get the touches , draw everything , etc at each frame.
Until the player presses "exit" and you break the loop.
On iOS , things are done differently:
You already have a main.m file in which you have a main function.
That starts up the app delegate. That app delegate tells you when the app finished launching , goes to background , comes in foreground , etc.
When the app finished launching , you go to your first actual screen.
There , you ADD subviews. You don't draw them at each frame. That is done automatically for you once you have added the view to a parent view.
The programming on iOS is based on events. You don't have to check for touches and see if the
touch location is on a button and then call the method of that button.
Instead , you set a callback method for the button and it's called automatically for you once the button is pressed.
Of course , you first need to alloc the button and add it to a parent view.
Once you get used to this event based programming model , you will for sure like it.
At the start it may seam very different and maybe it doesn't make sense to you , but don't worry.
Comming from a C++ background is surely a good start.
Cheers,
George
EDIT: In that case , I can give more specific info:
So , you go from the AppDelegate in your first screen. Let's call it MainAppScreen.
Now , you need to add those 2 buttons and set selectors ( callback methods ) for them. I can see you already did that.
Now :
- (IBAction)playPressed:(UIButton *)sender
{
running = TRUE;
[self performSelectorInBackground:#selector(myLoop) withObject:nil];
}
- (IBAction)stopPressed:(UIButton *)sender
{
running = FALSE;
}
- (void) myLoop
{
while(running)
{
//this is your loop. You can code in here.
}
}
Where running is an instance variable in the MainAppScreen class.
Hope this helps.
Cheers!
Every iOS app, as well as every executable file has an entry point - this is the main(). You can't have more than one entry points of an executable.And if you look closely into the project you will see that there is an automatically generated main.m file in the Supporting Files group in Xcode's navigator, which looks like this:
#import <UIKit/UIKit.h>
#import "MyAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
}
}
What you want to do is not clear enough, but it's a good start reading about the structure and the lifecycle of iOS apps, objective-c syntax, get familiar with the UIKit and at least some of the frameworks Apple provide.
You don't have a main in iOS apps (well, technically you do have a main, but you don't have to worry about writing it). That's all handled for you. The runloop is all done for you too. All you have to do is create your button and then tell it (via addTarget method) which method to run when it gets pressed.
Update:
This is pseudo(ish) code for what you'd need to do....
[startButton addTarget:#selector(startPressed:)];
[stopButton addTarget:#selector(stopPressed:)];
-(void)startPressed {
backgroundThread = [[NSThread alloc] initWithWhateverYouWantToRun];
[backgroundThread start];
}
-(void)stopPressed {
[backgroundThread stop];
}
In your background thread, if you want to update the UI, you would call sendMessageOnMainThread (or something similar - can't remember the exact details at the moment!)

Cocoa/WebKit, having "window.open()" JavaScript links opening in an instance of Safari

I am building a really basic Cocoa application using WebKit, to display a Flash/Silverlight application within it. Very basic, no intentions for it to be a browser itself.
So far I have been able to get it to open basic html links (<a href="..." />) in a new instance of Safari using
[[NSWorkspace sharedWorkspace] openURL:[request URL]];
Now my difficulty is opening a link in a new instance of Safari when window.open() is used in JavaScript. I "think" (and by this, I have been hacking away at the code and am unsure if i actually did or not) I got this kind of working by setting the WebView's policyDelegate and implementing its
-webView:decidePolicyForNavigationAction:request:frame:decisionListener:
delegate method. However this led to some erratic behavior.
So the simple question, what do I need to do so that when window.open() is called, the link is opened in a new instance of Safari.
Thanks
Big point, I am normally a .NET developer, and have only been working with Cocoa/WebKit for a few days.
I made from progress last night and pinned down part of my problem.
I am already using webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: and I have gotten it to work with anchor tags, however the method never seems to get called when JavaScript is invoked.
However when window.open() is called webView:createWebViewWithRequest:request is called, I have tried to force the window to open in Safari here, however request is always null. So I can never read the URL out.
I have done some searching around, and this seems to be a known "misfeature" however I have not been able to find a way to work around it.
From what I understand createWebViewWithRequest gives you the ability to create the new webview, the the requested url is then sent to the new webView to be loaded. This is the best explanation I have been able to find so far.
So while many people have pointed out this problem, I have yet to see any solution which fits my needs. I will try to delve a little deeper into the decidePolicyForNewWindowAction again.
Thanks!
Well, I'm handling it by creating a dummy webView, setting it's frameLoad delegate to a custom class that handles
- (void)webView:decidePolicyForNavigationAction:actionInformation :request:frame:decisionListener:
and opens a new window there.
code :
- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
//this is a hack because request URL is null here due to a bug in webkit
return [newWindowHandler webView];
}
and NewWindowHandler :
#implementation NewWindowHandler
-(NewWindowHandler*)initWithWebView:(WebView*)newWebView {
webView = newWebView;
[webView setUIDelegate:self];
[webView setPolicyDelegate:self];
[webView setResourceLoadDelegate:self];
return self;
}
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
}
-(WebView*)webView {
return webView;
}
There seems to be a bug with webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener: in that the request is always nil, but there is a robust solution that works with both normal target="_blank" links as well as javascript ones.
Basically I use another ephemeral WebView to handle the new page load in. Similar to Yoni Shalom but with a little more syntactic sugar.
To use it first set a delegate object for your WebView, in this case I'm setting myself as the delegate:
webView.UIDelegate = self;
Then just implement the webView:createWebViewWithRequest: delegate method and use my block based API to do something when a new page is loaded, in this case I'm opening the page in an external browser:
-(WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
return [GBWebViewExternalLinkHandler riggedWebViewWithLoadHandler:^(NSURL *url) {
[[NSWorkspace sharedWorkspace] openURL:url];
}];
}
That's pretty much it. Here's the code for my class. Header:
// GBWebViewExternalLinkHandler.h
// TabApp2
//
// Created by Luka Mirosevic on 13/03/2013.
// Copyright (c) 2013 Goonbee. All rights reserved.
//
#import <Foundation/Foundation.h>
#class WebView;
typedef void(^NewWindowCallback)(NSURL *url);
#interface GBWebViewExternalLinkHandler : NSObject
+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler;
#end
Implemetation:
// GBWebViewExternalLinkHandler.m
// TabApp2
//
// Created by Luka Mirosevic on 13/03/2013.
// Copyright (c) 2013 Goonbee. All rights reserved.
//
#import "GBWebViewExternalLinkHandler.h"
#import <WebKit/WebKit.h>
#interface GBWebViewExternalLinkHandler ()
#property (strong, nonatomic) WebView *attachedWebView;
#property (strong, nonatomic) GBWebViewExternalLinkHandler *retainedSelf;
#property (copy, nonatomic) NewWindowCallback handler;
#end
#implementation GBWebViewExternalLinkHandler
-(id)init {
if (self = [super init]) {
//create a new webview with self as the policyDelegate, and keep a ref to it
self.attachedWebView = [WebView new];
self.attachedWebView.policyDelegate = self;
}
return self;
}
-(void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
//execute handler
if (self.handler) {
self.handler(actionInformation[WebActionOriginalURLKey]);
}
//our job is done so safe to unretain yourself
self.retainedSelf = nil;
}
+(WebView *)riggedWebViewWithLoadHandler:(NewWindowCallback)handler {
//create a new handler
GBWebViewExternalLinkHandler *newWindowHandler = [GBWebViewExternalLinkHandler new];
//store the block
newWindowHandler.handler = handler;
//retain yourself so that we persist until the webView:decidePolicyForNavigationAction:request:frame:decisionListener: method has been called
newWindowHandler.retainedSelf = newWindowHandler;
//return the attached webview
return newWindowHandler.attachedWebView;
}
#end
Licensed as Apache 2.
You don't mention what kind of erratic behaviour you are seeing. A quick possibility, is that when implementing the delegate method you forgot to tell the webview you are ignoring the click by calling the ignore method of the WebPolicyDecisionListener that was passed to your delegate, which may have put things into a weird state.
If that is not the issue, then how much control do you have over the content you are displaying? The policy delegate gives you easy mechanisms to filter all resource loads (as you have discovered), and all new window opens via webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:. All window.open calls should funnel through that, as will anything else that triggers a new window.
If there are other window opens you want to keep inside your app, you will to do a little more work. One of the arguments passed into the delegate is a dictionary containing information about the event. Insie that dictionary the WebActionElementKey will have a dictionary containing a number of details, including the original dom content of the link. If you want to poke around in there you can grab the actual DOM element, and check the text of the href to see if it starts with window.open. That is a bit heavy weight, but if you want fine grained control it will give it to you.
By reading all posts, i have come up with my simple solution, all funcs are in same class,here it is, opens a link with browser.
- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request {
return [self externalWebView:sender];
}
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
[[NSWorkspace sharedWorkspace] openURL:[actionInformation objectForKey:WebActionOriginalURLKey]];
}
-(WebView*)externalWebView:(WebView*)newWebView
{
WebView *webView = newWebView;
[webView setUIDelegate:self];
[webView setPolicyDelegate:self];
[webView setResourceLoadDelegate:self];
return webView;
}
Explanation:
Windows created from JavaScript via window.open go through createWebViewWithRequest.
All window.open calls result in a createWebViewWithRequest: with a null request, then later a location change on that WebView.
For further information, see this old post on the WebKit mailing list.
An alternative to returning a new WebView and waiting for its loadRequest: method to be called, I ended up overwriting the window.open function in the WebView's JSContext:
First, I set my controller to be the WebFrameLoadDelegate of the WebView:
myWebView.frameLoadDelegate = self;
Then, in the delegate method, I overwrote the window.open function, and I can process the URL there instead.
- (void)webView:(WebView *)webView didCreateJavaScriptContext:(JSContext *)context forFrame:(WebFrame *)frame{
context[#"window"][#"open"] = ^(id url){
NSLog(#"url to load: %#", url);
};
}
This let me handle the request however I needed to without the awkward need to create additional WebViews.