View of iOS 8 action extension app has UIWebView. When I open this action extension in Safari and then extension app shows UIWebView of mobile Safari's URL.
But extension app crashes sometimes during loading web page or scrolling it for some web pages like nytimes.com.
I know extension app's usable memory depends on mobile Safari.
But I found that 'Awesome Screenshot for Safari' does not crash.
(https://itunes.apple.com/us/app/awesome-screenshot-for-safari/id918780145)
I am wondering how to prevent action extension app from crashing.
#interface ActionViewController ()
#property(strong,nonatomic) IBOutlet UIImageView *imageView;
#property (strong, nonatomic) IBOutlet UIWebView *webView;
#end
#implementation ActionViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
__weak typeof(self) weakSelf = self;
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
if (error) {
[weakSelf.extensionContext cancelRequestWithError:error];
return;
}
if (![(NSObject *)item isKindOfClass:[NSURL class]]) {
NSError *unexpectedError = [NSError errorWithDomain:NSItemProviderErrorDomain
code:NSItemProviderUnexpectedValueClassError
userInfo:nil];
[weakSelf.extensionContext cancelRequestWithError:unexpectedError];
return;
}
NSURL *url = (NSURL *)item;
[weakSelf.webView loadRequest:[NSURLRequest requestWithURL:url]];
}];
} else {
NSError *unavailableError = [NSError errorWithDomain:NSItemProviderErrorDomain
code:NSItemProviderItemUnavailableError
userInfo:nil];
[self.extensionContext cancelRequestWithError:unavailableError];
}
}
I was experiencing the same issue, using a UIWebView in the background to scrape some content for my app. It worked fine when I was connected to the debugger. But running the same build without the debugger always crashed.
I resolved it by migrating to WKWebView, which was actually pretty easy. I guess UIWebView is just too old and inefficient to be running in an extension inside of Safari. Using WKWebView worked perfectly.
I then discovered a crash when I was done using the web view and was saving my data. I was saving it to NSUserDefaults (using my group container) to pass to the main app. This was always crashing too, so I removed that code. I ended up using CoreData instead, which worked fine too.
Now I have a crash free Safari extension! :)
Related
I am using a technique similar to the question linked below to capture window.print() and trigger printing of a WKWebView. This works fine for the main page.
Capturing window.print() from a WKWebView
What I am hoping to do is print an iFrame in the page, not the page itself. In JavaScript, I focus the iFrame and call window.print(), which works in Safari on iOS (which uses WKWebView?), and various desktop browsers.
Does anybody know how to use the WKWebView printFormatter to print an iFrame of HTML content?
i've been struggling with the exact same issue for the last 3 days, and was desperately looking across the web for a solution, which I didn't find.
But I finally got it running, so here is my solution for this:
Add the following user script to WKWebViewconfig.userContentController:
WKUserScript *printScript = [[WKUserScript alloc] initWithSource:#"window.print = function() {window.webkit.messageHandlers.print.postMessage('print')}"
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[webViewConfig.userContentController addUserScript:printScript];
[webViewConfig.userContentController addScriptMessageHandler:self name:#"print"];
=> this is the same as in Capturing window.print() from a WKWebview, with one crucial difference: forMainFrameOnly is set to NO, because we want to inject this in our iframe, and not only in the top window
Define a message handler in the view controller holding your WKWebview:
In the header:
#interface MyViewcontroller : UIViewController <UIGestureRecognizerDelegate, WKNavigationDelegate, WKScriptMessageHandler>
Implementation:
-(void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
Use the message handler to retrieve iframe's source (which is your pdf), and use-it as printing item for iOS's PrintController:
-(void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
if([message.name isEqualToString:#"print"])
{
[self.webView evaluateJavaScript:#"window.document.querySelector(\"iframe\").src" completionHandler:^(NSString *result, NSError *error)
{
UIPrintInteractionController *printController = UIPrintInteractionController.sharedPrintController;
printController.printingItem = [NSURL URLWithString:result];
UIPrintInteractionCompletionHandler completionHandler = ^(UIPrintInteractionController *printController, BOOL completed, NSError *error)
{
if(!completed)
{
if(error != nil) NSLog(#"Print failed: \nDomain: %#\nError code:%ld", error.domain, (long) error.code);
else NSLog(#"Print canceled");
}
NSLog(#"Print completed!\nSelected Printer ID: %#", printController.printInfo.printerID);
};
if(printController != nil)
[printController presentAnimated:YES completionHandler:completionHandler];
}];
}
else
NSLog(#"Unrecognized message received from web page"); }
NOTE: i use window.document.querySelector(\"iframe\") because our iframe does not have an id, otherwise I would have used window.getComponentByID. This important in case you have multiple iframes, which was not my case
I hope this can help you and others as well!
I'm trying to parse weather data into my App with Xcode 7.3.1, iOS 9.3 and JSON by using Weather Underground API (but I get the same problem with other APIs such as OpenWeatherMap).
I don't get error when I build my app but when I call the weather in the Simulator I get a "Thread 1: signal SIGABRT" error. I used breakpoint to speculate that my problem come from the Serialization.
I have already tried to clean my project and I didn't have double connections.
When I download and run the project of this tutorial I have the same problem...
Here is my code :
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIButton *affichermeteo;
#property (weak, nonatomic) IBOutlet UILabel *meteo;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)affichermeteo:(id)sender {
NSData *allCoursesData = [[NSData alloc] initWithContentsOfURL:
[NSURL URLWithString:#"http://api.wunderground.com/api/e5cdee14984e242b/conditions/q/CA/San_Francisco.json"]];
NSError *error;
NSDictionary *allCourses = [NSJSONSerialization
JSONObjectWithData:allCoursesData
options:NSJSONReadingMutableContainers
error:&error];
if( error )
{
NSLog(#"%#", [error localizedDescription]);
}
else {
NSArray *currentobservation = allCourses[#"estimated"];
for ( NSDictionary *theCourse in currentobservation )
{
_meteo.text=theCourse[#"weather"];
}
}
}
#end
My error window :
Here
Thank you in advance for your help and sorry for my english, I am french !
I am able to get data using your code. You are missing the iOS 9's newly added App Transport Security flag.
Add the App Transport Security Settings key and mark Allow Arbitrary Loads to YES like in the screenshot below:
This should solve your problem.
Check this link to learn more about App Transport Security.
I have a browser webgame ported to iOS using UIWebView (storyboard, not xib). The app is already published and works relatively well. My challenge now is to implement GameCenter properly so I can get the local player's playerID and then have a trustable unique id of the user. That's the only feature I really need from GameCenter.
The GameCenter functionality is actually implemented and working well, but for a reason I still couldn't understand I can't make the setAuthenticateHandler, located in a different class file, to access the UIebView so I can load an URL passing the playerId (among other parameters) via POST.
The WebViewController.m has a method who loads an URL in the UIWebView. The setAuthenticateHandler do call this method, but the UIWebView is null and of course it's not possible to load the URL. If I call the method inside WebViewController.m the URL is loaded correctly.
I was using ARC and then changed to MRC according to https://stackoverflow.com/a/14613361/3063226, but the behavior didn't change, the UIWebView is still null when GameCenter setAuthenticateHandler calls the Load_URL method.
Any help is very welcome. I have tried and researched a LOT for days before coming here to ask a question, I'm stuck!
GCHelper.m
- (void)authenticateLocalUser {
if (!gameCenterAvailable) return;
NSLog(#"Authenticating local user...");
if (![GKLocalPlayer localPlayer].isAuthenticated) {
[[GKLocalPlayer localPlayer] setAuthenticateHandler:(^(UIViewController* viewcontroller, NSError *error) {
if (!error && [GKLocalPlayer localPlayer].playerID != nil)
{
NSLog(#"Player logged in GameCenter!");
NSLog([NSString stringWithFormat: #"1111 (GCHelper) playerID: [%#]", [GKLocalPlayer localPlayer].playerID);
// --
WebViewController *yourClassInstance = [[WebViewController alloc] init];
[yourClassInstance Load_URL:true]; // this is not working as expected!!
// -
}
else
{
NSLog(error.localizedDescription);
}
})];
} else {
NSLog(#"Already authenticated!");
}
}
WebViewController.h
#import <UIKit/UIKit.h>
#interface WebViewController : UIViewController <UIWebViewDelegate>
-(void) Load_URL:(BOOL)Nativa;
#property (strong, nonatomic) IBOutlet UIWebView *PagPrincipal;
#end
WebViewController.m
#import "WebViewController.h"
#import "NSData+AESCrypt.h"
#import "NSString+AESCrypt.h"
#import "NSString+URL_Encode.h"
#import "GCHelper.h"
#interface WebViewController ()
-(void) Load_URL:(BOOL)Nativa;
#end
-(void) Load_URL :(BOOL)Nativa
{
NSURL *url = [NSURL URLWithString: #"http://www.webgameurl.com"];
NSString *body = [NSString stringWithFormat: #"iac=%#", [self URL_Codigo :Nativa :vip_aux]];
NSMutableURLRequest *requestObj = [[NSMutableURLRequest alloc]initWithURL: url];
[requestObj setHTTPMethod: #"POST"];
[requestObj setHTTPBody: [body dataUsingEncoding: NSUTF8StringEncoding]];
// HERE!! This returns null when called from GCHelper.m, but is ok when called from WebViewController.m !!
NSLog([NSString stringWithFormat:#"Webview >>> >>> >>> [%#]", _PagPrincipal.description]);
_PagPrincipal.delegate = self;
[_PagPrincipal loadRequest:requestObj];
This is it. The _PagPrincipal is the UIWebView itself, it's a storyboard. I'm not an iOS Objective-C specialist, so for sure there are things I just don't master. Using Xcode 6.1.1, the app is designed for iOS8+. Testing in a real iPad Mini 1 (non-retina) and iPhone 5.
I have been playing a bit with the Chromecast SDK those days. What I am currently trying to do is to send a UIImage (for example a photo taken with the iPhone) on the TV using the Chromecast.
I am able to load "external" image using an URL but I can't figure out how to send a locally stored image!
So is there a way to send it using base64 encoding, or to set up a stream, or even to mirror the screen ? I am a bit lost, if someone could give me a hint or some sample code, that'll be great !
You can host a small web server in your app and then provide the URL to that server to the Chromecast receiver app to load the photos from your device. The Cast protocol channel is not designed to handle large binary transfers.
Building up on responses provided by Leon and Alok, i.e. serving images from your iOS device over HTTP using Cocoa HTTP server, you can find an example on at GitHub with detailed explanation in this blog post.
Also don't forget that to be served to your ChromeCast, you will need to enable CORS.
In short, and once you have added Cocoa HTTP Server to your project, you can
subclass HTTPDataResponse as follows in order to enable CORS
CamCaptureDataResponse.h
#import "HTTPDataResponse.h"
#interface CamCaptureDataResponse : HTTPDataResponse
#end
CamCaptureDataResponse.m
#import "CamCaptureDataResponse.h"
#implementation CamCaptureDataResponse
-(NSDictionary*)httpHeaders {
return #{
#"Access-Control-Allow-Origin":#"*",
#"Access-Control-Allow-Methods":#"GET,PUT,POST,DELETE",
#"Access-Control-Allow-Headers":#"Content-Type"
};
}
#end
Use this new DataResponse class in your own request handler by subclassing HTTPConnection
CamCaptureConnection.h
#import "HTTPConnection.h"
#interface CamCaptureConnection : HTTPConnection
#end
CamCaptureConnection.m
#import "CamCaptureConnection.h"
#import "CamCaptureHTTPServer.h"
#import "CamCaptureDataResponse.h"
#implementation CamCaptureConnection
-(NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI: (NSString *)path {
NSArray* pathComponents = [path componentsSeparatedByString:#"/"];
if ([pathComponents count] < 2) {
return [[CamCaptureDataResponse alloc] initWithData:[#"ERROR" dataUsingEncoding:NSUTF8StringEncoding]];
}
NSString *command = [pathComponents objectAtIndex:1];
if ([command isEqualToString:#"PING"]) {
return [[CamCaptureDataResponse alloc] initWithData:[#"PONG" dataUsingEncoding:NSUTF8StringEncoding]];
}
if ([command isEqualToString:#"PIC"]) {
// Change the following line with whichever image you want to serve to your ChromeCast!
NSData *imageData = UIImageJPEGRepresentation([CamCaptureHttpServer instance].captureImage, 0.3);
if (imageData) {
return [[CamCaptureDataResponse alloc] initWithData:imageData];
} else {
return [[CamCaptureDataResponse alloc] initWithData:[#"NO_IMAGE" dataUsingEncoding:NSUTF8StringEncoding]];
}
}
return [[CamCaptureDataResponse alloc] initWithData:[#"ERROR_UNKNOWN_COMMAND" dataUsingEncoding:NSUTF8StringEncoding]];
}
#end
Then before you start, your web server, first register your new connection class as follows
NSError *error;
httpServer = [[CamCaptureHttpServer alloc] init];
[httpServer setConnectionClass:[CamCaptureConnection class]];
[httpServer setType:#"_http._tcp."];
[httpServer setPort:1234];
[httpServer start:&error];
Yes ! you can use CocoaHTTPServer is a small, lightweight, embeddable HTTP server for Mac OS X or iOS applications.
#import "iPhoneHTTPServerAppDelegate.h"
#import "iPhoneHTTPServerViewController.h"
#import "HTTPServer.h"
#import "DDLog.h"
#import "DDTTYLogger.h"
// Log levels: off, error, warn, info, verbose
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#implementation iPhoneHTTPServerAppDelegate
#synthesize window;
#synthesize viewController;
- (void)startServer
{
// Start the server (and check for problems)
NSError *error;
if([httpServer start:&error])
{
DDLogInfo(#"Started HTTP Server on port %hu", [httpServer listeningPort]);
}
else
{
DDLogError(#"Error starting HTTP Server: %#", error);
}
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Configure our logging framework.
// To keep things simple and fast, we're just going to log to the Xcode console.
[DDLog addLogger:[DDTTYLogger sharedInstance]];
// Create server using our custom MyHTTPServer class
httpServer = [[HTTPServer alloc] init];
// Tell the server to broadcast its presence via Bonjour.
// This allows browsers such as Safari to automatically discover our service.
[httpServer setType:#"_http._tcp."];
// Normally there's no need to run our server on any specific port.
// Technologies like Bonjour allow clients to dynamically discover the server's port at runtime.
// However, for easy testing you may want force a certain port so you can just hit the refresh button.
// [httpServer setPort:12345];
// Serve files from our embedded Web folder
NSString *webPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Web"];
DDLogInfo(#"Setting document root: %#", webPath);
[httpServer setDocumentRoot:webPath];
[self startServer];
// Add the view controller's view to the window and display.
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self startServer];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// There is no public(allowed in AppStore) method for iOS to run continiously in the background for our purposes (serving HTTP).
// So, we stop the server when the app is paused (if a users exits from the app or locks a device) and
// restart the server when the app is resumed (based on this document: http://developer.apple.com/library/ios/#technotes/tn2277/_index.html )
[httpServer stop];
}
#end
Is it possible to download and add a passbook from within a webview without modifying the app to support the new MIME type or unknown MIME types like Sparrow did?
I have a news ios app with a webview. In the webview I display the news items and a banner. When you click the banner I want to open a url to a .pkpass file and add it to my passbook. Instead I get a FrameLoadInterrupted error and nothing visible happens.
If I open the url from safari this works fine, chrome, since earlier this week (version 23) also opens the url like intended.
Is this some weird strategy from Apple maybe? not allowing this MIME type to properly open from a webview?
My best bet is that the UIWebView is just not capable of handling the Passbook passes. You could however try and catch the downloading in the UIWebViewDelegate method -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType.
What I mean to say is that you have to handle this part your self, since the http://passkit.com/samples/ I used does not return an url which ends pkpass thus it is totally depended on how you request the passbook files.
If you do in include the .pkpass extension you can check for the extension in the request.
If you know what kind of URL the passbook file is at you write your own download code here and pass it to passbook via the passbook api.
There does not seem to be any great on fix for this, you could load the failed ULR in safari:
- (void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
NSLog(#"Webview: %#", error);
if ([error.domain isEqualToString:#"WebKitErrorDomain"] && error.code == 102) {
NSString *failedURL = [error.userInfo objectForKey:NSURLErrorFailingURLStringErrorKey];
if (failedURL == nil) {
return;
}
NSURL *url = [NSURL URLWithString:failedURL];
[[UIApplication sharedApplication] openURL:url];
}
}
But this is just really bad coding.
Okay, talked to the engineers at WWDC and this is a know bug in UIWebView but Apple probably won't fix it because they're encouraging people to adopt the new SFSafariViewController. We did come up with a hack to fix it should you need to support iOS 6-8:
Add the PassKit framework to the project if it isn't already.
#import <PassKit/PassKit.h>
Set up a delegate for the UIWebView (for example the view controller launching the UIWebView)
<UIWebViewDelegate>
Add a class variable to cache the UIWebView requests
NSURLRequest *_lastRequest;
Set the delegate
self.webView.delegate = self;
Add the callback to grab all requests and cache in case of failure
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
_lastRequest = request;
return YES;
}
Add the failure callback and re-fetch the URL to see if it is a pass and if so, present the pass to the user
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
// try to get headers in case of passbook pass
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:_lastRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// check for PKPass
if ([response.MIMEType isEqualToString:#"application/vnd.apple.pkpass"]) {
NSError *error;
PKPass *pass = [[PKPass alloc] initWithData:data error:&error];
if (error) {
NSLog(#"Error: %#", error);
} else {
PKAddPassesViewController *apvc = [[PKAddPassesViewController alloc] initWithPass:pass];
[self presentViewController:apvc animated:YES completion:nil];
}
}
}];
}
It's a horrible hack for what should be supported, but it works regardless of the extension and should support re-directs. If you want to pile on the bug train, you can reference radar://21314226