I've implemented a view controller which is displaying a WKWebView. The web view displays certain pages containing links with a tags, e. g.:
<a id="dien10000513721" href="/ge/tr/issue.html" class="dien">Issue</a>
I would like to add a preview to these links by activating 3d-Touch in the web view, but the delegate method webView:shouldPreviewElement: is never called, when I run my app on a iPhone 7 with iOS 11 and doing a force touch on that link.
When I've created my web view and set the necessary properties with
theWebView.navigationDelegate = self;
theWebView.UIDelegate = self;
theWebView.allowsLinkPreview = YES; // Setting this to NO doesn't help
self.webView = theWebView;
The three delegate methods for previewing are implemented as dummies:
- (BOOL)webView:(WKWebView *)inWebView shouldPreviewElement:(WKPreviewElementInfo *)inElementInfo {
NSLog(#"url = %#", inElementInfo.linkURL);
return NO;
}
- (UIViewController *)webView:(WKWebView *)inWebView previewingViewControllerForElement:(WKPreviewElementInfo *)inElementInfo defaultActions:(NSArray<id<WKPreviewActionItem>> *)inPreviewActions {
return nil;
}
- (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController {
NSLog(#"");
}
Do I still have to perform any important configuration steps? What else could prevent the execution of 3D touch events?
Related
In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad
I am trying to print a txt file from within a iPad 8.x application. So, I have this code:
- (void)onOpenWith:(UIButton *)theButton path:(NSString *)path
{
NSURL *URL = [NSURL fileURLWithPath:path];
if (URL) {
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:URL];
self.documentInteractionController.delegate = self;
[self.documentInteractionController presentPreviewAnimated:YES];
}
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
return self;
}
- (UIView *)documentInteractionControllerViewForPreview:(UIDocumentInteractionController *)controller
{
return self.view;
}
- (CGRect)documentInteractionControllerRectForPreview:(UIDocumentInteractionController *)controller
{
return self.view.frame;
}
Now everything is going as expected, I see a preview of the file, then I touch the icon on the top right corner of the screen and I am able to share the document in those applications that can handle it. However, if I touch PRINT I get this error message:
Application tried to present inside popover with transition style
other than UIModalTransitionStyleCoverVertical
and the app crashes. Sure, i understand it, but to which viewcontroller should I apply this transition? I have no control on the popover showing the print dialog...
In iOS 7 (real iPad not simulator) everything works...
Can anybody help me?
Thanks
Fabio
I also came across this issue, and the solution was that I had to build and run my app with Xcode 6 installed.
On my older machine, I have Xcode 5.1.1, and when run from there, this same issue appeared, and some more issues from this view (like cannot dismiss mail controller when opened from the top right corner).
I'm building an iOS app.
I have HTML content in a UIWebView with hyperlinks, and I'm unable to open a link to another UIWebView.
I used UIWebView as a subview of ViewController. Here is the code:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
switch (navigationType) {
case UIWebViewNavigationTypeLinkClicked: { //this is when a user tap is detected
//write the handling code here.
isWebViewLoaded = false; //set this to false only if you open another view controller.
return NO; //prevent tapped URL from loading inside the UIWebView.
}
// some other typical parameters within a UIWebView. Use what is needed
case UIWebViewNavigationTypeFormResubmitted: return YES;
case UIWebViewNavigationTypeReload: return YES;
//for all other cases, including UIWebViewNavigationTypeOther called when UIWebView is loading for the first time
default: {
if (!isWebViewLoaded) {
isWebViewLoaded = true;
return YES;
}
else
return NO;
}
}
}
You only need to segue to a new controller with a web view, and pass the request to it. So something like this in your first case,
case UIWebViewNavigationTypeLinkClicked: { //this is when a user tap is detected
[self performSegueWithIdentifier:#"Next" sender:request];
isWebViewLoaded = false; //set this to false only if you open another view controller.
return NO; //prevent tapped URL from loading inside the UIWebView.
}
Notice that the sender argument in performSegueWithIdentifier:sender: is the request returned by the delegate method. Pass this request to a property in the view controller you're segueing to,
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(NSURLRequest *)sender {
NextViewController *next = segue.destinationViewController;
next.request = sender;
}
Finally, use that request to load the web view in NextViewController.
Im building an app with multiple uiwebviews. I wanted to add loading screens to make the app easier to use. I've added one for one of my webviews using this code.
- (void)webViewDidStartLoad:(UIWebView *)vieWeb
{
_loadingmain.hidden=NO;
}
- (void)webViewDidFinishLoad:(UIWebView *)viewWeb
{
_loadingmain.hidden=YES;
}
viewWeb is the name of one of my uiwebviews. How would I add a load scree for my other two webviews? adding identical code with a different name for the webview causes the error "duplicate declaration of method webViewDidFinishLoad". Any help would be greatly appreciated. Thanks!
So you have multiple UIWebViews on one page, right? If so, you have a few options:
1) Create a subclass of UIWebView and set itself as the web view delegate, that way each UIWebView subclass handles it's own loading view.
2) You could also create multiple instances of your loading views and do something like
-(void)webViewDidStartLoad:(UIWebView *)webView
{
if( webView == self.webView1 )
{
[self.webView1 addSubview:self.loadingView1];
}
else if( webView == self.webView2 )
{
[self.webView2 addSubview:self.loadingView2];
}
//repeat for other webviews
}
-(void) webViewDidFinishLoad:(UIWebView *)webView
{
if( webView == self.webView1 )
{
[self.loadingView1 removeFromSuperview];
}
else if( webView == self.webView2 )
{
[self.loadingView2 removeFromSuperview];
}
//repeat for other webviews
}
I use a UITextView in a subclass of UIViewController which I've named FacebookPostViewController. So the purpose of the text view is to allow the user to review the content of a prepared post, possibly make changes to it, and then decide if the text from the text view should be posted to Facebook, or not.
I've set the returnKeyType property of the UITextView to UIReturnKeySend, because I want to use it to actually post to Facebook. Canceling is possible via a back button in the NavBar.
So the FacebookPostViewController is delegate of the UITextView, and in the textView:shouldChangeTextInRange:replacementText: delegate method, I check for a newline insertion, and then call my sendTextToFacebook method:
- (void)sendTextToFacebook;
{
// prepare sending
NSMutableDictionary* sendParameters = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.textView.text, #"message", nil];
// request sending to own feed
[[FacebookController sharedFacebookController].facebook requestWithGraphPath:#"me/feed" andParams:sendParameters andHttpMethod:#"POST" andDelegate:self];
}
As you can see, I specify self as the FBRequestDelegate, so I also implement
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
- (void)request:(FBRequest *)request didLoad:(id)result;
where I give the user feedback if posting was successfull or not.
However, I've figured that it takes some time until the Facebook server replies, so I want to have the complete UI inactive until one of the two delegate methods is called. Therefore, I've implemented the following method:
- (void)disableUserInterface;
{
self.textView.editable = NO;
self.loginButton.enabled = NO;
self.logoutButton.enabled = NO;
[self.navigationItem setHidesBackButton:YES animated:YES];
[self.spinner startAnimating];
self.spinner.hidden = NO;
}
My problem is that calling self.textView.editable = NO will hide the keyboard, which looks stupid. If I remove that line, the keyboard won't disappear, but the user can change the textView content while the text is sent to Facebook. However, as posting is already initiated. these changes won't actually appear in Facebook.
I thought I could simply implement another UITextViewDelegate method:
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
return NO;
}
The UI result is then exactly how I'd like to have it. However, this will lead to a EXC_BAD_ACCESS crash later when I've popped the FacebookPostViewController and then push another view to the UINavigationController. Ther error then is
*** -[UITextView isKindOfClass:]: message sent to deallocated instance 0x20c650
So, has anybody an idea how I can solve my problem to make the UITextView uneditable, but prevent the keyboard from disappearing?!?
Okay, thanks to the comment from David H above, I've figured out a solution. But here are the most important insights:
When I pop my FacebookPostViewController from the UINavigationController, the UITextView is asked automatically by the system to resign being the first responder.
You should never let textViewShouldEndEditing: always return NO, because this will always prevent the keyboard to hide, which will then lead to the crash described above, as soon as the UITextView is deallocated.
The solution is to prevent the keyboard from disappearing only in case we are still waiting for a response from the Facebook server. Here are a couple of lines of codes that I've added, which do exactly this:
#interface FacebookPostViewController ()
// ...
#property (nonatomic, assign, getter = isWaitingForFBResponse) BOOL waitingForFBResponse;
// ...
#end
#implementation FacebookPostViewController
// ...
#synthesize waitingForFBResponse = _waitingForFBResponse;
- (id)init;
{
self = [super init];
if (self) {
// set default values
self.waitingForFBResponse = NO;
// ...
}
return self;
}
#pragma mark -
#pragma mark UITextViewDelegate methods
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
if ([self isWaitingForFBResponse]) {
return NO;
} else {
return YES;
}
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// Did user press 'return' (Send) key?
if([text isEqualToString:#"\n"]) {
// enter waiting state
self.waitingForFBResponse = YES;
// disable the user interface
[self disableUserInterface];
FacebookController *fbController = [FacebookController sharedFacebookController];
if ([fbController.facebook isSessionValid] == YES) {
// Post to Facebook!
[self sendTextToFacebook];
} else {
// we need to login first
[self loginToFacebook];
// remember that user wants to post to Facebook
self.sendTextAfterFBLogin = YES;
}
// Don't allow textView to insert a LF into the text property
return NO;
}
// allow all other edits
return YES;
}
#pragma mark -
#pragma mark FBRequestDelegate methods
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
{
// request failed, so display error status overlay.
[[FeedbackController sharedFeedbackController] displayStatusOverlayWithString:NSLocalizedString(#"StatusMessage_Failure", nil)];
// leave waiting state
self.waitingForFBResponse = NO;
// enable UI again
[self enableUserInterface];
}
- (void)request:(FBRequest *)request didLoad:(id)result;
{
// request succeeded, give user appropriate feedback.
[[FeedbackController sharedFeedbackController] displayStatusOverlayWithString:NSLocalizedString(#"StatusMessage_Sent", nil)];
// leave waiting state
self.waitingForFBResponse = NO;
// pop the view, as the user accomplished his goal.
[self.navigationController popViewControllerAnimated:YES];
}
#end