Text drawn to CGContextRef Not Always complete - objective-c

I am new to the Mac world and am seeing some behavior that is puzzling to me. I am working on a basic full screen utility that will capture the main display of the Mac and display some text. The problem I am having is that if I capture the display multiple times, after the first time the text that I am writing the the contextRef begins to degrade at the beginning of the line each time it is written. The first time it might be that the fill doesn't get drawn in the first letter, after that whole sections of the text at the beginning of the line disappear. The code I am using is as follows:
CGDisplayCapture(kCGDirectMainDisplay);
CGDirectDisplayID display = kCGDirectMainDisplay;
CGContextRef ctx = CGDisplayGetDrawingContext (display);
CGContextSelectFont (ctx, "Times-Roman", 48, kCGEncodingMacRoman);
CGContextSetTextDrawingMode (ctx, kCGTextFillStroke);
CGContextSetRGBFillColor (ctx, 1, 1, 1, 0.75);
CGContextSetRGBStrokeColor (ctx, 1, 1, 1, 0.75);
CGContextShowTextAtPoint (ctx, 400, 400, text, strlen(text));
I have found that if I put a '[NSThread sleepForTimeInterval:0.25]' before the CGContextShowTextAtPoint that the text would always display properly but that is what I would consider a hack.
My question for those who are smarter then I is, what is going on to cause the problem and is there something different that I should be doing to try and resolve it?
TIA,
JT

To make a basic full screen app, do not get the CGDisplay and associated CGContextRef and directly draw on it. I guess you're drawing to the context without being notified by the system to do so at all. That's not what you are supposed to do.
Unless absolutely necessary, create a big view which covers the entire screen, (i.e. keeping windows of other applications behind,) and draw on it just as you would do to a normal view. I.e. never get the context manually and write it actively. Rather, implement a subclass of NSView, implement drawRect, and perform the drawing inside. Finally, use enterFullScreenMode:withOptions:
to make the view full screen.

Related

UIButtons titleLabel clips Text after being rotated by CGAffineTransformMakeRotate()

I am developing a iOS-6 app. I have a UIViewController with a view that needs fixed orientation (portrait mode). But when the phone is rotated, one control on that view needs to be moved and rotated (so that it will always be in the upper left corner, and its text will be readable).
I am achieving this by shifting the control(a UIView) using the frame-property of my control (it is a custom view, more on that later), and then using CGAffineTRansformMakeRotate() afterwards, since I know that it's not advisable to use the frame after rotating a view. Everything is fine so far, but here's the thing: That custom view has three UIButtons of type UIButtonTypeCustom as its subviews. Because I rotated the View, but cannot rotate the buttons inside the view (they are not squares), I need to rotate the titleLabels of the Buttons for the text to be readable in the new deviceOrientation.
But it won't work very well. The text will be rotated, as I intended, but it will be clipped by the titleLabel, because the titleLabel has the wrong frame. I checked this by applying borders to the label. So I need to change the titleLabels frame, right? But how can I do that? I tried setting it using [titleLabel setFrame: frameThatFits];, but to no avail. (frameThatFits is a CGRect I created). Also, calling [button.titleLabel sizeToFit]; has no effect that I could see.
I am using [button setTitle:title forControlState: UIControlStateNormal];to set the title.
TL;DR: I'm trying to change the frame/bounds of a UIButtons titleLabel after rotating it using an affine transformation. Any help?
Thanks.
PS: I can supply code when needed, but I wouldn't know what to show you. Tell me what you need, I'll post it.
OK, first of all, thanks to everyone who tried to help. Im posting an alternative solution for my problem, and although it doesnt really address the problem of changing the titleLabels dimensions, it will result in the proper display of my ViewController.
It turns out using the frame is a bad idea. I initially used the frame to reposition the view and i figured that this couldnt be a problem because i only ever applied transformations afterwards, but i was wrong. Because OBVIOUSLY i tried to change the titleLabels frame. AFTER the rotation. And that didnt work.
So the way to go here is using the center-property and the bouds of the view consistently throughout the code. It will result in properly rotated Buttons, that do not need any fidgeting afterwards.
My takeaway here is that i will never ever again use the frame-property outside of a NSLog-statement. But why [button sizeToFit];wouldnt yield any results is still beyond me. If i ever figure it out, i might post it if i remember.
EDIT:
#ZevEisenberg nailed it with this comment:
“Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.” So you are right to use the center and bounds here, but if you do not have a transform, the frame is perfectly safe to use.
NEXT EDIT:
Heres how i ended up repositioning the Buttons:
-(CGPoint)centerForView:(UIView *)view{
//calculate a suitableposition for the view
//depending on the current orientation and the device type (iphone 4S/5, etc)
return point;
}
Then, as a reaction to the deviceOrientation change notification, i apply CGAffineTransformIdentity to all the views, reposition them using my centerForView shown above, and apply the correct rotation transformation to the View. I do this for all the subviews every time the divice rotates, like so:
-(void)setRightRotationTransformations{
[self resetAllTransformations];
self.someSubview.transform = CGAffineTransformRotate(self.someSubview.transform, -M_PI_2);
}
In my case works such hack:
set Line Break mode to Word Wrap
Add extra line to title (even for one line title)

CoreGraphics: Draw rect in center of view

I have a UIView subclass with the drawRect: method overriden. In there, I have long lines of generated code that draw something.
The generated code has a problem of having all the vertices/coordinates of the paths/lines hard-coded. So, to draw a 100x100 square, it would start at 0,0 and go to 0,100 -> 100,100 -> 100,0. To make this shape scale based on the UIView bounds property was done as follows:
// This is the size of the drawing. I always know this value beforehand.
CGSize contentSize = SHAPE_XMARK_SIZE;
CGFloat scaleX = self.bounds.size.width / contentSize.width;
CGFloat scaleY = self.bounds.size.height / contentSize.height;
CGContextScaleCTM(context, MIN(scaleX, scaleY), MIN(scaleX, scaleY));
All good as far as the scale is concerned. Now, I would like the position of the drawing to be ralative to the bounds, too. I want to somehow make the drawRect: method align the drawing based on the bounds, too.
I am thinking of something like:
push context method
draw code
pop context
position previous context
Is that a sane approach? Or is the push and pop context not able to accomplish such sorcery?
One Simple Approach:
To answer the "what have you tried" question, I tried making the view that I draw isolated with a bounds size equal to the drawing size, then embed that view inside another view... and it works. However, this method is tedious and I would prefer avoiding it, if possible.
Pfft, the answer was super simple. Me 1, SO 0, I guess.
CGContextTranslateCTM(context, xOffset, yOffset);
Yeah, I somehow overlooked the fact that an awesome function should exist to translate CGContextRef, since the scale function existed.
The rest is all simple math to achieve vertical & horizontal alignment.

Animation for rotating a view doesn't show up when another view is being animated underneath

The question has changed, please read the entire post.
I must be missing something. I've successfully done this with rotations around the y axis but when I try to do it around the x axis everything gets messed up and I'm not sure why. The idea is to rotate the layer around an edge (in this case, the bottom edge). The view will rotate into and out of visibility. I've searched Stack Exchange, Googled it, read a couple tutorials and read the Apple doc's. My head is telling me this should work but it's just not. I get the kind of animation I want, but it's rotating around the top edge. I can get it to rotate around the bottom edge but then it's in the wrong direction. Here's my code when it rotates in the correct direction, but around the top edge.
Note I have the anchor set:
rLayer.anchorPoint = CGPointMake(.5f, 0.0f);
Also, the origin of the y is negative size.height when I want the view to disappear. So the percentRotation will be 1, giving me a rotation of M_PI_2. When the origin is 0, the percentRotation is also 0, giving me a rotation of 0. This also allows me to track a drag, which is convenient.
The following code is in a animation block (usually, unless I'm tracking a drag). Note the cRect is the rect I've set the _toolbar.frame to.
CALayer *rLayer = _toolbar.layer;
CGFloat zDist = self.view.window.bounds.size.height * 1.5;
CATransform3D rT = CATransform3DIdentity;
CGFloat percentRotation = 1.0f - ((cRect.size.height + cRect.origin.y) / cRect.size.height);
rT.m34 = 1.0f / -zDist;
rT = CATransform3DRotate(rT, M_PI_2 * percentRotation, 1.0f, 0.0f, 0.0f);
rLayer.transform = rT;
I've also tried using a -M_PI_2, but the view just disappears without animation. Any help would be greatly appreciated.
UPDATE
Ok, I did figure it out, but I'm going to change my question slightly. First, I created a separate project so no other code would interfere with the animations. I learned that to rotate around the bottom x-axis in iOS, the anchor point need to be {0.5f, 1.0f}. Annoying that their docs don't mention that, and with Core-anything, it can be tough to know what the coordinate system is on iOS.
It appears that my code was perfectly fine (aside from the anchor), as it was exactly the same as in the stand alone project. So I started removing other elements off the page. Turns out, I was hiding another view behind the one I was trying to animate. It was also animating (no 3D rotation, though, just position). During the animation, this view was appearing above the view I was trying to rotate. At the end, the rotating view would just appear above the other one, giving the impression that there was no rotation animation.
The 'easy' fix is to forego animating the hidden view. But this makes my code a little more complicated when I want to actually show that view. What I would really like to know is why core-animation is doing this, and what I can do to fix it.

Same Command, Different Results setFrame: NSView

I'm experiencing what I think is a very strange issue.
First and foremost, my application moves UI elements around the screen using the following example command:
[view setFrame:NSRectFromCGRect(CGRectMake(0, 0, 0, 0))];
I give my users the possibility to resize the application's NSWindow to one other size with this command:
[self.window setFrame:NSRectFromCGRect(CGRectMake(0, 0, 0, 0)) display:YES animate:YES];
When in 'resized mode', I obviously change every single setFrame command to the appropriate coordinate system.
However, there is an issue: when (if) the user switches back to the original NSWindow size by clicking on the button again, and the application performs the UI movements again, the views that are moved around are not in the location they are supposed to be in.
To clarify: my UI movement code runs fine, over and over, either in normal or resized mode. However, if I switch from one to the other, some items (not all) are shifted.
What could be causing this strange behavior? I'm using the same exact commands (within each screen resolution), and NSLogs confirm the UI elements are in the location that I specified; however, this location is clearly not the one I'm attempting to move to.
Has anyone experienced a similar issue here?
You've probably set some sort of autoresizing mask in Interface Builder that's interfering with your manual placements. Go in to your nib and remove all the springs and struts in the Size inspector. Although if possible you should let autosizing handle the placement, or move to Auto Layout which was introduced in 10.7.
After days and days of trying to figure this out, I've come to the following conclusion: the resizing code simply wasn't working.
I confirmed this by using NSLog after I resized my window. In the end, I changed my resizing code by adding the following line after the traditional setFrame: method:
[self.window setContentSize:NSSizeFromCGSize(CGSizeMake(desiredWidth, desiredHeight))];

Using resizableImageWithCapInsets: image for button only works for the state set, other states show a "gap"

When using resizableImageWithCapInsets: to create an image for a UIButton only the normal state (the state set the image with using setBackgroundImage:forState:) works. All other states show a gap instead of the drawn image. UIButton says that if no image is set for a particular state, the normal state image will be used with an overlay for disabled and selected states.
Here is the normal state:
Here is the selected state:
And here is the source image:
It clearly is using the resizable image I provided, but the image is not drawing the resized area. (You can see the left and right edges but the middle area that is to be stretched just isn't drawn).
Interestingly, stretchableImageWithLeftCapWidth:topCapHeight: does work. Now this is a deprecated method in iOS 5, but with the gap being shown in the new API, I may be stuck using it.
I do recognize that I can provide more images for each state but that defeats the purpose I'm trying to achieve of reducing memory footprint plus adds extra dependency on my graphics designer which I'd like to avoid.
// This is the gist of the code being used
UIImage* image = [UIImage imageNamed:#"button.png"];
UIEdgeInsets insets = UIEdgeInsetsMake(image.size.height/2, image.size.width/2, image.size.height/2, image.size.width/2);
image = [image resizableImageWithCapInsets:insets];
[self.button setBackgroundImage:image forState:UIControlStateNormal];
// Even doing the following results in the same behaviour
[self.button setBackgroundImage:image forState:UIControlStateSelected];
You aren't creating your insets properly for the image capping. I've reproduced your issue and corrected it by using the correct insets.
With your current code, you are creating caps of half of the image height and width - this leaves you with a "stretchable" area of 0x0 pixels - so you get nothing in the middle.
Why this isn't showing up as wrong in the normal state of the button I'm not sure - perhaps there is some optimisation built in to UIButton to fix things or auto-strectch if you don't supply a stretchable image, and this is not applied to the other states.
The caps are supposed to define the area of the image that must not be stretched. In the case of your button.png image, this is 6 pixels on the left and right sides, and 16 pixels in from the top and bottom. This isn't quite standard, you should tell your graphics designer that (at least for left-right which is the most common stretching) you should only have a 1px area in the centre, however this does not affect the outcome. If you do have a 1px stretchable area then you can standardise your code by deriving the caps from the image size as you have tried to do in your question (each cap is then (image.size.height - 1) / 2 for top/bottom, same but with width for left/right).
To get the correct images on your button, use the following code for creating the stretchable image:
UIEdgeInsets insets = UIEdgeInsetsMake(16, 6, 16, 6);
image = [image resizableImageWithCapInsets:insets];
I was experiencing problems while using resizable images on iOS5 too. It turns out that if your button is of type "RountedRect" and you manipulate the background images, the resizable images will not behave as expected. (iOS6 handles this ok, presumably by assuming your new button type and adjusting as needed.)