Is there a simple way to add rounded corners to NSRect elements in Objective-C? Currently we're applying a PNG image that simulates corners to this:
NSRect newFrame = NSMakeRect(0, 0, size.width, size.height);
But, performance becomes an issue because there are many instances of this NSRect along with the image being rendered with Core Animation. Perhaps rendering a native NSRect with rounded edges would be better from a performance standpoint? Do said edges look smooth (anti-aliased) when rendered with Core Animation?
NSRect is a struct containing an NSPoint and an NSSize, so I think you mean anything that accepts NSRects (so subclasses of NSView). All NSView subclass layers respond appropriately to -cornerRadius (except something about NSScrollView).
self.view.layer.masksToBounds = YES;
self.view.layer.cornerRadius = 10.0;
Related
I am looking to alter an existing iOS application so that instead of using multi-touch gestures to size and rotate images (two-finger pinch/zoom and twist), I want there to be a handle on all four corners of the image and one at the top so that the user can grab one of the handles to re-size or rotate.
I have been researching the topic but am unable to find anything pointing me in the right direction.
See this image for an example of what I'm talking about-
I'm assuming that because you're starting with a app that already has working pinch-zoom and twist gestures that your question is merely how to show those translucent circles for the handles. I'd be inclined to create UIView subclass that draws the circle, like so:
#implementation HandleView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, rect);
CGContextSetFillColorWithColor(context, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]); // white translucent
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithWhite:0.25 alpha:0.5] CGColor]); // dark gray translucent
CGContextSetLineWidth(context, 1.0);
CGContextDrawPath(context, kCGPathEOFillStroke); // draw both fill and stroke
}
#end
You could achieve the same effect with CAShapeLayer layers, too, if you didn't want to write your own drawRect with Core Graphics calls like I did above. But the idea would be the same.
Your view controller can then add those five views and add gesture recognizers for them, like so:
HandleView *handleView = [[HandleView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
[self.view addSubview:handleView];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[handleView addGestureRecognizer:pan];
Just repeat that for each of the handles you want on the screen. and then write your gesture recognizer to do whatever you want (e.g. move the bounding rectangle, move the appropriate handles, etc.).
Sounds fairly straight forward. The view hierarchy of one possible solution as ASCII art:
Container (ScalingRotatingView)
|
+----imageView (UIImageView)
|
+----upperLeftScalingHandle (HandleView)
|
+----upperRightScalingHandle (HandleView)
|
+----lowerLeftScalingHandle (HandleView)
|
+----lowerRightScalingHandle (HandleView)
|
+----rotatingHandle (HandleView)
All instances of HandleView would have a pan gesture recognizer, that feeds one of two methods in your controller:
--updateForScaleGesture:, where you’d use the gesture recognizer’s -translationInView: to compute and store the new scale, before updating the frames of all views appropriately, and resetting the translation to 0, and
- -updateForRotationGesture:, where you’d use the gesture recognizer’s -translationInView: to compute and store the new angle before updating the frames and resetting the recognizer’s translation.
For both calculations you need the translation in the coordinate system of the image view. For the scaling part, you can then simply divide the new edge lengths by the natural image dimensions, for the rotation you can use the approximation that only the x component of the translation matters:
sin(x) = x (for small values of x)
Oh, and it sure helps if the anchor point of your image view sits at its center…
In my iPad app, I have a UITableView that alloc/inits a UIView subclass every time a new cell is selected. I've overridden drawRect: in this UIView to draw a radial gradient and it works fine, but performance is suffering - when a cell is tapped, the UIView takes substantially longer to draw a gradient programmatically as opposed to using a .png for the background. Is there any way to "cache" my drawRect: method or the gradient it generates to improve performance? I'd rather use drawRect: instead of a .png. My method looks like this:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
size_t gradLocationsNum = 2;
CGFloat gradLocations[2] = {0.0f, 1.0f};
CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.5f};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum);
CGColorSpaceRelease(colorSpace);
CGPoint gradCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ;
CGContextDrawRadialGradient (context, gradient, gradCenter, 0, gradCenter, gradRadius, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
}
Thanks!
You can render graphics into a context and then store that as a UIImage. This answer should get you started:
drawRect: is a method on UIView used to draw the view itself, not to pre-create graphic objects.
Since it seems that you want to create shapes to store them and draw later, it appears reasonable to create the shapes as UIImage and draw them using UIImageView. UIImage can be stored directly in an NSArray.
To create the images, do the following (on the main queue; not in drawRect:):
1) create a bitmap context
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
2) get the context
CGContextRef context = UIGraphicsGetCurrentContext();
3) draw whatever you need
4) export the context into an image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
5) destroy the context
UIGraphicsEndImageContext();
6) store the reference to the image
[yourArray addObject:image];
Repeat for each shape you want to create.
For details see the documentation for the above mentioned functions. To get a better understanding of the difference between drawing in drawRect: and in arbitrary place in your program and of working with contexts in general, I would recommend you read the Quartz2D Programming Guide, especially the section on Graphics Contexts.
I'm looking for a way to draw a fade effect on a table view (and outline view, but I think it will be the same) when the content is scrolled. Here is an example from the Fantastical app:
Also a video of a similar fade on QuickLook windows here.
To make this I tried subclassing the scrollview of a tableview with this code:
#define kFadeEffectHeight 15
#implementation FadingScrollView
- (void)drawRect: (NSRect)dirtyRect
{
[super drawRect: dirtyRect];
NSGradient* g = [[NSGradient alloc] initWithStartingColor: [NSColor blackColor] endingColor: [NSColor clearColor]];
NSRect topRect = self.bounds;
topRect.origin.y = self.bounds.size.height - kFadeEffectHeight;
topRect.size.height = kFadeEffectHeight;
NSRect botRect = self.bounds;
botRect.size.height = kFadeEffectHeight;
[NSGraphicsContext saveGraphicsState];
[[NSGraphicsContext currentContext] setCompositingOperation: NSCompositeDestinationAtop];
// Tried every compositing operation and none worked. Please specify wich one I should use if you do it this way
[g drawInRect: topRect angle: 90];
[g drawInRect: botRect angle: 270];
[NSGraphicsContext restoreGraphicsState];
}
...but this didn't fade anything, probably because this is called before the actual table view is drawn. I have no idea on how to do this :(
By the way, both the tableview and the outlineview I want to have this effect are view-based, and the app is 10.7 only.
In Mac OS X (as your question is tagged), there are several gotchas that make this difficult. This especially true on Lion with elastic scrolling.
I've (just today) put together what I think is a better approach than working on the table or outline views directly: a custom NSScrollView subclass, which keeps two "fade views" tiled in the correct place atop its clip view. JLNFadingScrollView can be configured with the desired fade height and color and is free/open source on Github. Please respect the license and enjoy. :-)
I have an NSTableView in my application with data being drawn in for both the X and Y axes (ie, every row is matched with every column.) I've got the data populating the cells the way I'd like, but it looks terrible with the columns stretched out horizontally.
I would like to turn the NSTextFieldCell on its side, so that the text is written vertically instead of horizontally. I realize that I'm probably going to have to subclass the NSTextFieldCell, but I'm not sure which functions I'm going to need to override in order to accomplish what I want to do.
What functions in NSTextFieldCell draw the text itself? Is there any built-in way to draw text vertically instead of horizontally?
Well, it took a lot of digging to figure this one out, but I eventually came across the NSAffineTransform object, which apparently can be used to shift the entire coordinate system with respect to the application. Once I had figured that out, I subclassed NSTextViewCell and overrode the -drawInteriorWithFrame:inView: function to rotate the coordinate system around before drawing the text.
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
// Save the current graphics state so we can return to it later
NSGraphicsContext *context = [NSGraphicsContext currentContext];
[context saveGraphicsState];
// Create an object that will allow us to shift the origin to the center
NSSize originShift = NSMakeSize(cellFrame.origin.x + cellFrame.size.width / 2.0,
cellFrame.origin.y + cellFrame.size.height / 2.0);
// Rotate the coordinate system
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: originShift.width yBy: originShift.height]; // Move origin to center of cell
[transform rotateByDegrees:270]; // Rotate 90 deg CCW
[transform translateXBy: -originShift.width yBy: -originShift.height]; // Move origin back
[transform concat]; // Set the changes to the current NSGraphicsContext
// Create a new frame that matches the cell's position & size in the new coordinate system
NSRect newFrame = NSMakeRect(cellFrame.origin.x-(cellFrame.size.height-cellFrame.size.width)/2,
cellFrame.origin.y+(cellFrame.size.height-cellFrame.size.width)/2,
cellFrame.size.height, cellFrame.size.width);
// Draw the text just like we normally would, but in the new coordinate system
[super drawInteriorWithFrame:newFrame inView:controlView];
// Restore the original coordinate system so that other cells can draw properly
[context restoreGraphicsState];
}
I now have an NSTextCell that draws its contents sideways! By changing the row height, I can give it enough room to look good.
I have a UIImageView that is displaying an image that is wider and taller than the UIImageView is. I would like to pan the image within the view using an animation (so that the pan is nice and smooth).
It seems to me that I should be able to just adjust the bounds.origin of the UIImageView, and the image should move (because the image should paint inside the view with that as its origin, right?) but that doesn't seem to work. The bounds.origin changes, but the image draws in the same location.
What almost works is to change the contentsRect of the view's layer. But this begins as a unit square, even though the viewable area of the image is not the whole image. So I'm not sure how I would detect that the far edge of the image is being pulled into the viewable area (which I need to avoid, since it displays by stretching the edge out to infinity, which looks, well, sub-par).
My view currently has its contentsGravity set to kCAGravityTopLeft via Interface Builder, if that makes a difference (Is it causing the image to move?). No other options seemed to be any better, though.
UPDATE: to be clear, I want to move the image inside the view, while keeping the view in the same spot.
I'd highly recommend enclosing your UIImageView in a UIScrollView. Have the UIImageView display the full image, and set the contentSize on the UIScrollView to be the same as your UIImageView's size. Your window into the image will be the size of the UIScrollView, and by using scrollRectToVisible:animated: you can pan to particular areas on the image in an animated fashion.
If you don't want scroll bars to appear, you can set the showsHorizontalScrollIndicator and showsVerticalScrollIndicatorproperties to NO.
UIScrollView also provides pinch-zooming functionality, which may or may not be useful to you.
Brad Larson pointed me down the right road with his suggestion to put the UIImageView inside a UIScrollView.
In the end I put the UIImageView inside of a UIScrollView, and set the scrollView's contentSize and the imageView's bounds to be the same size as the image in the UIImage:
UIImage* image = imageView.image;
imageView.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
scrollView.contentSize = image.size;
Then, I can animate the scrollView's contentOffset to achieve a nice panning effect:
[UIView beginAnimations:#"pan" context:nil];
[UIView setAnimationDuration:animationDuration];
scrollView.contentOffset = newRect.origin;
[UIView commitAnimations];
In my particular case, I'm panning to a random space in the image. In order to find a proper rect to pan to and a proper duration to get a nice constant speed, I use the following:
UIImage* image = imageView.image;
float xNewOrigin = [TCBRandom randomIntLessThan:image.size.width - scrollView.bounds.size.width];
float yNewOrigin = [TCBRandom randomIntLessThan:image.size.height - scrollView.bounds.size.height];
CGRect oldRect = scrollView.bounds;
CGRect newRect = CGRectMake(
xNewOrigin,
yNewOrigin,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
float xDistance = fabs(xNewOrigin - oldRect.origin.x);
float yDistance = fabs(yNewOrigin - oldRect.origin.y);
float hDistance = sqrtf(powf(xDistance, 2) + powf(yDistance, 2));
float hDistanceInPixels = hDistance;
float animationDuration = hDistanceInPixels / speedInPixelsPerSecond;
I'm using a speedInPixelsPerSecond of 10.0f, but other applications might want to use a different value.