Xcode with OpenGL: new windows do not automatically become active context - objective-c

I have a strange problem when I create a Documents Based Xcode project with a NSOpenGLView. The application works fine with a single document, responding to mouse clicks. When you choose File/New to open up a second document a new front window appears on top, but mouse clicks are sent to the previous (background) object. You can move the new window and clicks are still sent to the previous object. However, if you resize the new window or click back and forth with the previous window all works well. So the problem seems to be that the new (front) window does not become the active context. Can you suggest a solution? I have placed the Xcode 4.4 code (44kb) at http://www.mccauslandcenter.sc.edu/CRNL/sw/GLdoc.zip.
Create a new Document based application
Add the MyOpenGLView.m and MyOpenGLView.h files to project
For Targets/BuildPhases, add OpenGL.framework to "Link Binary With Libraries"
For Targets/BuildPhases, add MyOpenGLView.m to "Compile Sources"
In interface builder
a.) select Document's Window and in the Attributes inspector turn off "One shot"
b.) add a OpenGL View from the Object Library
c.) with the Identity inspector, set the Class of newly created NSOpenGLView to MyOpenGLView
Run the project
----START MyOpenGLView.h
#import <Cocoa/Cocoa.h>
#import <GLUT/GLUT.h>
#interface MyOpenGLView : NSOpenGLView
{
NSTimer* timer; //animation timer
NSPoint mouseloc;
NSPoint screenSize;
int hourglassSize;
BOOL updateGL;
}
#end
----START MyOpenGLView.m
// document based OpenGL application inspired by http://www.alecjacobson.com/weblog/?p=2110
#import "MyOpenGLView.h"
#implementation MyOpenGLView
-(void)prepareOpenGL
{
//NSLog(#"preparing");
}
- (void)reshape
{
//NSLog(#"reshaping");
NSRect rectView = [self bounds];
screenSize.x = rectView.size.width;
screenSize.y = rectView.size.height;
}
void enter2D (int width, int height) //Enter2D = reshapeGL
{
glDisable(GL_DEPTH_TEST);
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, width, 0, height,-1,1); //map view to match pixel size, e.g. gluOrtho2D(0, width, 0, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable (GL_BLEND); //allow transparent objects
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void drawTriangle (int sz, int left, int bottom)
{
glLoadIdentity ();
glTranslatef(left,bottom,0.0f);
glBegin(GL_TRIANGLES);
glVertex3f( sz/2, sz, 0.0f);// Top
glVertex3f(0.0,0.0, 0.0); // Bottom Left
glVertex3f( sz,0.0f, 0.0f); // Bottom Right
glEnd(); // Finished Drawing The Triangle
}
void drawHourGlass (int sz, NSPoint center)
{
glLoadIdentity ();
glTranslatef(center.x,center.y,0.0f);
glBegin(GL_TRIANGLES); //lower triangle
glVertex3f( 0.0, 0.0, 0.0f); // Top
glVertex3f(-sz,-sz, 0.0);// Bottom Left
glVertex3f( sz,-sz, 0.0f);// Bottom Right
glEnd();
glBegin(GL_TRIANGLES); //upper triangle
glVertex3f( 0.0, 0.0, 0.0f); // bottom
glVertex3f(-sz,sz, 0.0);// Top Left
glVertex3f( sz,sz, 0.0f);// Top Right
glEnd();
}
-(void)drawRect:(NSRect)rect
{
updateGL = FALSE;
//NSLog(#" %f %f",rect.size.width,rect.size.height);
enter2D(screenSize.x,screenSize.y);
glClearColor(0.8,0.9,1,1); //background - blue sky
glClear(GL_COLOR_BUFFER_BIT);
glColor4f(0.6,0.3,0.1,1.0); //mountains - brown opaque
drawTriangle(100,10,0);
drawTriangle(70,80,0);
glColor4f(0.1,0.1,0.1,0.7); //crosshair - gray, slightly translucent
drawHourGlass (hourglassSize, mouseloc);
glFlush(); // Flush all OpenGL calls
}
- (void)mouseDown:(NSEvent *)event
{
mouseloc = [self convertPoint:[event locationInWindow] fromView:nil];
updateGL = TRUE;
}
int constrain (int size)
{
if (size < 5)
return 5;
else if (size > 50)
return 50;
else
return size;
}
- (void) magnifyWithEvent:(NSEvent *)event;
{
if ([event magnification] > 0)
hourglassSize = hourglassSize + 5;
else
hourglassSize = hourglassSize - 5;
hourglassSize = constrain(hourglassSize);
updateGL = TRUE;
}
- (void)scrollWheel:(NSEvent *)event
{
if (event.deltaY < 0)
hourglassSize = hourglassSize + 5;
if (event.deltaY > 0)
hourglassSize = hourglassSize - 5;
hourglassSize = constrain(hourglassSize);
updateGL = TRUE;
}
- (void)animationTimer:(NSTimer *)timer
{
if (updateGL == TRUE)
[self drawRect:[self bounds]];
}
-(BOOL)acceptsFirstResponder { return YES; }
-(BOOL)becomeFirstResponder { return YES; }
-(BOOL)resignFirstResponder { return YES; }
- (void) awakeFromNib
{
// [[self window] setAcceptsMouseMovedEvents:YES];
mouseloc.x = 30;
mouseloc.y = 40;
hourglassSize = 30;
timer = [NSTimer timerWithTimeInterval:(1.0f/60.0f) target:self selector:#selector(animationTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// ensure timer fires during resize
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
updateGL = TRUE;
}
#end

For others who encounter this problem (which is present in some of the older demos Apple distributes), with modern versions of OSX (10.4 and later) you use CVDisplayLink instead of a timer to update OpenGL views.
http://developer.apple.com/library/mac/#qa/qa1385/_index.html
Alec Jacobson outlines this solution here
http://www.alecjacobson.com/weblog/?p=2185

Related

NSSplitView Fixed Splitter on Window Resize?

I'm having a bit of difficulty getting a NSSplitView to behave itself.
What I have at the moment is:
NSWindow
NSView
NSSplitView
navView <NSView>
contentView <NSView>
The problem I'm having is with the splitter shifting position when I resize the window.
In the split view delegate I've already got:
-(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200;
}
-(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200;
}
Despite this the splitter still moved when I resize the window.
if I click ont he splitter, it snaps back to 200px as it should. How can I stop this from moving?
I've tried Autolayout, which is a bit of a nightmare to use, so I've literally disabled it and manually tried to do it with no joy..
Any ideas?
I wrote the above code for Swift and extended it with the possibility to define, whether the left or the right view has to be preferred:
var preferringLeftSideOfSplitView = true
func splitView(splitView: NSSplitView, resizeSubviewsWithOldSize oldSize: NSSize) {
var dividerThickness = splitView.dividerThickness
var leftRect = splitView.subviews[0].frame
var rightRect = splitView.subviews[1].frame
// Resizing and placing the left view
splitView.subviews[0].setFrameOrigin(NSMakePoint(0, 0))
if self.preferringLeftSideOfSplitView == true {
splitView.subviews[0].setFrameSize(NSMakeSize(leftRect.width, splitView.frame.size.height))
} else {
splitView.subviews[0].setFrameSize(NSMakeSize(splitView.frame.size.width - rightRect.width - dividerThickness, splitView.frame.size.height))
}
// Resizing and placing the right view
if self.preferringLeftSideOfSplitView == true {
splitView.subviews[1].setFrameOrigin(NSMakePoint(leftRect.size.width + dividerThickness, 0))
splitView.subviews[1].setFrameSize(NSMakeSize(splitView.frame.size.width - leftRect.size.width - dividerThickness, splitView.frame.size.height))
} else {
splitView.subviews[1].setFrameOrigin(NSMakePoint(splitView.frame.size.width - rightRect.width, 0))
splitView.subviews[1].setFrameSize(NSMakeSize(rightRect.size.width, splitView.frame.size.height))
}
}
I've worked it out...
-(void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize
{
CGFloat dividerThickness = [sender dividerThickness];
NSRect leftRect = [[[sender subviews] objectAtIndex:0] frame];
NSRect rightRect = [[[sender subviews] objectAtIndex:1] frame];
NSRect newFrame = [sender frame];
leftRect.size.height = newFrame.size.height;
leftRect.origin = NSMakePoint(0, 0);
rightRect.size.width = newFrame.size.width - leftRect.size.width
- dividerThickness;
rightRect.size.height = newFrame.size.height;
rightRect.origin.x = leftRect.size.width + dividerThickness;
[[[sender subviews] objectAtIndex:0] setFrame:leftRect];
[[[sender subviews] objectAtIndex:1] setFrame:rightRect];
}
- (BOOL)splitView:(NSSplitView *)aSplitView shouldAdjustSizeOfSubview:(NSView *)subview
{
return subview == rightView;
}
add this to your delegate.
if your NSSplitView would contain lefView and rightView, this keeps leftView at a fixed width, and rightView will resize, when resizing the main window.

NSWindow doesn't respond until click

I've got an image editor window. After drag event I minimize my window to miniature in the bottom left corner, after drop on the miniature I return window back. Above the miniature I change cursor to operationNotAllowedCursor.
The problem is: NSWindow does not change the cursor on miniature after the first draggingEntered (after the second and more everything's fine). Moreover, after drop on the miniature NSWindow does not receive any events until a click on any area of it.
Code for minimizing window (in NSWindow subclass):
-(void)minimize
{
const double miniSize = 240;
MSDraggingMiniature *mini = [[MSDraggingMiniature alloc] init];
[mini setMiniImage:[[MSScreenMaker getInstance] makeEditorScreen:(int)[self windowNumber]]];
_mainContentView = self.contentView;
_oldFrame = [self frame];
[self setStyleMask:NSBorderlessWindowMask];
self.contentView = mini;
NSRect drect = [[self screen] frame];
double width, height;
if (self.frame.size.width < self.frame.size.height) {
height = miniSize;
width = self.frame.size.width / self.frame.size.height * miniSize;
} else {
width = miniSize;
height = self.frame.size.height / self.frame.size.width * miniSize;
}
_anima = MSEditorResizeAnimationMinimize;
[self setFrame:NSMakeRect(drect.origin.x + 20, drect.origin.y + 20 , width, height) display:YES animate:YES];
}
-(void)deminimize
{
self.contentView = _mainContentView;
[self setStyleMask:NSTitledWindowMask];
_anima = MSEditorResizeAnimationDeminimize;
[self setFrame:_oldFrame display:YES animate:YES];
[self makeKeyWindow];
[self makeMainWindow];
}
After lots of variants I found the answer. It seems that area, acting like Dragging Source, must be just NSView, not a subclass of NSButton, which I had.
I rewrote my Dragging Source class - now everything works fine.

Autoscroll UIScrollView when subview is out of the visible area

EDIT:
I uploaded my implementation to github. Maybe it is better to understand what I want and what my problem is.
I changed the code in the github project a little bit than the posted code here. I think the implementation in the github project is better, but not perfect.
What I want to do:
Have a UIScrollView with movable UIViews (e.g. Images). The user can pan this subviews and zoom in and out. When the user zooms in and moves a subview over the current visible area the scrollView should automatically scroll. As lang as the subview is over the edge the scrollview should scroll. When the subview isn't over the visible area anymore the scrollview should stop moving.
I try to explain my problem as good as possible.
What I have managed to do:
Zoom the scrollview, move the subviews with the UIPanGestureRecognizer, recognize when the subview is over the visible area and start moving (changing the contentOffset) the scrollview. Here I using a NSTimer to move the scrollview as long as the subview is over the visible area.
My problem:
When the subview is over the visible area a NSTimer is started, to change the contentOffset of the subview and the frame (position) of the subview.
After that I can't pan the subview anymore.
I can't figure out how to implement the pan gesture with changing the subview frame in a correct way.
My implementation:
I am using three views:
UIScrollView
MyImageContainerView (UIView, added as a subview to the scrollview)
MyImageView (UIView, added as a subview to MyImageContainerView)
Currently MyImageContainerView manages the workflow. A MyImageView has a UIPanGestureRecognizer attached. The method for this recognizer is implemented in MyImageContainerView:
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
//UIView which is moved by the user
MyImageView *currentView = gestureRecognizer.view;
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStatePossible:
{
break;
}
case UIGestureRecognizerStateBegan:
{
//save both values in global instance variables
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//global BOOL variable, to check if scrollView movement is performed
scrolling = NO;
break;
}
case UIGestureRecognizerStateChanged:
{
CGRect rect = CGRectMake(currentFrameOriginX + [gestureRecognizer translationInView:currentView.superview].x, currentFrameOriginY + [gestureRecognizer translationInView:currentView.superview].y, currentView.frame.size.width, currentView.frame.size.height);
if (CGRectContainsRect(currentView.superview.frame, rect)) {
/*PROBLEM: Here is a problem. I need this change of the frame here, to move the UIView along the movement from the user. In my autoScroll-method I have to set the frame of currentView, too. But I can't set the frame of currentView here and in the autoScroll. But as long as the NSTimer runs and is calling autoScroll: this if-statement isn't called, so I can't move the UIView with my finger anymore. */
if (!scrolling) {
//currently the NSTimer method for the automatically scrolling isn't performed, so:
//change the frame according to the pan gesture
currentView.frame = rect;
}
UIScrollView *scrollView = self.myScrollView; //reference to the "root" UIScrollView
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGRect frame = currentView.frame;
CGFloat scale = 1.0 / scrollView.zoomScale;
visibleRect.origin.x *= scale;
visibleRect.origin.y *= scale;
visibleRect.size.width *= scale;
visibleRect.size.height *= scale;
CGSize scrollZone = CGSizeMake(10.0f, 10.0f);
float scrollStep = 3.0f;
CGPoint scrollAmount = CGPointZero;
//determine the change of x and y
if (frame.origin.x+scrollZone.width < visibleRect.origin.x) {
scrollAmount.x = -scrollStep;
}
else if((frame.origin.x+frame.size.width)-scrollZone.width > visibleRect.origin.x + visibleRect.size.width) {
scrollAmount.x = scrollStep;
}
else if (frame.origin.y+scrollZone.height < visibleRect.origin.y) {
scrollAmount.y = -scrollStep;
}
else if((frame.origin.y+frame.size.height)-scrollZone.height > visibleRect.origin.y + visibleRect.size.height) {
scrollAmount.y = scrollStep;
}
if ((scrollAmount.x != 0) | (scrollAmount.y != 0)) {
if (![scrollTimer isValid]) {
//scrollTimer is a global NSTimer instance variable
[scrollTimer invalidate];
scrollTimer = nil;
NSString *scrollString = NSStringFromCGPoint(scrollAmount);
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:scrollString, #"scrollString", currentView, #"currentView", nil];
scrollTimer = [[NSTimer alloc]initWithFireDate:[NSDate date] interval:0.03f target:self selector:#selector(autoScroll:) userInfo:info repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:scrollTimer forMode:NSRunLoopCommonModes];
}
}
else {
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
}
}
break;
}
case UIGestureRecognizerStateEnded:
{
//quite know the scrolling should stop, maybe it would be better when the scrollView scrolls even if the user does nothing when the subview is over the visible area
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
default:
{
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
}
}
-(void)autoScroll:(NSTimer*)timer {
scrolling = YES; //the scroll method is executed quite know
NSDictionary *info = [timer userInfo];
UIScrollView *scrollView = self.myScrollView;
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGPoint scrollAmount = CGPointFromString([info objectForKey:#"scrollString"]);
MyImageView *currentView = [info objectForKey:#"currentView"];
//stop scrolling when the UIView is at the edge of the containerView (referenced over 'self')
if ((currentView.frame.origin.x <= 0 | currentView.frame.origin.y <= 0) ||
((currentView.frame.origin.x+currentView.frame.size.width) > self.frame.size.width | (currentView.frame.origin.y+currentView.frame.size.height) > self.frame.size.height)
) {
scrolling = NO;
return;
}
//move the UIView
CGFloat scale = 1.0 / scrollView.zoomScale;
if (scrollAmount.x != 0) {
scrollAmount.x *= scale;
}
if (scrollAmount.y != 0) {
scrollAmount.y *= scale;
}
CGRect frame = currentView.frame;
frame.origin.x += scrollAmount.x;
frame.origin.y += scrollAmount.y;
currentView.frame = frame;
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//move the scrollView
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.x += scrollAmount.x;
contentOffset.y += scrollAmount.y;
[scrollView setContentOffset:contentOffset animated:NO];
}

UIView hitTest:withEvent: and pointInside:withEvent

I'm working with Mixare AR SDK for iOS and I need to solve some bugs that happends, one of them is show the information of a POI when the POI's view is tapped.
Prelude:
Mixare has an overlay UIView within MarkerView views are placed, MarkerView views are moving around the screen to geolocate the POIs and each one has two subviews, an UIImageView and an UILabel.
Issue:
Now, for example, there are 3 visible POIs in the screen, so there are 3 MarkerView as overlay subviews. If you touch anywhere in the overlay, a info view associated to a random POI of which are visible is showed.
Desired:
I want that the associated POI's info is shown only when the user tapped a MarkerView
Let's work. I've see that MarkerView inherits from UIView and implements hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
viewTouched = (MarkerView*)[super hitTest:point withEvent:event];
return self;
}
I've put a breakpoint and hitTest is called once for each visible MarkerView but loadedView always is null so I can't work with it, so I've tried to check if the hit point is inside the MarkerView frame implementing pointInside:withEvent: by this way
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"ClassName: %#", [[self class] description]);
NSLog(#"Point Inside: %f, %f", point.x, point.y);
NSLog(#"Frame x: %f y: %f widht:%f height:%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
if (CGRectContainsPoint(self.frame, point))
return YES;
else
return NO;
return YES;
}
But this function always returns NO, even when I touch the MarkerView. When I check the log I saw that X and Y point values has negative values sometimes and width and height of the view are very small, 0.00022 or similar instead of 100 x 150 that I set the MarkerView frame on its initialization.
Here you are a extract of my log in which you can see the class name, the point and the MarkerView frame values.
ClassName: MarkerView
2011-12-29 13:20:32.679 paisromanico[2996:707] Point Inside: 105.224899, 49.049023
2011-12-29 13:20:32.683 paisromanico[2996:707] Frame x: 187.568573 y: 245.735138 widht:0.021862 height:0.016427
I'm very lost with this issue so any help will be welcome. Thanks in advance for any help provided and I'm sorry about this brick :(
Edit:
At last I've found that the problem is not in hitTest:withEvent: or pointInside:withEvent, problem is with CGTransform that applies to the MarkerView for scaling based on distande and rotating the view, if I comment any code related to this, the Mixare AR SDK works fine, I mean, info view is shown correctly if you touch a marker and doesn't do anything if any other place in the screen is touched.
So, by the moment, I've not solved the problem but I applied a patch removing the CGTransform related code in AugmentedViewController.m class - (void)updateLocations:(NSTimer *)timer function
- (void)updateLocations:(NSTimer *)timer {
//update locations!
if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
return;
}
int index = 0;
NSMutableArray * radarPointValues= [[NSMutableArray alloc]initWithCapacity:[ar_coordinates count]];
for (PoiItem *item in ar_coordinates) {
MarkerView *viewToDraw = [ar_coordinateViews objectAtIndex:index];
viewToDraw.tag = index;
if ([self viewportContainsCoordinate:item]) {
CGPoint loc = [self pointInView:ar_overlayView forCoordinate:item];
CGFloat scaleFactor = 1.5;
if (self.scaleViewsBasedOnDistance) {
scaleFactor = 1.0 - self.minimumScaleFactor * (item.radialDistance / self.maximumScaleDistance);
}
float width = viewToDraw.bounds.size.width ;//* scaleFactor;
float height = viewToDraw.bounds.size.height; // * scaleFactor;
viewToDraw.frame = CGRectMake(loc.x - width / 2.0, loc.y-height / 2.0, width, height);
/*
CATransform3D transform = CATransform3DIdentity;
//set the scale if it needs it.
if (self.scaleViewsBasedOnDistance) {
//scale the perspective transform if we have one.
transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
}
if (self.rotateViewsBasedOnPerspective) {
transform.m34 = 1.0 / 300.0;
double itemAzimuth = item.azimuth;
double centerAzimuth = self.centerCoordinate.azimuth;
if (itemAzimuth - centerAzimuth > M_PI) centerAzimuth += 2*M_PI;
if (itemAzimuth - centerAzimuth < -M_PI) itemAzimuth += 2*M_PI;
double angleDifference = itemAzimuth - centerAzimuth;
transform = CATransform3DRotate(transform, self.maximumRotationAngle * angleDifference / (VIEWPORT_HEIGHT_RADIANS / 2.0) , 0, 1, 0);
}
viewToDraw.layer.transform = transform;
*/
//if we don't have a superview, set it up.
if (!(viewToDraw.superview)) {
[ar_overlayView addSubview:viewToDraw];
[ar_overlayView sendSubviewToBack:viewToDraw];
}
} else {
[viewToDraw removeFromSuperview];
viewToDraw.transform = CGAffineTransformIdentity;
}
[radarPointValues addObject:item];
index++;
}
float radius = [[[NSUserDefaults standardUserDefaults] objectForKey:#"radius"] floatValue];
if(radius <= 0 || radius > 100){
radius = 5.0;
}
radarView.pois = radarPointValues;
radarView.radius = radius;
[radarView setNeedsDisplay];
[radarPointValues release];
}
Any CoreGrapics or UI expert could give us his point of view about this issue??
You should either try to hittest as attached:
if ([self pointInside:point withEvent:event]) {
// do something
}
I would suggest you add the hit test on the superview, and do the following in the hit test of the parent of the markerViews
if ([markerView pointInside:point withEvent:event]) {
// extract the tag and show the relevant info
}
Hope this helps

NSTextFieldCell vertical alignment, solutions seem to squash the horizontal alignment

I have a NSTextFieldCell that I wish to display with middle vertical alignment. Thanks to an older question here and a blog entry I have two working solutions.
However, both solutions seem to squash my ability to set the cell as right aligned. Can anyone help me make either of these solutions support both forms of alignment?
Here is the code for one solution:
#implementation MiddleAlignedTextFieldCell
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
titleFrame.origin.y = theRect.origin.y - .5 + (theRect.size.height - titleSize.height) / 2.0;
return titleFrame;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSRect titleRect = [self titleRectForBounds:cellFrame];
[[self attributedStringValue] drawInRect:titleRect];
}
#end
The alternative solution is (obtained from this blog):
#implementation RSVerticallyCenteredTextFieldCell
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
NSRect newRect = [super drawingRectForBounds:theRect];
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
}
return newRect;
}
- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength
{
aRect = [self drawingRectForBounds:aRect];
mIsEditingOrSelecting = YES;
[super selectWithFrame:aRect inView:controlView editor:textObj delegate:anObject start:selStart length:selLength];
mIsEditingOrSelecting = NO;
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent
{
aRect = [self drawingRectForBounds:aRect];
mIsEditingOrSelecting = YES;
[super editWithFrame:aRect inView:controlView editor:textObj delegate:anObject event:theEvent];
mIsEditingOrSelecting = NO;
}
#end
I'm posting this answer to the question since it does work, however, I find the fact that I couldn't find another way to check the alignment setting from IB is very annoying. Accessing _cFlags just seems a little dirty, and I'd love to find a cleaner method.
Based on the code posted earlier from this blog entry.
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
// Get the parent's idea of where we should draw
NSRect newRect = [super drawingRectForBounds:theRect];
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
// For some reason right aligned text doesn't work. This section makes it work if set in IB.
// HACK: using _cFlags isn't a great idea, but I couldn't find another way to find the alignment.
// TODO: replace _cFlags usage if a better solution is found.
float widthDelta = newRect.size.width - textSize.width;
if (_cFlags.alignment == NSRightTextAlignment && widthDelta > 0) {
newRect.size.width -= widthDelta;
newRect.origin.x += widthDelta;
}
}
return newRect;
}
You can use NSParagraphStyle/NSMutableParagraphStyle to set the alignment (and other attributes). Add an appropriately-configured NSParagraphStyle object to the full range of your attributed string.
There are a couple of potential solutions posted in a similar question which I asked a while back.
In all honesty, I still use the undocumented _cFlags.vCentered boolean (tsk tsk, bad programmer!) to get the job done. It's simple, and it works. I'll reinvent the wheel later on if I have to.
update:
OK, I think I've figured it out. Both solutions rely on a call to super to get the default rect, and then modify origin.y and size.height to perform the vertical centering. The calls to super, however, return a rectangle whose width has already been adjusted to fit the text horizontally.
The solution is to use origin.x and size.width from the bounds rect that is passed in to the method:
In solution #1:
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
// modified:
theRect.origin.y += (theRect.size.height - titleSize.height)/2.0 - 0.5;
return theRect;
}
In solution #2:
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
NSRect newRect = [super drawingRectForBounds:theRect];
// modified:
newRect.origin.x = theRect.origin.x;
newRect.size.width = theRect.size.width;
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
}
return newRect;
}