"Infinite UIScrollView" containing UIWebViews - iphone

The background:
First of all, the background. I've created an "infinite scrollview" by using an UIScrollView with the width of three pages (0, 1 and 2) and then made sure that the UIScrollView always is centered on page 1 when not being scrolled upon. When you scroll to either page 0 or page 2 and the UIScrollViewDelegate senses that the scrolling ended, the content of all the three pages first switches in the direction that you've scrolled and then the UIScrollView instantly moves back to page 1, simulating the effect that you can continue to scroll through an endless stream of pages.
At first, I used a UITextView to display the text content of each page, but pretty soon wanted something more flexible when it comes to styling the text. I therefore ended up using a UIWebView in which I load an NSString containing the styled HTML content I wish to display. This worked perfectly, just the way I wanted it to look.
The problem:
Naturally, it takes longer time to load, parse and show a simple HTML page in a UIWebView than it takes to just load a static text in a UITextView. Because of this, when you've scrolled in whatever direction, and the scroll view automatically moves back and switches the content of the pages, you can hint the old content for a tenth of a second before the new one shows.
My solution (that did not work):
I searched for the UIWebViewDelegate in the documentation and found that there's an event when the page is done loading. I hooked this up to do this:
User scrolls from page 1 to page 2.
UIScrollViewDelegate calls scrollViewDidEndDecelerating, telling page 1 to switch it's content to the same content currently being displayed at page 2.
UIWebViewDelegate of page 1 calls webViewDidFinishLoad, telling the UIScrollView to move back to page 1 and then switch the content of page 0 and 2.
In my head this would fix this problem, but apparently it's still not enough. Therefore, I ask you for help. Any ideas? Might be worth mentioning that I've had NSLog in all the methods and made sure that they're being called. If there's anything else, I'd be glad to answer any questions you have.
Thanks in advance!
View Controller containing UIScrollView:
// Sense when the UIScrollView stops accelerating
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
if(pScrollView.contentOffset.x > pScrollView.frame.size.width) {
[self switchToNextPost];
} else if (pScrollView.contentOffset.x < pScrollView.frame.size.width) {
[self switchToPreviousPost];
}
}
// Update the currentPost variable and switch page 1
- (void)switchToNextPost {
[prefs setInteger:[[self getNextKey:nil] intValue] forKey:#"currentPost"];
[self loadPostWithKey:[self getCurrentKey] atPage:[NSNumber numberWithInt:1] withViewController:currentPage];
}
// Update the currentPost variable and switch page 1
- (void)switchToPreviousPost {
[prefs setInteger:[[self getPreviousKey:nil] intValue] forKey:#"currentPost"];
[self loadPostWithKey:[self getCurrentKey] atPage:[NSNumber numberWithInt:1] withViewController:currentPage];
}
// Custom delegate function. Scrolls UIScrollView to page 1 and then loads new content on page 1 and 3
- (void)webViewFinishedLoading {
[pScrollView scrollRectToVisible:CGRectMake(pScrollView.frame.size.width, 0, pScrollView.frame.size.width, pScrollView.frame.size.height) animated:NO];
[self loadPostWithKey:[self getPreviousKey:nil] atPage:[NSNumber numberWithInt:0] withViewController:previousPage];
[self loadPostWithKey:[self getNextKey:nil] atPage:[NSNumber numberWithInt:2] withViewController:nextPage];
}
View Controller for the subviews being displayed as separate pages in the UIScrollView
// The delegate shown in previous code
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[[self delegate] webViewFinishedLoading];
}

what you can do , i think, is
populate all three pages with relevant content from the beginning.. (you are probably doing that already)...
when scrolling starts, the next (or previous) page shows up as you scroll..
when scrolling finishes, move the scroll content view to center as you already do, and instead of changing text in each web view, change the frames of the views so that relevant web view (i.e. the one user has scrolled to) remains in the center, and other two are adjusted on both sides
change the content of the both web views (which are not visible) so that they are ready for next scroll movement..
this would still fail in case of fast scrolling.. but because of the lag in loading content i any approach would file there (i think)
so you can probably make all web views blank (load nothing) and show an activity indicator in case of fast scrolling..

Related

Assertion failure in UIQueuingScrollView didScrollWithAnimation:force:

I've got a UIPageViewController set up paging my ImageViewController.
The ImageViewController contains a UIScrollView with a UIImageView inside. Nothing else.
I'm testing at the moment with 3 "items" in my datasource for the UIPageViewController (i.e. three pages).
It all works fine and I can scroll and zoom and then page for about 30 seconds and then suddenly I get this warning...
*** Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /SourceCache/UIKit/UIKit-2372/_UIQueuingScrollView.m:778
I've got no idea where to start debugging it though as it doesn't point to any of my code and there isn't any of my code in the stack or anything.
Can someone give me a pointer as to where to start debugging this.
EDIT
I've done a bit more testing. It seems to happen if the scrollView is decelerating (i.e. after a flick) and then I try to transition the PageViewController to another ViewController as the scrollview is still moving.
The app crashes about 20% of the way through the transition to the next page.
EDIT 2
The error seems to stop on the line _cache_getImp (not sure if that's a capital i or lowercase L).
EDIT 3
This gets better. I just downloaded Apple's PhotoScroller sample app to see if they got round the problem a different way. Well, no, they didn't. The sample app crashes in exactly the same way mine does! You have to zoom and scroll and transition pages at the same time to make it more likely to crash but it happens on it's own too it just might take longer to happen.
Came up with a solution! In my case I have a UIPageViewController with UIPageViewControllerTransitionStyleScroll as well as next buttons that allow the user to advance through my viewpager by tapping. I am getting this crash when the user presses a next button and drags around slightly before releasing their finger (still within the frame of the button). It appears that the dragging within the button is interfering with UIPageViewController's pan gesture recognizer in this case, and that's what causes the crash.
While it's pretty unlikely that the user will get in this state, I've come up with a relatively simple solution the prevents my app from crashing if it does happen. I have a boolean that represents if the user is in a valid state to move to the next screen and set it to YES on touch down, then to NO if the user drags anywhere inside my button. Then, on touchUp (nextPressed) I check the boolean before moving my UIPageViewController programatically.
- (IBAction)touchDown:(id)sender
{
self.shouldAdvanceToNextScreen = YES;
}
- (IBAction)touchDragInside:(id)sender
{
self.shouldAdvanceToNextScreen = NO;
}
- (IBAction)nextPressed:(id)sender
{
if (self.shouldAdvanceToNextScreen) {
UIViewController *initialViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"TutorialScreen2"];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
}
The downside is that nothing will happen even though the user still released their finger within the button frame. However, I prefer this over a crash and see this as a pretty rare edge case regardless. I'd expect the user would just tap again - this time without a tap & drag - and move forward successfully.
I'd welcome any ideas on taking this a step further and preventing the clash between the touch drag and the UIPageViewController altogether.
Have you tried disabling bouncing in the UIScrollView? That worked for me and solved my other problem too alluded to in my comment above.
I have been fighting with this all day.
My Conclusions:
If you have a scrollview as the showing ViewController and you are delegate of the scrolls: you're in trouble. Even with the PageViewController configured with Horizontal scrolling, a vertical scrolling on your view will trigger an event. -> this does not cause trouble if: you scroll back to the top of your view before (Not sure how to fix this).
There are good StackOverflow threads like this one.
Basically the solution is:
1 [yourView setViewControllers:yourControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
2 Use UIPageViewControllerTransitionStylePageCurl as the transition Style.
This fixed most of my problems.
If someone has a solution for the delegation problems with the scrolling, will be most wellcome
I had a similar problem. My setup was a UIPageViewController and I was loading view controllers with an UIImageView inside. When interacting while the UIPageViewController was scrolling I got the same crash log.
I fixed it by creating a UILongPressGestureRecognizer and add to the UIPageViewController's scroll view.
Created my own subclass of UIPageViewController (Solution is specific for my case but can easily used as a generic solution)
Find the inner UIScrollView of the UIPageViewController
- (UIScrollView *)findScrollView
{
UIScrollView *scrollView;
for (id subview in self.view.subviews)
{
if ([subview isKindOfClass:UIScrollView.class])
{
scrollView = subview;
break;
}
}
return scrollView;
}
Add the long tap gesture recognizer to the inner scroll view and point its action to a method or nil
/**
* On tap-hold the page view controller crashes as soon as it pages to a new view controller.
* Setting a long press gesture to ignore the hold.
*/
- (void)listenForLongPressGestureOnScrollView:(UIScrollView *)scrollView
{
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:nil];
[longPressGestureRecognizer setMinimumPressDuration:0.5];
[scrollView addGestureRecognizer:longPressGestureRecognizer];
}
It surely looks like a bug in iOS 8 and 9.
(and the "answers" down below attempt to work around that)
Feel free to file a ticket on bugreport.apple.com
Do be sure to specify how to crash PhotoScroller
Include all iOS versions you've seen it crash on
To date I've seen:
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIPageViewController.m:2028
Assertion failure in -[_UIQueuingScrollView _didScrollWithAnimation:force:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/_UIQueuingScrollView.m:785
(lldb)
I'll keep the list updated (Despite the downvoting, however massive).
Stay tuned.
It does not look like our bug.
UPD 2016 09 27 (on Xcode 8 now):
Assertion failure in -[XXX.TrackingPageViewController queuingScrollView:didEndManualScroll:toRevealView:direction:animated:didFinish:didComplete:], /SourceCache/UIKit/UIKit-3347.44.2.2/UIPageViewController.m:1875
awesome

UIWebView delegate not working the second time

I have an rss feed which returns a json containing a title, short text and full-size text. The title and part of he short text is displayed in a TableView.
I have 3 controllers with 3 views.
View 1 displays a TableView containing articles from the json( the title and short text ), each article has a "Read more" button, in the IBAction of the button, i push a viewcontroller(2) into the navigation controller, the controller has a UIWebView(second View), which loads a html string. The string contains links, when the user taps a link, i want to present a modal view controller with another WebView(3), this time using the request(keep in mind that both WebViews have separate controllers and nibs).
The delegates are set properly on both of them, i tried both from code and using visual tools. In the delegate method shouldStartLoadWithRequest in the first WebView i check to see if the request parameter contains "http" or "www", because only then i want to call the second WebView. The second WebView works well the first time, the view is presented modally fine, but when i dismiss it and tap the same link or another, the shouldStartLoadWithRequest delegate method is not called, but the link loads in the same WebView(the first one).
I googled this but didn't find a similar case to mine, or a solution. I don't have that much experience with ios app development, but a colleague is pretty good, and we didn't manage to find a solution to this problem. We took the code apart and analyzed it, but we didn't find a potential source for this problem, and he has a separate project where he has the same problem, only he uses only one controller and one WebView (our controllers and nibs where created independantly). We were pretty thorough with the research and the code examination
It seems that the solution was quite easy. The first WebView, aparently looses the connection to it's delegate, so, in the second WebView controller just add
- (void)viewWillDisappear:(BOOL)animated {
[self.parentViewController viewWillAppear:YES];
}
and in the first controller of the first WebView:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
webView_.delegate = self;
}
I hope this helps someone

TableViewCells deleted after leaving the page

I have a UITableView and you can add/delete cells if you like. There are 3 pages. The second page allows the user to add cells to the table view, which is on the third page. The first page is just a navigation page. If i add any number of cells to the table view, i can see them on the 3rd page fine! I can return to the second page, and then return back to the 3rd page. And the cells will still be there, but if i go to the 2nd page, then the 1st, and back to the table view, all the cells are gone!! How can i fix this? My Code for navigating through the pages is as follows:
-(IBAction)button1:(id)sender{
RootViewController * black = [[BlackFacePlateViewControlller alloc]init];
black.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:black animated:YES];
[black release];
}
and:
- (IBAction)back:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
Thanks in advance!
Kurt, you have to understand something about persistence on not just the iPhone, but any system. You most likely have an array that is loaded into the table, but when the controller class containing that array is dismissed (as you have it with [self dismissModalViewControllerAnimated:YES];), the chance is more likely than not that dealloc (or in the case of ARC, a mass release of your objects) will be called on that controller, and your array will be set to nil and reclaimed but the system. You need a storage mechanism, whether that's a plist, a specific file format, or XML, you need some means of retrieving the datasource array even after it's been destroyed.
I myself am now huge advocate of the plist route, as they're just so convenient and easy to use, and they can be edited with absolutely massive amounts of data with little side effects.

UIKeyboardWillShowNotification issue

This is the flow of my app. 1st view -> 2nd View -> 3rd View
On 3rd view, when I click on any row of tableView one UIView gets displayed, which has one textField which accepts only numbers. For this I have implemented UIKeyboardWillShowNotification and displayed a UIButton for 'dot' button on the down-left corner of keyboard (For this I have created two images and sets that image to UIButton object).
My problem is, After using this custom keyboard(for 2-3 times), when I redirect form 3rd view to 1st View, This UIButton (with dot image) is appearing on 1st view. I have used default keyboard at there but this image not getting away.
While moving from 3rd view to 1st view, I m removing Observer for the keyboard notification which I have registered earlier & also I m checking wether,
if ([dotButton retainCount] > 0) {
[dotButton release];
dotButton = nil;
}
I have allocated dot button only once in viewDidLoad.
I m using popToRootViewController method to go back to 1st view from 3rd view.
I dont want to display this dot button on my 1st view. How can I do this.
Follow this steps
1) First make the doneButton an instance varible of your class, this will help u maintain the reference to the button
2) Add this code at the beginning of ur keyboardWillShow:(NSNotification *)note method
if(dotButton){
[dotButton removeFromSuperview];
dotButton = nil;
}
and one more thing implement UIKeyboardWillHideNotification method with NSNotificationCenter and perfrom step 2 over there.
I assume when you created the dotButton, you are calling addSubview: to put it on the screen.
When you want to remove it, you need to remove by calling [dotButton removeFromSuperview]. If you just release it, it will still be retained by the view that is containing it.
Finally, you should NEVER be calling retainCount unless you are debugging something. I've been writing Objective-C code for years and I have NEVER used retainCount, even when I was doing weird runtime stuff.
The rule is simple. If you need an object to stick around, you call retain. When you are done with it, you call release. If somebody else has retained it that's none of your business.

UIScrollView retrieve data from the server one page at a time

I have a UIScrollView which scrolls horizontally, I have lot of data to be displayed on that UIScrollView, when i create the uiscrollview I know exactly the size of the content that I would like to display so I create the frame accordingly.
I get the data from the server that i have to populate in the uiscrollview. I would like to retrieve one page of data at a time from the server, also I would like to retrieve the second page only when user has scrolled through the end of the first page, that way I will avoid pulling unnecessary data from the server.
Any suggestions on how to do this?
You should use the protocoled method - (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.y == _currentOffsetHeight)
{
// Do what you want
}
}
See: iOS Developer Library: PageControl Sample Code
When you detect scrolls via the delegate methods, you can start loading data for the next views into memory. Also, be sure to keep the previously viewed view in memory. So for example (if the 5th view is on the screen):
0: not in memory
1: not in memory
2: not in memory
3: not in memory
4: in memory
5: in memory
6: in memory
7: not in memory
8: not in memory
Once you detect the user has arrived at page 5, for example, you start loading the next page. You might even want to consider loading +2 pages ahead, in case the scrolling occurs faster, or the user scrolls past a view. The scrollview also bounces, so if you're missing content on one side it might look bad.
Good luck!