iPhone SDK: repeat subviews - iphone

I have one UIView, which I'm using as my main view, and I want to repeat the subview across the screen. How exactly can I do this?

You can look at Core Animation. There is a layer called the CAReplicatorLayer that might help you. Alternatively you can use generic CALayers and set their contents all to the same image. You would just need to figure out the width of your parent view and how big you want each tile to be and then just create CALayers for each tile shifting the position of each new layer depending on your grid dimensions. Something like this:
UIImage *imageToReplicate = [UImage imageNamed:#"tile"];
for (i = 0; i < 10; ++i)
{
for (j=0; j < 10; ++j)
{
CGFloat xPos = 0.0; // Calculate your x position
CGFloat yPos = 0.0; // Calculate your y position
CALayer *layer = [CALayer layer];
[layer setBounds:CGRectMake(0.0f, 0.0f, TILE_WIDTH, TILE_HEIGHT)];
[layer setPosition:CGPointMake(xPos, yPos)];
[layer setContents:(id)[image CGImage]];
[[[self view] layer] addSublayer:layer];
}
}
You'll have to figure out the calculation for each iteration of your layer positions. Remember that by default the anchor point of the layer is its center. You either calculate it by subtracting half of the layer tile size or you can change the anchor point to be a corner instead. For more information on that, take a look at the layer geometry section of the Core Animation documentation.

Related

How can I draw a curved shadow?

Like so:
I know that this will not work with NSShadow, drawing it in drawRect: will work just fine.
You can do this and many other kinds of shadows using Core Animations layers and the shadowPath property. The shadow that you are describing can be make with an elliptical shadow path.
The code to produce this shadow is below. You can tweak the size of the ellipse to have a rounder shape of the shadow. You can also tweak the position, opacity, color and blur radius using the shadow properties on the layer.
self.wantsLayer = YES;
NSView *viewWithRoundShadow = [[NSView alloc] initWithFrame:NSMakeRect(30, 30, 200, 100)];
[self addSubview:viewWithRoundShadow];
CALayer *backingLayer = viewWithRoundShadow.layer;
backingLayer.backgroundColor = [NSColor orangeColor].CGColor;
// Configure shadow
backingLayer.shadowColor = [NSColor blackColor].CGColor;
backingLayer.shadowOffset = CGSizeMake(0, -1.);
backingLayer.shadowRadius = 5.0;
backingLayer.shadowOpacity = 0.75;
CGRect shadowRect = backingLayer.bounds;
CGFloat shadowRectHeight = 25.;
shadowRect.size.height = shadowRectHeight;
// make narrow
shadowRect = CGRectInset(shadowRect, 5, 0);
backingLayer.shadowPath = CGPathCreateWithEllipseInRect(shadowRect, NULL);
Just to show some examples of other shadows than can be created using the same technique; a path like this
will produce a shadow like this
It's far from perfect but I think it does draw the sort of shadow you are looking for. Bear in mind that I have left a plain linear gradient in place from a total black to a clear color. Being so dark, this will not give you a super-realistic shadow unless you tweak the values a bit. You may want to play with the gradient by adding more locations with different alpha values to get whatever stepping you like. Some experimentation is probably required but the values are all there to play with.
As per your suggestion it's a drawRect:(CGRect)rect thing. Just create a custom view and only override it:
- (void)drawRect:(CGRect)rect {
// Get the context
CGContextRef context = UIGraphicsGetCurrentContext();
// Setup the gradient locations. We just want 0 and 1 as in the start and end of the gradient.
CGFloat locations[2] = { 0.0, 1.0 };
// Setup the two colors for the locations. A plain black and a plain black with alpha 0.0 ;-)
CGFloat colors[8] = { 0.0f, 0.0f, 0.0f, 1.0f, // Start color
0.0f, 0.0f, 0.0f, 0.0f }; // End color
// Build the gradient
CGGradientRef gradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(),
colors,
locations,
2);
// Load a transformation matrix that will squash the gradient in the current context
CGContextScaleCTM(context,1.0f,0.1f);
// Draw the gradient
CGContextDrawRadialGradient(context, // The context
gradient, // The gradient
CGPointMake(self.bounds.size.width/2,0.0f), // Starting point
0.0f, // Starting redius
CGPointMake(self.bounds.size.width/2,0.0f), // Ending point
self.bounds.size.width/2, // Ending radius
kCGGradientDrawsBeforeStartLocation); // Options
// Release it an pray that everything was well written
CGGradientRelease(gradient);
}
This is how it looks like on my screen...
I simply placed an image just over the shadow but you can easily merge the shadow with an image if you subclass UIImageView and override it's drawRect method.
As you can see, what I did was to simply setup a circular gradient but I loaded a scaling matrix to squash it before drawing it to the context.
If you plan to do anything else in that method, remember that you have the matrix in place and everything you do will be deformed by it. You may want to save the the CTM with CGContextSaveGState() before loading the matrix and then restore the original state with CGContextRestoreGState()
Hope this was what you where looking for.
Cheers.
I could explain how to do this in code, or explain how to use a tool which generate this code for you. I choose the latter.
Using PaintCode (free demo available, 1 hour limit per session).
Draw an oval
Draw a Rectangle which intersects with the bottom of the oval.
CMD click both the rectangle and the oval, in the "Objects" list in the top left corner.
Press the Intersect button in the Toolbar.
Select the Bezier from the Objects list.
Set its Stroke to "No Stroke"
Click the Gradient button (located on the left, below the Selection Inspector)
Press the "+" button
Change the gradient color to light grey.
From the Selection inspector, change the Fill Style to "Gradient"
Select Gradient: Linear
adjust the gradient till you are satisfied.
- (void)viewDidLoad
{
UIImage *natureImage = [UIImage imageNamed:#"nature.jpg"];
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0, 0, 200, 200);
layer.position = CGPointMake(380, 200);
layer.contents = (id)natureImage.CGImage;
layer.shadowOffset = CGSizeMake(0,2);
layer.shadowOpacity = 0.70;
layer.shadowPath = (layer.shadowPath) ? nil : [self bezierPathWithCurvedShadowForRect:layer.bounds].CGPath;
[self.view.layer addSublayer:layer];
}
- (UIBezierPath*)bezierPathWithCurvedShadowForRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint topLeft = rect.origin;
CGPoint bottomLeft = CGPointMake(0.0, CGRectGetHeight(rect) + offset);
CGPoint bottomMiddle = CGPointMake(CGRectGetWidth(rect)/2, CGRectGetHeight(rect) - curve);
CGPoint bottomRight = CGPointMake(CGRectGetWidth(rect), CGRectGetHeight(rect) + offset);
CGPoint topRight = CGPointMake(CGRectGetWidth(rect), 0.0);
[path moveToPoint:topLeft];
[path addLineToPoint:bottomLeft];
[path addQuadCurveToPoint:bottomRight controlPoint:bottomMiddle];
[path addLineToPoint:topRight];
[path addLineToPoint:topLeft];
[path closePath];
return path;
}
Hope this will help you.

Finding Bounding Box of Rotated Image that has Transparent Background

I have a UIImage that has a transparent background. When rotating this image, I'd like to find the bounding box around the graphic (ie the nons transparent part, if you rotate it in a UIImageView, it will find the bounding box around the entire UIImage including the transparent part).
Is there an Apple library that might do this for me? If not, does anyone know how this can be done?
If I understood your questions correctly, you can retrieve the frame (not bounds) of uiimageview then get the individual cgpoints and explicitly transform these points to get a transformed rectangle. Because in Apple's documentation it says: You can operate on a CGRect structure by calling the function CGRectApplyAffineTransform. This function returns the smallest rectangle that contains the transformed corner points of the rectangle passed to it. Transforming points 1 by 1 should avoid this auto-correcting behavior.
CGRect originalFrame = UIImageView.frame;
CGPoint p1 = originalFrame.origin;
CGPoint p2 = p1; p2.x += originalFrame.width;
CGPoint p3 = p1; p3.y += originalFrame.height;
//Use the same transformation that you applied to uiimageview here
CGPoint transformedP1 = CGPointApplyAffineTransform(p1, transform);
CGPoint transformedP2 = CGPointApplyAffineTransform(p2, transform);
CGPoint transformedP3 = CGPointApplyAffineTransform(p3, transform);
Now you should be able to define a new rectangle from these 3 points (4th one is optional because width and height can be calculated from 3 points. One point to note is that you cannot store this new rectangle in a cgrect because cgrect is defined by an origin and a size so its edges are always parallel to x and y axis. Apple's cgrect definition does not allow rotated rectangles to be stored.

Alternative for UIPicker

Is there any other method for a SLOT-MACHINE app? This because a UIPICKER method doesn't have a slow animation effect.
This could be done using CALayer animation.
Your main slotMachineView layer's class will need to be CATransformLayer to allow 3D transforms of the sublayers.
Let's say you have 10 square images that represent the symbols on the reel. For each of your images, create a CALayer who's contents property is one of your images. Then to each layer you'll need to apply 2 transforms:
First you'll need to rotate each layer (2 * PI) / 10 about its X axis
Then translate some distance (which you'll need to calculate) along
the Z axis.
Now add these layers to your view's layer. You should now see a get "cylinder" of images around the X axis.
To rotate the cylinder you'll need to adjust the first transform - either with a CAAnimation or by using a timer and adjusting the X axis rotation by an offset she the timer fires.
I'll leave it to you to figure out the full implementation details - but here's some code to load and create an initial "reel"
int imageCount = 16;
for (int i = 0; i < imageCount; i++) {
// Load image from the bundle
NSString * fileName = [NSString stringWithFormat: #"Image-%d", i];
NSString * filePath = [[NSBundle mainBundle] pathForResource: fileName ofType: #"jpeg"];
UIImage * image = [UIImage imageWithContentsOfFile: filePath];
// Create a layer with the image as content - My image size was 60x60
CALayer * imageLayer = [CALayer layer];
imageLayer.bounds = (CGRect){CGPointZero, {60.0, 60.0}};
imageLayer.contents = (id)image.CGImage;
imageLayer.position = (CGPoint){CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)};
// Set the initial image transform - I've hardcoded the translate along the
// z-axis to 150, but this will vary depending on the number of images
// and their size - you'll need to do some maths to work it out... sines or
// cosines or somesuch
CGFloat angle = (2.0 * M_PI) / imageCount * i;
CATransform3D transform = CATransform3DMakeRotation(angle, 1.0, 0.0, 0.0);
transform = CATransform3DTranslate(transform, 0.0, 0.0, 150.0);
imageLayer.transform = transform;
[self.layers addObject: imageLayer];
[self.view.layer addSublayer: imageLayer];
}
}
To rotate it, all you need to do is change the rotation part of the transform. For extra credit, you could add an increasing shadow to the layers as they move away from the centre.
You could use UIImageView for each digit/symbol, and animate the movement of the images that yo display. Using UIPicker there would be limited to letters and digits; with UIImageView you could add other typical slot-machine visuals, such as cherries, etc.
Are we doing your homework?
Use vertical UIScrollViews side by side, have paging enabled, UIImageViews for the views.
It will look flat by default - but it would be functionally equivalent.
You'd need a bit of Core Animation / Core Graphics to make it look more like the UIPickerView.

Antialiasing edges of UIView after transformation using CALayer's transform

I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.

Draw line or rectangle on cocos2d Layer

Can you let me know what is the best way to draw a line or rectangle on a scene layer using Cocos2d ios4 iphone.
So far have tried Texture2d, but it is more like a paint brush and is not so good. Tried drawing a line using draw method, but previous line disappears on drawing another line.
Basically want to draw multiple horizontal ,vertical, oblique beams. Please suggest. Any code would help a lot .
The code to draw using texture is below:
CGPoint start = edge.start;
CGPoint end = edge.end;
// begin drawing to the render texture
[target begin];
// for extra points, we'll draw this smoothly from the last position and vary the sprite's
// scale/rotation/offset
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[brush setScale:0.3];
// Call visit to draw the brush, don't call draw..
[brush visit];
}
}
// finish drawing and return context back to the screen
[target end];
The rendering is not good esp. with oblique lines as the scaling affects the quality.
Cheers
You could create a separate layer and call the draw method like this:
-(void) draw
{
CGSize s = [[Director sharedDirector] winSize];
drawCircle( ccp(s.width/2, s.height/2), circleSize, 0, 50, NO);
It's for a circle but the principle is the same. This is from a project I made a while back and it worked then. Don't know if anything has changed since.
You need to add draw method to your layer:
-(void) draw {
// ...
}
Inside it you can use some openGL like functions and cocos2d wrapper methods for openGL.
Hint: other methods can be called inside draw method.
But keep in mind that using other name for method
containing openGL instructions, that's not called inside draw mentioned above simply won't work.
Even when called from update method or other method used by scheduleUpdate selector.
So you will end up with something like this:
-(void) draw {
glEnable(GL_LINE_SMOOTH);
glColor4ub(255, 0, 100, 255);
glLineWidth(4);
CGPoint verts[] = { ccp(0,200), ccp(300,200) };
ccDrawLine(verts[0], verts[1]);
[self drawSomething];
[self drawSomeOtherStuffFrom:ccp(a,b) to:ccp(c,d)];
[someObject doSomeDrawingAsWell];
}
For more information check out cocos2d-iphone programming guide :
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:draw_update?s[]=schedule#draw