What happens to WKWebView when scrolled out of bounds - ios

I'm using a paged scroll view displaying different web views as pages. The scroll view has set clipsToBounds = false to allow the display of previous and following pages in the scroll view.
When scrolling through the pages, there is one exact moment, where the WKWebViews content becomes completely invisible. This happens exactly at the moment, where the web view leaves the bounds of the scroll view (see below). On some websites, the visible content out of the bounds is different to the actual content.
These problems only occur when using WKWebView instead of UIWebView (Everything works as intended with UIWebView).
Has someone experienced similar issues with the WKWebView and has a solution for them?
EDIT:
There is a now a sample project on Github, where you can experience the bug and play around with it:
WKWebView test project

To figure it out i think you need to use UICollectionView.
The cell should exist with WKWebView while it will be on screen.
1) You need to place it like Here
And next problem will be to make content pageble.
2) To do it right you need to add UIScrollView over your collection like Here
3) After, make your UIScrollView pageble and write in viewdidload:
collectionView.addGestureRecognizer(scrollView.panGestureRecognizer)
4) After you receive models for showing in UICOllectionView - update your contentSize of UIScrollView like
scrollView.contentSize = CGSize(width: (models.count*(cellWidth+cellIndent) + sectionIndent * 2), height: cellHeight)
5) Implement UIScrollViewDelegate
extension YourVC: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == self.scrollView {
collectionView.contentOffset = scrollView.contentOffset
} else if scrollView == self.collectionView {
self.scrollView.contentOffset = scrollView.contentOffset
}
}
}
Thats it. Happy coding =)

WKWebView is designed to be more performant than UIWebView. It will stop running some parts of a webpage or stop rendering it completely if it is not on top of the view hierarchy. The problem here is that once the webpage is out of the bounds of the scrollview, WKWebView does not consider itself to be on screen even though it is visible by virtue of setting clipsToBounds = false.
The easiest thing to do would be to use UIWebView. If for some reason you need WebKit, consider adjusting you paging code so your scrollview spans the entire view controller.

Related

UIWebView on scrollView resize the height

Ihave a UIWebView on a scroll view. I want to disable scrolling for my web view. It should scroll when the main scroll view scrolling.But my problem is when the webview content is long I cant see the bottom lines. I am resizing the webview like this.
func webViewDidFinishLoad(webView: UIWebView) {
let url = NSBundle.mainBundle().URLForResource("Script", withExtension: "js")!
let javascript = try! String(contentsOfURL: url)
self.webvw.stringByEvaluatingJavaScriptFromString(javascript)
var frame:CGRect=webView.frame
frame.size.height=1
let fittingSize:CGSize = webView.sizeThatFits(CGSizeZero)
frame.size=fittingSize
self.webvw.frame=frame
self.mainScroll.contentSize=CGSizeMake(self.view.frame.size.width, self.webvw.frame.origin.y+self.webvw.frame.size.height)
self.hud.hide(true, afterDelay: 1.0)
}
But still same. Didn't help me that. How can I solvethis pronlem.
Please help me.
Thanks
WebViews have built in scrolling. If you want to scroll through WebView's content, you would not need to use UIScrollView.
However, if you choose to do so, (I would not recommend), then you must set
you UIWebView's instance's scrollEnabled to false.
And then you will have to track you UIScrollView's content offset, and match UIWebView's scroll's content offset to that value. Doesn't seem like a good idea though. You can just scroll webview itself (without having to use UIScrollView).
And to get UIScrollView's content offset, you must override didScroll method of scrollView delegate.

Conserving Momentum from UIScrollView to inner UITableView

I'm looking to implement something very similar to the iOS Twitter Profile page, as seen here:
(source: twimg.com)
Based on what I can see, they have a UIView at the top, and a UIScrollView covering the entire view with a UITableView within the UIScrollView.
This is a tutorial on replicating it, and can be seen here: http://www.thinkandbuild.it/implementing-the-twitter-ios-app-ui/
The issue I've run into is how to maintain the momentum from scrolling on the UIScrollView vs. the UITableView. With the Twitter Profile page, you can scroll in one smooth swipe and it will move the UIScrollView up (showing the UITableView more) and any 'momentum' that is still there will start scrolling the UITableView.
I assume this must be done within the scrollViewDidScroll and check for any offset left over after reaching the bottom of the UIScrollView.
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView == self.myScrollView {
var maxOffset = 25.0
let offset = scrollView.contentOffset.y
self.myScrollView.frame = CGRectMake(0, -min(offset, maxOffset), view.frame.width, view.frame.height)
if offset - maxOffset > 0 {
self.myTableView.setContentOffset(CGPointMake(0,offset-maxOffset), animated:true)
}
}
This kind of works, although it certainly isn't smooth and doesn't appear as though it's maintaining momentum.
I don't think twitter's profile uses two scrollviews. It uses one tableview and adjusts the scroll indicator dynamically with the scrollIndicatorOffsets property. If you look closely at the scroll indicator you can see it shimmy a bit as you start to scroll up, which is consistent with this approach.

iOS 7 - Layout advice

I have the following layout in my view controller. I want to be able to scroll vertically with the header scrolling off the view and the UISegmentedControl sticking to the top of the view, beyond that the remaining scroll should be handled by the Collection View.
However I'm a bit confused as to what is the best approach to implemented this layout.
I tried a few implementations with mixed results:
UIScrollView with UICollectionView as subviews: UIScrollView as the parent view with the header, segmented control and collection views as child controls. The problem with this approach is that the nested scrolling does not seem to work correctly. To be able to scroll the UIScrollView the tap needs to be outside the CollectionView area otherwise only the CollectionView scrolls and the header and segmented control don't move.
Header and Segmented Control in Header cell: I tried another approach by using a single CollectionView. I added the header and Segmented Control as subviews of a single Header cell of the collection view. When the segmented control value was changed, I switch the data source property of the CollectionView to achieve the 3 views required for the collection view. Visually everything works perfectly. The only problem here is the race condition when switching quickly between first,second and third tabs. I load the data from a web service, if the web service takes time and is still loading the data and I quickly switch the tabs then I run into bugs where the data returned is for a different collection view than what is currently selected, a lot of out of order sync issues.
Update constant value for Autolayout Constraint: Another approach I tried is to change the constant value of the auto layout constraint applied to "Header" view. Then I added a gesture to the view controller's view to track the scroll, as the user scrolls vertically I adjust the constant of the auto layout constraint so that the "header" cell pops out of view. Again this doesn't seem to work that smoothly, but I suppose I can tweak it, but it seems sort of a hack.
Is there a better way to implement this layout?
#2 seems like a good solution — the scrolling gestures will be most consistent with what users expect, since it's all a single scroll view. (I agree that #3 sounds like a hack.) You can make the header "sticky" with some custom layout attributes.
The only problem here is the race condition when switching quickly between first, second and third tabs.
This is a common problem with asynchronous loading when views are being switched out (especially when you are loading data into individual cells, which are being reused as you scroll). It is important that upon receiving the data you always check whether the receiver is still expecting it; i.e., you should check the segmented control value before changing the backing data source. You could also:
Use separate data source objects for the different segments, having each one manage its own data fetching so they can't get mixed up.
Cancel the outstanding requests, if you can, when quickly switching tabs, to avoid unnecessary network requests.
Cache data to avoid re-fetching every time you switch tabs.
I think you want the same functionality that pinterest profile page have. To implement such functionality at easy way, you need to do following things.
Step 1 : Add UIView as tableHeaderView those who showing off while scrolling up.
self.tableHeaderView = yourView
Step 2 : Add UISegmentControl in section header view.
- (UITableViewHeaderFooterView *)headerViewForSection:(NSInteger)section{
return your_segmentcontrolView;
}
Step 3 : Add UICollectionView into first row of first section.
By implementing following way, you can got your desire functionality.
Hope this help you.
An alternative approach you could consider:
Use a UITableView to contain your UI
Create a UITableView, and set your header as the UITableView's headerView.
Use a sectionHeader to contain the segmentedControl.
Place your collectionView inside of a single UITableViewCell. Or alternatively, you may be able to use the UITableView's footerView to contain the gridView.
By using the sectionHeader, this should allow the header to scroll out of view, but then the sectionHeader will stick below the navigationBar or top of the contentView until another section comes into view (and in your case you will only have one section.)
Add Header View, Body View (Holding Segment View & Collection View) into scroll view.
Initially set userInteractionEnabled property to "NO" for collection view.
Track the insect of scroll view always.
If the y-coordinate of the scrolled insect is more than the height of header view, then set userInteractionEnabled property to "YES" so that thereafter collection view can be scrolled.
If user scroll outside the scroll view and try to bring the header view down, i.e Scroll view y-coordinate insect is less than the height of header view, then immediately change the user iteration mode of collection view and allow user to scroll the scroll view till the top.
Rather than implementing this by hand, you could use a library/cocoapod to set this up for you. This one looks like a pretty good fit: https://github.com/iosengineer/BMFloatingHeaderCollectionViewLayout
Plus, the code is open-source, so you can always modify as needed.
All I can say is that you need to subclass UIView and make it a delegate of UIGestureRecognizerDelegate and UICollectionViewDelegate, then in your UIView subclass, do the following, I can't give out anymore information on this because the code, although owned by myself, is proprietary to the point of probably enraging quite a few organizations that I've used this for, so here's the secret sauce:
CGPoint contentOffset = [scrollView contentOffset];
CGFloat newHeight = [_headerView maxHeight] - contentOffset.y;
CGRect frame = [_headerView frame];
if (newHeight > [_headerView maxHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView maxHeight];
[_headerView setFrame:frame];
} else if (newHeight < [_headerView minHeight]) {
frame.origin.y = contentOffset.y;
frame.size.height = [_headerView minHeight];
[_headerView setFrame:frame];
} else {
frame.origin.y = contentOffset.y;
frame.size.height = newHeight;
[_headerView setFrame:frame];
}
if ([_delegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
return [_delegate scrollViewDidScroll:scrollView];
}
You must subclass another UIView that is defined as the header for this custom UiCollectionView. Then, you must declare the UIView custom header view inside the custom subview of the UIView/UICollectionView delegate, and then set the header of that custom subview inside the UICollctionViewdelegate. You should then pull in this compounded subclass of UIView/UIcollectionView into your UIViewController. Oh yes, and in your layoutSubViews, make sure you do the height calculations that are passed through a double layered subclass. So, you will have the following files:
UIVew this is the delegate of UICollectionView and what I mentioned before
UIView this is a UISCrollViewDelegate and this is the header view
UIViewController that pulls in the subclassed UIView in number 1
UIView subclass of number 1 that pulls in number 2 and sets it as its header
In the number 4 part, make sure you do something like this:
- (CGFloat)maxHeight
{
if (SCREEN_WIDTH == 414)
{
return 260;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
- (CGFloat)minHeight
{
if (SCREEN_WIDTH == 414)
{
return 90;
}else if (SCREEN_WIDTH == 375)
{
return 325;
}else
{
return 290;
}
}
This will then pass through to the UIView subclass that is a compounded subclass as I already explained. The idea is to capture the maxHeight of you header in the subclass of this header UIView (number 2 above), and then pass this into the main UIView subclass that intercepts these values in the scrollViewDidScroll.
Last tidbit of information, make sure you set up your layoutSubviews in all methods to intercept scroll events. For example in number 1 above, the layoutsubviews method is this:
- (void)layoutSubviews
{
CGRect frame = [_headerView frame];
frame.size.width = [self frame].size.width;
[_headerView setFrame:frame];
[super layoutSubviews];
}
This is all I can give you, I wish I could post more, but this should give you an idea of how it's done in production environments for the big time apps you see out in the wild.
One more thing to note. When you start going down the road of intense implementations like this, don't be surprised to learn that, for example, a single view controller in an app that works with methods like I've explained will have anywhere from 30-40 custom subclasses that are either subclasses in their own right or compounded subclasses or subclasses of my own subclasses or my own subclasses. I'm telling you this so you get an idea of how much code is required to get this right, not to scare you, but to let you know that it might take a while to get right, and to not kick yourself in the butt if it takes awhile to make work. Good luck!!

UITableView dragging distance with UIRefreshControl

I'm having some trouble on implementing a UIRefreshControl on a UITableView.
Everything is working fine except the fact that I have to scroll something like 80% of the screen for the UIRefreshControl to get triggered. Sometimes I'm not even able to trigger it since there is a tab bar on the bottom of the screen, which cancels the scrolling movement when the finger reaches it.
I've looked at other apps, namely Apple's 'Mail', where the UIRefreshControl is triggered after scrolling only 30% of the screen.
What am I missing? Really need help on this one!
Thanks in advance
I had a similar problem and it's quite possible that's the same cause for you.
For me happens that I hided the scroll indicator making me unable to see the obvious cause of the problem: the UIScrollView's height is much greater than its superView...
Double check your UIScrollView's height because the "dragging distance" it's just a percentage of that height. Same goes for UITableView too, since it's a child class of UIScrollView.
EDIT:
Seems that this isn't the only way to reproduce this problem, since the required drag distance to trigger the refresher is calculated in a buggy way. Refer to this question for more info.
But in general it will happen if your UIScrollView's height is different than his parent container (e.g the screen itself).
You probably need to not use UIRefreshControl and just utilize scrollViewDidScroll (or tableViewDidScroll if a tableView) on handle your refresh accordingly since UIRefreshControl can't be modified.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height)
{
// Refresh from here
}
}
After some extensive testing and playing around with UIKit I have come to a conclusion.
TL;DR
UIRefreshControl isn't smart enough. To fix add this code in viewDidAppear or viewDidLayoutSubviews
let refreshControl = scrollView.refreshControl
scrollView.refreshControl = nil
scrollView.refreshControl = refreshControl
Test setup
Single UIViewController in a Storyboard with a UIView as its view which in turn has a UIScrollView as only subview which in turn has a single UIView as subview with top,right,bottom,left,width,height constraints equal to superview. The viewController has freeform size with 1200p height.
In the subclass of the UIViewController a UIRefreshControl is added by setting the UIScrollView#refreshControl to a new UIRefreshControl inside viewDidLoad.
When running the application in an iPhone X simulator and dragging the scrollView to perform a "Pull to refresh" one must drag considerably longer to make the refreshControl animating and send its notification it was pulled down.
The problem
One of my hypotheses was that the UIRefreshControl gets its dragging distance set once it is added to the scrollView and since AutoLayout hasen't updated the root view's subviews at viewDidLoad the scrollView has a height of 1180.0p instead of the correct 768.0p thus the refreshControl's dragging distance would be calculated for the height 1180.0p instead of 768.0p and thus become much longer than expected when running the app.
The solution
By updating the scrollView's refreshControl after the correct size for the scrollView has been set the correct dragging distance is calculated.
This update has to occur in a function where the correct size of the scrollView has been calculated, for example viewDidAppear and viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let refreshControl = scrollView.refreshControl
scrollView.refreshControl = nil
scrollView.refreshControl = refreshControl
}

Prevent UIWebView to scroll when keyboard appears

I have a UIWebView presenting a page containing input fields ().
When the user touches the field to enter data, the UIWebView is scrolled up and left to make the field stay visible for the user. Well ok.
I don't mind it is scrolled up (because everything below my form is hidden by the keyboard) but I would like to prevent the view from being scrolled left.
Is there a way ( headers in the html page, or iOS code) to prevent this behavior ?
Thanks
not beautiful but effective:
If you don't want your webView to scroll at all, you can implement the UIWebView's UIScrollViewDelegate and set the contentOffset to 0 on scrollViewDidScroll
aWebView.scrollView.delegate = self
func scrollViewDidScroll(scrollView: UIScrollView) {
scrollView.contentOffset = CGPointMake(0, 0)
}
I don't know how to prevent the scrolling entirely, but if you don't mind a little jitter, you can add this handler to the input field:
onfocus="setTimeout('window.scroll(0,0)',100)"
First you will have to capture the display of a Keyboard using UIKeyboardWillShowNotification .
Then restrict the contentSize width of the UIScrollView (of the UIWebview) to screen width which will ensure that the screen doesnot scroll horizontally.

Resources