How can I animate the road in iPhone Xcode? - iphone

I am going to animate the road, simply calling 1 image again and again from top to bottom. Can anyone suggest how this is possible?

You'll want to look at a UIScrollView. Put a UIImageView in it, then use the following code:
-(void)scroll{
CGPoint currentPoint = scrollView.contentOffset;
CGPoint scrollPoint = CGPointMake(currentPoint.x, currentPoint.y + 1);
[scrollView setContentOffset:scrollPoint animated:NO];
if(scrollPoint.y == scrollView.contentSize.height+5)//If we've reached the bottom, reset slightly past top, to give ticker look
{
[scrollView setContentOffset:CGPointMake (0,-125)];
}
}
Run that code with a looping NSTimer and you'll be set :)

Related

How to make an UICollectionView with infinite paging?

I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/

Zooming a background image without zooming the overlay

I'm a beginner to the iOS development, and I'm trying to display an image (a map in fact) which need to be zoomable and pannable. My problem is that I don't know how to add an overlay which will follow the pan to always represent the same location, but won't be scaled by the zoom. There will be several overlays, and each one must be clickable.
To zoom and pan the image (map), I added my UIImageView in a UIScrollView and it works great, but I have no idea how to add this feature.
I found this thread but it is not really the same problem since his overlay is static :
UIScrollview with two images - Keeping 1 image zoomable and 1 image static (fixed size)
I've developped the same application in android, and to make this work I was converting a pixel of the map in screen coordinates thanks to the transformation matrix, and I overrode the onDraw method to display it, but I don't know how to do the same on iOS, and I don't know if this is the better solution.
Thanks for any help.
Ok so I found a way to do it, if it can helps anybody :
I added my overlay in the scrollView, with the background image (the map).
+CustomScrollView
----> UIImageView (map)
----> OverlayImageView (overlay)
In order to zoom, the custom scrollview need a delegate with the following methods :
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
//The background image with the map
return mapView;
}
//When a zoom occurs, move the overlay
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIImageView* overlayView = [scroll.subviews objectAtIndex:1];
float x;
float y;
float width;
float height;
//keep the width and height of the overlay
width = overlayView.frame.size.width;
height = overlayView.frame.size.height;
//Move it to stay over the same pixel of the map, and centers it
x = (self.overlay.point.x * scroll.zoomScale - width/2);
y = (self.overlay.point.y * scroll.zoomScale - height/2);
overlayView.frame = CGRectMake(x,y,width,height);
}
With this, we are saying that the zoom only occurs on the background image, but as the overlay is in the UIScrollView, it pans with it. So the only thing we need to care is to move the Overlay when the zoom change, and we know it with the scrollViewDidZoom method.
To handle the touch events, we override the touchEnded:withEvent: of CustomScrollView and we forward it to the overlay if there is only one tap. I don't show the OverlayImageView since it only override this same method (toucheEnded:withEvent:) to handle a touch on it.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
// Coordinates in map view
CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]];
//forward
if(touch.tapCount == 1){
OverlayImageView* overlayView = [self.subviews objectAtIndex:1];
CGPoint newPoint = [touch locationInView:overlayView];
BOOL isInside = [overlayView pointInside:newPoint withEvent:event];
if(isInside){
[overlayView touchesEnded:touches withEvent:event];
}
}
// zoom
else if(touch.tapCount == 2){
if(self.zoomScale == self.maximumZoomScale){
[self setZoomScale:[self minimumZoomScale] animated:YES];
} else {
CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];
[self zoomToRect:zoomRect animated:YES];
//[self setZoomScale:[self maximumZoomScale] animated:YES];
}
[self setNeedsDisplay];
}
}
Hope this will help.
You can put an imageView on top as an overLay and set its userInteractionEnabled property to NO. Then you have to pan it programmatically.

How to test if a point is in a view

I have a UIImageView and I have a CGPoint on the screen. I want to be able to test that point to see if it is in the UIImageView. What would be the best way to do this?
CGPoint is no good with a reference point. If your point is in window's coordinates then you can get it using
CGPoint locationInView = [imageView convertPoint:point fromView:imageView.window];
if ( CGRectContainsPoint(imageView.bounds, locationInView) ) {
// Point lies inside the bounds.
}
You may also call pointInside:withEvent: method
if ( [imageView pointInside:locationInView withEvent:nil] ) {
// Point lies inside the bounds
}
Tested in Swift 4
view.frame.contains(point)
if(CGRectContainsPoint([myView frame], point))
where point is your CGPoint and myView is your UIImageView
I'll assume you have a full-screen window (pretty reasonable, I think). Then you can transform the point from the window's coordinate space to the UIImageView's using:
CGPoint point = ...
UIWindow window = ...
UIImageView imageView = ...
CGPoint transformedPoint = [window convertPoint:point toView:imageView];
Then, you can test if the point is in the image view's frame as follows:
if(CGRectContainsPoint(imageView.frame, transformedPoint))
{
// do something interesting....
}
In Swift 3
let isPointInFrame = UIScreen.main.bounds.contains(newLocation)

PagingEnabled for multiple pages in UIScrollView

Edit: See the answer below.
I finally give up and come here to ask you for my problem...
I'm using a UIScrollView for a scrolling menus with little icons.
On each page, with paging enabled, there's an icon in the center, and 2 and a half other visible icons on the left and right. I can move from one icon to its neighbour, and that is fine, but the point is that if I do a fast scrolling, it will not move from more than 3 icons, which is the width of the screen.
What I would want is to be able to scroll on more than 3 icons, and that the magnet behaviour is only triggered when it's slowing down.
I've tried to schedule the scroll view to calculate its velocity, and set the pagingEnabled attribute to NO when it's moving fast and YES again when it's slowing down, but as soon as it is set to YES, the view comes back very fast at its original position, as if it was not detecting that I had brought it to a new page. Would anyone know why it does this? And if I have a way to tell the view "ok, now the paging is enabled but look, you're 15 pages later. Just center on the current page, don't come back at the beginning."
Here's my update function (if it can help):
-(void)update:(ccTime)dt
{
float velocity = fabsf((self.previousOffset-self.scrollView.contentOffset.y)/dt);
self.previousOffset = self.scrollView.contentOffset.y;
CCLOG(#"Velocity: %f", velocity);
if(self.scrollView.pagingEnabled)
{
if(velocity > 100)
{
self.scrollView.pagingEnabled = NO;
}
}
else
{
if(velocity < 100)
{
self.scrollView.pagingEnabled = YES;
}
}
}
I finally found a solution, which was pretty obvious, but that I did not see at the beginning, by using setContentOffset on the scrollView.
Here is the new update function:
-(void)update:(ccTime)dt
{
float velocity = 1000;
if(self.previousOffset)
{
velocity = fabsf((self.previousOffset-self.scrollView.contentOffset.y)/dt);
}
self.previousOffset = self.scrollView.contentOffset.y;
if(velocity < 300)
{
CGSize screenSize = [[CCDirector sharedDirector] winSize];
float halfScreen = screenSize.width/2;
CCLayer *panel = (CCLayer *)[self getChildByTag:1];
SQScrollViewMenu *menu = (SQScrollViewMenu *)[panel getChildByTag:1];
SQMissionItem *currentItem = (SQMissionItem *)[menu getChildByTag:currentPage];
float contentOffsetY = [self.scrollView contentOffset].y;
CCLOG(#"Currentpage: %i ; currentoffsetY: %f", currentPage, contentOffsetY);
float distance = contentOffsetY + [currentItem position].x - halfScreen + panel.position.x + menu.position.x + panel.parent.position.x - 60;
CCLOG(#"Which gives a distance of: %f", distance);
[self.scrollView setContentOffset:CGPointMake(0, distance) animated:YES];
self.previousOffset = 0;
[self unschedule:#selector(update:)];
CCLOG(#"Is unscheduled");
}
}
And it is almost working... at least, it is on simulator. But as soon as I try it on my iPhone 4, it does not work anymore. It always go into that update function, but 7 times among 8, it just blocks the scrollView as it is and does not drags it back to the position I give it... but sometimes it does.
Would anyone have an idea? I found similar issues on the net, but none of them could resolve this...

Pull Menu in CocoaTouch

I am attempting to make a UIView/UIControl that people can drag up and reveal a text box, and then drag down to hide it. However, I have yet to find a method to make this "fluid" - it always seems to stop at random places and doesn't allow any more movement. At the moment I am using a UIView for the top part of the view and here is the current code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == topMenuView) {
CGPoint location = [touch locationInView:self.superview];
CGPoint locationInsideBox = [touch locationInView:self];
CGPoint newLocation = location;
newLocation.x = self.center.x;
newLocation.y = newLocation.y + (self.frame.size.height - locationInsideBox.y) / 2;
if ((self.superview.frame.size.height - newLocation.y) < (self.frame.size.height / 2) && (self.superview.frame.size.height - newLocation.y) > -32)
{
self.center = newLocation;
}
return;
}
}
Any help would be much appreciated!
I would use a pan gesture recognizer. The following code will simply move the view up and down along with the user's finger. If you want to limit how far up it moves, have it snap to place or have momentum you'll need to add to it.
UIView * view; // The view you're moving
CGRect originalFrame; // The frame of the view when the touch began
- (void) pan:(UIPanGestureRecognizer *)pan {
switch (pan.state) {
case UIGestureRecognizerStateBegan: {
originalFrame = view.frame;
} break;
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded: {
CGPoint translation = [pan translationInView:view];
CGRect frame = originalFrame;
frame.origin.y += translation.y;
view.frame = frame;
} break;
}
}
Removing this line may well help you:
newLocation.y = newLocation.y + (self.frame.size.height - locationInsideBox.y) / 2;
What you're trying to accomplish is really nothing more than a scrollable view, so I would recommend using a UIScrollView.
Put your UIView in a UIScrollView with transparent background, and place the UIScrollView on top of your textbox. Set the correct contentSize and you're good to go.
use a uiview animation block to update the frame of the sliding view corresponding with the touch point recieved. set the animation blocks duration to something really short like .01 or lower.
I'd recommend splitting the issue in two:
Implement the view which has the text box at the bottom - You will just have to implement your own custom view/view controller.
Add your view as a subview of a UIScrollView.
This is a good tutorial which demonstrates proper initialization of UIScrollView and embedding content in it.
Custom views/controllers are a bit of a broader subject :)