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.
When the scrollViewDidEndDecelerating is called I wanted to display the image name in the active page.
How do I solve this? Why does this happens?
scrollViewDidEndDragging will call after each scroll complete
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{}
scrollViewDidEndDecelerating don't call in each drag (if you don't drag with acce speed after draged stop it will don't never call this func)
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{}
scrollViewDidEndScrollingAnimation if you want move the scroll view use api not with drag, this api will called.
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{}
Im doing the same thing with a scroll view - lazy loading of images. We actually had to load a thumbnail that is stretched (making a blurry low res version of the image) and then load the real image when the page is actually being displayed 100% in view.
What I had to do was use the following code in the scrollViewDidScroll:scrollView.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (!_isCurrentlyRotating) {
CGFloat pageWidth = _scrollView.frame.size.width;
int page = floor((_scrollView.contentOffset.x - pageWidth / 2) / pageWidth)+1;
if (_currentPage != page)
[self setCurrentPage:page];
}
}
The above calculates the page that is going to be displayed (as your scrolling). It will actually change the current page number when the page view is 50% displayed. This can be used in conjunction with the page control to highlight the current page dot.
Using the above, (with paging enabled on the scroll view) when the user uses their finger to change the page (the normal behavior) scrollViewDidEndDecelerating:scrollView is called when the page is 100% displayed. By 100% I mean you can only see one page not part of one part of another.
But we have a 'Start Over' button at the end of the scroll view that calls my scrollToPage: function.
- (void) scrollToPage:(int)page {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * page;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
[self.scrollView scrollRectToVisible:frame animated:YES];
}
Using scrollRectToVisible:animated: method does not trigger the scrollViewDidEndDecelerating: method so I simply added the sharpen method (which simply loads te high res image in the image view) call to both methods so that it will be called if the user scrolls or if the start over button is pressed.
- (void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
[self sharpenImageView];
}
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self sharpenImageView];
}
I hope this helps!
Check if your UIScrollView's delegate is set or not, or was set to nil.
Normally set in this way scrollView.delegate = self.
scrollViewDidEndDecelerating: is not meant to do your page layout in. You should implement scrollViewDidScroll: and calculate the currently visible page(s) based on the contentOffset of the scroll view. If the visible range has changed, add views for the pages that were previously not visible and remove those that are no longer visible.
Sometimes just clean your project and re-run
Shift & Cmd & K
Shift & Cmd & Alt & k
Related
For example in Tweetbot's iPhone app. When you open up the app and new tweets come in, it will just be appended to the top of the UIScrollView and the current tweet you see did not get refreshed. How can I achieve the same thing effect?. Say I have a UIScrollView with a UIView added to the UIScrollView starting at origin 10,10.
I then downloaded a few items and I want to put it at 10,10.. so I basically need to shift this old item at 10,10 down right? If I do so then during that shifting user's will see it shifted, which is not the effect I want. Where as in Tweetbot's app it seems that nothing is being shifted around, it's just that you grow the area above the 10,10 and append new stuff's there.
How do I do this?
Basically I wanted to implement the insertRowAtIndexPath in a UIScrollView.
Will restate the question this way: how to add content to the top of a UIScrollView without moving the content that's already there (relative to it's current offset).
If this is the right question, the right answer is to do the add and shift down just as you suggested, but then scroll by the same height as added content, giving the illusion that the old content didn't move.
- (void)insertRowAtTop {
// not showing insert atIndexPath because I don't know how you have your subviews indexed
// inserting at the top, we can just shift everything down. you can extend this idea to
// the middle but it requires that you can translate from rows to y-offsets to views
// shift everything down
for (UIView *view in self.scrollView.subviews) {
if ([view isKindOfClass:[MyScrollViewRow self]]) {
MyScrollViewRow *row = (MyScrollViewRow *)view; // all this jazz so we don't pick up the scroll thumbs
row.frame = CGRectOffset(row.frame, 0.0, kROW_HEIGHT); // this is a lot easier if row height is constant
}
}
MyScrollViewRow *newRow = [[MyScrollViewRow alloc] initWithFrame:CGRectMake(0.0,0.0,320.0,kROW_HEIGHT)];
// init newRow
[self.scrollView addSubview:newRow];
// now for your question. scroll everything so the user perceives that the existing rows remained still
CGPoint currentOffset = self.scrollView.contentOffset
self.scrollView.contentOffset = CGPointMake(currentOffset.x, currentOffset.y + kROW_HEIGHT);
}
If you set the contentOffset without animating there wont be a visible scrolling animation. So if your new view is 200 points tall you can set the origin of the new view at (10,10) and the old view at (10,210) and set the contentOffset of the scroll view to (10,210) you should achieve the effect you intend. You'll also need to increase the contentSize of your scroll view to be big enough for all of the content it contains.
I'm trying to achieve an effect similar to what twitter does when you bring up the new tweet dialogue. They drop down a view from the top, shrinking the other views but still allowing you to interact with all of them if you dismiss the keyboard. It obviously isn't a modal view, but I can't put my finger on what the starting point to do something similar to this would be.
It looks straight-forward as a view hierarchy, just cleverly dressed with art. The bottom is the regular interface, above is a view containing the UITextView with some nice notepad art around it.
One way to achieve this is to hang two subviews under the view controller's main view. The first child contains the notepad art and the text view. It's positioned at 0,-NOTEPAD_HEIGHT. The second child is at 0,0 and occupies the entire parent view's bounds.
The compose button tells the text view to become first responder, and when editing begins...
- (void)textViewDidBeginEditing:(UITextView *)textView {
[self setNotepadHidden:NO animated:YES];
}
I often make a show/hide method of the following form to rearrange things like this ...
- (void)setNotepadHidden:(BOOL)hidden animated:(BOOL)animated {
NSTimeInterval duration = (animated)? 0.3 : 0.0;
CGFloat offset = (hidden)? -NOTEPAD_HEIGHT : NOTEPAD_HEIGHT;
[UIView animateWithDuration:duration animations:^{
self.firstChild.frame = CGRectOffset(self.firstChild.frame, 0.0, offset);
self.secondChild.frame = UIEdgeInsetsInsetRect(self.secondChild.frame, UIEdgeInsetsMake(offset, 0, 0, 0));
}];
}
Call with ...Hidden:YES whenever you want to hide it again. Make sure that the second child's subviews have autoresizing behavior setup so that they do the right thing when their parent shrinks.
I often find the need for one like this, also...
- (BOOL)isNotepadHidden {
return self.firstChild.frame.origin.y < 0.0;
}
Hopefully, that's a good start.
I have a table that I'm doing some special loading for. The user starts scrolled to the bottom. When the user scrolls near the top, I detect this through scroll view delegate methods, and I quickly load some additional content, and populate more of the top part of the list. I want this to look seamless, like an "infinite scroll" upward. To do this, I have to set the content offset, so that the user doesn't see the table "jump" upward. When I scroll slowly, this works perfectly. When I scroll quickly, so that the table is decelerating, the content offset I set is ignored. Here is the code I'm using:
CGFloat oldHeight = self.tableView.contentSize.height;
CGFloat oldOffset = self.tableView.contentOffset.y;
self.tableContentsArray = newTableContentsArray;
[self.tableView reloadData];
CGFloat newHeight = self.tableView.contentSize.height;
CGFloat newOffset = oldOffset + (newHeight - oldHeight);
self.tableView.contentOffset = CGPointMake(0, newOffset);
So if I scroll up quickly with a table 100px high and hit the top while decelerating, I load more data, get a new table height of, say, 250px. I set the offset to 150. However, since it's still decelerating, the Apple code leaves the offset set to 150 for .1 seconds or something, then goes to the next calculated offset for deceleration, and sets the offset to 0, which makes it look to the user like they just skipped 150px of content, and are now at the new top of the list.
Now I'd LOVE to keep the acceleration from the list, so that it keeps going up for a while, slows down, and ends up somewhere around 120px offset, just like you would expect. Question is, how?
If I use [self.tableView setContentOffset: CGPointMake(0, newOffset) animated: NO]; it stops the content offset from being ignored, but stops the list dead.
We had an interesting situation like this at work a few months back. We wanted to use the UITableViewController because of it's caching, loading, and animations, but we wanted it to scroll horizontally (in your case it would be scroll upward). The solution? Rotate the table, then rotate the cells the other direction.
In your case, the code would look like this:
#define degreesToRadian(x) (M_PI * (x) / 180.0) in the header
- (void) viewDidLoad {
[super viewDidLoad];
self.table.transform = CGAffineTransformMakeRotation(degreesToRadian(-180));
...
}
Then rotate the cell, so it appears in the right orientation for the user
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
cell.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
}
Now you can postpend your cells like any normal table, and they'll be added on top instead of the bottom.
I am afraid you are trying to do things too complicated without really understanding them.
Do you really have an infitite table or a very long table? I think it would be possible to just tell the table it has 1000000 cells. Each cell is loaded when you need it. And that's basically what you want to do.
I'm a beginner when it comes to page control, and that's why I'm not sure 100% if my title agrees with what I want. I want to make a UIPageControl that when the user swipes on the screen, the view would switch over to another view and the UIPageController at the bottom of the screen would update itself. Also, to make my request even more confusing, I want a tab bar at the bottom of the screen that would stay put as the views change.
A great example of this is The Iconfactory's Ramp Champ:
http://img.slidetoplay.com/screenshots/ramp-champ_5.jpg
The bar at the bottom stays put while the rest of the items on the screen moves. What would be the easiest way to do this?
EDIT: I know I have to use a UISrollView, I just don't know how to go about implementing it...
I believe what you're looking for is actually a UIScrollView with pagingEnabled set to YES. You can leave the scrollview as a view above a regular UITabBar. You'll use a UIPageControl to get the little dots. You can update it programmatically when the UIScrollView scrolls to a page by implementing an appropriate delegate method of the scroll view, maybe -scrollViewDidScroll:.
Assume you have two ivars: scrollView and pageControl. When you know how many pages your scroll view will have, you can set the contentSize of scrollView. It should be a multiple of the scrollView's bounds. For example, if the number of pages is static you can hardcode it in your -viewDidLoad...
- (void)viewDidLoad {
// Any other code.
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.bounds.size.width * 3, scrollView.bounds.size.height); // 3 pages wide.
scrollView.delegate = self;
}
Then, to update your little dots...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.bounds.size.width;
NSInteger pageNumber = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = pageNumber;
}
You need to use a UIScrollView
Assuming you have a named ivar called scrollView
int amountOfFrames = 10;
scrollView.pagingEnabled = TRUE;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * amountOfFrames, scrollView.frame.size.height);
scrollView.delegate = self;
You will then need to implement the required delegate methods, so that you can update your page control
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
}
You need to place whatever content you want to be scrollable inside these scrollview, ideally lazyload into it, if the content you will displaying will require a lot of heap memory, use the scrollviewDidScroll to remove and add content at the required positions
I have the UIScrollView with pagingEnabled set to YES, and programmatically scroll its content to bottom:
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.y = scrollView.contentSize.height - scrollView.frame.size.height;
[scrollView setContentOffset:contentOffset animated:YES];
it scrolls successfully, but after that, on single tap its content scrolls up to offset that it has just before it scrolls down. That happens only when I programmaticaly scroll scrollView's content to bottom and then tap. When I scroll to any other offset and then tap, nothing is happened.
That's definitely not what I'd like to get. How that should be fixed?
Much thanks in advance!
Timur.
This small hack prevents the UIScrollView from scrolling when tapped. Looks like this is happening when the scroll view has paging enabled.
In your UIScrollView delegate add this method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
scrollView.pagingEnabled = self.scrollView.contentOffset.x < (self.scrollView.contentSize.width - self.scrollView.frame.size.width);
}
This disables the paging when the scroll view reaches the right end in horizontal scrolling (my use case, you can adapt it to other directions easily).
I just figured out what causes this problem, and how to avoid it. If you having pagingEnabled set to YES on a scroll view, you must set the contentOffset to be a multiple of the scroll view's visible size (i.e. you should be on a paging boundary).
Concrete example:
If your scroll view was (say) 460 pixels high with a content area of 920, you would need to set the content offset to EITHER 0 or 460 if you want to avoid the "scroll to beginning on tap" problem.
As a bonus, the end result will probably look better since your scroll view will be aligned with the paging boundaries. :)
The following workaround did help (assume that one extends UIScrollView with a category, so 'self' refers to its instance):
-(BOOL) scrolledToBottom
{
return (self.contentSize.height <= self.frame.size.height) ||
(self.contentOffset.y == self.contentSize.height - self.frame.size.height);
}
Then, one should sometimes turn pagingEnabled off, just at the position where scroll view reaches its bottom. In the delegate (pagingEnabled is initialy on of course, since the problem occurs only when it is enabled):
-(void) scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.pagingEnabled == YES)
{
if ([scrollView scrolledToBottom] == YES)
scrollView.pagingEnabled = NO;
}
else
{
if ([scrollView scrolledToBottom] == NO)
scrollView.pagingEnabled = YES;
}
}
This seems to be a bug:
UIScrollView doesn't remember the position
I have tested this on iOS 4.2 (Simulator) and the issue remains.
When scrolling a ScrollView I would suggest using
[scrollView scrollRectToVisible:CGRectMake(0,0,1,1) animated:YES];
Where the rect is the position you're after. (In this case the rect would be the top of the scrollview).
Changing the content offset is not the correct way of scrolling a scrollview.