constraint animation doesn't animate as expected - animation

the following code is used to move textfield position to an editing location on the app.
unfortunately it isn't producing the desired effect.
textfield1 moves in X to its finished location and then in y to its finished location.
i want both constraints to produce a diagonal motion of the textfield, but it's not working when tested on an iPhone.
the animation works but i end up with an inverted "L" shaped movement instead of a diagonal "\" motion
any suggestions how to fix this will be appreciated.
-(void)textFieldEditingPosition
{
NSLog(#"editing");
[_regular setNeedsUpdateConstraints];
switch (_activeTextField.tag)
{
case 1:
self.textField2WidthConstraint.constant =_regular.frame.size.width/4 -16;
self.textField2XConstraint.constant = -_regular.frame.size.width/4 - self.textField2WidthConstraint.constant/2 -8;
self.textField2YConstraint.constant = -(_regular.frame.size.height/2 -_inputView.frame.size.height/2);
_textField1YConstraint.constant = 0;
_textField1XConstraint.constant = 0;
_textField1WidthConstraint.constant = _placeHolderTextfield.frame.size.width;
break;
case 2:
self.textField1XConstraint.constant = (_regular.frame.size.width /4) /2;
self.textField1YConstraint.constant = -(_regular.frame.size.height/8 -8) ;
self.textField1WidthConstraint.constant = _regular.frame.size.width/4 * 3 -16;
self.textField2YConstraint.constant =0;
self.textField2XConstraint.constant = 0;
self.textField2WidthConstraint.constant = _placeHolderTextfield.frame.size.width;
break;
default:
break;
}
[UIView animateWithDuration:0.7
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.view layoutIfNeeded];
}
completion:nil];
}

I've managed to solve this issue as follows
first i turned on slow animation in the simulator.
this enabled me to see exactly what was happening.
The L shaped movement i was seeing was actually an illusion caused by the textfield width expanding as the textfield was moving to a new x and y position.
the expanding width kind of cancelled out the movement in x i was expecting to see.
by separating the width expansion into the animation completion, i get the result i need which is a diagonal movement of the textfield to the editing position.
the expansion of the textfield width happens in the completion block without animation, which is fine for me, as the textfield background is hidden, and the user sees only the placeholder text.
herewith the amended code:
-(void)textFieldEditingPosition
{
NSLog(#"editing");
[_regular setNeedsUpdateConstraints];
switch (_activeTextField.tag)
{
case 1:{
self.textField2WidthConstraint.constant =_regular.frame.size.width/4 -16;
self.textField2XConstraint.constant = -_regular.frame.size.width/4 - self.textField2WidthConstraint.constant/2 -8;
self.textField2YConstraint.constant = -(_regular.frame.size.height/2 -_inputView.frame.size.height/2);
_textField1YConstraint.constant = 0;
_textField1XConstraint.constant = 0;
[UIView animateWithDuration:0.7
delay: 0.0
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
[self.regular layoutIfNeeded];
}
completion:^(BOOL finished){
self->_textField1WidthConstraint.constant = self->_placeHolderTextfield.frame.size.width;
[self.regular layoutIfNeeded];}];
}
break;
case 2:{
self.textField1XConstraint.constant = (_regular.frame.size.width /4) /2;
self.textField1YConstraint.constant = -(_regular.frame.size.height/8 -8) ;
self.textField1WidthConstraint.constant = _regular.frame.size.width/4 * 3 -16;
self.textField2YConstraint.constant =0;
self.textField2XConstraint.constant = 0;
[UIView animateWithDuration:0.7
delay: 0.0
options: UIViewAnimationOptionBeginFromCurrentState
animations:^{
[self.regular layoutIfNeeded];
}
completion:^(BOOL finished){
self->_textField2WidthConstraint.constant = self->_placeHolderTextfield.frame.size.width;
[self.regular layoutIfNeeded];}];
}
break;
default:
break;
}
}
I'm sure there's a cleaner way of coding this, but it works so I'm relieved
thanks

Related

Weird screen resizing bug when calling shadowCastBitMask=

I'm making a ball/paddle "breakout" style game for iOS8 where the blocks fall from the top of the screen.
I decided to try Apple's new SKLightNode in sprite-kit and it worked wonderfully, casting a light from the top of the screen:
in levelScene.h:
#import <SpriteKit/SpriteKit.h>
in levelScene.m:
-(id)initWithSize:(CGSize)size {
...
SKLightNode* light = [[SKLightNode alloc] init];
// light.enabled = YES;
light.categoryBitMask = lightCategory;
light.falloff = 1;
light.ambientColor = [UIColor whiteColor];
light.lightColor = [[UIColor alloc] initWithRed:0.0 green:1.0 blue:0.5 alpha:0.5];
light.shadowColor = [[UIColor alloc] initWithRed:0.0 green:0.0 blue:0.0 alpha:0.2];
light.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height - 20);
[self addChild:light];
...
}
and casting a shadow from the paddle near the bottom of the screen:
-(id)initWithSize:(CGSize)size {
...
self.paddle.shadowCastBitMask = lightCategory;
...
}
However, when I try to make my falling blocks cast shadows by defining the shadowCastBitMask of my rectangle (or block) spriteNodes, which are, unlike the paddle, added at various intervals throughout play, I experience a bizarre kind of clipping where the entire screen and all of its contents resize to around 60%-80% of the original size, squashing vertically slightly. It is only for the briefest of moments so I cannot gain a decent idea of what it is even doing to the image, let alone why. I have found nothing relating to this bug online.
I can say that it is reproduced everytime that a block enters from the top of the screen, even the first time, suggesting that it has nothing to do with multiple instances being called simultaneously. Since the paddle (and ball when tested) seems to cast a shadow without problems, I can only assume that it is either something to do with the fact that the call is made during gameplay, not before it has started as is the case with the paddle, or that there is something in my -addRectangle call that I'm missing.
So, here's -(void)addRectangle in its entirety, the shadowCastBitMask=... call is near the end:
- (void)addRectangle {
// Create sprite
self.rectangle = [SKSpriteNode spriteNodeWithImageNamed:#"Rectangle"];
// Determine where to spawn the rectangle along the X axis
int minX = (CGRectGetMinX(self.frame) + (self.rectangle.size.width/2)) ;
int maxX= (( CGRectGetMaxX(self.frame)) - (self.rectangle.size.width)) ;
int actualX = ( arc4random_uniform(maxX) +minX);
// Create the rectangle slightly off-screen
self.rectangle.position = CGPointMake(actualX, self.frame.size.height + self.rectangle.size.height/1);
self.rectangle.zPosition = 5;
// Determine speed of the rectangle
if(multiplier>=29 && multiplier<49){
int minDuration = 5.5;
int maxDuration = 7.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.rectangle.size.height/1) duration:actualDuration];
[self.rectangle runAction:actionMove];
}
if(multiplier>=49 && multiplier < 99){
int minDuration = 4.0;
int maxDuration = 5.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.rectangle.size.height/1) duration:actualDuration];
[self.rectangle runAction:actionMove];
}
if(multiplier>=99){
int minDuration = 3.0;
int maxDuration = 4.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.rectangle.size.height/1) duration:actualDuration];
[self.rectangle runAction:actionMove];
}
else if (multiplier<29){
int minDuration = 6.0;
int maxDuration = 10.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction * actionMove = [SKAction moveTo:CGPointMake(actualX, -self.rectangle.size.height/1) duration:actualDuration];
[self.rectangle runAction:actionMove];
}
self.rectangle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.rectangle.size];
self.rectangle.physicsBody.dynamic = YES;
self.rectangle.physicsBody.restitution = 0.4f;
self.rectangle.physicsBody.density = 1000;
self.rectangle.physicsBody.categoryBitMask = blockCategory;
self.rectangle.physicsBody.contactTestBitMask = bottomCategory | paddleCategory | laserCategory;
self.rectangle.physicsBody.collisionBitMask = 0x0 ;
self.rectangle.physicsBody.affectedByGravity = NO;
//the offending line:
self.rectangle.shadowCastBitMask = lightCategory;
[self addChild:self.rectangle];
[_blocks addObject:self.rectangle];
_EyeLeft = [SKSpriteNode spriteNodeWithImageNamed:#"Eye"];
_EyeLeft.position = CGPointMake(_EyeLeft.parent.position.x-10, _EyeLeft.parent.position.y) ;
// _EyeLeft.zPosition = 7;
_EyeLeft.physicsBody.allowsRotation = YES;
_EyeLeft.name = #"Eye";
[self.rectangle addChild: _EyeLeft];
[_Eyes addObject:_EyeLeft];
_EyeRight = [SKSpriteNode spriteNodeWithImageNamed:#"Eye"];
_EyeRight.position = CGPointMake(_EyeRight.parent.position.x+10, _EyeRight.parent.position.y) ;
// _EyeRight.zPosition = 7;
_EyeRight.physicsBody.allowsRotation = YES;
_EyeRight.name = #"Eye";
// _EyeLeft.physicsBody.angularDamping = 0.2;
[self.rectangle addChild: _EyeRight];
[_Eyes addObject:_EyeRight];
}
The bug is not reproduced if I simply delete the shadowCastBitMask=... call, however then I get no shadows.
I also don't understand why the whole picture would resize, as I am not, as far as I'm aware, calling any commands related to the scale or scene at the time, certainly not triggered by such a call.
Any help at all would be greatly appreciated,
Thanks in advance for your time and any help offered.
I had this problem, I noticed it happened when using a tilemap, so I just used a static big image for the bG and seems to have solved the problem

Xcode : Need help moving an image inside screen bounds

I followed a guide about dragging multiple images on screen with gesture recognizers however i have a problem. I want the images to be dragged inside the bounds of the screen which maybe iPad or iPhone. The code i am using estimates the image's center so half of the image is getting off screen when reaching the bounds. Could you please help me with this?
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200; //original divider 200
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult /4; // Increase for more of a slide (original doesn't have a divider)
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);
[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];
}
}
Well, i managed to find out the solution to my problem. I divided the image width and height by two. In my case the results were 100 and 70. The rest can be seen in code box :
finalPoint.x = MIN(MAX(finalPoint.x, 70), self.view.bounds.size.width-70);
finalPoint.y = MIN(MAX(finalPoint.y, 100), self.view.bounds.size.height-100);

Cocos2d sprite with CCAnimation skipping when being moved only on device

I wrote a demo application that works perfectly on the simulator, but when I put it on an actual device, the dice skip around. Here is a video as an example. After I restart the app, the animations are fine. The errors happened about 1 minute of repeatadly hitting the roll button.
http://youtu.be/N1k1QPa1brg
The code is all live at:
https://github.com/rnystrom/MartionDemo
How I make the animation in the dice object:
CCAnimation *anim = [CCAnimation animationWithFrames:frames delay:delay];
if(self.sprite){
// Animate the sprite
[self.sprite runAction:[CCAnimate actionWithAnimation:anim restoreOriginalFrame:NO]];
}
The rolling function:
-(void)roll
{
// Array that contains the new positions of dice
// Predetermine the position, check if that will be on top of other dice
NSMutableArray* checkPos = [NSMutableArray array];
for(Dice* d in rollDiceArray){
[d resetPosition];
// Select a random position within bounds
NSInteger x = arc4random() % 600 + 50;
NSInteger y = arc4random() % 600 + 150;
CGPoint location = CGPointMake(x, y);
// Check if die will touch other dice
while (! [self checkPositionWithPoint:location array:checkPos]) {
// If position overlaps another die, get a new position
// PROBLEM: This is O(infinity)!
x = arc4random() % 600 + 50;
y = arc4random() % 600 + 150;
location = CGPointMake(x, y);
}
// If position does not overlap, add it to array of positions to be checked
[checkPos addObject:[NSArray arrayWithObjects:[NSNumber numberWithInteger:x], [NSNumber numberWithInteger:y], nil]];
// Animate the dice to a position
// Addition in the switch is for some randomness and correcting the animation's timing offset
NSInteger numberFrames;
float frameRate;
float mod = (float)(arc4random() % 60) / 100;
switch (d.fileNum) {
case 0:
numberFrames = kRayFrames;
frameRate = numberFrames/24 + mod;
break;
case 1:
numberFrames = kRayFrames;
frameRate = numberFrames/24 + mod - 0.4;
break;
case 2:
numberFrames = kTankFrames;
frameRate = numberFrames/24 + mod + 0.2;
break;
case 3:
numberFrames = kChickenFrames;
frameRate = numberFrames/24 + mod;
break;
case 4:
numberFrames = kCowFrames;
frameRate = numberFrames/24 + mod + 0.2;
break;
case 5:
numberFrames = kHumanFrames;
frameRate = numberFrames/24;
break;
default:
break;
}
id action = [CCMoveTo actionWithDuration:frameRate position:location];
id ease = [CCEaseOut actionWithAction:action rate:4.0];
[d.sprite runAction:ease];
}
}
This turned out to be a performSelector:afterDelay: issue. I resolved it by adding some padding time to my delay so things did not happen simultaneously.
I also put in a block so that action can only be made after the animations were finished. There seems to be some sort of issue when interrupting a CCMoveTo-esque animation.

drawRect performance

I need to draw lots of polygons 500k to a million on the iPad. After experimenting, I can only get only get 1 fps if that. This is just an example my real code has some good sized polygons.
Here are a few question:
Why don't I have to add the Quartz Framework to my project?
If many of the polygons repeat can I leverage that with views or are they too heavy etc?
Any alternatives, QTPaint can handle this but dips into the gpu. Is there is anything like QT or ios?
Can Opengl increase 2d performance of this type?
Example drawrect:
//X Y Array of boxes
- (void)drawRect:(CGRect)rect
{
int reset = [self pan].x;
int markX = reset;
int markY = [self pan].y;
CGContextRef context = UIGraphicsGetCurrentContext();
for(int i = 0; i < 1000; i++)//1,000,000
{
for(int j = 0; j < 1000; j++)
{
CGContextMoveToPoint(context, markX, markY);
CGContextAddLineToPoint(context, markX, markY + 10);
CGContextAddLineToPoint(context, markX + 10, markY + 10);
CGContextAddLineToPoint(context, markX + 10, markY);
CGContextAddLineToPoint(context, markX, markY);
CGContextStrokePath(context);
markX+=12;
}
markY += 12;
markX = reset;
}
}
The pan just move the array of boxes around on screen with pan gesture. Any help or hints would greatly appreciated.
The key issue with your example is that it is not optimized. Whenever drawRect: is called, the device is rendering all 1,000,000 squares. Worse still, it's making 6,000,000 calls to those APIs in the loop. If you want to refresh this view at even a modest 30fps, that is 180,000,000 calls / second.
With your 'simple' example, the size of the draw area is 12,000px × 12,000px; the maximum area you can display on the iPad's display is 768×1024 (assuming full-screen portrait). Therefore, the code is wasting a lot of CPU resources drawing outside the visible area. UIKit has ways of handling this scenario with relative ease.
When managing content that is significantly larger than the visible area, you should limit drawing to only that which is visible. UIKit has a couple of ways of handing this; UIScrollView in combination with a view backed by a CATiledLayer is your best bet.
Steps:
Disclaimer: This is specifically an optimization of your example code above
Create a new View Based Application iPad project
Add a reference to the QuartzCore.framework
Create a new class, say MyLargeView, subclassed from UIView and add the following code:
:
#import <QuartzCore/QuartzCore.h>
#implementation MyLargeView
- (void)awakeFromNib {
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.tileSize = CGSizeMake(512.0f, 512.0f);
}
// Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
return [CATiledLayer class];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// only draws what is specified by the rect parameter
CGContextRef context = UIGraphicsGetCurrentContext();
// set up some constants for the objects being drawn
const CGFloat width = 10.0f; // width of rect
const CGFloat height = 10.0f; // height of rect
const CGFloat xSpace = 4.0f; // space between cells (horizontal)
const CGFloat ySpace = 4.0f; // space between cells (vertical)
const CGFloat tWidth = width + xSpace; // total width of cell
const CGFloat tHeight = height + ySpace;// total height of cell
CGFloat xStart = floorf(rect.origin.x / tWidth); // first visible cell (column)
CGFloat yStart = floorf(rect.origin.y / tHeight); // first visible cell (row)
CGFloat xCells = rect.size.width / tWidth + 1; // number of horizontal visible cells
CGFloat yCells = rect.size.height / tHeight + 1; // number of vertical visible cells
for(int x = xStart; x < (xStart + xCells); x++) {
for(int y = yStart; y < (yStart + yCells); y++) {
CGFloat xpos = x*tWidth;
CGFloat ypos = y*tHeight;
CGContextMoveToPoint(context, xpos, ypos);
CGContextAddLineToPoint(context, xpos, ypos + height);
CGContextAddLineToPoint(context, xpos + width, ypos + height);
CGContextAddLineToPoint(context, xpos + width, ypos);
CGContextAddLineToPoint(context, xpos, ypos);
CGContextStrokePath(context);
}
}
}
#end
Edit the view controller nib and add a UIScrollView to the view
Add a UIView to the UIScrollView and make sure it fills the UIScrollView
Change the class to MyLargeView
Set frame size of MyLargeView to 12,000×12,000
Finally, open up the view controller .m file and add the following override:
:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
UIScrollView *scrollView = [self.view.subviews objectAtIndex:0];
scrollView.contentSize = CGSizeMake(12000, 12000);
}
If you look at the drawRect: call, it is only drawing into the area specified by the rect parameter, which will correspond to the tile size (512×512) for the CATiledLayer we configured in the awakeFromNib method. This will scale to a 1,000,000×1,000,000 pixel canvas.
Alternatives to look at are the ScrollViewSuite example, specifically 3_Tiling.
OpenGL is GPU hardware accelerated on iOS devices. Core Graphics drawing is not, and can be many many times slower when dealing with a large number of small graphics primitives (lines).
For lots of small squares, just writing them into a bitmap in C code is faster than Core Graphics line drawing. Then just draw the bitmap to the view once when done. But Open GL would be even faster.
point 4. OpenGL should do that fine. Check if you could reuse those objects and whether you could move some of the logic to GLSL code.
OpenGL performance optimization (in context of WebGL but most of it should apply): http://www.youtube.com/watch?v=rfQ8rKGTVlg
I don't know the details of iOS history so this may not have been an option when the question was first posted. However, I wanted to call out CAShapeLayer as a simple option when dealing with path performance problems. "iOS Core Animation: Advanced Techniques" (find it on Google Books) says CAShapeLayer "uses hardware-accelerated drawing" which I'm taking to mean that it's a GPU-based implementation. The same book has a good usage example in chapter 6, which boils down to this:
Create a CAShapeLayer
Configure its lineWidth, fillColor, strokeColor, etc.
Add the layer as a sublayer of your view's containerView.layer
To draw a path, just set it to the layer's "path" property
This made a gigantic performance difference in my app, as measured by Instruments. If your performance problem is path-based, don't wade into OpenGL before you've tried CAShapeLayer.
I encountered the same problem. After endless searching on google,CAShapeLayer saved me finally! Here is the detail steps you need to do:
Create a view with CAShapeLayer as it's layer type by override UIView's + (Class)layerClass method
Configure the layer's lineWidth, fillColor, strokeColor, etc.
Create an UIBezierPath instance
To draw a path,use UIBezierPath instance to add lines,curve,or acr etc, after you finished drawing, just set bezierPath.CGPath to the
layer's "path" property
Here is a simple demo to draw a simple curve when you touch the demo view:
//Simple ShapelayerView.m
-(instancetype)init {
self = [super init];
if (self) {
_bezierPath = [UIBezierPath bezierPath];
CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
shapeLayer.lineWidth = 5;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.strokeColor = [UIColor yellowColor].CGColor;
shapeLayer.fillColor = [UIColor blueColor].CGColor;
}
return self;
}
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (void) customDrawShape {
CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
[_bezierPath removeAllPoints];
[_bezierPath moveToPoint:CGPointMake(10, 10)];
[_bezierPath addQuadCurveToPoint:CGPointMake(2, 2) controlPoint:CGPointMake(50, 50)];
shapeLayer.path = _bezierPath.CGPath;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[super touchesBegan:touches withEvent:event];
[self customDrawShape];
}

How to get frame for NSStatusItem

Is it possible to get the frame of a NSStatusItem after I've added it to the status bar in Cocoa? When my app is launched, I am adding an item to the system status bar, and would like to know where it was positioned, is possible.
The following seems to work - I have seen similar solutions for iOS applications and supposedly they permit submission to the app store because you are still using standard SDK methods.
NSRect frame = [[statusBarItem valueForKey:#"window"] frame];
With 10.10, NSStatusItem has a button property that be used to get the status item position without setting a custom view.
NSStatusBarButton *statusBarButton = [myStatusItem button];
NSRect rectInWindow = [statusBarButton convertRect:[statusBarButton bounds] toView:nil];
NSRect screenRect = [[statusBarButton window] convertRectToScreen:rectInWindow];
NSLog(#"%#", NSStringFromRect(screenRect));
You can use statusItem.button.superview?.window?.frame in swift
If you have set a custom view on the status item:
NSRect statusRect = [[statusItem view] frame];
NSLog(#"%#", [NSString stringWithFormat:#"%.1fx%.1f",statusRect.size.width, statusRect.size.height]);
Otherwise I don't think it's possible using the available and documented APIs.
Edit: Incorporated comments.
It's possible to do this without any private API. Here's a category for NSScreen. This uses image analysis to locate the status item's image on the menu bar. Fortunately, computers are really fast. :)
As long as you know what the status item's image looks like, and can pass it in as an NSImage, this method should find it.
Works for dark mode as well as regular mode. Note that the image you pass in must be black. Colored images will probably not work so well.
#implementation NSScreen (LTStatusItemLocator)
// Find the location of IMG on the screen's status bar.
// If the image is not found, returns NSZeroPoint
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
{
CGColorSpaceRef csK = CGColorSpaceCreateDeviceGray();
NSPoint ret = NSZeroPoint;
CGDirectDisplayID screenID = 0;
CGImageRef displayImg = NULL;
CGImageRef compareImg = NULL;
CGRect screenRect = CGRectZero;
CGRect barRect = CGRectZero;
uint8_t *bm_bar = NULL;
uint8_t *bm_bar_ptr;
uint8_t *bm_compare = NULL;
uint8_t *bm_compare_ptr;
size_t bm_compare_w, bm_compare_h;
BOOL inverted = NO;
int numberOfScanLines = 0;
CGFloat *meanValues = NULL;
int presumptiveMatchIdx = -1;
CGFloat presumptiveMatchMeanVal = 999;
// If the computer is set to Dark Mode, set the "inverted" flag
NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
id style = globalPrefs[#"AppleInterfaceStyle"];
if ([style isKindOfClass:[NSString class]]) {
inverted = (NSOrderedSame == [style caseInsensitiveCompare:#"dark"]);
}
screenID = (CGDirectDisplayID)[self.deviceDescription[#"NSScreenNumber"] integerValue];
screenRect = CGDisplayBounds(screenID);
// Get the menubar rect
barRect = CGRectMake(0, 0, screenRect.size.width, 22);
displayImg = CGDisplayCreateImageForRect(screenID, barRect);
if (!displayImg) {
NSLog(#"Unable to create image from display");
CGColorSpaceRelease(csK);
return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
}
size_t bar_w = CGImageGetWidth(displayImg);
size_t bar_h = CGImageGetHeight(displayImg);
// Determine scale factor based on the CGImageRef we got back from the display
CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;
// Greyscale bitmap for menu bar
bm_bar = malloc(1 * bar_w * bar_h);
{
CGContextRef bmCxt = NULL;
bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
// Draw the menu bar in grey
CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);
uint8_t minVal = 0xff;
uint8_t maxVal = 0x00;
// Walk the bitmap
uint64_t running = 0;
for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
{
bm_bar_ptr = bm_bar + (bar_w * yi);
for (int xi = 0; xi < bar_w; xi++)
{
uint8_t v = *bm_bar_ptr++;
if (v < minVal) minVal = v;
if (v > maxVal) maxVal = v;
running += v;
}
}
running /= bar_w;
uint8_t threshold = minVal + ((maxVal - minVal) / 2);
//threshold = running;
// Walk the bitmap
bm_bar_ptr = bm_bar;
for (int yi = 0; yi < bar_h; yi++)
{
for (int xi = 0; xi < bar_w; xi++)
{
// Threshold all the pixels. Values > 50% go white, values <= 50% go black
// (opposite if Dark Mode)
// Could unroll this loop as an optimization, but probably not worthwhile
*bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
bm_bar_ptr++;
}
}
CGImageRelease(displayImg);
displayImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
CGContextRef bmCxt = NULL;
CGImageRef img_cg = NULL;
bm_compare_w = scaleFactor * IMG.size.width;
bm_compare_h = scaleFactor * 22;
// Create out comparison bitmap - the image that was passed in
bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);
NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
NSRect imgRect = imgRect_og;
img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];
CGContextClearRect(bmCxt, imgRect);
CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));
CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);
// Draw the image in grey
CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
CGContextDrawImage(bmCxt, imgRect, img_cg);
compareImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
// We start at the right of the menu bar, and scan left until we find a good match
int numberOfScanLines = barRect.size.width - IMG.size.width;
bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
// We use the meanValues buffer to keep track of how well the image matched for each point in the scan
meanValues = calloc(sizeof(CGFloat), numberOfScanLines);
// Walk the menubar image from right to left, pixel by pixel
for (int scanx = 0; scanx < numberOfScanLines; scanx++)
{
// Optimization, if we recently found a really good match, bail on the loop and return it
if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
break;
}
CGFloat xOffset = numberOfScanLines - scanx;
CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);
CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);
// Draw the image from our menubar
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);
// Blend mode difference is like an XOR
CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);
// Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);
CGContextFlush(compareCxt);
// Walk through the result image, to determine overall blackness
bm_compare_ptr = bm_compare;
for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
{
meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
bm_compare_ptr++;
}
meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));
// If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
// a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
if (meanValues[scanx] < 0.07) {
if (meanValues[scanx] < presumptiveMatchMeanVal) {
presumptiveMatchMeanVal = meanValues[scanx];
presumptiveMatchIdx = scanx;
}
}
CGImageRelease(displayCrop);
CGContextRelease(compareCxt);
}
}
// After we're done scanning the whole menubar (or we bailed because we found a good match),
// return the origin point.
// If we didn't match well enough, return NSZeroPoint
if (presumptiveMatchIdx >= 0) {
ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
ret.x -= (IMG.size.width + presumptiveMatchIdx);
ret.y -= 22;
}
CGImageRelease(displayImg);
CGImageRelease(compareImg);
CGColorSpaceRelease(csK);
if (bm_bar) free(bm_bar);
if (bm_compare) free(bm_compare);
if (meanValues) free(meanValues);
return ret;
}
#end
you can hack the window ivar like this :
#interface NSStatusItem (Hack)
- (NSRect)hackFrame;
#end
#implementation NSStatusItem (Hack)
- (NSRect)hackFrame
{
int objSize = class_getInstanceSize( [NSObject class] ) ;
id * _ffWindow = (void *)self + objSize + sizeof(NSStatusBar*) + sizeof(CGFloat) ;
NSWindow * window = *_ffWindow ;
return [window frame] ;
}
#end
This is useful for status items without a custom view.
Tested on Lion

Resources