iphone code - CGPoint question - iphone

i have 10 moving objects (UIImageView),
is there a better way to write this code?
- (void) jumpOnTimer {
jumpBall1.center = CGPointMake(jumpBall1.center.x+pos1.x,jumpBall1.center.y+pos1.y);
if(jumpBall1.center.x > 60 || jumpBall1.center.x < 0)
pos1.x = -pos1.x;
if(jumpBall1.center.y > 211 || jumpBall1.center.y < 82)
pos1.y = -pos1.y;
jumpBall2.center = CGPointMake(jumpBall2.center.x+pos2.x,jumpBall2.center.y+pos2.y);
if(jumpBall2.center.x > 40 || jumpBall2.center.x < 0)
pos2.x = -pos2.x;
if(jumpBall2.center.y > 206 || jumpBall2.center.y < 82)
pos2.y = -pos2.y;
and so on...

Judging by that code snippet, it looks like you have a single controller which "owns" the ten balls, and you want the balls to bounce around according to a set of rules that are unique to each ball. A more object-oriented approach would be as follows:
#interface JumpBallClass
{
CGPoint center;
CGPoint speed;
CGPoint lowerLimit;
CGPoint upperLimit;
}
#property (assign) CGPoint lowerLimit;
#property (assign) CGPoint upperLimit;
- (void)update;
#end
#implementation JumpBallClass
- (void)update
{
center.x += speed.x;
center.y += speed.y;
if (center.x > upperLimit.x || center.x < lowerLimit.x)
{ speed.x = -speed.x; }
if (center.y > upperLimit.y || center.y < lowerLimit.y)
{ speed.y = -speed.y; }
}
#end
This setup would allow you to configure all of the balls once, by setting their upper and lower limits:
[jumpBall1 setUpperLimit:CGPointMake(60, 211)];
[jumpBall1 setLowerLimit:CGPointMake(0, 82)];
...
And then simply calling update on each ball in your timer method:
- (void) jumpOnTimer {
[jumpBall1 update];
[jumpBall2 update];
...
}
You can simplify this even further by storing all of the balls in an NSArray:
NSArray * balls = [NSArray arrayWithObjects:jumpBall1, jumpBall2, ..., nil];
And then calling makeObjectsPerformSelector:
[balls makeObjectsPerformSelector:#selector(update)];

You can make an array of jumpBalls and then loop through each and do the code for that. You can do something like this:
JumpBallClass *myjumpballs[10];
for (i=0; i<10; i++) {
myjumpballs[i].center = CGPointMake(myjumpballs[i].center.x+pos1.x,myjumpballs[i].center.y+pos1.y);
if(myjumpballs[i].center.x > 60 || myjumpballs[i].center.x < 0)
pos1.x = -pos1.x;
if(myjumpballs[i].center.y > 211 || myjumpballs[i].center.y < 82)
pos1.y = -pos1.y;
}

Looks like you're trying to manually animate. Have a look at using UIView animations instead

Related

cut a sprite sheet on cocos2d for animation

I want to cut a sprite in 6 equivalent parts just with one image, a .png file which I found on the web, no with texturepacker, (the image below by example)
I can take other way, but I want to know if I can do that. any one haves idea?
I'll revise my answer if this isn't what you were asking about, but I think you are asking how to 'manually run an animation' using a spritesheet without a plist.
Here's one way. It would be better if you encapsulated this into it's own class, but this can push you in the right direction I think:
ManualAnimationTest.h
#interface ManualAnimationTest : CCLayer
{
CCSprite *animatedSprite;
int x,y;
float animatedSpriteWidth, animatedSpriteHeight;
int animatedSpriteColumns, animatedSpriteRows;
}
#end
ManualAnimationTest.m
#import "ManualAnimationTest.h"
#implementation ManualAnimationTest
-(id) init
{
if( (self=[super init]))
{
CGSize s = [CCDirector sharedDirector].winSize;
x = 0;
y = 0;
animatedSpriteColumns = 3;
animatedSpriteRows = 2;
animatedSpriteWidth = 95.0f;
animatedSpriteHeight = 125.0f;
animatedSprite = [CCSprite spriteWithFile:#"animal_animation.png" rect:CGRectMake(x * animatedSpriteWidth,y * animatedSpriteHeight,animatedSpriteWidth,animatedSpriteHeight)];
[self addChild:animatedSprite];
[animatedSprite setPosition:ccp(s.width / 2.0f, s.height / 2.0f)];
[self schedule:#selector(animateAnimatedSprite) interval:0.5f];
}
return self;
}
-(void) animateAnimatedSprite
{
[animatedSprite setTextureRect:CGRectMake(x * animatedSpriteWidth, y * animatedSpriteHeight, animatedSpriteWidth, animatedSpriteHeight)];
x +=1;
if(x > (animatedSpriteColumns - 1))
{
x = 0;
y +=1;
}
if(y > (animatedSpriteRows - 1))
{
y = 0;
}
}
#end

How to add multiple UIImageViews with same property

I want to have multiple images (in this case, basketballs) move around on the screen.
How can I create multiple balls with the same name? I have one ball which is already moving around. So all the other balls should use the same calculation with the same property name for bumping and stuff like that.
Can someone help me? I know Java well, but not Obj C.
EDIT:
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ball movement.y);
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
This is my simple calculation. This code is in my loop method. this loop method is called by this code in my viewDidLoad method:
[NSTimer scheduledTimerWithTimeInterval:0.050 target:self selector:#selector(loop) userInfo:nil repeats:YES];
In the MainView .h file i wrote
BasketballView *ball;
new Balls are created by this code in my button action method:
for (int x = 0; x < numberOfBalls; x++)
{
ball = [[BasketballView alloc]initWithFrame:CGRectMake(arc4random()%100,arc4random()%100, 40, 40) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:ball];
}
When i run my application, only one ball is moving!
do you need more code?
thanks for your help
EDIT 2:
every ball is moving now, but they are not moving correctly .. here is what i did. i created a NSMuteableArray. i added this code in my Button action method:
[array addObject:ball];
after that, i added in the loop method some code. here is the new loop code that i have right now:
for (int i=0; i<array.count;i++){
ball = [array objectAtIndex:i];
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ballmovement.y);
}
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
right now, every ball is changing direction when one ball hits the boarder. so all the other balls move like the this one ball. they ignore the boarders. only if this one ball hits the boarder, they change their direction.
if i put the code for the ball movement inside the for-loop, the balls change their direction everytime one of the balls hits the boarder. they are moving like crazy, because every .5 seconds a ball hits one of the boarders
I think what you looking for is a way to add a bunch of objects from the same class. To do this, I would suggest that you overwrite an UIImageView class. If you do that, you can add custom properties to the balls. See the example below:
In your header file, you would have the following:
#import <UIKit/UIKit.h>
#interface BasketBallView : UIImageView
#property (nonatomic, strong) NSString *customProperty;
#property (assign) NSInteger anotherProperty;
//custom init method if you want
- (id) initWithFrame:(CGRect)frame andCustomProperty:(NSString *)CustomProperty andAnotherProperty:(NSInteger)AnotherProperty;
-(void)startMovement;
#end
The implementation would be something like this:
#import "BasketBallView.h"
#implementation BasketBallView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setImage:[UIImage imageNamed:#"your_bb_image.png"]];
}
return self;
}
-(id) initWithFrame:(CGRect)frame andCustomProperty:(NSString *)CustomProperty andAnotherProperty:(NSInteger)AnotherProperty
{
self = [self initWithFrame:frame];
if (self) {
self.customProperty = CustomProperty;
self.anotherProperty = AnotherProperty;
}
return self;
}
-(void)startMovement{
[NSTimer scheduledTimerWithTimeInterval:0.050 target:self selector:#selector(loop) userInfo:nil repeats:YES];
}
-(void)moveBall{
self.center = CGPointMake(self.center.x + ballmovement.x, self.center.y + ballmovement.y);
if (self.center.x > self.superview.frame.size.width || self.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (self.center.y > self.superview.frame.size.height || self.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
To then use and access your object from your viewcontroller, do it as follows:
BasketBallView *bb = [[BasketBallView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:bb];
You could, of course, throw the above lines of code into a loop, and add it multiple times to the view.
Edit:
If you want to add the BasketBallView to the view for the Interface Builder, you'll need to customize the initWithCoder method in the implementation file. In that method, you would similarly set your image, etc.
I would suggest that you don't add the BasketBallViews via the Interface Builder, however. Since you need a random number of balls, you need to add them from the code and use the method I outlined above. You would add them like this:
for(int i = 0; i < yourRandomNumber; i++){
BasketBallView *bb = [[BasketBallView alloc]initWithFrame:CGRectMake(0, 0, 100, 100) andCustomProperty:#"my string" andAnotherProperty:100];
[self.view addSubview:bb];
}
You can't add copies of views to another view, in other words, you can't create the ball in the IB, then make copies of it in the code and add it to your view.
The properties I added to the class are just examples of some customization that you could add to the class if you needed to.
You can certainly still use the center property since the BasketBallView is an subclass of the UIImageView, and therefore inherits all the UIImageView properties.
If you have access to iOS7, or can wait another month, Apple's new SpriteKit framework will be able to do a lot of the physics and GPU rendering for you. Then it's just a case of making multiple instances of your "Basketball Class".
You need to add the uiimageview several times on the screen.... and set tag with number 100 or 1001
like
balview.tag = 100;
for (int x=0; x < 5; x++)
{
[self.view addSubview:ballView];
}
do calculation for all in one time
forin(UIImageView *view in [self.view subviews])
{
if (view.tag == 100)
{
//do something
}
}
for (int x = 0; x < 5; x++)
{
UIImageView* ball = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"YourImageName"]];
ball.frame = CGRectMake(20, 20*x, 20, 20);
[self.view addSubview:ball];
}
I can think of two solutions:
1: Add all balls to an array and access them by the index (seems more feasible to me)
2: Add them tags and access them by
[self.view viewWithTag:<BallTag>];
Edit: Seems like you have chosen the first alternative. Now you need to improve your method for only one ball to move and invoke it whenever you desire.
- (void)moveBallWithIndex:(int)idx{
//for (int i=0; i<array.count;i++){
//Loop removed. It was the reason why all the balls were moving
BasketballView * ball = [array objectAtIndex:idx];
ball.center = CGPointMake(ball.center.x + ballmovement.x, ball.center.y + ballmovement.y);
if (ball.center.x > self.view.frame.size.width || ball.center.x < 0) {
ballmovement.x = -1* ballmovement.x;
}
if (ball.center.y > self.view.frame.size.height || ball.center.y < 0) {
ballmovement.y = -1* ballmovement.y;
}
}
Edit: It seems like you carried the move method to the ball class, for limitations I can think o fthree alternatives:
Create two properties on the ball class named "maxX" and "maxY" during init/set after init and use them in the move method
Improve your move method with two parameters like
- (void)moveWithhinMaxX:(float)maxX andMaxY:(float)maxY;
and use them for limitation inside the move method
Improve your move method by giving the superview on the move method like:
- (void)moveWithinView:(float)newSuperview{
//[newSuperview addSubview:self]; add as subview if necessary
//Use newSuperview's bounds for limitations
and inside it you can add the ball as subview to superview and use its bounds for limitation

How to implement powerUps and other game altering objects in objective-C or cocos2d

Ok, so I have these powerups that I want to slow/speed up the movement of the other objects in the game for a few seconds.
I have an array of objects that I have a variable called spawnInterval that gets faster and faster as the game progresses, making the ame get harder after a few mins.
But I can't really grasp how to make it so the character in the game will react differently to different objects as in when the fastPowerUp is hit by the character sprite, the spawn interval doesn't change.
And vice versa with the slowPowerUp.
the code I have at the moment is this in a move sequence method that gets called in an update method:
-
(void) updateObstacles:(ccTime)delta{
for (int i = 0; i < 20; i++) {
//int randomizer = CCRANDOM_0_1() * [obstacles count];
//NSLog(#"randomizer: %i",randomizer);
CCSprite* randomObject = [obstacles randomObject];
currentObject = [obstacles indexOfObject:randomObject];
if ([randomObject numberOfRunningActions] == 0) {
[self runObstacleMoveSequence:randomObject withTimer:delta];
break;
}
}
}
-(void) runObstacleMoveSequence:(CCSprite *)object withTimer:(ccTime)delta{
static int time;
//Slowly increase object speed
numObstaclesMoved++;
if (!slowPowerUp && !fastPowerUp) {
time += delta;
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration -= 0.2f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
}
}
}else if (slowPowerUp && !fastPowerUp) {
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration += 3.0f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
if (time >= (delta + 3)) {
slowPowerUp = NO;
obstacleMoveDuration -= 3.0f;
}
}
}
}else if (!slowPowerUp && fastPowerUp) {
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration -= 3.0f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
if (time >= (delta + 3)) {
fastPowerUp = NO;
obstacleMoveDuration += 3.0f;
}
}
}
}
CGSize screenSize = [[CCDirector sharedDirector]winSize];
CGPoint aboveScreenPosition = CGPointMake(object.position.x, screenSize.height - object.position.y);
int rotations = (CCRANDOM_0_1()*3) * 360;
float duration = (CCRANDOM_0_1()*5.0f) + 8.0f;
CCMoveTo* move = [CCMoveTo actionWithDuration:obstacleMoveDuration position:aboveScreenPosition];
CCRotateTo* rotate = [CCRotateBy actionWithDuration:duration angle:rotations];
CCSpawn* moveRotate = [CCSpawn actions: move, rotate, nil];
CCCallFuncN* call = [CCCallFuncN actionWithTarget:self selector:#selector(objectAboveScreen:)];
CCSequence* sequence = [CCSequence actions:moveRotate, call, nil];
[object runAction:sequence];
if (time >= (delta + 3)) {
fastPowerUp = NO;
}
}
-(void) objectAboveScreen:(id) sender{
//make sure sender is actually of the right class
NSAssert([sender isKindOfClass:[CCSprite class]], #"sender is not a CCSprite!");
CCSprite* obstacle = (CCSprite*)sender;
//move the back to the bottom of the screen
CGPoint pos = obstacle.position;
CGSize screenSize = [[CCDirector sharedDirector]winSize];
pos.y = (-screenSize.height - [obstacle texture].contentSize.height);
pos.x = CCRANDOM_0_1() * screenSize.width;
obstacle.position = pos;
}
I really just don't know where to go from here... Should I make the powerUps a different class? If so, how would I implement something like this? I really hate trying to ask for someone to solve my question, but I really just can't rack my brain around this and I'm rather new... if it were explained to me, then I know I would be able to implement it in future games on my own...
Thanks in advance, and let me know if more information is needed...
I'd do something like
in the .h file
float speedModifier;
-(void)resetPowerUp;
in the .m
-(void)resetPowerUp
{
speedModifier = 1;
}
wherever you are initializing the level
[self resetPowerUp];
upon collision with powerup:
speedModifier = 2;
[self performSelector:#selector(resetPowerUp) withObject:nil afterDelay:5];
then wherever you are moving whatever it is which speed should be effected by the powerup mode, multiply the speed of the animation (or divide the duration it takes for it to get wherever it's going) by speedModified
hope that helps

increasing and maintaing a constant speed of an object in objective C

I have posted something similar before and I got some suggestion which I have tried but I am still having problems...thus, will like to ask for any assistance on reviewing this code.
I am actually trying to get the ball object in the code to have a better or smoother animation and to increase in its speed constantly until it attains a maximum speed but right now what I am getting is the ball starting at the specified speed and then its movement becomes erratic as the speed increases and after a few second the ball just moves off the iphone simulator screen.
Can someone help me run this code(please add an image to represent the ball in IB) to see what I am not doing correctly.
Thanks in advance for your help.
.h
#interface ViewController : UIViewController
{
UIImageView *ball;
int speedX;
int speedY;
CGPoint ballMovement;
}
#property (nonatomic, retain) IBOutlet UILabel *scoreLabel;
#property (nonatomic, retain) IBOutlet UIImageView *ball;
- (void)initializeTimer;
- (void)animateBall:(NSTimer *)theTimer;
#end
.m
#implementation ViewController
#synthesize ball;
- (void)dealloc {
[ball release];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
ball.center = CGPointMake(50,50);
speedX = 1;
speedY = 1;
ballMovement = CGPointMake(speedX,speedY);
[self initializeTimer];
}
- (void)initializeTimer {
float theInterval = 0.1f;
[NSTimer scheduledTimerWithTimeInterval:theInterval target:self selector:#selector(animateBall:) userInfo:nil repeats:YES];
}
- (void)animateBall:(NSTimer *)theTimer {
ball.center = CGPointMake(ball.center.x+speedX, ball.center.y+speedY);
speedX += 1;
speedY += 1;
NSLog(#"%d,%d", speedX, speedY);
if (speedX <= 1)
speedX ==1;
if (speedY <= 1)
speedY ==1;
if(ball.center.x > 300 || ball.center.x < 20)
speedX = -abs(speedX);
if(ball.center.y > 440 || ball.center.y < 40)
speedY = -abs(speedY);
}
You are not capping the speed anywhere, so how is it supposed to reach a constant speed? From your code, once you pass ball.center.x > 300, you reverse the speed.
The rows
if (speedX <= 1) speedX ==1;
if (speedY <= 1) speedY ==1;
Do nothing, since they don't actually assign a value to speedX / speedY. It looks like this is what you mean:
if (speedX <= 1) speedX = 1;
if (speedY <= 1) speedY = 1;
Even so, this won't actually do what you want. If you want a max speed, then it should be something like this:
// Compact version
speedX = MIN(speedX + 1, TARGET_SPEED_X);
// More like your code:
speedX += 1;
if (speedX > TARGET_SPEED_X) speedX = TARGET_SPEED_X;
If you want the ball to bounce off the bottom, with gravity along the y axis:
- (void)animateBall:(NSTimer *)theTimer {
ball.center = CGPointMake(ball.center.x+speedX, ball.center.y+speedY);
speedY += 1;
if(ball.center.x > 300) {
speedX = -abs(speedX);
}
if(ball.center.y > 440)
speedY = -abs(speedY);
}
The main cause of the erratic movement you see is probably because of the bounce off the top/side of the screen. For instance:
if(ball.center.y > 440 || ball.center.y < 40)
speedY = -abs(speedY);
What happens when the ball is speeding towards the top here? Say ball.center.y = 30, speedY = -4.
Your speedY will then stay at -4. Next pass it's -3, then -2, then -1, and then it'll oscillate between -1 and 0. The same happens for speedX eventually.
The question is what sort of movement you are looking for. Your original code had something that looked like constant acceleration in the (1, 1) direction, with a bounce effect in the right and bottom corner, but your description said you wanted acceleration to a maximum speed.
For an accelerating ball with a maximum speed:
#interface ViewController : UIViewController {
UIImageView *ball;
double speedX;
double speedY;
int maxSpeed;
double acceleration;
CGPoint ballMovement;
}
#property (nonatomic, retain) IBOutlet UIImageView *ball;
- (void)initializeTimer;
- (void)animateBall:(NSTimer *)theTimer;
#end
#implementation ViewController
#synthesize ball;
- (void)dealloc {
[ball release];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
ball.center = CGPointMake(50,50);
speedX = 1;
speedY = 1;
maxSpeed = 20;
acceleration = 1;
ballMovement = CGPointMake(speedX,speedY);
[self initializeTimer];
}
- (void)initializeTimer {
float theInterval = 0.1f;
[NSTimer scheduledTimerWithTimeInterval:theInterval target:self selector:#selector(animateBall:) userInfo:nil repeats:YES];
}
- (void)animateBall:(NSTimer *)theTimer {
ball.center = CGPointMake(ball.center.x + speedX, ball.center.y + speedY);
// Discover the actual speed.
double actualSpeed = sqrt(speedX * speedX + speedY * speedY);
// Increase the speed by the acceleration (actually acceleration * time interval),
// Cap at max speed
double newSpeed = MIN(acceleration + actualSpeed, maxSpeed);
// Increase velocity uniformly along both x and y axis
// (This example does not properly handle when speedX and speedY both start at zero)
speedX = speedX * newSpeed / actualSpeed;
speedY = speedY * newSpeed / actualSpeed;
// Make sure to bounce
if (ball.center.x > 300) speedX = -abs(speedX);
if (ball.center.x < 20) speedX = abs(speedX);
if (ball.center.y > 440) speedY = -abs(speedY);
if (ball.center.y < 40) speedY = abs(speedY);
}
#end
Acceleration = Speed / Delta_time
So you have to modify the code:
- (void)animateBall:(NSTimer *)theTimer {
ball.center = CGPointMake(ball.center.x+speedX, ball.center.y+speedY);
static float acceleration = 1.0f;
speedX += acceleration*theTimer.timeInterval;
speedY += acceleration*theTimer.timeInterval;
NSLog(#"%d,%d", speedX, speedY);
if (speedX <= 1)
speedX ==1;
if (speedY <= 1)
speedY ==1;
if(ball.center.x > 300 || ball.center.x < 20)
speedX = -abs(speedX);
if(ball.center.y > 440 || ball.center.y < 40)
speedY = -abs(speedY);
}

How can I test if the scroll view is bouncing?

How can I test if the scroll view is bouncing? Is there a notification or something when the bounce ends?
Here is how I detected if the scroll view is bouncing horizontally:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.x < 0) {
NSLog(#"bouncing left");
}
if (scrollView.contentOffset.x > (scrollView.contentSize.width - scrollView.frame.size.width)) {
NSLog(#"bouncing right");
}
}
I've implemented extension for UIScrollView to handle this for vertical and horizontal scrolling. This will work even with non-zero contentInsets and in case when content is not big enough for covering scrollView insets:
Objective-C
#interface UIScrollView (Bouncing)
#property (nonatomic, readonly) BOOL isBouncing;
#property (nonatomic, readonly) BOOL isBouncingTop;
#property (nonatomic, readonly) BOOL isBouncingLeft;
#property (nonatomic, readonly) BOOL isBouncingBottom;
#property (nonatomic, readonly) BOOL isBouncingRight;
#end
#implementation UIScrollView (Bouncing)
- (BOOL)isBouncing
{
return self.isBouncingTop || self.isBouncingLeft || self.isBouncingBottom || self.isBouncingRight;
}
- (BOOL)isBouncingTop
{
return self.contentOffset.y < - self.contentInset.top;
}
- (BOOL)isBouncingLeft
{
return self.contentOffset.x < - self.contentInset.left;
}
- (BOOL)isBouncingBottom
{
BOOL contentFillsScrollEdges = self.contentSize.height + self.contentInset.top + self.contentInset.bottom >= CGRectGetHeight(self.bounds);
return contentFillsScrollEdges && self.contentOffset.y > self.contentSize.height - CGRectGetHeight(self.bounds) + self.contentInset.bottom;
}
- (BOOL)isBouncingRight
{
BOOL contentFillsScrollEdges = self.contentSize.width + self.contentInset.left + self.contentInset.right >= CGRectGetWidth(self.bounds);
return contentFillsScrollEdges && self.contentOffset.x > self.contentSize.width - CGRectGetWidth(self.bounds) + self.contentInset.right;
}
#end
Swift 3.0+
extension UIScrollView {
var isBouncing: Bool {
return isBouncingTop || isBouncingLeft || isBouncingBottom || isBouncingRight
}
var isBouncingTop: Bool {
return contentOffset.y < -contentInset.top
}
var isBouncingLeft: Bool {
return contentOffset.x < -contentInset.left
}
var isBouncingBottom: Bool {
let contentFillsScrollEdges = contentSize.height + contentInset.top + contentInset.bottom >= bounds.height
return contentFillsScrollEdges && contentOffset.y > contentSize.height - bounds.height + contentInset.bottom
}
var isBouncingRight: Bool {
let contentFillsScrollEdges = contentSize.width + contentInset.left + contentInset.right >= bounds.width
return contentFillsScrollEdges && contentOffset.x > contentSize.width - bounds.width + contentInset.right
}
}
For RxSwift you can just map scrollViewDidScroll with isBouncing and filter with distinctUntilChanged.
A minor amendment to Justin's method, allowing for contentInset:
if( scrollView.contentOffset.x < -scrollView.contentInset.left )
{
NSLog( #"bounce left" );
}
if( scrollView.contentOffset.x > scrollView.contentSize.width - scrollView.frame.size.width + scrollView.contentInset.right )
{
NSLog( #"bounce right" );
}
Yes... check out the UIScrollViewDelegate spec, implement the methods including the two below, and set your UIScrollView's delegate accordingly:
// User stops dragging the table view.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate;
// Control slows to a halt after the user drags it
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
You will probably be most interested in scrollViewDidEndDecelerating. These also work in UITableView which is where I originally found them (UITableView inherits from UIScrollView).
Old question but I just ran into a similar issue and wanted to add that it is a good idea to also check that the scroll views content is larger than the scroll view's frame:
+ (BOOL) isScrollViewBouncing:(UIScrollView *)scrollView
{
return scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.frame.size.height
&& scrollView.contentSize.height > scrollView.frame.size.height;
}
This now makes sure the scroll view is large enough to be in a scroll-bouncing state, so that if the scroll view is small it does NOT always evaluate to true.
Cheers
For those of you who would be able to "bounce" downwards in a scrollview in order to update contents of the view. (And the event is only fired once.)
- (void)scrollViewDidEndDragging:(UIScrollView *)aScrollView
willDecelerate:(BOOL)decelerate{
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
//Distance in points before update-event occur
float reload_distance = 50;
//
if(y > h + reload_distance) {
NSLog(#"load more rows");
}
}
Using Glavid's answer, I added bouncing to bottom also, and added as a category
#import "UIScrollView+Additions.h"
#implementation UIScrollView (Additions)
- (BOOL)isBouncing
{
BOOL isBouncingBottom = self.contentOffset.y >= self.contentSize.height - self.frame.size.height
&& self.contentSize.height >= self.frame.size.height;
BOOL isBouncingTop = self.contentOffset.y <= 0;
BOOL isBouncing = isBouncingBottom || isBouncingTop;
return isBouncing;
}
#end