Questions regarding NSRect...In the Hillegass book we are creating an NSRect into which we are drawing an oval (NSBezierPath *). Depending on where in our view we mouse down and subsequently drag, the NSRect's size.width and/or size.height may be negative (i.e. if we start in upper right, drag lower left - both are negative). When actually drawing, does the system use our negative width and/or height to merely locate the NSPoint of where we dragged? Thus updating the NSRect? And if we ever need the size of the NSRect, should we just use the absolute values?
In the chapter, the authors used the MIN() and MAX() macros to create an NSRect. However, in the challenge solution they provide these three methods in response to mouse events:
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] fromView:nil];
// Why do we offset by 0.5? Because lines drawn exactly on the .0 will end up spread over two pixels.
workingOval = NSMakeRect(pointInView.x + 0.5, pointInView.y + 0.5, 0, 0);
[self setNeedsDisplay:YES];
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] fromView:nil];
workingOval.size.width = pointInView.x - (workingOval.origin.x - 0.5);
workingOval.size.height = pointInView.y - (workingOval.origin.y - 0.5);
[self setNeedsDisplay:YES];
}
- (void)mouseUp:(NSEvent *)theEvent
{
[[self document] addOvalWithRect:workingOval];
workingOval = NSZeroRect; // zero rect indicates we are not presently drawing
[self setNeedsDisplay:YES];
}
This code produces a successful rectangle regardless of the potential negative values. I understand that the negative values merely reflect the shift left with respect to the origin ( the point from which we "mouse downed"). What is going on behind the scenes in properly calculating the NSPoint to which we dragged?
An NSRect is just defined as an NSPoint and an NSSize. An NSSize is just defined as a pair of CGFloats, but the documentation says:
Normally, the values of width and height are non-negative. The functions that create an NSSize structure do not prevent you from setting a negative value for these attributes. If the value of width or height is negative, however, the behavior of some methods may be undefined.
In the code you show above, absolutely nothing fancy is going on behind the scenes. You're creating a rectangle (workingOval) that happens to have a negative size or width, and you're not actually using it anywhere.
Depending on what you do with workingOval elsewhere, what's going on behind the scenes will be different. But it'll be one of three very simple things. Some methods treat a rect like (origin=(30, 40), size=(-10, -20)) as identical to (origin=(20, 20), size=(10, 20)); others treat it as an invalid rect; some make assumptions that they don't test and just give you garbage results. For example, NSMinX will return 30, not 10.
Related
I am working on a Coverflow style layout for UICollectionView. Its a simple enough concept, and just to make it easier there are about 100 tutorials on the subject. However as anyone doing transforms knows, m34 is the magic number. However, as soon as I set it to get some perspective for my rotations, the views disappear. The only appear when the rotation is near 0.
I suspect this is a bounds/zIndex issue, however after days of monkeying around with zIndexes and clipping adjustments, the cells just refuse to appear. Any help is greatly appreciated.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size};
CGFloat maxDistance = visibleRect.size.width / 2.0;
CGFloat maxRotation = M_PI_2;
[array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attributes, NSUInteger idx, BOOL *stop) {
CGFloat distanceFromCenter = CGRectGetMidX(visibleRect) - attributes.center.x;
CGFloat percentageFromCenter = distanceFromCenter / maxDistance;
percentageFromCenter = MIN(percentageFromCenter, 1.0);
percentageFromCenter = MAX(percentageFromCenter, -1.0);
CGFloat angle = percentageFromCenter * maxRotation;
CATransform3D transform = CATransform3DIdentity;
// transform.m34 = 1.0 / -800.0;
transform = CATransform3DRotate(transform, angle, 0.0, 1.0, 0.0);
attributes.transform3D = transform;
}];
return array;
}
So thanks to one #mpospese on Twitter. The problem wasn't with the m34 value itself. It was with a particular feature of UICollectionView.
when frame height exceeds collection view’s height, cells are removed
So in my case I had a UICollectionViewLayout specially designed to grab the height of the UICollectionView and use it for the height of the cells. Problem is, when m34 is factored in that obviously adjusts the frame and therefore exceeds the bounds. Rather than just clipping the cell like most UIView operations would have done pointing directly to the problem, UICollectionView actually automatically removes cells entirely if they so much as brush the outer-bounds.
I made a painting program. Everything works as I expected. But while drawing, sometimes some strange things happen.
I run app, and press left mouse button on image. It should draw point from code:
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, brushTextura);
glPointSize(100);
glVertexPointer(2, GL_FLOAT, 0,GLVertices);
glDrawArrays(GL_POINTS, 0, count);
glDisableClientState(GL_VERTEX_ARRAY);
at point where I press. mouseDown registers mouseDown location, converts it to NSValue, sends to array, and then before drawing I extract NSValue to CGPoint and send it to GLfloat so that it could be drawn by glDrawArrays. But no matter where I click the mouse on the image it draws the point at coordinates (0,0). After that every thing works OK. See image:
This was first problem. The second problem is that when I paint with it (drag pressed mouse), sometimes points appear where they are not drawn. Image:
When I continue drag it disappears. After some dragging it appears again and disappears again. And so on. Image:
Any Ideas why it could happen? I will post code bellow:
Mouse down:
- (void) mouseDown:(NSEvent *)event
{
location = [self convertPoint: [event locationInWindow] fromView:self];
NSValue *locationValue = [NSValue valueWithPoint:location];
[vertices addObject:locationValue];
[self drawing];
}
Mouse dragged:
- (void) mouseDragged:(NSEvent *)event
{
location = [self convertPoint: [event locationInWindow] fromView:self];
NSValue *locationValue = [NSValue valueWithPoint:location];
[vertices addObject:locationValue];
[self drawing];
}
Drawing:
- (void) drawing {
int count = [vertices count] * 2;
NSLog(#"count: %d", count);
int currIndex = 0;
GLfloat *GLVertices = (GLfloat *)malloc(count * sizeof(GLfloat));
for (NSValue *locationValue in vertices) {
CGPoint loc = locationValue.pointValue;
GLVertices[currIndex++] = loc.x;
GLVertices[currIndex++] = loc.y;
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, brushTextura);
glPointSize(100);
glVertexPointer(2, GL_FLOAT, 0, GLVertices);
glDrawArrays(GL_POINTS, 0, count);
glDisableClientState(GL_VERTEX_ARRAY);
}
You are setting your count variable (the one used in glDrawArrays) to [vertices count] * 2, which seems strange.
The last argument to glDrawArrays is the number of vertices to draw, whereas in your code it seems you are setting it to double the number (maybe you thought it's the number of floats?), which means you are just drawing rubbish after the first count vertices.
The fact the vertices aren't rendered in the exact location you clicked on should be a hint the problem is you've not properly determined the hit point within the view.
Your code has:
location = [self convertPoint: [event locationInWindow] fromView: self];
which tells the view to convert the point from its coordinates (self) to the same view's coordinates (self), even though the point is actually relative to the window.
To convert the point from the window's coordinates to the view, change that line to the following:
location = [self convertPoint: [event locationInWindow] fromView: nil];
The arguments to glDrawArrays are defined as (GLenum mode, GLint first, GLsizei count).
The second arguments defines the first index of the vertex attributes used when drawing. You're passing 1 as the first index which makes your vertex coordinates unmatch. I assume that you want 0 there.
http://www.opengl.org/sdk/docs/man/xhtml/glDrawArrays.xml
I'm pretty sure this is more a of a math question, but I'll phrase it in the context of UIView and iPad-related objective C
I am importing raw data from a mapping file I have created from some public domain material downloaded elsewhere, then split out to isolate various regions within the map. Each region has a number of sub-regions, much like, for example, the continental US and then the various states which appear within the US, and then each sub-region is broken down again, into, let's say, counties.
Each state, and each county has a bounding box which tells me the origin, the width, and height each is.
In my initial setup, I created a separate view for each state, and then another view for each county. The polygon representing the area of the state/county was rendered (obviously with the county on top of the state so it would be visible) relative to a view I created through interface builder, called mainContainerView. This initial setup worked correctly.
Now I am trying to change things a bit, by adding the counties to the UIView holding the polygon for the state, so I will be able to overlay the state as a clipping mask on the counties. The problem is that no matter what I try, I cannot seem to get the county to translate to the right place within the state's view.
It seems like it should be straightforward addition or subtraction as the scaling for each item is exactly the same, and I'm not trying to do any major transformations, so I do not believe the CFAffineTransformation family is needed.
I can post code if necessary, but I'm not trying to get someone to write my program for me; I just want someone to point me in the right direction here, by giving me a suggestion on how to set the county relative to the state within the state's view.
As per a request, here's the relevant code that I am working on right now. This code does not work, but it gives you the idea as to what I'm doing. Posting sample data is a little more difficult, as it involves arrays of points and data extracted from a .SHP file designed to produce a map (and subdivisions). I'll include some comments in the code with some real point values as I step through the program to show you what's happening to them.
MASK_MAX_EASTING, MASK_MAX_NORTHING, MASK_MIN_EASTING, and MASK_MIN_NORTHING are constants which define the bounding box for the entire map of the country when made up of states.
DIST_MAX_EASTING, DIST_MAX_NORTHING, DIST_MIN_EASTING, and DIST_MIN_NORTHING are constants which define the bounding box for a map of the country when made up of the counties. The scales of the two maps are slightly different, so, by using the different bounding boxes, I've been able to scale the two maps to the same size.
-(void)didLoadMap:(NSNotification *)notification {
id region = [notification object];
ShapePolyline *polygon = [region polygon];
if ([notification name] == #"MapsLoadingForState") {
// m_nBoundingBox is an array which contains the RAW northing and easting values for each subdivision. [0] - west limit, [1] - south limit, [2] - east limit, [3] - north limit.
// The code below, combined with the drawrect method in DrawMap.m (below) puts all the states on the map in precisely the right places, so for the state maps, it works just fine.
CGFloat originX = ((polygon->m_nBoundingBox[0]-MASK_MIN_EASTING)*stateScaleMultiplier)+([mainContainerView frame].size.width/2);
CGFloat originY = ((MASK_MAX_NORTHING-(polygon->m_nBoundingBox[3]))*stateScaleMultiplier)+[mainContainerView frame].origin.y;
CGFloat width = polygon->m_nBoundingBox[2] - polygon->m_nBoundingBox[0];
CGFloat height = polygon->m_nBoundingBox[3] - polygon->m_nBoundingBox[1];
CGFloat scaledWidth = width*stateScaleMultiplier;
CGFloat scaledHeight = height*stateScaleMultiplier;
UIColor *subViewColor = [UIColor colorWithRed:0.0 green:1.0 blue:1.0 alpha:0.0];
stateMapView = [[DrawMap alloc] initWithFrame:CGRectMake(originX, originY, scaledWidth, scaledHeight)];
[stateMapView setBackgroundColor:subViewColor];
[stateMapView setStateScale:stateScaleMultiplier];
[stateMapView setCountyScale:countyScaleMultiplier]; // Not actually needed.
[stateMapView setClippingMask:polygon];
UIColor *colorMask = [UIColor colorWithWhite:1.0 alpha:1.0];
[stateMapView setForeground:colorMask];
[states addObject:stateMapView]; // Add the state map view to an array (for future use)
[mapView addSubview:stateMapView]; // MapView is a UIView of equivalent size and shape as mainContainerView.
} else {
// This is where the problems occur.
CGFloat originX = (polygon->m_nBoundingBox[0]-DIST_MIN_EASTING); // 4431590 (raw data)
originX *= countyScaleMultiplier; // 303.929108
originX += ([mainContainerView frame].size.width/2); // 815.929077
CGFloat originY = (DIST_MAX_NORTHING-polygon->m_nBoundingBox[3]); 4328997
originY *= countyScaleMultiplier; // 296.893036
originY -= [mainContainerView frame].origin.y; // 340.893036
CGRect frame = [stateMapView frame]; // Dummy variable created for watches in the debugger. x=856.237183, y=332.169922 width=34.3800087, height=28.7534008
// When I was invoking DrawMap.h and the included drawrect method, the county map would easily be displayed in the right place, as you can see by the values above.
// This is where I think the problem is. The X value is WAY off as far as I can tell.
originX -= frame.origin.x; // -40.3081055
originY -= frame.origin.y; // 8.72311401
CGPoint countyOrigin = CGPointMake(originX,originY);
// Translate the county's origin so it is relative to the origin of stateMapView, not MainContainerView (doesn't work)
[stateMapView addCountyMap:[region polygon] withColor:winner translatedBy:countyOrigin];
[stateMapView setNeedsDisplay];
}
I am aware that there are several issues with this code and some stuff outside the scope of this question may make a few of you raise an eyebrow (or two) but this is definitely a work in progress...
Here's the relevant code from DrawMap.m; I've cut a bunch of stuff out because it is extraneous.
- (void)drawRect:(CGRect)rect {
// Set up
for (int i=0;i<[countyMaps count];i++) {
// Draw the polygon.
[[countyColors objectAtIndex:i] setFill];
[self drawPolygon:[countyMaps objectAtIndex:i]
usingScale:stateScale
translatedBy:CGPointMake([[countyTranslations objectAtIndex:2*i] floatValue],
[[countyTranslations objectAtIndex:2*i+1] floatValue])];
}
// Set the blend mode to multiply
CGContextSetBlendMode(context, kCGBlendModeMultiply);
// Draw a path with clippingMask
[[UIColor colorWithWhite:1.0 alpha:1.0] setFill];
// CGPoint translate = CGPointMake(0,0);
[self drawPolygon:clippingMask usingScale:stateScale translatedBy:CGPointMake(0,0)];
}
-(void)drawPolygon:(ShapePolyline *)aPolygon usingScale:(float)mapScale translatedBy:(CGPoint)trans {
for (int j=0;j<[aPolygon numParts];j++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineJoinStyle:kCGLineJoinRound];
int startIndex = [[[aPolygon m_Parts] objectAtIndex:j] intValue];
int endIndex = [aPolygon numPoints];
CGPoint startPoint;
[[[aPolygon m_Points] objectAtIndex:startIndex] getValue:&startPoint];
startPoint.x *=mapScale;
startPoint.y *=mapScale;
startPoint.x -= trans.x;
startPoint.y -= trans.y;
[path moveToPoint:startPoint];
if (j+1 != [aPolygon numParts]){
endIndex = [[[aPolygon m_Parts] objectAtIndex:j+1] intValue];
}
for (int k=startIndex+1; k<endIndex; k++)
{
CGPoint nextPoint;
[[[aPolygon m_Points] objectAtIndex:k] getValue:&nextPoint];
nextPoint.x *= mapScale;
nextPoint.y *= mapScale;
nextPoint.x -= trans.x;
nextPoint.y -= trans.y;
[path addLineToPoint:nextPoint];
}
[path closePath];
// [path stroke];
[path fill];
}
}
This tome is really may be too much information, or it may not be enough. Either way, hopefully by adding code, I've given you some information to go on...
-SOLVED-
And it was so simple. I'm surprised it took me this long to figure it out, as I was right in my initial question - it was simple addition and subtraction:
All translations are now done inside the methods which render the polygons. For each point in the polygon, I needed to add the origin of the state's view, and subtract the origin of the county's bounding box, then subtract 44 from the Y-value (the height of the control bar).
This, I think, is an example of over-thinking a problem, getting frustrated, over-thinking more, only to find out three days later that the answer is staring you in the face, waving a red flag, and shouting, "I'M OVER HERE!!!!"
I want to rotate an UILabel around an arbitrary point in a circular manner, not a straight line. This is my code.The final point is perfect but it goes through a straight line between the initial and the end points.
- (void)rotateText:(UILabel *)label duration:(NSTimeInterval)duration degrees:(CGFloat)degrees {
/* Setup the animation */
[UILabel beginAnimations:nil context:NULL];
[UILabel setAnimationDuration:duration];
CGPoint rotationPoint = CGPointMake(160, 236);
CGPoint transportPoint = CGPointMake(rotationPoint.x - label.center.x, rotationPoint.y - label.center.y);
CGAffineTransform t1 = CGAffineTransformTranslate(label.transform, transportPoint.x, -transportPoint.y);
CGAffineTransform t2 = CGAffineTransformRotate(label.transform,DEGREES_TO_RADIANS(degrees));
CGAffineTransform t3 = CGAffineTransformTranslate(label.transform, -transportPoint.x, +transportPoint.y);
CGAffineTransform t4 = CGAffineTransformConcat(CGAffineTransformConcat(t1, t2), t3);
label.transform = t4;
/* Commit the changes */
[UILabel commitAnimations];
}
You should set your own anchorPoint
Its very much overkill to use a keyframe animation for what really is a change of the anchor point.
The anchor point is the point where all transforms are applied from, the default anchor point is the center. By moving the anchor point to (0,0) you can instead make the layer rotate from the bottom most corner. By setting the anchor point to something where x or y is outside the range 0.0 - 1.0 you can have the layer rotate around a point that lies outside of its bounds.
Please read the section about Layer Geometry and Transforms in the Core Animation Programming Guide for more information. It goes through this in detail with images to help you understand.
EDIT: One thing to remember
The frame of your layer (which is also the frame of your view) is calculated using the position, bounds and anchor point. Changing the anchorPoint will change where your view appears on screen. You can counter this by re-setting the frame after changing the anchor point (this will set the position for you). Otherwise you can set the position to the point you are rotating to yourself. The documentation (linked to above) also mentions this.
Applied to you code
The point you called "transportPoint" should be updated to calculate the difference between the rotation point and the lower left corner of the label divided by the width and height.
// Pseudocode for the correct anchor point
transportPoint = ( (rotationX - labelMinX)/labelWidth,
(rotationX - labelMinY)/labelHeight )
I also made the rotation point an argument to your method. The full updated code is below:
#define DEGREES_TO_RADIANS(angle) (angle/180.0*M_PI)
- (void)rotateText:(UILabel *)label
aroundPoint:(CGPoint)rotationPoint
duration:(NSTimeInterval)duration
degrees:(CGFloat)degrees {
/* Setup the animation */
[UILabel beginAnimations:nil context:NULL];
[UILabel setAnimationDuration:duration];
// The anchor point is expressed in the unit coordinate
// system ((0,0) to (1,1)) of the label. Therefore the
// x and y difference must be divided by the width and
// height of the label (divide x difference by width and
// y difference by height).
CGPoint transportPoint = CGPointMake((rotationPoint.x - CGRectGetMinX(label.frame))/CGRectGetWidth(label.bounds),
(rotationPoint.y - CGRectGetMinY(label.frame))/CGRectGetHeight(label.bounds));
[label.layer setAnchorPoint:transportPoint];
[label.layer setPosition:rotationPoint]; // change the position here to keep the frame
[label.layer setTransform:CATransform3DMakeRotation(DEGREES_TO_RADIANS(degrees), 0, 0, 1)];
/* Commit the changes */
[UILabel commitAnimations];
}
I decided to post my solution as an answer. It works fine accept it doesn't have the old solutions's curve animations (UIViewAnimationCurveEaseOut), but I can sort that out.
#define DEGREES_TO_RADIANS(angle) (angle / 180.0 * M_PI)
- (void)rotateText:(UILabel *)label duration:(NSTimeInterval)duration degrees:(CGFloat)degrees {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path,nil, 160, 236, 100, DEGREES_TO_RADIANS(0), DEGREES_TO_RADIANS(degrees), YES);
CAKeyframeAnimation *theAnimation;
// animation object for the key path
theAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
theAnimation.path=path;
CGPathRelease(path);
// set the animation properties
theAnimation.duration=duration;
theAnimation.removedOnCompletion = NO;
theAnimation.autoreverses = NO;
theAnimation.rotationMode = kCAAnimationRotateAutoReverse;
theAnimation.fillMode = kCAFillModeForwards;
[label.layer addAnimation:theAnimation forKey:#"position"];
}
CAKeyframeAnimation is the right tool for this job. Most UIKit animations are between start and end points. The middle points are not considered. CAKeyframeAnimation allows you to define those middle points to provide a non-linear animation. You will have to provide the appropriate bezier path for your animation. You should look at this example and the one's provided in the Apple documentation to see how it works.
translate, rotate around center, translate back.
I am creating a custom NSSlider with a custom NSSliderCell. All is working beautifully, other than the knob. When I drag it to the max value the knob is being clipped, I can only see 50% of the knob image.
When assigning my custom NSSliderCell I am setting the knobThickness to the width of the image I am using as the knob. I assumed (I guess wrongly) that it would take that into account and stop it from clipping?
Any ideas what I am doing wrong? The slider is hitting the maxValue only when the knob is clipped at 50%, so its not travelling without adding any value.
- (void)drawKnob:(NSRect)knobRect {
NSImage * knob = _knobOff;
knobRectVar = knobRect;
[[self controlView] lockFocus];
[knob
compositeToPoint:
NSMakePoint(knobRect.origin.x+4,knobRect.origin.y+knobRect.size.height+20)
operation:NSCompositeSourceOver];
[[self controlView] unlockFocus];
}
- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped {
rect.size.height = 8;
[[self controlView] lockFocus];
NSImage *leftCurve = [NSImage imageNamed:#"customSliderLeft"];
[leftCurve drawInRect:NSMakeRect(5, 25, 8, 8) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1];
NSRect leftRect = rect;
leftRect.origin.x=13;
leftRect.origin.y=25;
leftRect.size.width = knobRectVar.origin.x + (knobRectVar.size.width/2);
[leftBarImage setSize:leftRect.size];
[leftBarImage drawInRect:leftRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction:1];
[[self controlView] unlockFocus];
}
The NSSLider expects a special sizes off the knob images for each control size:
NSRegularControlSize: 21x21
NSSmallControlSize: 15x15
NSMiniControlSize: 12x12
Unfortunately the height of your knob image mustn't exceed one of this parameters. But it's width may be longer. If it is you may count an x position for the knob like this:
CGFloat newOriginX = knobRect.origin.x *
(_barRect.size.width - (_knobImage.size.width - knobRect.size.width)) / _barRect.size.width;
Where _barRect is a cellFrame of your bar background from:
- (void)drawBarInside:(NSRect)cellFrame flipped:(BOOL)flipped;
I've created a simple solution for the custom NSSlider. Follow this link
https://github.com/Doshipak/LADSlider
You can override [NSSliderCell knobRectFlipped:] in addition to [NSSliderCell drawKnob:].
Here is my solution:
- (void)drawKnob:(NSRect)rect
{
NSImage *drawImage = [self knobImage];
NSRect drawRect = [self knobRectFlipped:[self.controlView isFlipped]];
CGFloat fraction = 1.0;
[drawImage drawInRect:drawRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:fraction respectFlipped:YES hints:nil];
}
- (NSRect)knobRectFlipped:(BOOL)flipped
{
NSImage *drawImage = [self knobImage];
NSRect drawRect = [super knobRectFlipped:flipped];
drawRect.size = drawImage.size;
NSRect bounds = self.controlView.bounds;
bounds = NSInsetRect(bounds, ceil(drawRect.size.width / 2), 0);
CGFloat val = MIN(self.maxValue, MAX(self.minValue, self.doubleValue));
val = (val - self.minValue) / (self.maxValue - self.minValue);
CGFloat x = val * NSWidth(bounds) + NSMinX(bounds);
drawRect = NSOffsetRect(drawRect, x - NSMidX(drawRect) + 1, 0);
return drawRect;
}
Know it's been awhile but I ran into this issue myself and found a quick-and-dirty workaround.
I couldn't get around the initial reason for this but it seems that NSSlider is expecting a quadratic handle image.
The easiest way I found was to set the range of your slider to be from 0.0f - 110.0f for example.
Then you check in the valueChanged target method assigned if the value is > 100.0f and set it back to that value if it is. I created a background image with some pixels of alpha-only pixels on the right side so your background isn't wider than the actual fader range.
Quick-and-dirty but doesn't require a lot code and works pretty well.
Hope this helps other guys stumbling upon the same issue.
You don’t need to lock and unlock focus on the controlView from inside cell drawing methods. These methods are only called by your controlView’s -drawRect: method, which is called with the view’s focus locked.
Why are you adding 20 points to the Y coordinate the knob image is composited to in -drawKnob?