in my application I have a class name playGrid which contains three objects, an NSArray of images named plays, and two NSUIntegers rowCount and columCount for the amount of rows and columns in the popover that will be used.
For this example, I have 6 images and I am trying to show them over 2 columns and three rows. I am trying to display these views in a popover. Previously I was able to do this with blocks of colors, but now that I am trying to do with images I am unable to generate the popover correctly. Listed below is my drawRect code for the successful popover that shows the colors.
How would I convert this to work with UIImages instead of the colors?
In the example below, rowCount and columnCount are 2 and 3 just like the one we are trying to make, but the array is named colors, and contains six UIColor items.
- (void)drawRect:(CGRect)rect {
CGRect b = self.bounds;
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGFloat columnWidth = b.size.width / columnCount;
CGFloat rowHeight = b.size.height / rowCount;
for (NSUInteger rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for (NSUInteger columnIndex = 0; columnIndex < columnCount; columnIndex++) {
NSUInteger colorIndex = rowIndex * columnCount + columnIndex;
UIColor *color = [self.colors count] > colorIndex ? [self.colors objectAtIndex:colorIndex] : [UIColor whiteColor];
CGRect r = CGRectMake(b.origin.x + columnIndex * columnWidth,
b.origin.y + rowIndex * rowHeight,
columnWidth, rowHeight);
CGContextSetFillColorWithColor(myContext, color.CGColor);
CGContextFillRect(myContext, r);
}
}
}
I know I will not need certain things like the color or the CGContextSetFillColorWithColor line, but how do i replace myContent with the image, it seems like this is what I would have to do, but I have not been able to successfully do this. Thanks again for your help as I am newer to Objective C.
Assuming that you want to continue to do drawing into the view using drawRect:, I think you just want to use the drawInRect: method on UIImage.
So:
UIImage *image = [plays objectAtIndex:rowIndex * columnCount + columnIndex];
[image drawInRect:r];
I've heard the performance of drawInRect is not great - but I haven't measured it myself. Note also that drawInRect: scales the image to fit the rect as needed; so visually, the results may not be what you intend.
An alternative is to compose your popover's view in a nib where you could layout your view statically. If you always want a 2x3 matrix, you could setup your view there with a 2x3 grid of UIImageView instances.
EDIT: (to clarify that you are drawing images in the rects now, no filling blocks of color)
- (void)drawRect:(CGRect)rect {
CGRect b = self.bounds;
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGFloat columnWidth = b.size.width / columnCount;
CGFloat rowHeight = b.size.height / rowCount;
for (NSUInteger rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for (NSUInteger columnIndex = 0; columnIndex < columnCount; columnIndex++) {
CGRect r = CGRectMake(b.origin.x + columnIndex * columnWidth,
b.origin.y + rowIndex * rowHeight,
columnWidth, rowHeight);
UIImage *image = [plays objectAtIndex:rowIndex * columnCount + columnIndex];
[image drawInRect:r];
}
}
}
Related
Ok, so I'm trying to draw a dashed box that's subdivided into sections where I'm going to put content. Here's my code:
NSBezierPath *path = [NSBezierPath bezierPathWithRect:dirtyRect];
[path setLineWidth:3];
CGFloat pattern[2] = {5, 5};
[path setLineDash:pattern count:2 phase:0];
CGFloat totalHeight = header.frame.origin.y - 10;
CGFloat sectionOffset = 0;
if([game getNumPlayers] == 2) {
sectionOffset = totalHeight / 2;
} else if([game getNumPlayers] == 3) {
sectionOffset = totalHeight / 3;
} else if([game getNumPlayers] == 4) {
sectionOffset = totalHeight / 4;
}
for(int i = 0; i < [[game getPlayers] count]; i++) {
[path moveToPoint:NSMakePoint(0, totalHeight - (sectionOffset * i))];
[path lineToPoint:NSMakePoint(dirtyRect.size.width, totalHeight - (sectionOffset * i))];
}
[path stroke];
This is contained within my custom view's drawRect method, so dirtyRect is an NSRect equivalent to the bounds of the view. The variable header refers to another view in the superview, which I'm basing the location of the lines off of.
Here's a screenshot of what this code actually draws (except the label obviously):
As you can see, unless we're dealing with a very unfortunate optically illusion, which I doubt, the dividers contained in the box appear to be thicker than the outline of the box. I've explicitly set the lineWidth of the path object to be three, so I'm not sure why this is. I would be much appreciative of any suggestions that can be provided.
OK, I think the problem is that your outer box is just getting clipped by the edges of its view. You ask for a line that’s 3 points wide, so if your dirtyRect is the actual bounds of the view, then 1.5 points of the enclosing box will be outside the view, so you’ll only see 1.5 points of the edge lines.
The inner lines are showing the full 3-point thickness.
You can fix this by doing something like:
const CGFloat lineWidth = 3;
NSBezierPath *const path = [NSBezierPath bezierPathWithRect:NSInsetRect(dirtyRect, lineWidth/2, lineWidth/2)];
path.lineWidth = lineWidth;
This has been driving me crazy.. I have a large image, and need to have a view that is both zoomable, and scrollable (ideally it should also be able to rotate, but I've given up on that part). Since the image is very large, I plan on using CATiledLayer, but I simply can't get it to work.
My requirements are:
I need to be able to zoom (on mouse center) and pan
The image should not change its width:height ratio (shouldn't resize, only zoom).
This should run on Mac OS 10.9 (NOT iOS!)
Memory use shouldn't be huge (although up to like 100 MB should be ok).
I have the necessary image both complete in one file, and also tiled into many (even have it for different zoom levels). I prefer using the tiles, as that should be easier on memory, but both options are available.
Most of the examples online refer to iOS, and thus use UIScrollView for the zoom/pan, but I can't get to copy that behaviour for NSScrollView. The only example for Mac OS X I found is this, but his zoom always goes to the lower left corner, not the middle, and when I adapt the code to use png files instead of pdf, the memory use gets around 400 MB...
This is my best try so far:
#implementation MyView{
CATiledLayer *tiledLayer;
}
-(void)awakeFromNib{
NSLog(#"Es geht los");
tiledLayer = [CATiledLayer layer];
// set up this view & its layer
self.wantsLayer = YES;
self.layer = [CALayer layer];
self.layer.masksToBounds = YES;
self.layer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
// set up the tiled layer
tiledLayer.delegate = self;
tiledLayer.levelsOfDetail = 4;
tiledLayer.levelsOfDetailBias = 5;
tiledLayer.anchorPoint = CGPointZero;
tiledLayer.bounds = CGRectMake(0.0f, 0.0f, 41*256, 22*256);
tiledLayer.autoresizingMask = kCALayerNotSizable;
tiledLayer.tileSize = CGSizeMake(256, 256);
self.frame = CGRectMake(0.0f, 0.0f, 41*256, 22*256);
self.layer = tiledLayer;
//[self.layer addSublayer:tiledLayer];
[tiledLayer setNeedsDisplay];
}
-(void)drawRect:(NSRect)dirtyRect{
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
CGFloat scale = CGContextGetCTM(context).a;
CGSize tileSize = tiledLayer.tileSize;
tileSize.width /= scale;
tileSize.height /= scale;
// calculate the rows and columns of tiles that intersect the rect we have been asked to draw
int firstCol = floorf(CGRectGetMinX(dirtyRect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(dirtyRect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(dirtyRect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(dirtyRect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) {
for (int col = firstCol; col <= lastCol; col++) {
NSImage *tile = [self tileForScale:scale row:row col:col];
CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
tileSize.width, tileSize.height);
// if the tile would stick outside of our bounds, we need to truncate it so as
// to avoid stretching out the partial tiles at the right and bottom edges
tileRect = CGRectIntersection(self.bounds, tileRect);
[tile drawInRect:tileRect];
}
}
}
-(BOOL)isFlipped{
return YES;
}
But this deforms the image, and doesn't zoom or pan correctly (but at least the tile selection works)...
I can't believe this is so hard, any help would be greatly appreciated. Thanks :)
After a lot of research and tries, I finally managed to get this to work using this example. Decided to post it for future reference. Open the ZIP > CoreAnimationLayers> TiledLayers, there's a good example there. That's how CATiledLayer works with OS X, and since the example there doesn't handle zoom very well, I leave here my zoom code
-(void)magnifyWithEvent:(NSEvent *)event{
[super magnifyWithEvent:event];
if (!isZooming) {
isZooming = YES;
BOOL zoomOut = (event.magnification > 0) ? NO : YES;
if (zoomOut) {
[self zoomOutFromPoint:event.locationInWindow];
} else {
[self zoomInFromPoint:event.locationInWindow];;
}
}
}
-(void)zoomInFromPoint:(CGPoint)mouseLocationInWindow{
if(zoomLevel < pow(2, tiledLayer.levelsOfDetailBias)) {
zoomLevel *= 2.0f;
tiledLayer.transform = CATransform3DMakeScale(zoomLevel, zoomLevel, 1.0f);
tiledLayer.position = CGPointMake((tiledLayer.position.x*2) - mouseLocationInWindow.x, (tiledLayer.position.y*2) - mouseLocationInWindow.y);
}
}
-(void)zoomOutFromPoint:(CGPoint)mouseLocationInWindow{
NSInteger power = tiledLayer.levelsOfDetail - tiledLayer.levelsOfDetailBias;
if(zoomLevel > pow(2, -power)) {
zoomLevel *= 0.5f;
tiledLayer.transform = CATransform3DMakeScale(zoomLevel, zoomLevel, 1.0f);
tiledLayer.position = CGPointMake((tiledLayer.position.x + mouseLocationInWindow.x)/2, (tiledLayer.position.y + mouseLocationInWindow.y)/2);
}
}
I would like to know the width of the dark grey frame on either side of the inside picker part and the width of the separators between components. This would be at default settings (eg. what's shown in the .xib). Or, how to find such values programmatically. Thank you
To clarify, I'm looking for what the "suggested minimums" might be for those values. I'm putting my UIPickerView in a popover, and the UIPickerView is wider than 320 px (which is what the .xib gives as the "default"). I would like to know these values so I can add them to the popover's width to make sure it doesn't cut off components on the right side of the UIPickerView.
Well this can be a little tricky since the Picker can have a dynamic number of components. So in order to find the widths you have to take into account the number of components. This can be done like so:
NSInteger n = picker.numberOfComponents;
const CGFloat separatorWidth = 8.0f;
const CGFloat sectionExceedWidth = -2.0f;
CGFloat totalWidth = picker.bounds.size.width;
CGFloat panesWidth = totalWidth - separatorWidth * (n - 1);
for (NSInteger i = 0; i < n; i++) {
CGFloat sectionWidth = [picker rowSizeForComponent:i].width + sectionExceedWidth;
panesWidth -= sectionWidth;
}
CGFloat leftPaneWidth = ceilf(panesWidth * 0.5f);
CGFloat rightPaneWidth = panesWidth - leftPaneWidth;
CGFloat totalHeight = picker.bounds.size.height;
CGRect totalRect = CGRectMake(0, 0, totalWidth, totalHeight);
Where the left and right panes widths are found and the separator size is static.
This was pulled from here.
I've dragged a table view into my storyboard scene, but its height is static and won't adjust based on how many cells it contains unless I manually adjust it in storyboard. How can I get this table to adjust its own height based on the total height of its contents?
You're going to have to do some version of this:
Figure out how many cells will be displayed
Figure out the height for each cell
Do the math and set the height of the UITableView
Before you get mad (since I know that's pretty obvious), let me follow up with this:
If you know for sure how many cells will be displayed all the time (and it's a static value), you can just manually adjust the height in Interface Builder.
If you don't know for sure (which I'm guessing is the case, based on your question), you'll have to do #'s 1 thru 3 above at runtime. Then, because UITableView is a subclass of UIView, you can simply adjust its frame property.
For example:
I've got 7 cells.
Cells 1, 3 and 5 (indices 0, 2 and 4) are 20 pixels tall.
Cells 2, 4, 6 and 7 (indices 1, 3, 5 and 6) are 30 pixels tall.
I need a total display height of 180 pixels (you might actually need to play with this because of separators, etc..)
So, I can just call:
CGFloat newHeight = ... // Whatever you calculate
CGRect currentFrame = [[self MyTableView] frame];
[[self MyTableView] setFrame:CGRectMake(
currentFrame.origin.x,
currentFrame.origin.y,
currentFrame.size.width,
newHeight)
];
If you want to get really fancy, you can animate the change by wrapping it in a [UIView animationWithDuration:] block.
Hope this helps!
Update/Edit
Here's a kludgy way to do it... it works, but I'm sure there's a better way to do it. I do not claim this to be the best/fastest/most efficient way. This is more to show a principle. ;-)
- (CGFloat)totalHeightNeededForTableView:(UITableView *)tv {
CGFloat retVal = 0;
int sectionCount = [self numberOfSectionsInTableView:tv];
int rowCount = 0;
NSIndexPath *path = nil;
for (int i = 0; i < sectionCount; i = i + 1) {
rowCount = [self tableView:tv numberOfRowsInSection:i];
for (int j = 0; j < rowCount; j = j + 1) {
path = [NSIndexPath indexPathForRow:j inSection:i];
retVal = retVal + [self tableView:tv heightForRowAtIndexPath:path];
}
}
return retVal;
}
I have an NSBox, inside of which I am drawing small rectangles, with NSRectFill(). My code for this looks like this:
for (int i = 0; i <= 100; i++){
int x = (rand() % 640) + 20;
int y = (rand() % 315) + 196;
array[i] = NSMakeRect(x, y, 4, 4);
NSRectFill(array[i]);
}
This for loop creates 100 randomly placed rectangles within the grid. What I have been trying to do is create a sort of animation, created by this code running over and over, creating an animation of randomly appearing rectangles, with this code:
for (int i = 0; i <= 10; i++) {
[self performSelector:#selector(executeFrame) withObject:nil afterDelay:(.05*i)];
}
The first for loop is the only thing inside the executeFrame function, by the way. So, what I need to do is to erase all the rectangles between frames, so the number of them stays the same and they look like they are moving. I tried doing this by just drawing the background again, by calling [myNsBox display]; before calling executeFrame, but that made it seem as though no rectangles were being drawn. Calling it after did the same thing, so did switching in setNeedsDisplay instead of display. I cannot figure this one out, any help would be appreciated.
By the way, an additional thing is that when I try to run my code for executing the frames, without trying to erase the rectangles in between, all that happens is that 100 more rectangles are drawn. Even if I have requested that 1000 be drawn, or 10,000. Then though, if I leave the window and come back to it (immediately, time is not a factor here), the page updates and the rectangles are there. I attempted to overcome that by with [box setNeedsDisplayInRect:array[i]]; which worked in a strange way, causing it to update every frame, but erasing portions of the rectangles. Any help in this would also be appreciated.
It sounds like you're drawing outside drawRect: . If that's the case, move your drawing code into a view's (the box's or some subview's) drawRect: method. Otherwise your drawing will get stomped on by the Cocoa drawing system like you're seeing. You'll also want to use timers or animations rather than loops to do the repeated drawing.
I recently wrote an example program for someone trying to do something similar with circles. The approach I took was to create an array of circle specifications and to draw them in drawRect. It works pretty well. Maybe it will help. If you want the whole project, you can download it from here
#implementation CircleView
#synthesize maxCircles, circleSize;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
maxCircles = 1000;
circles = [[NSMutableArray alloc] initWithCapacity:maxCircles];
}
return self;
}
- (void)dealloc {
[circles release];
[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect {
NSArray *myCircles;
#synchronized(circles) {
myCircles = [circles copy];
}
NSRect bounds = [self bounds];
NSRect circleBounds;
for (NSDictionary *circleSpecs in myCircles) {
NSColor *color = [circleSpecs objectForKey:colorKey];
float size = [[circleSpecs objectForKey:sizeKey] floatValue];
NSPoint origin = NSPointFromString([circleSpecs objectForKey:originKey]);
circleBounds.size.width = size * bounds.size.width;
circleBounds.size.height = size * bounds.size.height;
circleBounds.origin.x = origin.x * bounds.size.width - (circleBounds.size.width / 2);
circleBounds.origin.y = origin.y * bounds.size.height - (circleBounds.size.height / 2);
NSBezierPath *drawingPath = [NSBezierPath bezierPath];
[color set];
[drawingPath appendBezierPathWithOvalInRect:circleBounds];
[drawingPath fill];
}
[myCircles release];
}
#pragma mark Public Methods
-(void)makeMoreCircles:(BOOL)flag {
if (flag) {
circleTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(makeACircle:) userInfo:nil repeats:YES];
}
else {
[circleTimer invalidate];
}
}
-(void)makeACircle:(NSTimer*)theTimer {
// Calculate a random color
NSColor *color;
color = [NSColor colorWithCalibratedRed:(arc4random() % 255) / 255.0
green:(arc4random() % 255) / 255.0
blue:(arc4random() % 255) / 255.0
alpha:(arc4random() % 255) / 255.0];
//Calculate a random origin from 0 to 1
NSPoint origin;
origin.x = (double)arc4random() / (double)0xFFFFFFFF;
origin.y = (double)arc4random() / (double)0xFFFFFFFF;
NSDictionary *circleSpecs = [NSDictionary dictionaryWithObjectsAndKeys:color, colorKey,
[NSNumber numberWithFloat:circleSize], sizeKey,
NSStringFromPoint(origin), originKey,
nil];
#synchronized(circles) {
[circles addObject:circleSpecs];
if ([circles count] > maxCircles) {
[circles removeObjectsInRange:NSMakeRange(0, [circles count] - maxCircles)];
}
}
[self setNeedsDisplay:YES];
}
#end