Is there a way to programmatically dismiss dialogs like the ones where the app wants to access photos, access contacts and access location?
I think there's a way by swizzling API methods, but I don't really know which. What is the methodology to find out which methods need to be swizzled? If swizzling is not the way then what could be another alternative?
As a note, this is not for a product, is just for testing so swizzling is a good option if it works.
1. Overriding "access to contacts" dialog
Type this code in your UIApplicationDelegate implementation (importing <AddressBook/AddressBook.h> is necessary:
ABAuthorizationStatus ABAddressBookGetAuthorizationStatus(void) {
return kABAuthorizationStatusAuthorized;
}
2. Create a swizzling method:
Type in the following method in your UIApplicationDelegate implementation, or wherever you need it, a helper class would be best. (importing <objc/runtime.h> is required:
- (void)swizzleSelector:(SEL)selector fromInstancesOfClass:(Class)clazz withBlock:(id)block {
id replaceBlock = Block_copy(block);
Method origMethod = class_getInstanceMethod(clazz, selector);
IMP origImpl = method_getImplementation(origMethod);
IMP replaceImpl = imp_implementationWithBlock(replaceBlock);
method_setImplementation(origMethod, replaceImpl);
}
- (void)swizzleSelector:(SEL)selector fromClass:(Class)clazz withBlock:(id)block {
id replaceBlock = Block_copy(block);
Method origMethod = class_getClassMethod(clazz, selector);
IMP origImpl = method_getImplementation(origMethod);
IMP replaceImpl = imp_implementationWithBlock(replaceBlock);
method_setImplementation(origMethod, replaceImpl);
}
3. Overriding "access to location" dialog:
Do the following in your UIApplicationDelegate implementation. (importing <CoreLocation/CoreLocation.h> is required):
[self swizzleSelector:#selector(startUpdatingLocation) fromInstancesOfClass:[CLLocationManager class] withBlock:^{}];
[self swizzleSelector:#selector(startMonitoringSignificantLocationChanges) fromInstancesOfClass:[CLLocationManager class] withBlock:^{}];
[self swizzleSelector:#selector(locationServicesEnabled) fromClass:[CLLocationManager class] withBlock:^{ return NO; }];
4. Overriding "access to photos" dialog:
Do the following in your UIApplicationDelegate implementation. (importing <AssetsLibrary/AssetsLibrary.h> is required):
[self swizzleSelector:#selector(authorizationStatus) fromClass:[ALAssetsLibrary class] withBlock:^{ return ALAuthorizationStatusAuthorized; }];
[self swizzleSelector:#selector(enumerateGroupsWithTypes:usingBlock:failureBlock:) fromInstancesOfClass:[ALAssetsLibrary class] withBlock:^{}];
You should be able to access an alert from anywhere in your app like this. This works for regular alerts but I have not tried it with system alerts (It is possible they don't sit in the same hierarchy as regular alerts, in which case you can't get to them at all). If it does work and you try to release an app with this, it will almost certainly get rejected.
for (UIWindow* w in [UIApplication sharedApplication].windows)
{
for (NSObject* o in w.subviews)
if ([o isKindOfClass:[UIAlertView class]])
{
// as an example, this will just dismiss/cancel the alert
[(UIAlertView*)o dismissWithClickedButtonIndex:[(UIAlertView*)o cancelButtonIndex] animated:YES];
}
}
I would like to use the Springboard services framework to make use of the following code.
SBSLaunchApplicationWithIdentifier(CFSTR("com.apple.preferences"), false);
However when I download the header files and use it in my project it won't build. Please let me know how to make this work.
What exactly are you planning on using that method for? I was under the impression it was for launching an application from a daemon?
There are other ways to launch an application quite easily. The most reliable I have found is to use the display stacks to launch the application properly. Other methods of launching the app tend to cause issues when you close it and attempt to relaunch and it crashes.
Using theos, you could do something like this:
NSMutableArray *displayStacks = nil;
// Display stack names
#define SBWPreActivateDisplayStack [displayStacks objectAtIndex:0]
#define SBWActiveDisplayStack [displayStacks objectAtIndex:1]
#define SBWSuspendingDisplayStack [displayStacks objectAtIndex:2]
#define SBWSuspendedEventOnlyDisplayStack [displayStacks objectAtIndex:3]
// Hook SBDisplayStack to get access to the stacks
%hook SBDisplayStack
-(id)init
{
%log;
if ((self = %orig))
{
NSLog(#"FBAuth: addDisplayStack");
[displayStacks addObject:self];
}
return self;
}
-(void)dealloc
{
[displayStacks removeObject:self];
%orig;
}
%end
And then to launch the app, do this:
id PreferencesApp = [[objc_getClass("SBApplicationController") sharedInstance] applicationWithDisplayIdentifier:#"com.apple.preferences"];
[SBWActiveDisplayStack pushDisplay:PreferencesApp];
However, if you really want to use that method, you need to specify what errors are stopping it from building and check which header files you are using to build it with. You also need to link against the SBS framework.
I'm writing a test to verify location services are started when a button click occurs. This requires a very simple if statement to make sure the phone has location services available.
A working test right now looks like this
- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled
{
[[[self.locationManager stub] andReturnValue:[NSNumber numberWithBool:true]] locationServicesEnabled];
[[self.locationManager expect] startUpdatingLocation];
[self.sut buttonClickToFindLocation:nil];
[self.locationManager verify];
}
The now tested implementation looks like this
- (IBAction)buttonClickToFindLocation:(id)sender
{
if ([self.locationManager locationServicesEnabled])
{
[self.locationManager startUpdatingLocation];
}
}
All good except the method was deprecated in iOS 4.0. So now I need to use the Class Method [CLLocationManager locationServicesEnabled] instead.
The problem is I can't seem to find if ocmock supports this functionality and if it doesn't how should I get around this issue for now.
hmmm, you could use methodExchange. Just make sure you exchange the method back to original after your done with it. It seems hacky, but I haven't found a better solution. I have done something similar for stubbing [NSDate date]
#implementation
static BOOL locationManagerExpectedResult;
- (void)testStartUpdatingLocationInvokedWhenLocationServicesAreEnabled
{
locationManagerExpectedResult = YES;
method_exchangeImplementations(
class_getClassMethod([CLLocationManager class], #selector(locationServicesEnabled)) ,
class_getClassMethod([self class], #selector(locationServicesEnabledMock))
);
[self.sut buttonClickToFindLocation:nil];
}
+ (BOOL)locationServicesEnabledMock
{
return locationManagerExpectedResult;
}
#end
EDIT: I thought you were verifying, but it seems like you are stubbing. Updated code
The simplest approach is to override locationServicesEnabled in a category in your unit test class:
static BOOL locationServicesEnabled = NO;
#implementation CLLocationManager (UnitTests)
+(BOOL)locationServicesEnabled {
return locationServicesEnabled;
}
#end
...
-(void)tearDown {
// reset to default after each test
locationServicesEnabled = NO;
[super tearDown];
}
It will override the superclass method only at test time, and you can set the static global to an appropriate value in each test.
Alternatively, you could wrap the check in your own instance method, and use a partial mock.
In the class under test:
-(BOOL)locationServicesEnabled {
return [CLLocationManager locationServicesEnabled];
}
In your test:
-(void)testSomeLocationThing {
MyController *controller = [[MyController alloc] init];
id mockController = [OCMockObject partialMockForObject:controller];
BOOL trackingLocation = YES;
[[[mockController stub] andReturnValue:OCMOCK_VALUE(trackingLocation)] locationServicesEnabled];
// test your controller ...
}
I don't think it does. The only approach I can think of would be to use a partial mock and then use runtime calls to swizzle in the implementation you need.
Doable, but complex.
A more pattern orientated solution might be to extract the checking for location services out to sit behind a protocol. Then you can simply use a mock for the protocol's implementation during testing to return YES or NO as your require. As the actual implementation would do nothing but return [CLLocationManager locationServicesEnabled] you could get away with not testing it.
This is supported by OCMock:
[[[[mockLocationManagerClass stub] classMethod] andReturnValue:OCMOCK_VALUE(YES)] locationServicesEnabled];
Hey there, I'm trying Multitasking in the Simulator (I only have a 2nd gen iPod and an iPad) and I'm still having some problems. My testing methods look like this:
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"Entering %s",__FUNCTION__);
if (enteredFromBackground) {
NSLog(#"Entering from Background");
enteredFromBackground = NO;
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(#"Entering %s",__FUNCTION__);
enteredFromBackground = YES;
}
Unforntunately, I'm not seeing the NSLog from applicationWillEnterForeground, that's why I added the line to show me something in applicationDidBecomeActive.
All I get is the
2010-11-20 15:58:12.796 iBeat[45997:207] Entering -[AppDelegate_Shared applicationDidEnterBackground:]
2010-11-20 15:58:18.160 iBeat[45997:207] Entering -[AppDelegate_Shared applicationDidBecomeActive:]
After having this problem in iOS 13, I found out that I was waiting for applicationWillEnterForeground(_ application: UIApplication) to be called instead of sceneWillEnterForeground(_ scene: UIScene).
For more information, read this answer:
enter link description here
Finally I found my problem!
Since I have a universal Application, I have an Appdelegate_Shared, an Appdelegate_iPhone, and an Appdelegate_iPad.
I had an empty implementation of "applicationWillEnterForeground" in the two subclasses but didn't call super!
And then I wondered why the method in Appdelegate_Shared never got called o.O
I need to have my iPhone Objective-C code catch Javascript errors in a UIWebView. That includes uncaught exceptions, syntax errors when loading files, undefined variable references, etc.
This is for a development environment, so it doesn't need to be SDK-kosher. In fact, it only really needs to work on the simulator.
I've already found used some of the hidden WebKit tricks to e.g. expose Obj-C objects to JS and to intercept alert popups, but this one is still eluding me.
[NOTE: after posting this I did find one way using a debugging delegate. Is there a way with lower overhead, using the error console / web inspector?]
I have now found one way using the script debugger hooks in WebView (note, NOT UIWebView). I first had to subclass UIWebView and add a method like this:
- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject {
// save these goodies
windowScriptObject = newWindowScriptObject;
privateWebView = webView;
if (scriptDebuggingEnabled) {
[webView setScriptDebugDelegate:[[YourScriptDebugDelegate alloc] init]];
}
}
Next you should create a YourScriptDebugDelegate class that contains methods like these:
// in YourScriptDebugDelegate
- (void)webView:(WebView *)webView didParseSource:(NSString *)source
baseLineNumber:(unsigned)lineNumber
fromURL:(NSURL *)url
sourceId:(int)sid
forWebFrame:(WebFrame *)webFrame
{
NSLog(#"NSDD: called didParseSource: sid=%d, url=%#", sid, url);
}
// some source failed to parse
- (void)webView:(WebView *)webView failedToParseSource:(NSString *)source
baseLineNumber:(unsigned)lineNumber
fromURL:(NSURL *)url
withError:(NSError *)error
forWebFrame:(WebFrame *)webFrame
{
NSLog(#"NSDD: called failedToParseSource: url=%# line=%d error=%#\nsource=%#", url, lineNumber, error, source);
}
- (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
NSLog(#"NSDD: exception: sid=%d line=%d function=%#, caller=%#, exception=%#",
sid, lineno, [frame functionName], [frame caller], [frame exception]);
}
There is probably a large runtime impact for this, as the debug delegate can also supply methods to be called for entering and exiting a stack frame, and for executing each line of code.
See http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx for the Objective-C++ definition of WebScriptDebugDelegate.
Those other methods:
// just entered a stack frame (i.e. called a function, or started global scope)
- (void)webView:(WebView *)webView didEnterCallFrame:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame;
// about to execute some code
- (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame;
// about to leave a stack frame (i.e. return from a function)
- (void)webView:(WebView *)webView willLeaveCallFrame:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame;
Note that this is all hidden away in a private framework, so don't try to put this in code you submit to the App Store, and be prepared for some hackery to get it to work.
I created a nice little drop-in category that you can add to your project...
It is based on Robert Sanders solution. Kudos.
You can dowload it here:
UIWebView+Debug
This should make it a lot easier to debug you UIWebView :)
I used the great solution proposed from Robert Sanders: How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView?
That hook for webkit works fine also on iPhone. Instead of standard UIWebView I allocated derived MyUIWebView. I needed also to define hidden classes inside MyWebScriptObjectDelegate.h:
#class WebView;
#class WebFrame;
#class WebScriptCallFrame;
Within the ios sdk 4.1 the function:
- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject
is deprecated and instead of it I used the function:
- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame
Also, I get some annoying warnings like "NSObject may not respond -windowScriptObject" because the class interface is hidden. I ignore them and it works nice.
One way that works during development if you have Safari v 6+ (I'm uncertain what iOS version you need) is to use the Safari development tools and hook into the UIWebView through it.
In Safari: Enable the Develop Menu (Preferences > Advanced > Show Develop menu in menu bar)
Plug your phone into the computer via the cable.
List item
Load up the app (either through xcode or just launch it) and go to the screen you want to debug.
Back in Safari, open the Develop menu, look for the name of your device in that menu (mine is called iPhone 5), should be right under User Agent.
Select it and you should see a drop down of the web views currently visible in your app.
If you have more than one webview on the screen you can try to tell them apart by rolling over the name of the app in the develop menu. The corresponding UIWebView will turn blue.
Select the name of the app, the develop window opens and you can inspect the console. You can even issue JS commands through it.
Straight Forward Way: Put this code on top of your controller/view that is using the UIWebView
#ifdef DEBUG
#interface DebugWebDelegate : NSObject
#end
#implementation DebugWebDelegate
#class WebView;
#class WebScriptCallFrame;
#class WebFrame;
- (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame
sourceId:(int)sid
line:(int)lineno
forWebFrame:(WebFrame *)webFrame
{
NSLog(#"NSDD: exception: sid=%d line=%d function=%#, caller=%#, exception=%#",
sid, lineno, [frame functionName], [frame caller], [frame exception]);
}
#end
#interface DebugWebView : UIWebView
id windowScriptObject;
id privateWebView;
#end
#implementation DebugWebView
- (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame
{
[sender setScriptDebugDelegate:[[DebugWebDelegate alloc] init]];
}
#end
#endif
And then instantiate it like this:
#ifdef DEBUG
myWebview = [[DebugWebView alloc] initWithFrame:frame];
#else
myWebview = [[UIWebView alloc] initWithFrame:frame];
#endif
Using #ifdef DEBUG ensures that it doesn't go in the release build, but I would also recommend commenting it out when you're not using it since it has a performance impact. Credit goes to Robert Sanders and Prcela for the original code
Also if using ARC you may need to add "-fno-objc-arc" to prevent some build errors.
I have created an SDK kosher error reporter that includes:
The error message
The name of the file the error happens in
The line number the error happens on
The JavaScript callstack including parameters passed
It is part of the QuickConnectiPhone framework available from the sourceForge project
There is even an example application that shows how to send an error message to the Xcode terminal.
All you need to do is to surround your JavaScript code, including function definitions, etc. with try catch. It should look like this.
try{
//put your code here
}
catch(err){
logError(err);
}
It doesn't work really well with compilation errors but works with all others. Even anonymous functions.
The development blog is here
is here and includes links to the wiki, sourceForge, the google group, and twitter. Maybe this would help you out.
I have done this in firmware 1.x but not 2.x.
Here is the code I used in 1.x, it should at least help you on your way.
// Dismiss Javascript alerts and telephone confirms
/*- (void)alertSheet:(UIAlertSheet*)sheet buttonClicked:(int)button
{
if (button == 1)
{
[sheet setContext: nil];
}
[sheet dismiss];
}*/
// Javascript errors and logs
- (void) webView: (WebView*)webView addMessageToConsole: (NSDictionary*)dictionary
{
NSLog(#"Javascript log: %#", dictionary);
}
// Javascript alerts
- (void) webView: (WebView*)webView runJavaScriptAlertPanelWithMessage: (NSString*) message initiatedByFrame: (WebFrame*) frame
{
NSLog(#"Javascript Alert: %#", message);
UIAlertSheet *alertSheet = [[UIAlertSheet alloc] init];
[alertSheet setTitle: #"Javascript Alert"];
[alertSheet addButtonWithTitle: #"OK"];
[alertSheet setBodyText:message];
[alertSheet setDelegate: self];
[alertSheet setContext: self];
[alertSheet popupAlertAnimated:YES];
}
See exception handling in iOS7:
http://www.bignerdranch.com/blog/javascriptcore-example/
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(#"%#", value);
}];
First setup WebViewJavascriptBridge ,
then override console.error function.
In javascript
window.originConsoleError = console.error;
console.error = (msg) => {
window.originConsoleError(msg);
bridge.callHandler("sendConsoleLogToNative", {
action:action,
message:message
}, null)
};
In Objective-C
[self.bridge registerHandler:#"sendConsoleLogToNative" handler:^(id data, WVJBResponseCallback responseCallback) {
NSString *action = data[#"action"];
NSString *msg = data[#"message"];
if (isStringValid(action)){
if ([#"console.error" isEqualToString:action]){
NSLog(#"JS error :%#",msg);
}
}
}];
A simpler solution for some cases might be to just add Firebug Lite to the Web page.