I'm attempting to scale a UIScrollView's subviews, based off of how far away they are from the center of the container. I think I'm close, but it isn't quite right. The views to the left of the center line are scaled a bit less than those to the right.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
for( unsigned int i=0; i<[scrollView.subviews count]; i++ )
{
UIView *v = [scrollView.subviews objectAtIndex:i];
// not quite right here
float scale = 1. - ( abs(scrollView.center.x - ( v.center.x - scrollView.contentOffset.x ) ) / scrollView.contentSize.width/2 );
v.alpha = scale;
v.transform = CGAffineTransformIdentity;
v.transform = CGAffineTransformScale(v.transform, scale, scale);
}
}
If anyone has any thoughts, I'd greatly appreciate it, as it has been a long day fighting with this.
Testing your scale calculation you can see there's something wrong in the first part of it. Imagine the following values:
scrollview center x = 10
scrollView contentOffset x = 30
v1 center x = 5
v2 center x = 15
v1 and v2 are mirrored (5 pixels to the left of the center and 5 to the right of the center), so they should return the same scale value.
If you do the calculation with your code, it returns 35 and 25, which means the calculation isn't right.
I'm not sure how do you want your scale to behave exactly but a suggestion would be:
float scale = 1. - ( (scrollView.contentOffset.x - abs( v.center.x - scrollView.center.x ) ) / scrollView.contentSize.width/2 );
Edit:
try this one:
float scale = 1. - (abs( v.center.x - scrollView.center.x ) ) / scrollView.contentSize.width/2 );
There are many ways of doing your calculation. My point is that you should make sure that your operations are giving you the expected results with fake values, and yours is not. Your dividend is giving wrong results.
Related
I made an app which there is an object that moves towards a moving point all the time - that is why I didn't use any animated function. The problem is that I made this function:
CGPoint center = self.im.center; // "i" is a CGPoint, im is an imageview.
if (!CGPointEqualToPoint(self.im.center, i))
{
a = (i.y-center.y)/(i.x-center.x);
//Y = a*X+b - this is a linear function in math
b = (center.y-(a*center.x));
if (i.y>center.y) {
self.im.center = CGPointMake(((center.y+1)-b)/a, center.y+1);
}
else
{
self.im.center = CGPointMake(((center.y-1)-b)/a, center.y-1);
}
}
The problem is that the closer the functions is becoming a straight horizontal line its faster because the change is mostly to the X axis which means that if I add 1 to Y the change to X is bigger which means it will move faster..
If there is another way to do this i will be glad to try it so if you know other ways tell me!
Managed to find a different solution
CGPoint center = self.im.center;//im = the image view
x = center.x;//starting point
y = center.y;//starting point
double distance = sqrtf(powf(i.x - x, 2) + powf(i.y - y, 2));// i = cgpoint (ending point)
float speedX = (2 * (i.x - x)) / distance;//(the 2 is the speed)
float speedY = (2 * (i.y - y)) / distance;//(the 2 is the speed)
self.im.center = CGPointMake(center.x+speedX, center.y+speedY);//im = the image view
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Making use of velocityInView with UIPanGestureRecognizer
I am creating an app in which I am drawing a path based on touchMoved. I want to make the width of line based on the velocity of touch. I can achieve this effect, but there is no smoothness in this.
CODE:
-(float) clampf:(float) v: (float) min: (float) max
{
if( v < min ) v = min;
if( v > max ) v = max;
return v;
}
- (float)extractSize:(UIPanGestureRecognizer *)panGestureRecognizer
{
vel = [gesture velocityInView:self.view];
mag = sqrtf((vel.x * vel.x) + (vel.y * vel.y)); // pythagoras theorem (a(square) + b(square) = c(square))
final = mag/166.0f;
final = [self clampf:final :1 :40];
NSLog(#"%f", final);
if ([velocities count] > 1) {
final = final * 0.2f + [[velocities objectAtIndex:[velocities count] - 1] floatValue] * 0.8f;
}
[velocities addObject:[NSNumber numberWithFloat:final]];
return final;
}
But i am getting something like this:
I have made the similar effect before. I was using the original touch event methods (touchesBegan, etc.) and calculated the velocity by myself. Every thing looks all right.
I am not sure if the velocityInView method always gives your the correct result during user moving their finger, maybe you should try to calculate the velocity by yourself and see if the problem is gone.
i have a list of images retrieved from xml i want to populate them to a uiscrollview in an order such that it will look like this.
1 2 3
4 5 6
7 8 9
10
if there is only 10 images it will just stop here.
right now my current code is this
for (int i = 3; i<[appDelegate.ZensaiALLitems count]-1; i++) {
UIButton *zenbutton2 =[UIButton buttonWithType:UIButtonTypeCustom];
Items *ZensaiPLUitems = [appDelegate.ZensaiALLitems objectAtIndex:i];
NSURL *ZensaiimageSmallURL = [NSURL URLWithString:ZensaiPLUitems.ZensaiimageSmallURL];
NSLog(#"FVGFVEFV :%#", ZensaiPLUitems.ZensaiimageSmallURL);
NSData *simageData = [NSData dataWithContentsOfURL:ZensaiimageSmallURL];
UIImage *itemSmallimage = [UIImage imageWithData:simageData];
[zenbutton2 setImage:itemSmallimage forState:UIControlStateNormal];
zenbutton2.frame=CGRectMake( (i*110+i*110)-660 , 300, 200, 250);
[zenbutton2 addTarget:self action:#selector(ShowNextZensaiPage) forControlEvents:UIControlEventTouchUpInside];
[scrollView addSubview:zenbutton2];
}
notice the CGRectMake , i have to manually assign fixed values to position them.
Is there any way to populate them out without manually assigning.
for e.g the images will automatically go down a position once the first row has 3 images and subsequently for the rest.
If I understand what you are saying, you should be able to write a simple block of code that assigns a position based on the image number.
Something like this (where i is the image number, starting from 0):
- (CGPoint)getImageOrigin:(NSInteger)imageNumber {
CGFloat leftInset = 30;
CGFloat xOffsetBetweenOrigins = 80;
CGFloat topInset = 20;
CGFloat yOffsetBetweenOrigins = 80;
int numPerRow = 3;
CGFloat x = leftInset + (xOffsetBetweenOrigins * (imageNumber % numPerRow));
CGFloat y = topInset + (yOffsetBetweenOrigins * floorf(imageNumber / numPerRow));
CGPoint imageOrigin = CGPointMake(x, y);
return imageOrigin;
}
The origin being calculated here is the upper left corner of each image.
To calculate the x value, I start with the minimum distance from the left side of the screen (leftInset). Then, I add the distance from the left side of one image to the next image, multiplied by the column (imageNumber % numPerRow).
Y is calculated in a similar fashion, but to calculate the row, I use the imageNumber / numPerRow rounded down.
Edit:
You asked me to explain further, so I'll see what I can do.
OK, so I want to be able to input the image number (starting at 0) into my function, and I want the origin (upper left corner point) back.
leftInset is the distance between the left edge of the view, and the left edge of the first image.
xOffsetBetweenOrigins is the distance from the left edge of one image to the left edge of the next image on the same row. So, if I set it to 80 and my image is 50px wide, there will be a 30px gap between two images in the same row.
topInset is like left inset. It is the distance from the top edge of the view to the top edge of the images in the top row.
yOffsetBetweenOrigins is the distance from the top edge of an image to the top edge of the image below it. If I set this to 80, and my image is 50px tall, then there will be a 30px vertical gap between rows.
numPerRow is straightforward. It is just the number of images per row.
To calculate the x value of the upper left corner of the image, I always start with the leftInset, because it is constant. If I am on the first image of a row, that will be the entire x value. If I am on the second image of the row, I need to add xOffsetBetweenOrigins once, and if I am on the third, I need to add it twice.
To do this, I use the modulus (%) operator. It gives me the remainder of a division operation, so when I say imageNumber % numPerRow, I am asking for the remainder of imageNumber/numPerRow.
If I am on the first image (imageNumber = 0), then 3 goes into 0 no times, and the remainder is 0. If I am on the second image (imageNumber = 1), then I have 1/3. 3 goes into 1 0 times, but the remainder is 1, so I get xOffsetBetweenOrigins*1.
For the y value, I do something similar, but instead of taking the modulus, I simply divide imageNumber/numPerRow and round down. Doing this, I will get 0 for 0, 1, and 2. I will get 1 for 3, 4, and 5.
Edit:
It occurred to me that you might actually have been asking how to use this method. In your code, you would say something like
CGRect newFrame = zenbutton2.frame;
newFrame.origin = [self getImageOrigin:i];
zenbutton2.frame = newFrame;
Another Edit:
Maybe you could try this?
CGPoint origin = [self getImageOrigin:i];
zenbutton2.frame = CGRectMake(origin.x, origin.y, width, height);
If that doesn't work, throw in
NSLog("Origin Values: %f,%f", origin.x, origin.y);
to make sure that you are actually getting something back from getImageOrigin.
I think you probably want to wrap your loop in another loop, to get what I'm going to call a 2D loop:
for (int row = 0; row < num_rows; row++) {
for (int col = 0; col < num_cols; col++) {
// code before
zenButton2.frame = CGRectMake(origin x dependent on col,
origin y dependent on row,
width,
height);
// code after
}
}
Where the x and y of the CGRectMake() are multiples of the width and height of your image times the row and column respectively. Hope that makes sense.
How do I move a ball along a specific UiBezierPath? Is that possible?
I've tried everything including doing a hit test using
-(BOOL)containsPoint:(CGPoint)point onPath:(UIBezierPath*)path inFillArea:(BOOL)inFill
How do I detect a touch along a path?
Firstly ... it is an absolute fact that:
there is, definitely, NO method, provided by Apple,
to extract points from a UIBezierPath.
That is an absolute fact as of February 2011, in the latest OS, and everything just on the horizon. I've spoken to the relevant engineers at Apple about the issue. It's annoying in many game programming contexts, but that's the fact. So, it could be that answers your question?
Secondly: don't forget it's as easy as pie to animate something along a UIBezierPath. There are numerous answers on SO, for example How can I animate the movement of a view or image along a curved path?
Thirdly: Regarding finding out where touches happened, if that's what you're asking, as Cory said, this is a hard problem! But you can use CGContextPathContainsPoint to find out if a point (or touch) was on a path of a given thickness.
After that, assuming we are talking about cubics, you need to find points and likely velocities (a.k.a. tangents) along a bezier.
Here is the exact, total code to do that: Find the tangent of a point on a cubic bezier curve (on an iPhone). I pasted it in below, too.
You will have to implement your own MCsim or iteration to find where it hit, depending on your situation. That's not so hard.
(Fourthly -- as a footnote, there's that new thing where you can progressively draw a bezpath, probably not relevant to your question but just a note.)
For the convenience of anyone reading in the future, here is the summary from the linked question of the two "handy" routines...
Finally, here in the simplest possible fashion are the two routines to calculate equidistant points (in fact, approximately equidistant) and the tangents of those, along a bezier cubic:
CGFloat bezierPoint(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a );
return ( C1*t*t*t + C2*t*t + C3*t + C4 );
}
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
CGFloat C4 = ( a );
return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
}
The four precalculated values, C1 C2 C3 C4, are sometimes called the coefficients of the bezier. (Recall that a b c d are usually called the four control points.) Of course, t runs from 0 to 1, perhaps for example every 0.05. Simply call these routines once for X and separately once for Y.
Hope it helps someone!
there is a method in bezierpath class called containsPoint: .. refer: http://developer.apple.com/library/ios/#documentation/2ddrawing/conceptual/drawingprintingios/BezierPaths/BezierPaths.html
and you can detect weather the touch point is in bezier path object or not. I have used this with my own method by which a user can easily detect a touch on the bezier path (not inside or out sied if a circle or close path is there).
This code lets user select a bezier path drawing object on touch of it and a dashed line with animation appears on it. hope it helps someone.
Here is code from my own project:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if([[self tapTargetForPath:path] containsPoint:startPoint]) // 'path' is bezier path object
{
[self selectShape:currentSelectedPath];// now select a new/same shape
NSLog(#"selectedShapeIndex: %d", selectedShapeIndex);
break;
}
}
// this method will let you easily select a bezier path ( 15 px up and down of a path drawing)
- (UIBezierPath *)tapTargetForPath:(UIBezierPath *)path
{
if (path == nil) {
return nil;
}
CGPathRef tapTargetPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, fmaxf(35.0f, path.lineWidth), path.lineCapStyle, path.lineJoinStyle, path.miterLimit);
if (tapTargetPath == NULL) {
return nil;
}
UIBezierPath *tapTarget = [UIBezierPath bezierPathWithCGPath:tapTargetPath];
CGPathRelease(tapTargetPath);
return tapTarget;
}
-(void)selectShape:(UIBezierPath *)pathToSelect
{
centerline = [CAShapeLayer layer];
centerline.path = pathToSelect.CGPath;
centerline.strokeColor = [UIColor whiteColor].CGColor;
centerline.fillColor = [UIColor clearColor].CGColor;
centerline.lineWidth = 1.0;
centerline.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:10], [NSNumber numberWithInt:5], nil];
[self.layer addSublayer:centerline];
// showing animation on line
CABasicAnimation *dashAnimation;
dashAnimation = [CABasicAnimation animationWithKeyPath:#"lineDashPhase"];
[dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
[dashAnimation setToValue:[NSNumber numberWithFloat:30.0f]];
[dashAnimation setDuration:1.0f];
[dashAnimation setRepeatCount:10000];
[centerline addAnimation:dashAnimation forKey:#"linePhase"];
}
This is a really hard problem. You'll need to determine the point on the path closest to the touched point, which is a complicated math problem. This is the best thing I could find using a quick Google search. (Beware: the code is in BASIC.)
http://www.tinaja.com/glib/bezdist.pdf
I admit, I'm tempted to write this myself. It'd be very useful, and I like a challenge.
Mathematically, you can not determine if a point inside a curve, a pure mathematical one, as a curve has no surface. What you can do, is test if the point is inside the surface defined by the bounds of the closed curve created by CGPathCreateCopyByStrokingPath with CGPathContainsPoint. It creates the curve that is filled to render your curve on the screen.
If CGPathCreateCopyByStrokingPath is called with its parameter:CGFloat lineWidth less or equal to the minimum radius of curvature of the original curve, it will not self-intersect. Also the original curve should not self intersects.
If CGPathContainsPoint returns true, and both conditions I mentioned are met, you are guaranteed to find one and only point on the curve that is at a distance of lineWidth. There is no algebraic solution to this, so you have to find an approximate point on the curve that is close to the touched point with the method described here: Find a point, a given distance, along a simple cubic bezier curve. (On an iPhone!)
Swit: for #rakesh's answer
func tapTargetForPath(_ path: CGPath) -> UIBezierPath? {
let path = UIBezierPath(cgPath: path)
guard let tapTargetPath = CGPath(__byStroking: path.cgPath,
transform: nil,
lineWidth: CGFloat(fmaxf(35.0, Float(path.lineWidth))),
lineCap: path.lineCapStyle,
lineJoin: path.lineJoinStyle,
miterLimit: path.miterLimit) else {
return nil
}
return UIBezierPath(cgPath: tapTargetPath)
}
I've been racking my brains over this problem for two days, I've tried different things but none of them work. I'm building an app which is a kind of quizz. There are three subjects which contain questions. I would like to use 3 sliders to define the percentage of questions they want on each subject.
ex : slider one = History
slider two = Maths
slider three = Grammar
If I choose to have more history, I slide the history slider up and the other sliders should decrease according to the values they have to reach 100% for the 3 sliders...
Any idea for an algorithm ? And what happens when one slider reach a zero value ?
Maths has never been my scene.
Any Help would be very much appreciated.
Thanks in advance.
Mike
Though Snowangelic's answer is good, I think it makes more sense to to constrain the ratio between the unchanged values as follows.
Let s1, s2, s3 be the current values of the sliders, so that s1+s2+s3=100. You want to solve for n1, n2, n3 the new values of the sliders, so that n1+n2+n3=100. Assume s1 is changed to n1 by the user. Then this adds the following constraint:
n2/n3 = s2/s3
So the solution to the problem, with n1+n2+n3=100, is
n2 = (100-n1)/(s3/s2 + 1) or 0 (if s2=0) and
n3 = (100-n1)/(s2/s3 + 1) or 0 (if s3=0)
Start all three sliders at 33.333...%
When the users moves a slider up say 10% : move the two other sliders down of 5%. But if one of two slider reaches 0 => only move the other one of ten percent. So it gives something like this :
User moved slider of x (my be positive or negative)
for first slider
if slider -x/2 > 0 and x/2 < 100
move this slider of -x/2
else
move the other slider of -x/2
for second slider
if slider -x/2 > 0 and x/2 < 100
move this slider of -x/2
else
move the other slider of -x/2
end
Another possibility would be to consider that the sum os the available ressources is 100, the ressources are separated into n buckets (in your case 3). When the user moves a slider, he fixes the number of ressources in the corresponding bucket. And so you may either take ressources from other bucket or put ressources in these other buckets.
You have something like :
state 1 ; modified bucket ; new number of ressources in that bucket
modification = new number of ressources in the bucket - number of rescources in the state 1
for (int i=0 ; modification > 0 ; i++){
i=i%nbr of buckets;
if(bucket i != modified bucket){
if(number of ressources in bucket i-- > 0 && number of ressources in bucket i-- < 100){
number of ressources in bucket i--;
modification --;
}
}
}
That is assuming the modification is positive (new number in the modified bucket is higher than before). This small algorithm would work with any number of buckets (sliders in your case).
The following algorithm should be reviewed and of course optimized. It is only something that I have put together and I've not tested it.
initialize each slider with a max and minimum value and set the inital value as desired, but respecting that x + y + z = 1.
[self.slider1 setMinimumValue:0.0];
[self.slider1 setMaximumValue:1.0];
[self.slider1 setValue:0.20];
[self.slider2 setMinimumValue:0.0];
[self.slider2 setMaximumValue:1.0];
[self.slider2 setValue:0.30];
[self.slider3 setMinimumValue:0.0];
[self.slider3 setMaximumValue:1.0];
[self.slider3 setValue:0.50];
Set the three slider to the same selector:
[self.slider1 addTarget:self action:#selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
[self.slider2 addTarget:self action:#selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
[self.slider3 addTarget:self action:#selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
The selector should do something like that:
- (void)valueChanged:(UISlider *)slider {
UISlider *sliderX = nil;
UISlider *sliderY = nil;
UISlider *sliderZ = nil;
if (slider == self.slider1) {
sliderX = self.slider1;
sliderY = self.slider2;
sliderZ = self.slider3;
} else if (slider == self.slider2) {
sliderY = self.slider1;
sliderX = self.slider2;
sliderZ = self.slider3;
} else {
sliderY = self.slider1;
sliderZ = self.slider2;
sliderX = self.slider3;
}
float x = sliderX.value;
float y = sliderY.value;
float z = sliderZ.value;
// x + y + z = 1
// Get the amout x has changed
float oldX = 1 - y - z;
float difference = x - oldX;
float newY = y - difference / 2;
float newZ = z - difference / 2;
if (newY < 0) {
newZ += y + newY;
newY = 0;
}
if (newZ < 0) {
newY += z + newZ;
newZ = 0;
}
[sliderY setValue:newY animated:YES];
[sliderZ setValue:newZ animated:YES];
}
If there is something wrong with this code, please let me know, and I can fix it!