I have a UIScrollView that I can swipe through to display new views. I have a button that I want to programmatically scroll through the views. So if I click it once, it will scroll to the next view. How can I implement this?
Here is the scrollview code
- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{
NSUInteger offset = aScrollView.contentOffset.x;
NSUInteger width = aScrollView.contentSize.width;
int anIndex = (offset == 0) ? 0 : (int)(width/(width - offset));
selectedDeal = [deals objectAtIndex:(anIndex > [deals count]- 1 ? [deals count]- 1 : anIndex)];
}
[myScrollView scrollRectToVisible:myView.frame animated:TRUE];
This will scroll so that myView becomes visible, assuming myView is added to the scroll view and it is not already visible. You need to keep track of current visible view and in the button handler you need to pass next view's frame as parameter to the above method.
[myScrollView setContentOffset:CGPointMake(self.view.frame.size.width, 0)animated:YES];
[myScrollView scrollRectToVisible:self.view.frame animated:YES];
Related
I got an gridview. Each cell within that grid is clickable. If a cell is clicked, another viewcontroller must be presented as a modal viewcontroller. The presentedviewcontroller must slide in fro the right to the left. After that, the modalviewcontroller can be dismissed with a slide. How do i achieve this? I got some images to show it :
Both views are separate viewcontrollers.
[Solution]
The answer from Matthew pointed me in the right direction. What i needed was a UIPanGestureRecognizer. Because UISwipeGestureRecognizer only registers one single swipe and i needed the view to follow the users finger. I did the following to accomplish it :
If i cell is tapped inside my UICollectionView, the extra view needs to pop up. So i implemented the following code first :
/* The next piece of code represents the action called when a touch event occours on
one of the UICollectionviewCells.
*/
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
NSString* release_id = releases[indexPath.row][0];
// Next boolean makes sure that only one new view can be seen. In the past, a user can click multiple cells and it allocs multiple instances of ReleaseViewController.
if(releaseViewDismissed) {
// Alloc UIViewController and initWithReleaseID does a request to a server to initialize some data.
ReleaseViewController *releaseViewController = [[ReleaseViewController alloc] initWithReleaseID: release_id];
// Create a new UIView and assign the height and width of the grid
UIView *releaseViewHolder = [[UIView alloc] initWithFrame:CGRectMake(gridSize.width, 0, gridSize.width, gridSize.height)];
// Add the view of the releaseViewController as a subview of the newly created view.
[releaseViewHolder addSubview:releaseViewController.view];
// Then add the UIView with the view of the releaseViewController to the current UIViewController's view.
[self.view addSubview:releaseViewHolder];
// Place the x coordinate of the new view to the same as width of the screen. Then after that get the x to 0 with an animation.
[UIView animateWithDuration:0.3 animations:^{
releaseViewHolder.frame = CGRectMake(0, 0, releaseViewHolder.frame.size.width, releaseViewHolder.frame.size.height);
// This is important. alloc an UIPanGestureRecognizer and set the method that handles those events to handleSwipes.
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipes:)];
// Add the UIPanGestureRecognizer to the created view.
[releaseViewHolder addGestureRecognizer:_panGestureRecognizer];
releaseViewDismissed = NO;
}];
}
}
Then my handleSwipes is as follows:
-(void)handleSwipes:(UIPanGestureRecognizer *)sender {
CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];
CGPoint translation = [sender translationInView:sender.view];
CGRect newFrame = [sender view].frame;
[sender setTranslation:CGPointZero inView:sender.view];
if (sender.state == UIGestureRecognizerStateChanged)
{
newFrame.origin.x = newFrame.origin.x + translation.x;
// Makes sure it can't go beyond the left of the screen.
if(newFrame.origin.x > 0) {
[sender view].frame = newFrame;
}
}
if(sender.state == UIGestureRecognizerStateEnded){
CGRect newFrame = [sender view].frame;
CGFloat velocityX = (0.3*[(UIPanGestureRecognizer*)sender velocityInView:self.view].x);
// If the user swipes less then half of the screen, it has to bounce back.
if(newFrame.origin.x < ([sender view].bounds.size.width/2)) {
newFrame.origin.x = 0;
}
// If a user swipes fast, the velocity is added to the new x of the frame.
if(newFrame.origin.x + velocityX > ([sender view].bounds.size.width/2)) {
newFrame.origin.x = [sender view].bounds.size.width + velocityX;
releaseViewDismissed = YES;
}
// Do it all with a animation.
[UIView animateWithDuration:0.25
animations:^{
[sender view].frame = newFrame;
}
completion:^(BOOL finished){
if(releaseViewDismissed) {
// Finally remove the new view from the superView.
[[sender view] removeFromSuperview];
}
}];
}
}
If you want the presented view controller to slide in from the right to the left, it cannot be a modal view. #Juan suggested one way to achieve the right to left and swipe back, but it would result in the grid view being pushed out of the way by the new view. If you would like the new view to cover the grid view when it slides in, you will either need to accept the vertical slide of modal views or write your own code to slide the view in from the right -- the latter would not actually be all that difficult*.
As for the swipe to get back, the easiest way to do that from either a modally presented view or a view you animate in yourself is to use a UISwipeGestureRecognizer. You create the recognizer, tell it what direction of swipe to look for, and you tell it what method to call when the swipe occurs.
*The gist of this approach is to create a UIView, add it as a subview of the grid view, and give it the same frame as your grid view but an x-position equal to the width of the view, and then use the following code to make the view animate in from the right.
[UIView animateWithDuration:0.3 animations:^{
slidingView.frame = CGRectMake(0, 0, slidingView.frame.size.width, slidingView.frame.size.height);
}];
I believe what you need is the following:
Create another controller that is going to handle navigation between these two (ContentViewController for example). This controller should have a ScrollView with paging enabled.
Here is a simple tutorial if you don´t already know how to do this: click here
Once the cell is clicked you have to:
Create the new ViewController to be shown.
Enable paging and add this ViewController to the ContentViewController
Force paging to this newly created ViewController
Additionally you have to add some logic so that when the user swipes to change back to the first page, paging is disabled until a new cell is clicked to repeat the process.
I have an UIScrollView with a PageControl, that show some pictures inside the view.
I'd like to create an animation, like a banner effect that changes the image after X seconds automatically.
Can do this with UIScrollView or should I use another way?
EDIT: #selector
- (void) changePage:(UIPageControl *)sender
{
if ([pageControl currentPage] < 3) {
[pageControl setCurrentPage:[pageControl currentPage] + 1];
} else{
[pageControl setCurrentPage:0];
}
}
UIScrollView is fine.
You have to set pagingEnabled on the scrollView and after a certain time, change the page selected on the scrollView.
Its simple, pagingEnabled changes current position as a page.
Change pageControl.pagingEnabled into pageControl.scrollEnabled
scrollEnabled : Scrolls like x/y axis as you scroll
pagingEnabled : Scrolls like current scroll view as a page for each scroll.
Now replace your pagingEnabled into scrollEnabled.
I have UIScrollView on which I am using 2 UIButtons. UIScrollView initial frame is (0,0,768,1024). I want when I click on first button the UIScrollView y-cordinate position will be 500, means it will scrolldown 500 positions below to its actual position.
And when I click second Button which Will show on UIScrollView, Position Y-cordinate of UIScrollView y=535, then UIScrollView Move up to Position y= 0 again.
First button click UIScrollView y = 500
Second button click UIScrollView y = 0
Any hints from experts would be very welcome.
Maybe you do not want to change the frame of the scroll view (this would just displace the whole view), but the contentOffset. This can be animated, too.
CGPoint offset = scrollView.contentOffset;
offset.y += 500;
[scrollView setContentOffset:offset animated:YES];
This may solve ur pbm
-(IBAction) clickfirstbutton
{
[scrollview setContentOffset:CGPointMake(0, 500)) animated:TRUE];
}
-(IBAction) clicksecondbutton
{
[scrollview setContentOffset:CGPointMake(0, 0) animated:TRUE];
}
If u don't want animations use
[scrollview setContentOffset:CGPointMake(0, 0) animated:FALSE];
I've got a VC with a table view. When an event occurs, I want to drop in a UIView from the top of the main view. When the user scrolls the table view, I want to re-layout the view so that the dropped in view "scrolls away". I figured I'd do this by moving the upperView's frame and resizing the table view (both in relation to the scroll offset). I've got it almost working as follows:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
if (contentOffsetY > 0) {
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
}
NSLog(#"%f", self.upperView.frame.origin.y);
}
The upper view origin starts at 0,0.
The problem is, after a little dragging back and forth, I lose the top few pixels of that upper view. It can't seem to get it's origin y back to zero. The logging reads negative values, and only gets to -1, with the most careful dragging. Has anybody done something like this? Much obliged if you can help.
It sounds like you always scroll the table view to the top when you show the drop-in view. Assuming that's the case, there is a better way to do this.
UITableView inherits the contentInset property from UIScrollView. The contentInset property defines a border on each edge of the scroll view. Each border has its own thickness, which is zero by default. These borders just affect how far the scroll view is willing to let the user scroll the content - they don't hide the content! If you set the top inset larger than zero, and give the scroll view a subview with a negative Y origin, that subview can be visible in the border, and will scroll with the rest of the scroll view's content.
So we'll set the table view's top inset to the height of the drop-in view, and add the drop-in view as a subview of the table view with its origin set to the negative of its height. This will make it fit perfectly on the screen above the first row of the table view, and it will scroll with the table view. When we detect that the drop-in view has been scrolled fully off-screen, we can just remove it from the table view and set the table view's top inset back to zero.
We'll need an instance variable that tracks the current state of the drop-in view:
typedef enum {
DropInViewStateHidden,
DropInViewStateAppearing,
DropInViewStateVisible
} DropInViewState;
#implementation ViewController {
DropInViewState _dropInViewState;
}
In my test project, I just used a button to trigger the drop-in view. Here's the action:
- (IBAction)dropIn {
if (_dropInViewState != DropInViewStateHidden)
return;
CGRect frame = self.dropInView.frame;
frame.origin.y = -frame.size.height;
self.dropInView.frame = frame;
[self.tableView addSubview:self.dropInView];
self.tableView.contentInset = UIEdgeInsetsMake(frame.size.height, 0, 0, 0);
[self.tableView setContentOffset:frame.origin animated:YES];
_dropInViewState = DropInViewStateAppearing;
}
When the table view scrolls, we check the state of the drop-in view. If it is in the “visible” state and has been scrolled off-screen, we hide it. There's a tricky bit because when we make the drop-in view visible, and scroll it onto the screen, we can receive scrollViewDidScroll: messages that would make us think the drop-in view has been hidden. That's why we start out in the DropInViewStateAppearing state, and transition to the DropInViewVisible state when we know the view has appeared.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
switch (_dropInViewState) {
case DropInViewStateHidden:
break;
case DropInViewStateVisible:
if (scrollView.contentOffset.y >= 0) {
// dropInView has been scrolled off-screen
self.tableView.contentInset = UIEdgeInsetsZero;
[self.dropInView removeFromSuperview];
_dropInViewState = DropInViewStateHidden;
break;
}
case DropInViewStateAppearing:
// When I first add dropInView to tableView and tell tableView
// to scroll to reveal dropInView, I may get a bunch of
// scrollViewDidScroll: messages with contentOffset.y >= 0.
// I don't want those messages to hide dropInView, so I sit in
// DropInViewStateAppearing until contentOffset.y goes negative,
// which means at least part of dropInView is actually on-screen.
if (scrollView.contentOffset.y < 0)
_dropInViewState = DropInViewStateVisible;
break;
}
}
Figured this out: The UITableView doesn't thoroughly message didScroll during the bounce. This is why I was missing a few pixels. Resizing during the bounce makes the bounce get mixed up and stop. This fix on my code above allows the bounce to work (by moving, not resizing the table) and makes sure the upper view is correctly placed during the bounce (when contentOffset.y <= 0).
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat contentOffsetY = scrollView.contentOffset.y;
CGFloat upperHeight = self.upperView.frame.size.height;
CGFloat fullTableHeight = self.view.frame.size.height;
CGFloat offsetY = (contentOffsetY < upperHeight)? -scrollView.contentOffset.y : -upperHeight;
if (contentOffsetY > 0) {
self.upperView.frame = CGRectMake(0, offsetY, 320, upperHeight);
scrollView.frame = CGRectMake(0, upperHeight+offsetY, 320, fullTableHeight-(upperHeight+offsetY));
} else {
self.upperView.frame = CGRectMake(0, 0, 320, upperHeight);
scrollView.frame = CGRectMake(0.0, upperHeight, 320, scrollView.frame.size.height);
}
[super scrollViewDidScroll:scrollView];
}
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.