Graph not following orientation - objective-c

so I am trying to draw some grid lines that in landscape go all the way down to the bottom, however when I switch to landscape the the graph doesn't follow and the grid lines go smaller.
I have set the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
However it still doesn't work, here is my code. Can anyone spot the problem? this is the custom view file, the view controller is the default apart from the code above that returns yes.
.h file
#import <UIKit/UIKit.h>
#define kGraphHeight 300
#define kDefaultGraphWidth 900
#define kOffsetX 10
#define kStepX 50
#define kGraphBottom 300
#define kGraphTop 0
#interface GraphView : UIView
#end
And here is the implementation
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.6);
CGContextSetStrokeColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
// How many lines?
int howMany = (kDefaultGraphWidth - kOffsetX) / kStepX;
// Here the lines go
for (int i = 0; i < howMany; i++)
{
CGContextMoveToPoint(context, kOffsetX + i * kStepX, kGraphTop);
CGContextAddLineToPoint(context, kOffsetX + i * kStepX, kGraphBottom);
}
CGContextStrokePath(context);
}
Any help would be appreciated
btw I am following this tutorial
http://buildmobile.com/creating-a-graph-with-quartz-2d/#fbid=YDPLqDHZ_9X

You need to do three things:
In Interface Builder, select the view containing the graph and drag it so it fills the screen.
In Interface Builder, set the Autosizing masks on the view so it continues to fill the screen after rotation. You can change the simulated metrics of the outer view to make sure this happens.
The kGraphTop and kGraphBottom constants mean it only draws from 0 to 300 pixels. You could just make kGraphBottom larger, but that would not be reliable. Instead, you want to find the size of the view bounds and fill them from top to bottom.
Here's how to fill the bounds:
- (void) drawRect:(CGRect)rect
{
// Get the size of the view being drawn to.
CGRect bounds = [self bounds];
CGContextSetLineWidth(context, 0.6);
CGContextSetStrokeColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
// How many lines?
int howMany = (bounds.size.width - kOffsetX) / kStepX;
// Here the lines go
for (int i = 0; i < howMany; i++)
{
// Start at the very top of the bounds.
CGContextMoveToPoint(context, bounds.origin.x+kOffsetX + i * kStepX, bounds.origin.y);
// Draw to the bottom of the bounds.
CGContextAddLineToPoint(context, bounds.origin.x+kOffsetX + i * kStepX, bounds.origin.y+bounds.size.height);
}
CGContextStrokePath(context);
}

Related

I want to draw grid with following path & user can fill that as shown in image in ios

I want to draw grid like in first image for all devices & user is allowed to fill path only by dragging on grid as a second image,I have tried lots of stuff but no use of it,please suggest me how should i make?
Key Idea
First, you need to override UIView, so that you got to draw on something.
Second you need to know the height & width prior drawing the whole board.
Third need to maintain a data structure which will keep track where to draw & where not to.
First point is easy, we need to override the UIView class. We will do most of our task in drawRect method. For the example purpose say the extended class name is CustomGridView.
Before discussing point #2 we will discuss #3.
Each grid can be in one of the three states,
empty (no box drawn around this grid)
surrounded (box drawn around this grid)
filled surrounded (box drawn & filled with blue color)
so we will declare an enum for this three states
typedef enum{
EMPTY_BLOCK = 0,
SURROUNDED_BLOCK,
FILLED_SURROUNDED_BLOCK
}
Now we will keep track of the grid using a 2 dimensional array of numbers as declared follows:
NSMutableArray<NSMutableArray<NSNumber*>*>* gridStructure;
initialise the array with EMPTY_BLOCK & SURROUNDED_BLOCK as it is wanted in the grid to be shown. For example say you want a grid of height 5 block & width 4 block. All of the outer block will be SURROUNDED_BLOCK, then the array should look like this.
1, 1, 1, 1
1, 0, 0, 1
1, 0, 0, 1
1, 0, 0, 1
1, 1, 1, 1
now when ever a user will touch the grid, fill that grid position with FILLED_SURROUNDED_BLOCK.
Now the most point #2, most tedious thing. in the drawRect you will be specified with the rect which will denote the height & width of the CustomGridView. Here you will draw the grids according to the array co-ordinates. You have to split up the entire view into grid_width*grid_height spaces. Then according to the 2 dimensional array's value you will do the drawing. For example
if you found EMPTY_BLOCK then draw nothing and rest of them as follows.
Additional resources:
Add a UIPangestureRecognizer to track the user touch path & track the position in the 2 dimensional grid array. After updating the 2 dimensional array grid update the view's drawRect call [self setNeedsDisplay].
Draw 4 line two represent the FILLED_SURROUNDED_BLOCK thing.
How to draw a line is answered here
Best of luck.
Upon the above idea, here is a sample implementation
GridView.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE #interface GridView : UIView
#end
GridView.m
#import "GridView.h"
enum GRID_BOX_TYPE
{
GRID_BOX_BLANK = 0,
GRID_BOX_RED,
GRID_BOX_BLUE
};
#define NUMBER_OF_ROWS 5
#define NUMBER_OF_COLS 4
#define GRID_LINE_WIDTH 2
#implementation GridView
{
enum GRID_BOX_TYPE twoDGrid[NUMBER_OF_ROWS][NUMBER_OF_COLS];
CGSize gridSizeRatio;
}
#pragma mark init methods
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self){
//do initialization here
[self commonInitializer];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self){
//do initialization here
[self commonInitializer];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// CGContextClearRect(context, rect);
//global settings
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
for(int i=0;i<NUMBER_OF_ROWS;i++)
{
for(int j=0;j<NUMBER_OF_COLS;j++)
{
enum GRID_BOX_TYPE blockType = twoDGrid[i][j];
switch (blockType) {
case GRID_BOX_BLANK :
{
//draw white box
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextStrokeRectWithWidth(context, CGRectMake(j * rect.size.width * gridSizeRatio.width,
i * rect.size.height * gridSizeRatio.height,
rect.size.width * gridSizeRatio.width - GRID_LINE_WIDTH,
rect.size.height * gridSizeRatio.height - GRID_LINE_WIDTH), GRID_LINE_WIDTH);
CGContextDrawPath(context, kCGPathFill);
}
break;
case GRID_BOX_RED:
{
//draw red box
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextStrokeRectWithWidth(context, CGRectMake(j * rect.size.width * gridSizeRatio.width,
i * rect.size.height * gridSizeRatio.height,
rect.size.width * gridSizeRatio.width - GRID_LINE_WIDTH,
rect.size.height * gridSizeRatio.height - GRID_LINE_WIDTH), GRID_LINE_WIDTH);
CGContextDrawPath(context, kCGPathFill);
}
break;
case GRID_BOX_BLUE:
{
//draw blue box
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextStrokeRectWithWidth(context, CGRectMake(j * rect.size.width * gridSizeRatio.width,
i * rect.size.height * gridSizeRatio.height,
rect.size.width * gridSizeRatio.width - GRID_LINE_WIDTH,
rect.size.height * gridSizeRatio.height - GRID_LINE_WIDTH), GRID_LINE_WIDTH);
CGContextDrawPath(context, kCGPathFill);
}
break;
default:
break;
}
}
}
}
#pragma mark private initializer
-(void)commonInitializer
{
twoDGrid[0][0]= GRID_BOX_RED;
twoDGrid[0][1]= GRID_BOX_RED;
twoDGrid[0][2]= GRID_BOX_RED;
twoDGrid[0][3]= GRID_BOX_RED;
twoDGrid[1][0]= GRID_BOX_RED;
twoDGrid[1][1]= GRID_BOX_BLANK;
twoDGrid[1][2]= GRID_BOX_BLANK;
twoDGrid[1][3]= GRID_BOX_RED;
twoDGrid[2][0]= GRID_BOX_RED;
twoDGrid[2][1]= GRID_BOX_BLUE;
twoDGrid[2][2]= GRID_BOX_BLUE;
twoDGrid[2][3]= GRID_BOX_RED;
twoDGrid[3][0]= GRID_BOX_RED;
twoDGrid[3][1]= GRID_BOX_BLANK;
twoDGrid[3][2]= GRID_BOX_BLANK;
twoDGrid[3][3]= GRID_BOX_RED;
twoDGrid[4][0]= GRID_BOX_RED;
twoDGrid[4][1]= GRID_BOX_RED;
twoDGrid[4][2]= GRID_BOX_RED;
twoDGrid[4][3]= GRID_BOX_RED;
gridSizeRatio = CGSizeMake(1.0/NUMBER_OF_COLS, 1.0/NUMBER_OF_ROWS);
}
#end
Preview

How can I optimize drawing Core Graphics grid for low memory?

In my app, I draw a grid in a custom view controller. This gets embedded in a scroll view, so the grid gets redrawn from time to time after the user zooms in or out (not during zooming).
The problem is, I'm trying to optimize this method for low-memory devices like the iPad Mini, and it's still crashing. If I take this drawing away entirely, the app works fine, and it doesn't give me a reason for the crash; it just tells me the connection was lost. I can see in my instruments that memory is spiking just before it crashes, so I'm pretty certain this is the issue.
The view is 2.5 times the screen size horizontally and vertically, set programmatically when it's created. It has multiple CALayers and this grid drawing happens on several of them. The crash happens either immediately when I segue to this view, or soon after when I do some zooming.
Here are my drawing methods (somewhat simplified for readability and because they're pretty redundant):
#define MARKER_FADE_NONE 0
#define MARKER_FADE_OUT 1 // fades out at widest zoom
#define MARKER_FADE_IN 2 // fades in at widest zoom
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
if (layer == gridSubdividers) [self drawGridInContext:ctx spacing:1 lineWidth:1 markers:NO fade:MARKER_FADE_NONE];
if (layer == gridDividers) [self drawGridInContext:ctx spacing:5 lineWidth:1 markers:YES fade:MARKER_FADE_OUT];
if (layer == gridSuperdividers) [self drawGridInContext:ctx spacing:10 lineWidth:2 markers:YES fade:MARKER_FADE_IN];
}
// DRAW GRID LINES
- (void)drawGridInContext:(CGContextRef)context
spacing:(float)spacing
lineWidth:(NSInteger)lineWidth
markers:(BOOL)markers
fade:(int)fade
{
spacing = _gridUnits * spacing;
CGContextSetStrokeColorWithColor(context, [UIColor gridLinesColor]);
CGContextSetLineWidth(context, lineWidth);
CGContextSetShouldAntialias(context, NO);
float top = 0;
float bottom = _gridSize.height;
float left = 0;
float right = _gridSize.width;
// vertical lines (right of origin)
CGMutablePathRef path = CGPathCreateMutable();
for (float i = _origin.x + spacing; i <= _gridSize.width; i = i + spacing) {
CGPathMoveToPoint(path, NULL, i, top);
CGPathAddLineToPoint(path, NULL, i, bottom);
}
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
...then I repeat this for() loop three more times to draw the other lines of the grid. I also tried this slightly different version of the loop, where I don't create an individual path, but instead just add lines to the context and then stroke only at the very end of all four of these loops:
for (float i = _origin.x + spacing; i <= _gridSize.width; i = i + spacing) {
CGContextMoveToPoint(context, i, top);
CGContextAddLineToPoint(context, i, bottom);
}
CGContextStrokePath(context);
I also tried a combination of the two, in which I began and stroked within each cycle of the loop:
for (float i = _origin.x + spacing; i <= _gridSize.width; i = i + spacing) {
CGContextBeginPath(context);
CGContextMoveToPoint(context, i, top);
CGContextAddLineToPoint(context, i, bottom);
CGContextStrokePath(context);
}
So how can I streamline this whole drawing method so that it doesn't use as much memory? If it can't draw one path at a time, releasing each one afterward, like the first loop I showed above...I'm not really sure what to do other than read the available memory on the device and turn off the grid drawing function if it's low.
I'm also totally open to alternative methods of grid drawing.

NSSlider NSSliderCell clipping custom knob

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?

NSAffineTransforms not being used?

I have a subclass of NSView, and in that I'm drawing an NSImage. I'm unsing NSAffineTransforms to rotate, translate and scale the image.
Most of it works fine. However, sometimes, the transforms just don't seem to get activated.
For example, when I resize the window, the rotate transform doesn't happen.
When I zoom in on the image, it puts the lower left of the image in the correct place, but doesn't zoom it, but it does zoom the part of the image that would be to the right of the original sized image. If I rotate this, it zooms correctly, but translates wrong. (The transation may be a calculation error on my part)
Here is the code of my drawRect: (sorry for the long code chunk)
- (void)drawRect:(NSRect)rect
{
// Drawing code here.
double rotateDeg = -90* rotation;
NSAffineTransform *afTrans = [[NSAffineTransform alloc] init];
NSGraphicsContext *context = [NSGraphicsContext currentContext];
NSSize sz;
NSRect windowFrame = [[self window] frame];
float deltaX, deltaY;
NSSize superSize = [[self superview] frame].size;
float height, width, sHeight, sWidth;
NSRect imageRect;
if(image)
{
sz = [ image size];
imageRect.size = sz;
imageRect.origin = NSZeroPoint;
imageRect.size.width *= zoom;
imageRect.size.height *= zoom;
height = sz.height * zoom ;
width = sz.width *zoom ;
sHeight = superSize.height;
sWidth = superSize.width;
}
I need to grab the sizes of everything early so that I can use them later when I rotate. I am not sure that I need to protect any of that, but I'm paranoid from years of C...
[context saveGraphicsState];
// rotate
[afTrans rotateByDegrees:rotateDeg];
// translate to account for window size;
deltaX = 0;
deltaY = 0;
// translate to account for rotation
// in 1 and 3, X and Y are reversed because the entire FRAME
// (inculding axes) is rotated!
switch (rotation)
{
case 0:
// NSLog(#"No rotation ");
break;
case 1:
deltaY -= (sHeight - height);
deltaX -= sHeight ;
break;
case 2:
deltaX -= width;
deltaY -= ( 2*sHeight - height);
// it's rotating around the lower left of the FRAME, so,
// we need to move it up two frame hights, and then down
// the hieght of the image
break;
case 3:
deltaX += (sHeight - width);
deltaY -= sHeight;
break;
}
Since I'm rotating around the lower left corner, and I want the image to be locked to the upper left corner, I need to move the image around. When I rotate once, the image is in the +- quadrant, so I need to shift it up one view-height, and to the left a view-height minus an image height. etc.
[afTrans translateXBy:deltaX yBy:deltaY];
// for putting image in upper left
// zoom
[afTrans scaleBy: zoom];
printMatrix([afTrans transformStruct]);
NSLog(#"zoom %f", zoom);
[afTrans concat];
if(image)
{
NSRect drawingRect = imageRect;
NSRect frame = imageRect;
frame.size.height = MAX(superSize.height, imageRect.size.height) ;
[self setFrame:frame];
deltaY = superSize.height - imageRect.size.height;
drawingRect.origin.y += deltaY;
This makes the frame the correct size so that the image is in the upper left of the frame.
If the image is bigger than the window, I want the frame to be big enough so scroll bars appear. If it isn't I want the frame to be big enough that it reaches the top of the window.
[image drawInRect:drawingRect
fromRect:imageRect
operation:NSCompositeSourceOver
fraction:1];
if((rotation %2) )
{
float tmp;
tmp = drawingRect.size.width;
drawingRect.size.width = drawingRect.size.height;
drawingRect.size.height = tmp;
}
This code may be entirely historical, now that I look at it... the idea was to swap height andwidth if I rotated 90 or 270 degs.
}
else
NSLog(#"no image");
[afTrans release];
[context restoreGraphicsState];
}
Why do you use the superview's size? That's something you should almost never need to worry about. You should make the view work on its own without dependencies on being embedded in any specific view.
Scaling the size of imageRect is probably not the right way to go. Generally when calling -drawImage you want the source rect to be the bounds of the image, and scale the destination rect to zoom it.
The problems you're reporting kind of sound like you're not redrawing the entire view after changing the transformation. Are you calling -setNeedsDisplay: YES?
How is this view embedded in the window? Is it inside an NSScrollView? Have you made sure the scroll view resizes along with the window?

How would you give an Outline View alternating row colors?

I know there is a check box for it in IB but that only gives you the colors White and Blue. How would I make it so that it used different colors?
This article about gradient for TableView (cocoa not cocoa-touch) might give you some pointers how to go about it.
I have found this code to it,
// RGB values for stripe color (light blue)
#define STRIPE_RED (237.0 / 255.0)
#define STRIPE_GREEN (243.0 / 255.0)
#define STRIPE_BLUE (254.0 / 255.0)
static NSColor *sStripeColor = nil;
#implementation …
// This is called after the table background is filled in,
// but before the cell contents are drawn.
// We override it so we can do our own light-blue row stripes a la iTunes.
- (void) highlightSelectionInClipRect:(NSRect)rect {
[self drawStripesInRect:rect];
[super highlightSelectionInClipRect:rect];
}
// This routine does the actual blue stripe drawing,
// filling in every other row of the table with a blue background
// so you can follow the rows easier with your eyes.
- (void) drawStripesInRect:(NSRect)clipRect {
NSRect stripeRect;
float fullRowHeight = [self rowHeight] + [self intercellSpacing].height;
float clipBottom = NSMaxY(clipRect);
int firstStripe = clipRect.origin.y / fullRowHeight;
if (firstStripe % 2 == 0)
firstStripe++; // we're only interested in drawing the stripes
// set up first rect
stripeRect.origin.x = clipRect.origin.x;
stripeRect.origin.y = firstStripe * fullRowHeight;
stripeRect.size.width = clipRect.size.width;
stripeRect.size.height = fullRowHeight;
// set the color
if (sStripeColor == nil)
sStripeColor = [[NSColor colorWithCalibratedRed:STRIPE_RED
green:STRIPE_GREEN
blue:STRIPE_BLUE
alpha:1.0] retain];
[sStripeColor set];
// and draw the stripes
while (stripeRect.origin.y < clipBottom) {
NSRectFill(stripeRect);
stripeRect.origin.y += fullRowHeight * 2.0;
}
}
But I do not know how to Sub-class NSOutlineView. Could some one tell me how I could sub-class NSOutline View?