ScrollOffset in UIWebView? - iphone

I'm having a really hard time understanding delegates and object inheritance (if I may use this word) and I think I need a simple (or so I think) thing: catch scrollViewDidScroll event in UIWebView and get offset (basically, just to know if scroll is not on top/bottom, so I could hide navigation and tab bars).
Is there any way I could do it? I already using UIWebviewDelegate in my view controller to "shouldStartLoadWithRequest". Maybe I could some how use UIScrollViewDelegate too for scrollViewDidScroll? If yes, then how?
I really have trouble understanding delegates. I've red some articles, but still, in practice, I can't manage to use them.
Any help or info would be lovely.
Thank you in advance!

To retrieve scroll events on UIWebView I personnaly use this code to get the scrollview that is inside the UIWebView :
- (void) addScrollViewListener
{
UIScrollView* currentScrollView;
for (UIView* subView in self.myWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
currentScrollView = (UIScrollView*)subView;
currentScrollView.delegate = self;
}
}
}
It's working. You can also use it to call [currentScrollView setContentOffset:offSet animated:YES]; The only problem may be not to pass Apple code checking. I don't know yet since I'm still in coding phase.
[UPDATE] The app with this code is in the app store for 4 months now and used by 40 000 users. I didn't have any trouble [UPDATE]

You can use the following methods to solve your problem.
For getting the pageOffset:
int pageYOffset = [[webViewObj stringByEvaluatingJavaScriptFromString:#"window.pageYOffset"] intValue];
For getting the total scroll height of a webpage:
int scrollHeight = [[webViewObj stringByEvaluatingJavaScriptFromString:#"document.documentElement.scrollHeight"] intValue];
For scrolling the webpage to a particular offset:
[webViewObj stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"document.body.scrollTop = %d",scrollHeight ]];

I made a modification to detect the class with isKindOfClass. Works - but may have issues with Apple code checking as stated above.
UIScrollView* currentScrollView;
for (UIView* subView in terms.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
NSLog(#"found scroll view");
currentScrollView = (UIScrollView *)subView;
currentScrollView.delegate = self;
}
}

Old thread, I know -
As of iOS 5.0 you can use
myAccountWebView.scrollview
to access content size and offset.

There is a scrolling view in the UIWebView, but it a) isn't a UIScrollView, and b) is something Apple considers a private implementation detail (and you should too). I only really have two suggestions:
File a bug with Apple asking them to expose more of the infrastructure of the web view, or at least add some more delegate methods by which we can be notified of these sorts of events.
Add some JavaScript code to your page that listens from scroll events, and notifies your app of them.
The basic foundation of #2 is to load a fake URL, and have your web view delegate process (and abort!) that load. (This question has come up a few times here on Stack Overflow.)
UPDATE:
As of iOS 5, there is now a public scrollView property on UIWebView that you can use to customize scrolling behavior. The exact view hierarchy of the web view remains an undocumented implementation detail, but this gives you a sanctioned way to access this piece of it.

It's a good question. UIWebView is not a subclass of UIScrollView, although I can see why one might think it is. That means using the UIScrollViewDelegate methods is not an option to do what you want, and the UIWebViewDelegate protocol does not respond to those scrolling event type of messages. I don't think there's an easy way to detect scrolling events in a web view.

I tired the delegate method and found it prevented the view from scrolling when the keyboard was shown. I found that by adding an observer you do not override the current delegate and will prevent you from effecting the webview performance.
for (UIView* subView in myAccountWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]])
{
NSLog(#"found scroll view");
[((UIScrollView*)subView) addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
}

Related

How to refresh a UIWebView by a "pull down and release" gesture?

I know that this is possible in the Tweetie for iPhone or the xkcd iPhone app, but they are using a table. Any idea if this can be done for a simple UIWebView as well? I'm aware of the Javascript suggestions in this SO question, but what about making that natively?
To retrieve scroll events on UIWebView I personnaly use this code to get the scrollview that is inside the UIWebView :
- (void) addScrollViewListener
{
UIScrollView* currentScrollView;
for (UIView* subView in self.myWebView.subviews) {
if ([subView isKindOfClass:[UIScrollView class]]) {
currentScrollView = (UIScrollView*)subView;
currentScrollView.delegate = self;
}
}
}
It's working. You can also use it to call [(UIScrollView*)subView setContentOffset:offSet animated:YES]; The only problem may be not to pass Apple code checking. I don't know yet since I'm still in coding phase.
Anyone tried that yet ?
FYI, iOS 5 has officially introduced the property scrollView in UIWebView. I tested it. It worked perfectly with EGO's pull and refresh code. So the problem is no longer a problem for any iOS 5 devices.
For downward compatibility, you still need #CedricSoubrie's code though.
To tell the truth, UIWebVIew class has an undocumented getter method called _scrollView;
So the code goes:
scrollView = [webView _scrollView];
To get a reference for the UIScrollView in UIWebView, simply search it by iterating trough subviews.
for(id eachSubview in [webView subviews]){
if ([eachSubview isKindOfClass:[UIScrollView class]]){
scrollView = eachSubview;
break;
}
}
After that you can easily wire up things to your EGORefreshTableHeaderView interface with the UIWebView and the UIScrollView delegate callbacks.

iPhone dev: Increase scroll speed in UIWebView?

I have an app in which I render local HTML files in a UIWebView. The files, however, are sometimes large, and getting to where you want takes a long time with the default scroll speed. Is there any way to boost up the vertical scroll speed of a UIWebView?
In iOS 5 we can access the scrollView property of the UIWebView.
If you are targeting iOS 5+, you can simply call:
webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
Find a subview of UIWebView which is a UIScrollView, then set decelerationRate to UIScrollViewDecelerationRateNormal. This makes the UIWebView as fast as an ordinary UIScrollView.
In iOS 4/5, we can simply use the last subview of UIWebView.
UIScrollView *scroll = [webView.subviews lastObject];
if ([scroll isKindOfClass:[UIScrollView class]]) {
scroll.decelerationRate = UIScrollViewDecelerationRateNormal;
}
The default decelerationRate of UIWebView's UIScrollView is 0.989324, while UIScrollViewDecelerationRateFast is 0.99, and UIScrollViewDecelerationRateNormal is 0.998.
This method doesn't use any private API.
Search for a subview of UIWebView that responds to -setScrollDecelerationFactor: (it's UIScroller - a private class that's the only subview of UIScrollView). You'll find that it takes the same deceleration factors defined for the public UIScrollView class:
- (void)webViewDidFinishLoad:(UIWebView *)aView {
id decelerator = [aView viewWithSelector:#selector(setScrollDecelerationFactor:)];
[decelerator setScrollDecelerationFactor:UIScrollViewDecelerationRateNormal];
}
Note that the method I'm using viewWithSelector: is a method I defined in a category of UIView. Presumably, if UIWebView changes in future, my search will return nil and this method will become a no-op.
Have considered adding # tags into your html on significant boundaries?
You could actually use native UI to implement bookmarks or a ToC for easier navigation, or simply embed links to the appropriate targets right in your html.
If you 'speed up scrolling' your app is at risk of rejection for being non-standard, since it may confuse users who are used to webviews scrolling with a standard 'feel'.
Old question but these setting or trick really helped me even in 2018.
Follow these simple coding tricks to improve Android WebView Performance:
WebView mWebView = new WebView(this);
WebSettings settings = mWebView.getSettings();
settings.setSupportZoom(true);
settings.setBuiltInZoomControls(false);
settings.setJavaScriptEnabled(true);
settings.setLoadWithOverviewMode(true);
settings.setUseWideViewPort(true);
mWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
mWebView.setScrollbarFadingEnabled(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setDomStorageEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
mWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Lazy load pages in UIScrollView

I have a UIScrollView that contains large images and am using paging to scroll between images. In order to save memory, I am loading only one image before and after the currently visible one and loading/releasing new images after a scroll has completed.
The problem occurs when one scrolls quickly and scrollViewDidEndDecelerating is not called.
How can I detect continuous scrolling?
I could check item location on every scrollViewDidScroll but this seems a bit heavy...
Perhaps a table view with custom cell content would work better for you, since it has a lot of logic built in for only loading cells that are visible as opposed to everything at once. There are lots of tutorials for how to manage table views in various ways.
My temporary solution is to cancel continuous scrolling by disabling scroll from when the user lifts the finger until scroll has completed.
-(void) scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:NO];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[scrollView setScrollEnabled:YES];
}
if you are still looking for a solution... this what I do, it works really good having a good performance, too.
- (void)scrollViewDidEndDragging:(UIScrollView *)sv willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sv
{
// load images and unload the ones that are not needed anymore
[self someMethod]
}
You have to check both scrollViewDidEndDecelerating and scrollViewDidScroll.
When to user swipes and releases it letting to have the "momentum" scroll the first will be called at the and. If the user decides to stop the scroll with his finger by tapping to the tableview (or scroll comes to the most bottom, but I am not sure about that) the second event is fired.
You can do something like this
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 1.5) / pageWidth) + 1;
}
this method is called anytime the contentoffset changed whether programmatically or not.
From this method you can check to see if 'page' is the same as your current page, if not, you know you need to load something. The trick here is making images load without holding up the scrolling of the scrollview. That is where I am stuck.
the page calculation will change to next/previous when you are half way to the next page
As per my experience, #jd291 is correct; I am using following callbacks successfully for most places
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
but, the only case when a callback is not called is that you may not had set up the delegate properly.
The WWDC 2010 has talked about this. They do a reuse of the sub scrollviews, alloc the missing image view in scrollViewDidScroll:. The video may help:
Session 104 - Designing Apps with Scroll Views

iPhone OS: Tap status bar to scroll to top doesn't work after remove/add back

Using this method to hide the status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
When setting "hidden" back to NO, the tap-to-scroll-to-top (in UIWebView, UITableView, whatever) doesn't work any more, and requires a restart of the app to get the functionality back.
Is this a bug (I filed a rdar anyhow) or have I missed a step? Should I perhaps expect this behavior since the statusBar "loses touch" somehow with the respective view?
You could try setting the ScrollsToTop property to true again after re-showing it:
[currentView setScrollsToTop:YES];
If that's not working, are you definitely only showing one view? If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored...
In iOS 5.0 you can access the scrollview property of the UIWebView
webView.scrollView.scrollsToTop = YES;
The following fix by Alex worked for me. Thanks!
((UIScrollView *)[[webView subviews] objectAtIndex:0]).scrollsToTop = NO;
Being in a hurry this fix worked great, however given more time I might've subclassed the UIWebView and accessed the protected UIScrollView member directly.
The worry I have with Alex' method is that it assumes that UIScrollView is at index zero of the subviews (encapsulation allows private members to change). Which suggests another solution still:
for (UIView* v in [webView subviews])
{
if ([v isKindOfClass:[UIScrollView class]])
{
(UIScrollView *)v.scrollsToTop = NO;
}
}
I was having a similar problem where the scroll-to-top functionality was lost. Turns out this will only work when you have only one active view at a time (within the same scroll view). In my case I had a table view and another view which would fade in/out. Adding a removeFromSuperview at the end of the animation did the trick.
The answer was in the UIScrollView.h file comments:
/*
this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
flag set or the delegeat method returns NO, the view isn't scrolled
*/
#property(nonatomic) BOOL scrollsToTop; // default is YES. if set, special gesture will scroll to top of view after consulting delegate
You can use the following code to have the UIWebView ignore scrollToTop without the extra UIScrollView:
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = NO;
I had a similar problem after playing a Youtube video within my app. scrollsToTop was still set to YES but tapping the status bar had no effect.
I finally realised that my app window was no longer the key window. After adding the following line to a UIWindow subclass (which I already had for other reasons) everything worked as it should again:
if (![self isKeyWindow]) [self makeKeyWindow];
I just ran across a similar behavior in the app I'm currently working on. In its case, if you load a YouTube video from within a UIWebView, scroll to top stops working for the rest of the application's life cycle. I kind of assume this might happen after loading the movie player as well, but haven't confirmed. That functionality has been around a lot longer and probably has fewer bugs.
When there are multiple scrollview, you can also set scrollUpToTop to NO for the others scrollview. cf:
setScrollsToTop with multiple UIScrollView classes and/or subclasses(UITableView)
I want to add my case, I add an UIWebView on an UIScrollView, as h4xxr had answered on the top:
If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored
So, I get a simply way to make it work on webView: just set the scrollView·s scrollsToTop property false.
And when tap the status bar, it won`t got intercepted by the scrollView, and the webView scrolls to the top!
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.scrollsToTop = false; //igore scrollView`s scrollsToTop
[self.view addSubview:scrollView];
UIWebView *webView = [[UIWebView alloc] init];
webView.frame = scrollView.bounds;
[scrollView addSubview:webView];

iPhone - Is it possible to hide native scrollbar in UIWebView?

I would like to hide the native scrollbar / scroller that appears when you are scrolling a UIWebView, but still keep the scrolling functionality intact. Is this possible?
Thanks in advance,
William
It seems this question needs an updated answer:
You can directly access the scroll view associated with the web view. (read-only)
in iOS 5.0 an above.
I don't think developers should be supporting anything prior to iOS 5.0 unless in exceptional circumstances.
From the Apple developer docs.
#property(nonatomic, readonly, retain) UIScrollView *scrollView
Discussion
Your application can access the scroll view if it wants to customize the scrolling behavior of the web view.
Availability
Available in iOS 5.0 and later.
Declared In
UIWebView.h
Now you can directly write something like this:
webView.scrollView.showsHorizontalScrollIndicator = NO;
webView.scrollView.showsVerticalScrollIndicator = NO;
No need to go to the subviews of the webView.
UIWebView doesn't inherit directly from UIScrollView, but you may be able to use UIScrollView properties on the UIWebView subview:
[(UIScrollView*)[webview.subviews objectAtIndex:0] setShowsHorizontalScrollIndicator:NO];
[(UIScrollView*)[webview.subviews objectAtIndex:0] setShowsVerticalScrollIndicator:NO];
No idea if this is acceptable, but it builds okay and I think it should work. Please report back if this works for you.
Also consider filing a feature request to Apple at bugreport.apple.com to add this property to a future UIWebView implementation.
Do it in that way:
for (id subview in self.myWebView.subviews) {
if ([[subview class] isSubclassOfClass: [UIScrollView class]]) {
((UIScrollView *)subview).bounces = NO;
((UIScrollView *)subview).showsVerticalScrollIndicator = NO;
((UIScrollView *)subview).showsHorizontalScrollIndicator = NO;
}
}
In Swift :
webView.scrollView.showsHorizontalScrollIndicator = false
webView.scrollView.showsVerticalScrollIndicator = false
If someone is looking for swift solution then below is the code for swift 3.0
yourWebView.scrollView.showsHorizontalScrollIndicator = false
yourWebView.scrollView.showsVerticalScrollIndicator = false
There's seems to be the beginning of an answer here :
http://discussions.apple.com/thread.jspa?threadID=1781730
If you disable user interaction, this seems to remove the scrollbar (this may be ok if the web page you display does not exceed the screen height).
A kind of javascript hack seems to be described also but I'm not mastering it :/ (you need to have access to the web page you try to display however, and this may not be your case....)