Having wrestled with many problems with MKOverlayViews and MKMapKit I find myself with one more. I'm using an MKOverlayPathView subclass to draw a singular large overlay on the screen (it covers London at the moment). Within that overlay are lots of coloured squares for arguments sake:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
int size = 0;
min = 0;
max = 0;
CGFloat alpha = 0.4f;
int recursionLevel = 4;
QuadNodeOverlay *quadNodeOverlay = (QuadNodeOverlay *)self.overlay;
Cluster *clusters = quadtree_clusters(quadNodeOverlay.quadTree, recursionLevel, &size);
if (clusteringMethod == 1) {
/* Draw paths/squares */
CGPathRef *paths = [self createPaths:clusters size:size];
for (int i = 0; i < size; i++) {
CGPathRef path = paths[i];
Cluster cluster = clusters[i];
CGContextBeginPath(context);
CGContextAddPath(context, path);
//CGContextSetFillColorWithColor(context, colors[cluster.depth]);
CGColorRef gradientColor = [self newColor:cluster.count];
CGContextSetFillColorWithColor(context, gradientColor);
CGContextDrawPath(context, kCGPathFillStroke);
CGColorRelease(gradientColor);
CGPathRelease(path);
}
free(paths);
} else if (clusteringMethod == 2) {
// CUT
}
[self setAlpha:alpha];
}
The above code is incomplete but provides a solid basis for the question. The code works fine and performs admirable fast regardless of the number of squares I want to draw. The problem I have is below a certain point I'm not drawing this overlay; I manage this in the view controller containing the map:
- (void)mapView:(MKMapView *)map regionDidChangeAnimated:(BOOL)animated {
self.annotations = [locationServer itemsForMapRegion:map.region withMapView:map maxCount:100];
NSLog(#"There are like %d cards dude.",[self.annotations count]);
if ( [self.annotations count] > 40) {
[mapView removeAnnotations:annotations];
// Cluster
if (![[mapView overlays] containsObject:quadClusters]) {
[mapView addOverlay:quadClusters];
}
} else {
// Don't
[mapView removeOverlay:quadClusters];
// Add pins.
[mapView removeAnnotations:annotations];
[mapView addAnnotations:annotations];
}
}
Again this code works fine my purposes right now. The problem is when I zoom in to a level where the clusters are not displayed but the individual annotations are when I zoom back out a chuck of the overlay is rendered without transparency. Vis:
As a foot note. Removing the setAlpha from the overlay and modifying the code that draws the squares to read:
CGColorRef gradientColor = [self newColor:cluster.count];
CGColorRef alphaGradientColor = CGColorCreateCopyWithAlpha(gradientColor, alpha);
CGContextSetFillColorWithColor(context, alphaGradientColor);
resolves the issue, however surely applying the alpha channel to the overlay as a whole is more efficient.
Related
I have a problem when i try to join lines. Here is a picture:
and I like that it looks like this:
and my code is:
- (void) drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
const CGFloat *components = CGColorGetComponents(self.lineColor.CGColor);
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
if(CGColorGetNumberOfComponents(self.lineColor.CGColor) == 2)
{
red = 1;
green = 1;
blue = 1;
alpha = 1;
}
else
{
red = components[0];
green = components[1];
blue = components[2];
alpha = components[3];
if (alpha <= 0) alpha = 1;
}
// set the stroke color and width
CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
CGContextSetLineWidth(context, 2.0);
if (self.points.count >0) {
BezierPoint *firstPoint = [self.points objectAtIndex:0];
CGContextMoveToPoint(context, firstPoint.center.x, firstPoint.center.y);
int index = 0;
for (BezierPoint *point in self.points ) {
if(index == 0){
index++;
continue;
}
CGContextAddLineToPoint(context, point.center.x, point.center.y);
}
CGContextAddLineToPoint(context, firstPoint.center.x, firstPoint.center.y);
}
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
CGContextDrawPath(context, kCGPathFillStroke);
}
the problem I have is that for every point you add, the lines overlap and I would like that as I stay I add points for geometric figure that globulins do not overlap
If anyone can help me I will thank!!
I would move the logic that determines the order the lines are drawn to your view controller, and access the data needed to draw the view with from a delegate using a protocol. Views should not own their data. For example, in your .h try something like;
#import <UIKit/UIKit.h>
#protocol CowDrawViewDataSource
-(NSArray *) pointsToDraw;
#end
#interface CowDrawView : UIView
#property (nonatomic , weak) id <CowDrawViewDataSource> dataSource;
#end
Then, (and I hope this does answer you question) in the view controller, you set as your views delegate, construct the
-(NSArray *) pointsToDraw;
method, in such a way, to send your array of points in an order in from which they can be drawn. Say by finding the point the top/left point, then the top/right, then bottom/right, bottom/left. (although such an approach might not work so well with irregular polygons, and shapes that are contiguous, but not polygons.)
After you figure out how to get the array in the order you want, you can get it in your drawRect by sending a message to it's delegate such as
NSArray *points = [self.dataSource pointToDraw];
with little re-work in the drawRect it's self.
The easy way to correct your problem is first draw only single line segments between two points and see the results. Then you can form loops. I guess you are not assigning perfect coordinates points or your loop must consist of :
CGContextAddLineToPoint(context, point.center.x, point.center.y);
And then,
CGContextMoveToPoint(context, point.center.x, point.center.y);
This shows that first draw line and move to next point.
Hope this helps.
I have a simple oval shape (comprised of CGMutablePaths) from which I'd like the user to be able to drag an object around it. Just wondering how complicated it is to do this, do I need to know a ton of math and physics, or is there some simple built in way that will allow me to do this? IE the user drags this object around the oval, and it orbits it.
This is an interesting problem. We want to drag an object, but constrain it to lie on a CGPath. You said you have “a simple oval shape”, but that's boring. Let's do it with a figure 8. It'll look like this when we're done:
So how do we do this? Given an arbitrary point, finding the nearest point on a Bezier spline is rather complicated. Let's do it by brute force. We'll just make an array of points closely spaced along the path. The object starts out on one of those points. As we try to drag the object, we'll look at the neighboring points. If either is nearer, we'll move the object to that neighbor point.
Even getting an array of closely-spaced points along a Bezier curve is not trivial, but there is a way to get Core Graphics to do it for us. We can use CGPathCreateCopyByDashingPath with a short dash pattern. This creates a new path with many short segments. We'll take the endpoints of each segment as our array of points.
That means we need to iterate over the elements of a CGPath. The only way to iterate over the elements of a CGPath is with the CGPathApply function, which takes a callback. It would be much nicer to iterate over path elements with a block, so let's add a category to UIBezierPath. We start by creating a new project using the “Single View Application” template, with ARC enabled. We add a category:
#interface UIBezierPath (forEachElement)
- (void)forEachElement:(void (^)(CGPathElement const *element))block;
#end
The implementation is very simple. We just pass the block as the info argument of the path applier function.
#import "UIBezierPath+forEachElement.h"
typedef void (^UIBezierPath_forEachElement_Block)(CGPathElement const *element);
#implementation UIBezierPath (forEachElement)
static void applyBlockToPathElement(void *info, CGPathElement const *element) {
__unsafe_unretained UIBezierPath_forEachElement_Block block = (__bridge UIBezierPath_forEachElement_Block)info;
block(element);
}
- (void)forEachElement:(void (^)(const CGPathElement *))block {
CGPathApply(self.CGPath, (__bridge void *)block, applyBlockToPathElement);
}
#end
For this toy project, we'll do everything else in the view controller. We'll need some instance variables:
#implementation ViewController {
We need an ivar to hold the path that the object follows.
UIBezierPath *path_;
It would be nice to see the path, so we'll use a CAShapeLayer to display it. (We need to add the QuartzCore framework to our target for this to work.)
CAShapeLayer *pathLayer_;
We'll need to store the array of points-along-the-path somewhere. Let's use an NSMutableData:
NSMutableData *pathPointsData_;
We'll want a pointer to the array of points, typed as a CGPoint pointer:
CGPoint const *pathPoints_;
And we need to know how many of those points there are:
NSInteger pathPointsCount_;
For the “object”, we'll have a draggable view on the screen. I'm calling it the “handle”:
UIView *handleView_;
We need to know which of the path points the handle is currently on:
NSInteger handlePathPointIndex_;
And while the pan gesture is active, we need to keep track of where the user has tried to drag the handle:
CGPoint desiredHandleCenter_;
}
Now we have to get to work initializing all those ivars! We can create our views and layers in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
}
We create the path-displaying layer like this:
- (void)initPathLayer {
pathLayer_ = [CAShapeLayer layer];
pathLayer_.lineWidth = 1;
pathLayer_.fillColor = nil;
pathLayer_.strokeColor = [UIColor blackColor].CGColor;
pathLayer_.lineCap = kCALineCapButt;
pathLayer_.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:pathLayer_];
}
Note that we haven't set the path layer's path yet! It's too soon to know the path at this time, because my view hasn't been laid out at its final size yet.
We'll draw a red circle for the handle:
- (void)initHandleView {
handlePathPointIndex_ = 0;
CGRect rect = CGRectMake(0, 0, 30, 30);
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = nil;
circleLayer.strokeColor = [UIColor redColor].CGColor;
circleLayer.lineWidth = 2;
circleLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, circleLayer.lineWidth, circleLayer.lineWidth)].CGPath;
circleLayer.frame = rect;
handleView_ = [[UIView alloc] initWithFrame:rect];
[handleView_.layer addSublayer:circleLayer];
[self.view addSubview:handleView_];
}
Again, it's too soon to know exactly where we'll need to put the handle on screen. We'll take care of that at view layout time.
We also need to attach a pan gesture recognizer to the handle:
- (void)initHandlePanGestureRecognizer {
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleWasPanned:)];
[handleView_ addGestureRecognizer:recognizer];
}
At view layout time, we need to create the path based on the size of the view, compute the points along the path, make the path layer show the path, and make sure the handle is on the path:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self createPath];
[self createPathPoints];
[self layoutPathLayer];
[self layoutHandleView];
}
In your question, you said you're using a “simple oval shape”, but that's boring. Let's draw a nice figure 8. Figuring out what I'm doing is left as an exercise for the reader:
- (void)createPath {
CGRect bounds = self.view.bounds;
CGFloat const radius = bounds.size.height / 6;
CGFloat const offset = 2 * radius * M_SQRT1_2;
CGPoint const topCenter = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds) - offset);
CGPoint const bottomCenter = { topCenter.x, CGRectGetMidY(bounds) + offset };
path_ = [UIBezierPath bezierPath];
[path_ addArcWithCenter:topCenter radius:radius startAngle:M_PI_4 endAngle:-M_PI - M_PI_4 clockwise:NO];
[path_ addArcWithCenter:bottomCenter radius:radius startAngle:-M_PI_4 endAngle:M_PI + M_PI_4 clockwise:YES];
[path_ closePath];
}
Next we're going to want to compute the array of points along that path. We'll need a helper routine to pick out the endpoint of each path element:
static CGPoint *lastPointOfPathElement(CGPathElement const *element) {
int index;
switch (element->type) {
case kCGPathElementMoveToPoint: index = 0; break;
case kCGPathElementAddCurveToPoint: index = 2; break;
case kCGPathElementAddLineToPoint: index = 0; break;
case kCGPathElementAddQuadCurveToPoint: index = 1; break;
case kCGPathElementCloseSubpath: index = NSNotFound; break;
}
return index == NSNotFound ? 0 : &element->points[index];
}
To find the points, we need to ask Core Graphics to “dash” the path:
- (void)createPathPoints {
CGPathRef cgDashedPath = CGPathCreateCopyByDashingPath(path_.CGPath, NULL, 0, (CGFloat[]){ 1.0f, 1.0f }, 2);
UIBezierPath *dashedPath = [UIBezierPath bezierPathWithCGPath:cgDashedPath];
CGPathRelease(cgDashedPath);
It turns out that when Core Graphics dashes the path, it can create segments that slightly overlap. We'll want to eliminate those by filtering out each point that's too close to its predecessor, so we'll define a minimum inter-point distance:
static CGFloat const kMinimumDistance = 0.1f;
To do the filtering, we'll need to keep track of that predecessor:
__block CGPoint priorPoint = { HUGE_VALF, HUGE_VALF };
We need to create the NSMutableData that will hold the CGPoints:
pathPointsData_ = [[NSMutableData alloc] init];
At last we're ready to iterate over the elements of the dashed path:
[dashedPath forEachElement:^(const CGPathElement *element) {
Each path element can be a “move-to”, a “line-to”, a “quadratic-curve-to”, a “curve-to” (which is a cubic curve), or a “close-path”. All of those except close-path define a segment endpoint, which we pick up with our helper function from earlier:
CGPoint *p = lastPointOfPathElement(element);
if (!p)
return;
If the endpoint is too close to the prior point, we discard it:
if (hypotf(p->x - priorPoint.x, p->y - priorPoint.y) < kMinimumDistance)
return;
Otherwise, we append it to the data and save it as the predecessor of the next endpoint:
[pathPointsData_ appendBytes:p length:sizeof *p];
priorPoint = *p;
}];
Now we can initialize our pathPoints_ and pathPointsCount_ ivars:
pathPoints_ = (CGPoint const *)pathPointsData_.bytes;
pathPointsCount_ = pathPointsData_.length / sizeof *pathPoints_;
But we have one more point we need to filter. The very first point along the path might be too close to the very last point. If so, we'll just discard the last point by decrementing the count:
if (pathPointsCount_ > 1 && hypotf(pathPoints_[0].x - priorPoint.x, pathPoints_[0].y - priorPoint.y) < kMinimumDistance) {
pathPointsCount_ -= 1;
}
}
Blammo. Point array created. Oh yeah, we also need to update the path layer. Brace yourself:
- (void)layoutPathLayer {
pathLayer_.path = path_.CGPath;
pathLayer_.frame = self.view.bounds;
}
Now we can worry about dragging the handle around and making sure it stays on the path. The pan gesture recognizer sends this action:
- (void)handleWasPanned:(UIPanGestureRecognizer *)recognizer {
switch (recognizer.state) {
If this is the start of the pan (drag), we just want to save the starting location of the handle as its desired location:
case UIGestureRecognizerStateBegan: {
desiredHandleCenter_ = handleView_.center;
break;
}
Otherwise, we need to update the desired location based on the drag, and then slide the handle along the path toward the new desired location:
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint translation = [recognizer translationInView:self.view];
desiredHandleCenter_.x += translation.x;
desiredHandleCenter_.y += translation.y;
[self moveHandleTowardPoint:desiredHandleCenter_];
break;
}
We put in a default clause so clang won't warn us about the other states that we don't care about:
default:
break;
}
Finally we reset the translation of the gesture recognizer:
[recognizer setTranslation:CGPointZero inView:self.view];
}
So how do we move the handle toward a point? We want to slide it along the path. First, we have to figure out which direction to slide it:
- (void)moveHandleTowardPoint:(CGPoint)point {
CGFloat earlierDistance = [self distanceToPoint:point ifHandleMovesByOffset:-1];
CGFloat currentDistance = [self distanceToPoint:point ifHandleMovesByOffset:0];
CGFloat laterDistance = [self distanceToPoint:point ifHandleMovesByOffset:1];
It's possible that both directions would move the handle further from the desired point, so let's bail out in that case:
if (currentDistance <= earlierDistance && currentDistance <= laterDistance)
return;
OK, so at least one of the directions will move the handle closer. Let's figure out which one:
NSInteger direction;
CGFloat distance;
if (earlierDistance < laterDistance) {
direction = -1;
distance = earlierDistance;
} else {
direction = 1;
distance = laterDistance;
}
But we've only checked the nearest neighbors of the handle's starting point. We want to slide as far as we can along the path in that direction, as long as the handle is getting closer to the desired point:
NSInteger offset = direction;
while (true) {
NSInteger nextOffset = offset + direction;
CGFloat nextDistance = [self distanceToPoint:point ifHandleMovesByOffset:nextOffset];
if (nextDistance >= distance)
break;
distance = nextDistance;
offset = nextOffset;
}
Finally, update the handle's position to our newly-discovered point:
handlePathPointIndex_ += offset;
[self layoutHandleView];
}
That just leaves the small matter of computing the distance from the handle to a point, should the handle be moved along the path by some offset. Your old buddy hypotf computes the Euclidean distance so you don't have to:
- (CGFloat)distanceToPoint:(CGPoint)point ifHandleMovesByOffset:(NSInteger)offset {
int index = [self handlePathPointIndexWithOffset:offset];
CGPoint proposedHandlePoint = pathPoints_[index];
return hypotf(point.x - proposedHandlePoint.x, point.y - proposedHandlePoint.y);
}
(You could speed things up by using squared distances to avoid the square roots that hypotf is computing.)
One more tiny detail: the index into the points array needs to wrap around in both directions. That's what we've been relying on the mysterious handlePathPointIndexWithOffset: method to do:
- (NSInteger)handlePathPointIndexWithOffset:(NSInteger)offset {
NSInteger index = handlePathPointIndex_ + offset;
while (index < 0) {
index += pathPointsCount_;
}
while (index >= pathPointsCount_) {
index -= pathPointsCount_;
}
return index;
}
#end
Fin. I've put all of the code in a gist for easy downloading. Enjoy.
I have a UIView that contains a drawing that I've made using CALayers added as sublayers. It is a red square with a blue triangle centered inside. I am able to determine which shape has been touched using the following code:
CGPoint location = [gesture locationInView:self.view];
CALayer* layerThatWasTapped = [self.view.layer hitTest:location];
NSLog(#"Master Tap Location: %#", NSStringFromCGPoint(location));
NSLog(#"Tapped Layer Name: %#", layerThatWasTapped.name);
NSLog(#"Tapped Layer Parent: %#", layerThatWasTapped.superlayer.name);
int counter = layerThatWasTapped.superlayer.sublayers.count;
NSArray * subs = layerThatWasTapped.superlayer.sublayers;
//Loop through all sublayers of the picture
for (int i=0; i<counter; i++) {
CALayer *layer = [subs objectAtIndex:i];
CAShapeLayer* loopLayer = (CAShapeLayer*)layerThatWasTapped.modelLayer;
CGPathRef loopPath = loopLayer.path;
CGPoint loopLoc = [gesture locationInView:cPage];
loopLoc = [self.view.layer convertPoint:loopLoc toLayer:layer];
NSLog(#"loopLoc Tap Location: %#", NSStringFromCGPoint(loopLoc));
//determine if hit is on a layer
if (CGPathContainsPoint(loopPath, NULL, loopLoc, YES)) {
NSLog(#"Layer %i Name: %# Hit",i, layer.name);
} else {
NSLog(#"Layer %i Name: %# No Hit",i, layer.name);
}
}
My problem lies with areas where the bounds of the triangle overlap the square.
This results in the triangle registering the hit even when the hit is outside of the
triangles path. This is a simplified example (I may have many overlapping shapes stacked in the view)
Is there a way to loop through all of the sublayers and hittest each one to see if it lies under the tapped point?
OR
Is there a way to have the bounds of my layers match their paths so the hit occurs only on a visible area?
Since you're using CAShapeLayer, this is pretty easy. Make a subclass of CAShapeLayer and override its containsPoint: method, like this:
#implementation MyShapeLayer
- (BOOL)containsPoint:(CGPoint)p
{
return CGPathContainsPoint(self.path, NULL, p, false);
}
#end
Make sure that wherever you were allocating a CAShapeLayer, you change it to allocate a MyShapeLayer instead:
CAShapeLayer *triangle = [MyShapeLayer layer]; // this way
CAShapeLayer *triangle = [[MyShapeLayer alloc] init]; // or this way
Finally, keep in mind that when calling -[CALayer hitTest:], you need to pass in a point in the superlayer's coordinate space:
CGPoint location = [gesture locationInView:self.view];
CALayer *myLayer = self.view.layer;
location = [myLayer.superlayer convertPoint:location fromLayer:myLayer];
CALayer* layerThatWasTapped = [myLayer hitTest:location];
I have an MKMapView with 2 overlays. They represent 1. The route someone has taken. 2. A series of circular regions of interest. In order to update either of the overlays I update their data, then invalidate their related view:
[(RoutePolyline *)self.overlay appendPolylines:polylines];
MKOverlayPathView *overlayView = (MKOverlayPathView *)[self.mapView viewForOverlay:self.overlay];
[overlayView invalidatePath];
The problem is that adding a single line to my RoutePolyline and invalidating its related view causes every overlay view to be redrawn about 80 times. Given that this happens for every location update this is incredibly expensive.
Here is the code from the only method in my RouteOverlayView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
RoutePolyline *routePolyline = (RoutePolyline *)self.overlay;
int polylineCount = [routePolyline.polylines count];
for (int i = 0; i < polylineCount; i++)
{
MKPolyline *polyline = [routePolyline.polylines objectAtIndex:i];
CGPathRef path = [MKUtils newPolyPathWithPolyline:polyline overlayView:self];
if (path)
{
[self applyFillPropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathStroke);
[self applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
}
}
What could be causing these extra redraws?
I'm implementing a subclass of UIView that displays a gauge dial with a sprite for the indicator. It has angle property that I can vary to make the needle point to different angles. It works, but on the same values for the position of the needle make it show up in different locations on the phone and the simulator. It's an iPhone 4, so I'm sure the double resolution thing is behind this, but I don't know what to do about it. I tried setting the UIView's layer's contentScaleFactor but that fails. I thought UIView got the resolution thing for free. Any suggestions?
I should note that the NSLog statements report 150 for both .frame.size. dimensions, in both the simulator and the device.
Here's the .m file
UPDATE: In the simulator, I found how to set the hardware to iPhone 4, and it looks just like the device now, both are scaling and positioning the sprite at half size.
UPDATE 2: I made a workaround. I set the .scale of my sprite equal to the UIView's contentScaleFactor and then use it to dived the UIView in half if it's a lo-res screen and the full width if it's hi-res. I still don't see why this is necessary, as I should be working in points now, not pixels. It must have something to do with the custom drawing code in the Sprite or VectorSprite classes.
I'd still appreciate some feedback if anyone has some...
#import "GaugeView.h"
#implementation GaugeView
#synthesize needle;
#define kVectorArtCount 4
static CGFloat kVectorArt[] = {
3,-4,
2,55,
-2,55,
-3,-4
};
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
needle = [VectorSprite withPoints:kVectorArt count:kVectorArtCount];
needle.scale = (float)self.contentScaleFactor; // returns 1 for lo-res, 2 for hi-res
NSLog(#" needle.scale = %1.1f", needle.scale);
needle.x = self.frame.size.width / ((float)(-self.contentScaleFactor) + 3.0); // divisor = 1 for hi-res, 2 for lo-res
NSLog(#" needle.x = %1.1f", needle.x);
needle.y = self.frame.size.height / ((float)(-self.contentScaleFactor) + 3.0);
NSLog(#" needle.y = %1.1f", needle.y);
needle.r = 0.0;
needle.g = 0.0;
needle.b = 0.0;
needle.alpha = 1.0; }
}
self.backgroundColor = [UIColor clearColor];
return self;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform t0 = CGContextGetCTM(context);
t0 = CGAffineTransformInvert(t0);
CGContextConcatCTM(context, t0);
[needle updateBox];
[needle draw: context];
}
- (void)dealloc {
[needle release];
[super dealloc];
}
#end
I believe the answer is that iOS takes care of the resolution scaling automatically in drawRect methods, but in custom drawing code, you have to do it yourself.
In my example, I used the UIView's contentsScaleFactor to scale my sprite. In the future, in my custom draw method (not shown) I'll query [UIScreen mainScreen] scale and scale accordingly there.