My problem is that i want to detect the zoom scale of an UIWebView, i have tried searching it but did not come out with a proper answer.Any help is appreciated......
Well although the UIWebView doesn't have a zoomScale property, UIScrollView does!
So we just scan it's subView's for the scrollView everything sits in and get it that way.
Here's a little (1 method) category that will allow you to get the scale by calling [webView zoomScale].
UIWebView+zoom.h file
#interface UIWebView (zoom)
-(float)zoomScale;
#end
UIWebView+zoom.m file
#implementation UIWebView (zoom)
-(float)zoomScale{
UIScrollView *webViewContentView;
for (UIView *checkView in [self subviews] ) {
if ([checkView isKindOfClass:[UIScrollView class]]) {
webViewContentView = (UIScrollView*)checkView;
break;
}
}
return webViewContentView.zoomScale;
}
#end
UIScrollView Class Reference
UIWebView's View Hierarchy (Don't rely on it though, always scan the webView to avoid code breaking when apple makes changes to iOS)
NOTE: This code should work but has been written in the reply box so hasn't been tested.
Related
How do I check/call a method when a user is at the bottom of the UIWebView when scrolling? I want to popup a view (add a subiview) when the user is at the bottom of the content.
Building on other ideas here, UIWebView conforms to the UIScrollViewDelegate protocol. You could subclass UIWebView and override the appropriate UIScrollViewDelegate methods, calling [super ...] so the original behavior is still present.
#interface SpecialWebview : UIWebView
#end
#implementation SpecialWebview
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[super scrollViewDidScroll:scrollView];
// Check scroll position and handle events as needed...
}
#end
First get the reference to the UIWebView scrollView using this property:
#property(nonatomic, readonly, retain) UIScrollView *scrollView
Once you get the reference to it you could check for the contentSize of it and compare it with the contentOffSet. You cannot make yourself a delegate of it, since the UIWebView is already the delegate for the scrollView, but that trick should work.
The problem is that you don't get the callBack when the scrolling is happening... so you would have to check every so often. Otherwise you will have to make yourself the delegate of the scrollView, and implement all the methods that UIWebView is implementing and mimic that behavior, and then check on the didScroll for that condition.
Another option would be to inject some JavaScript into the web view and then use the window.onscroll event in JavaScript to detect when the user has scrolled to the bottom of the window (you can find plenty of examples online if detecting scroll events using JS).
Once you detect the the user is at the bottom, you could call back to the app by loading a fake url in the web view from the JavaScript by saying document.location.href = "http://madeupcallbackurl", then use the web view delegate to intercept that request and perform your native code logic.
Using the scrollview delegate is probably easier though if you can make that work!
Sorry, I was thinking UITableView. You are asking about a UIWebView. I don't know if this will work with the UIWebView.
I have code that does this using a UIScrollViewDelegate.
#interface myViewController : UIViewController_iPad <UIScrollViewDelegate> {
UITableView *myTableView;
...
}
- (void)viewDidLoad {
myTableView.delegate = self;
...
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// If necessary, verify this scrollview is your table:
// if (scrollView == myTableView) {
CGPoint offset = myTableView.contentOffset;
if (myTableView.contentSize.height - offset.y >= myTableView.bounds.size.height)
{
// The table scrolled to the bottom. Do your stuff...
}
// }
}
This is how I would do it.
UIWebView conforms to UIScrollViewDelegate. Use the - (void)scrollViewDidScroll:(UIScrollView *)scrollView callback to keep track of when your UIWebView is scrolling:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//The webview is is scrolling
int xPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollX"] intValue];
int yPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollY"] intValue];
}
Now this method will be invoked whenever your webView scrolls. Your webView will have scrolled to the bottom when:
webView_content_height - webView(y) = webView.frame.height; //pseudo code
Your delegate callback will now look like:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//The webview is is scrolling
int xPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollX"] intValue];
int yPosition = [[webView stringByEvaluatingJavaScriptFromString: #"scrollY"] intValue];
if([webView stringByEvaluatingJavaScriptFromString: #"document.body.offsetHeight"] - yPosition == webView.frame.height)
{
//The user scrolled to the bottom of the webview
}
}
What the code translates to is that when the user scrolls to the bottom of the content, the difference between the webView's y Position (which is the y-coordinate of the webView's top left corner) and the bottom of the content will be equal to the height of the webview frame. The code acts upon this condition being satisfied to enter inside the if condition.
To be on the safe side, you could modify the if condition to have a margin for error (maybe + or - 10 pixels).
I am using a UIWebView to show a PDF. I need to track some UIScrollView events, so I set the built-in UIScrollView like this:
for(UIScrollView *s in webView.subviews) {
s.delegate = self;
}
But the problem is that when I do this, I lose the ability to pinch to zoom (like I could before because the UIWebView's Scale Pages to Fit property was set). How come this happens? What can I do to fix it? I went ahead and implemented the UIScrollView shouldZoom method, but when I pass back the scrollView as the view to zoom on, it zooms from the corner, not where my fingers are.
Does anybody know a solution to this problem? How can I have the UIScrollView delegate set, and still retain natural zooming ability?
Thanks!
You are messing with some UIWebView internals. This feels like a bad hack, but I think you could forward all the UIScrollViewDelegate methods back to UIWebView in addition to doing whatever you need to do.
Also, UIWebView has many subviews. You should check and override the delegate just for the UIScrollView.
for (UIView *subview in webViews.subviews) {
if ([subview isKindOfClass:[UIScrollView class]]) {
subview.delegate = self;
}
}
Either handle the following events in your delegate:
– viewForZoomingInScrollView:
– scrollViewWillBeginZooming:withView:
– scrollViewDidEndZooming:withView:atScale:
– scrollViewDidZoom:
or save the original delegate
origDelegate = s.delegate;
s.delegate = self;
and forward the calls, e.g:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [origDelegate viewForZoomingInScrollView:scrollView];
}
Is there a way to make a UIWebView scroll to the top when I touch say a UISearchView within the same viewController (without using Javascript).
Something like this:
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[myWebView scrollToTop]; //pseudocode
}
In other words, what happens when I touch the top bar can also happen programmatically.
CGPoint top = CGPointMake(0, 0); // can also use CGPointZero here
[myWebView.scrollView setContentOffset:top animated:YES];
(Note that if you have set myWebView.scrollView.contentInset.top you will want to take that into account instead of just scrolling to CGPointZero.)
Here's a really ugly, terrible way to do this. I'm answering this to show you what never to do.
for (UIView *subview in webView.subviews)
{
if ([subview isKindOfClass:[UIScrollView class]])
[(UIScrollView*)subview setContentOffset:CGPointZero animated:YES];
}
If you don't want to use JavaScript there's no public API method to do this. UIWebViews don't inherit from scroll views, so you can't use any of the usual methods. As you've figured out, it's possible to do with JavaScript. You can try to find the actual scroll view in the UIWebView, but it's all undocumented and not really a good thing to do in a production app.
Update - as of iOS 5 you can now get direct access to a web view's UIScrollView - see Reconquistador's answer for more information.
Scroll the UIWebView by calling JavaScript through Objective-C:
NSString *script = #"scrollTo(0, 0)";
[webView stringByEvaluatingJavaScriptFromString:script];
For smooth scrolling, use a library like jQuery:
NSString *script = #"$('html, body').animate({scrollTop:0}, 'slow')";
[webView stringByEvaluatingJavaScriptFromString:script];
Access the UIWebView's scrollview and set YES to method scrollsToTop;
WebView.scrollView.scrollsToTop = YES;
Hope this helps!
Swift 3.x
webView.scrollView.setContentOffset(CGPoint.zero, animated: true)
I don't believe you will find any officially supported method to do what you are wanting.
This works for me:
CGPoint topOffset = CGPointMake(0, 0);
//[scrollView setContentOffset: topOffset animated: YES];
[[[webView subviews] lastObject] setContentOffset:topOffset animated:YES];
Where webView is the subclass of the UIWebView, of course.
why not just touch the status bar of your device. for all the scrollView based controls, tableView, webView, scrollView, there is a :
The scroll-to-top gesture is a tap on the status bar; when this property is YES, the scroll view jumps to the top of the content when this gesture occurs. The default value of this property is YES.
Displaying a PDF in a UIWebView, I found you can jump to the top simply by reloading the document.
I've been looking for the past week for the answer to this question.
I have a UIWebView, inside of a UIScrollView. Everything works great, but I want the content of the UIWebView to reset its zoom, when the orientation changes.
In the HTML inside the UIWebView, I set the width of the viewport (w/ a meta tag) to "device-width" and then on the Obj-C side, I set the scalesPagesToFit = YES;
I've tried resetting the zoom with javascript; by replacing the meta tags in runtime; reloading; accessing the UIScrollView inside of the UIWebView; etc...
but with no success.
Any of you gods know a workaround?
The only one I can think off is to recreate the UIWebViews every time we change the orientation, but that makes them flash to white whilst rendering content, which looks terrible :(
Any thoughts?
Many thanks,
Andre
I'm just guessing here and haven't tried, but AFAIK a UIWebView has a UIScrollView child. So one should be able to do:
for (UIScrollView *scroll in [myWebView subviews]) {
// Make sure it really is a scroll view and reset the zoom scale.
if ([scroll respondsToSelector:#selector(setZoomScale:)])
[scroll setZoomScale:1.0];
}
On iOS 5+ you have access to scrollView.
Just do:
[webView.scrollView setZoomScale:1.0];
If you want to do it programmatically this is the only way I could find to accomplish it: (specify your own sizes if you wish, i was attempting to zoom out after typing into a form field)
UIScrollView *sv = [[webViewView subviews] objectAtIndex:0];
[sv zoomToRect:CGRectMake(0, 0, sv.contentSize.width, sv.contentSize.height) animated:YES];
Update:
Downscaling wasn't working properly when using
[[[webView subviews] lastObject] setZoomScale:0.25];
The quality of the images being downscaled on the page was awful. Doing:
[[[webView subviews] lastObject] setZoomScale:0.25 animated:YES];
Fixed it. So that last line is the one you could use.
webView was subclassed of a UIWebView which lies on some IB file. I didn't use the Viewport at all. I find that one should pick by either doing this from the Cocoa Touch side or use JS.
I used:
webView.scalesPageToFit = YES;
I wonder if there's a way of resetting the scalesPageToFit.
Adapting from Captnwalker1's answer, I came up with this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if(toInterfaceOrientation ==UIInterfaceOrientationPortrait||toInterfaceOrientation==UIInterfaceOrientationMaskPortraitUpsideDown)
{
currentScrollView = [[webView subviews] objectAtIndex:0];
[currentScrollView zoomToRect:CGRectMake(0, 0, currentScrollView.contentSize.width, currentScrollView.contentSize.height) animated:NO];
}
else
{
currentScrollView = [[webView subviews] objectAtIndex:0];
[currentScrollView zoomToRect:CGRectMake(0, 0, currentScrollView.contentSize.width, currentScrollView.contentSize.height) animated:NO];
}
}
So load your webview image, and the image will reset it's size when rotated.
I have a UITextView included in a UITableViewCell. The layout is correct when the views are initially displayed, but once I click in the UITextView it automatically scrolls up a bit and the top half of the characters on the first line becomes invisible.
This image is when the UITextView is not active:
UITextView not active http://gerodt.homeip.net/uitextview-notactive.png
And this one is when I clicked in the UITextView to make it active:
UITextView active http://gerodt.homeip.net/uitextview-active.png
I do not the UITextView to scroll up at all, it should simple stay fixed. How can I achieve this? I already tried several settings in Interface Builder, but no luck so far.
Any suggestions are appreciated.
Gero
UITextView is a subclass of UIScrollView, so it has a configurable contentInset property. Unfortunately, if you try to change contentInset on a UITextView instance, the bottom edge inset always gets reset to 32. I've run into this before with short UITextView frames and found this to be an issue. I suspect this is what is causing your problem, but you should check the contentInset of your textview in the debugger to be sure.
The workaround/solution is simple: subclass UITextView and override the contentInset method so that it always returns UIEdgeInsetZero. Try this:
//
// BCTextView
//
// UITextView seems to automatically be resetting the contentInset
// bottom margin to 32.0f, causing strange scroll behavior in our small
// textView. Maybe there is a setting for this, but it seems like odd behavior.
// override contentInset to always be zero.
//
#interface BCZeroEdgeTextView : UITextView
#end
#implementation BCZeroEdgeTextView
- (UIEdgeInsets) contentInset { return UIEdgeInsetsZero; }
#end
This is how UITextView behaves according to Apple's engineer this is intended and UITextView is meant for text that are at least a few lines in height. There is no work around to this, use a UITextField instead or increase your UITextView to at least 3 lines in height.
You can also just do:
textView.contentInset=UIEdgeInsetsZero;
in your delegate file.
UITextView is a subclass of UIScrollView, so the answer involves the contentOffset property, which is what is being changed, not the insets or the content size. If the scroll position is correct when the view first appears, then you can store the content offset for later recall.
YourViewController.h snipped
#interface YourViewController : UIViewController <UITextViewDelegate, UIScrollViewDelegate>
#property(nonatomic, weak) IBOutlet UITextView *textView;
#end
YourViewController.m snippet
#implementation YourViewController {
#private
BOOL _freezeScrolling;
CGFloat _lastContentOffsetY;
}
// UITextViewDelegate
- (void)textViewDidBeginEditing:(UITextView *)textView {
// tell the view to hold the scrolling
_freezeScrolling = YES;
_lastContentOffsetY = self.textView.contentOffset.y;
}
// UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView {
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_freezeScrolling) {
// prevent the scroll view from actually scrolling when we don't want it to
[self repositionScrollView:scrollView newOffset:CGPointMake(scrollView.contentOffset.x, _lastContentOffsetY)];
}
}
// UIScrollViewDelegate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// scroll prevention should only be a given scroll event and turned back off afterward
_freezeScrolling = NO;
}
// UIScrollViewDelegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
// when the layout is redrawn, scrolling animates. this ensures that we are freeing the view to scroll
_freezeScrolling = NO;
}
/**
This method allows for changing of the content offset for a UIScrollView without triggering the scrollViewDidScroll: delegate method.
*/
- (void)repositionScrollView:(UIScrollView *)scrollView newOffset:(CGPoint)offset {
CGRect scrollBounds = scrollView.bounds;
scrollBounds.origin = offset;
scrollView.bounds = scrollBounds;
}
What's also important to note in the code sample above is the last method. Calling any sort of setContentOffset: will actually trigger scrolling, which results in calling scrollViewDidScroll:. So calling setContentOffset: results in an infinite loop. Setting the scroll bounds is the workaround for this.
In a nutshell, we tell the view controller to prevent the UITextView from scrolling when we detect that the user has selected the text for editing. We also store the current content offset (since we know that the position is what we want). If the UITextView tries to scroll, then we hold the content offset in place until the scroll has stopped (which triggers either scrollViewDidEndDecelerating: or scrollViewDidEndScrollingAnimation:). We also unfreeze the scrolling when the user is done editing.
Remember, this is a basic example, so you'll need to tweak the code based on the exact behavior you want.
I was experiencing a similar issue with undesired UITextView scrolling. I finally managed to fix it by resetting the contentSize at the end of my keyboardDidShow:
- (void)keyboardDidShow:(NSNotification *)notification {
textView.contentSize = CGSizeZero;
}
You also will need to register for the keyboard notification, like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
In my case I didn't want any scrolling since I was resetting the frame to the height of the textView's contentSize when textViewDidChange (growing textview inside a UIScrollView).
Try putting in Redraw on the textview instead of Scale to Fill. You still might have to capture the delegate and keep the content offset but it should at least prevent the jump to point (0,0). Also Autoresizes subview must be turned off. It was jumping to top of textview every time on me too and this solved that problem.