iOS Recipes - Matt Drance [30]
self.transformed.backgroundColor = [UIColor blackColor].CGColor;
[self.layer addSublayer:self.transformed];
CATransform3D t = CATransform3DMakeTranslation((width-self.cubeSize)/2, 0, 0);
for (int i =STARTNUM; i <= NUM ; i++) {
self.label.text = [NSString stringWithFormat:@"%d",i];
[self.transformed addSublayer:[self makeSurface:t]];
t = CATransform3DRotate(t, RADIANS(self.rotAngle), 0, 1, 0);
t = CATransform3DTranslate(t, self.cubeSize, 0, 0);
}
self.currentAngle = 0;
self.currentTileNum = 0;
CALayer *leftFade = [CALayer layer];
leftFade.frame = CGRectMake(0, 0, width/2-5, self.cubeSize);
leftFade.contents = (id)[UIImage imageNamed:@"leftFade.png"].CGImage;
leftFade.opacity = 0.5;
[self.layer addSublayer:leftFade];
CALayer *rightFade = [CALayer layer];
rightFade.frame = CGRectMake(width/2+5, 0, width/2, self.cubeSize);
rightFade.contents = (id)[UIImage imageNamed:@"rightFade.png"].CGImage;
rightFade.opacity = 0.5;
[self.layer addSublayer:rightFade];
}
The makeSurface method creates and composites the new layer and applies the specified transform. The method needs to calculate the zPosition, the radius of our circle, based on the number of sides and the size of the layer.
NumberSpinControl/NumberSpinControl/SpinNumbers.m
- (CALayer*)makeSurface:(CATransform3D)t
{
self.rotAngle = CIRCLE/NUM;
CALayer *imageLayer = [CALayer layer];
imageLayer.anchorPoint = CGPointMake(1, 1);
float factor = (cos(RADIANS(self.rotAngle/2))/sin(RADIANS(self.rotAngle/2)))/2;
imageLayer.zPosition = self.cubeSize*factor;
imageLayer.frame = self.tileRect;
imageLayer.transform = t;
imageLayer.contents = (id)[self.backImage PRPCompositeView].CGImage;
return imageLayer;
}
To set up the features of the numeral we need—such as font size, alignment, and color—the getter for the label property contains a lazy initializer. Now we just need to update the text value as each layer’s content is composited.
NumberSpinControl/NumberSpinControl/SpinNumbers.m
- (UILabel *)label
{
if (!label) {
label = [[UILabel alloc] initWithFrame:self.tileRect];
label.textAlignment = UITextAlignmentCenter;
label.font = [UIFont systemFontOfSize:self.cubeSize/1.4];
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor blackColor];
}
return label;
}
The backImage property is implemented in a similar way as the label. Note that the background color must be opaque and match the color of the background view. If the background color is set to clearColor, the layers now in the background become partly visible, which is probably undesirable.
NumberSpinControl/NumberSpinControl/SpinNumbers.m
- (UIImageView *)backImage
{
if (!backImage) {
backImage = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:@"redBackground.png"]];
backImage.frame = self.tileRect;
backImage.backgroundColor = [UIColor blackColor];
[backImage addSubview:self.label];
}
return backImage;
}
The beginTrackingWithTouch:withEvent: method initializes the touch recognition process by storing the initial touch position, which it uses for later comparison to the next touch.
NumberSpinControl/NumberSpinControl/SpinNumbers.m
- (BOOL)beginTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGPoint location = [touch locationInView:self];
self.flick = 0;
self.previousXPosition = location.x;
self.beganLocation = location.x;
newAngle = self.currentAngle;
[self sendActionsForControlEvents:UIControlEventTouchDown];
return YES;
}
As the user manipulates the control, the continueTrackingWithTouch:withEvent: method is called continuously. For each horizontal touch movement, we calculate the effective change in angle of the circle of layers and apply that as a rotation to the transformed parent layer. It is this transformation, based on the movement of the touch point, that provides the visual feedback to the user. The flick property is calculated to be a measure of velocity between the current touch point and the previous one.
NumberSpinControl/NumberSpinControl/SpinNumbers.m