Why is [[NSWindow animator] setFrame...] very laggy sometimes? - objective-c

So, I have the following code to show my NSWindow:
[_window makeKeyAndOrderFront:self];
[NSAnimationContext beginGrouping];
[[_window animator] setAlphaValue:1.0];
[[_window animator] setFrame:NSMakeRect([[NSApp currentEvent] window].frame.origin.x - 102, [[NSApp currentEvent] window].frame.origin.y - 238, _window.frame.size.width, _window.frame.size.height) display:YES];
[NSAnimationContext endGrouping];
This code is called right after the user has clicked on the app's status bar icon, that's why I use the [[NSApp currentEvent] window].frame.origin.y/x to get the location of the status bar icon.
This code runs perfectly but, sometimes, it's very laggy and "jumpy" and I don't know why.
Any ideas about this and how to fix it?

The NSWindow animator uses NSAnimation, which means that it rapidly fires a timer to animate the frame of the window. At each frame of the animation, every view inside the window is redrawn. If you have large views with somewhat complex view hierarchies, the performance is quite bad and there's no real way to work around it.
I would recommend JNWAnimatableWindow as a substitute for the default NSWindow animator, as it uses a Core Animation CALayer to perform animations on and therefore is much smoother.

Related

Invisible Fullscreen View with Cocoa

I'm trying to create an application which change the mouse cursor, but to do that the cursor must be inside a NSView. To do this all the time, I would need to have an intangible and invisible fullscreen view.
I've created a subclass of NSView which implement the following code to became full screen in initWithFrame:
[self enterFullScreenMode:[NSScreen mainScreen] withOptions:nil];
And using that code, I can change the cursor inside the view:
- (void)resetCursorRects{
[self addCursorRect:[self bounds] cursor:appCursor];
}
The cursor changed successfully, but here comes the first problem: the view is in fullscreen, but it's visible, like a gray background. I've tried this method:
[[self window] setOpaque:NO];
And this one:
[[self window] setBackgroundColor:[NSColor colorWithWhite:1.0 alpha:0.0]];
But both of them just change the view color to black instead of gray. How can I fix that?

NSScrollview not scrolling programmatically?

Note:Both Horizontal and vertical scrollers are visible on the screen and work fine.But I cant make them move Programatically.
I am working on a cocoa desktop application.I am using the NSScrollview in my Mainmenu.xib file and I am creating an outlet in its owner which is Appdelegate.h .
Here is the outlet
#property(nonatomic,retain) IBOutlet NSScrollView* scrollview;
When I try to set a new referencing outlet of my NSScrollview from the interface builder and take the line to file's owner I only see one option "delegate".I dont see the outlet scrollview.
So I connect the scrollview to the delegate in file's owner (As I cant see the scrollview outlet).
Now I am trying to do auto scrolling in my code.Here is the code
for(int a=0;a<10000;a++)
{
NSPoint pointToScrollTo = NSMakePoint ( 100+a,100+a ); // Any point you like.
[[self.scrollview contentView] scrollToPoint: pointToScrollTo];
[self.scrollview reflectScrolledClipView: [self.scrollview contentView]];
}
This code does not work and The scroller does not scroll automatically.
I am trying to make a slow scrolling animation with this code.
NSClipView has a scrollToPoint: method which can be used to scroll programmatically:
- (IBAction)scrollToMid:(id)sender
{
CGFloat midYPoint = [self.scrollView contentView].frame.size.height/2.0;
[[self.scrollView contentView] scrollToPoint:NSMakePoint(0.0, midYPoint)];
[self.scrollView reflectScrolledClipView:[self.scrollView contentView]];
}
If you want animated scrolling, you have to set the boundsOrigin via animator proxy. (Because neither NSScrollView nor NSClipView expose an animatable scroll point property)
- (IBAction)scrollToMidAnimated:(id)sender
{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:2.0];
NSClipView* clipView = [self.scrollView contentView];
NSPoint newOrigin = [clipView bounds].origin;
newOrigin.y = [self.scrollView contentView].frame.size.height/2.0;
[[clipView animator] setBoundsOrigin:newOrigin];
[NSAnimationContext endGrouping];
}
Better way to get this, flip the view. I have worked on this to get the scroll view to top.
-(void)scrollToTop:(NSScrollView *)scrollView
{
NSPoint newScrollOrigin;
if ([[scrollView documentView] isFlipped]) {
newScrollOrigin=NSMakePoint(0.0,0.0);
} else {
newScrollOrigin=NSMakePoint(0.0,NSMaxY([[scrollView documentView] frame]) -NSHeight([[scrollView contentView] bounds]));
}
[[scrollView documentView] scrollPoint:newScrollOrigin];
}
That should be called in windowDidLoad to get the position .
-(void)windowDidLoad
{
[super windowDidLoad];
[self scrollToTop:_myScroll];
}
In the identity inspector of your File's Owner, check its class. It must be set to NSApplication. You have to change it to AppDelegate to get the outlet scrollview to which you can connect your scrollview.
But the better option is to have a NSObject in your xib and set its class to AppDelegate. (However xcode create it by default, check if a item "AppDelegate" is present in the box "objects" below "Placeholders" where FileOwner is present). Now drag a Referencing outlet line from your scrollview to that object. It will show the IBOutlet object name of your Appdelegate class.
UPDATE:
You cannot create a slow animation using a for loop. The for loop is so fast that you will see only the final result, not the complete scrolling. You need to use core animation for slow animation or NSTimer to delay scrolling to make it slow.
Plus please try using [scrollview documentView] , everywhere in place of [scrollview contentView] . Although i haven't given it a try.
Ok I worked on it, and this is the final result
If your scroll bar is on top, the following will move it to end:
[scroll.contentView scrollToPoint:NSMakePoint(0, ((NSView*)scroll.contentView).frame.size.height - scroll.contentSize.height)];
[scroll reflectScrolledClipView: [scroll contentView]];
You may modify the above as per your need.
Also even your code is working, but you need to take the relative points in place of any point.
So instead of placing your code in the for loop, test it by dragging a button on your xib, and in its button action write:
NSPoint pointToScrollTo = NSMakePoint ( 100,100 ); // Any point you like.
[[scrollview contentView] scrollToPoint: pointToScrollTo];
[scrollview reflectScrolledClipView: [scrollview contentView]];
Now run the application, set the scroll of scrollview to some random position. Then click the button and check if scroll moved somewhere.
If it works then your code is fine, your only need to set the points accordingly. It worked for me.
I found that if you are using NSViewController, you need to do the 'scrollPoint' in your viewWillAppear method, rather than in viewWillLoad. FYI.

Animating label (NSTextField) horizontally

I have a NSTextField as a label, showing a string.
I want to animate this label from right to left, if the content of it is too large to be displayed at once.
I've done this with an NSTimer so far, it works, but it's just not a very good solution.
The labels are displayed in an NSTextFieldCell, in a Table View.
They often get out of sync, and I guess it's just eating up a lot of CPU/GPU resources.
Is there another way with Core Animation to do this?
I have tried it with layers, as you can see right here:
CALayer and drawRect
but I didn't get it working either.
I would really appreciate your help.
You can simply animate the position of NSTextField with animator like
[[textField animator] setFrameOrigin:NSMakePoint(x,y)];
you can also embed it in "CATrancation" code like this:
[CATransaction begin];
[CATransaction setAnimationDuration:0.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[textField animator] setFrameOrigin:NSMakePoint(x,y)];
[CATransaction commit];
if you need animation delegate, you can use CABasicAnimation
CABasicAnimation* animation = [CABasicAnimation animation];
animation.delegate = self;
NSDictionary *animations = [NSDictionary dictionaryWithObjectsAndKeys:animation,#"frameOrigin",nil];
[textField setAnimations:animations];
[[textField animator] setFrameOrigin:NSMakePoint(x,y)];
Delegate methods are
- (void)animationDidStart:(CAAnimation *)theAnimation;
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag;
If you need to mask your text field, just embed it in other NSView.
First, animate the label using one of the functions offered in the other answers.
Then, if you want to display another view on the sides without overlapping, you can:
Insert the label in a subview with the limits you wish
Use bringSubviewtoFront: or sendSubviewToBack: to make sure your label stays in the back

Animating an NSView frame, then jumping to specific frame

I'm trying to slide a gridded NSView up, revealing another row from another NSView. Then, once the animation is done, the second NSView will dissapear, and the first will immediately move back to its original position with the new data.
It works 4/5s of the time. The only error I'm getting is when the first view doesn't reset to the original position; it stays were the animation finishes.
Here's the basic idea (I don't have the code here):
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
[[myView animator] setFrame:newPos];
} completionHandler:^{
[myView setFrame:originalPos];
}];
I'm having trouble Googling since runAnimationGroup:completionHandler is new in Lion.

How to wait for an animator to finish?

This is probably a simple question but I can't seem to figure out how to do it. Basically all I want to do is fade a window before closing it:
[[window animator] setAlphaValue:0.0];
[window close];
This works fine without the [window close], but when that is included the window seems to close it before the animation finishes (which is obviously not what I want); the same seems to happen for orderOut:, performClose:, etc. Is there any way to avoid this?
[[window animator] setAlphaValue:0.0];
[window performSelector:#selector(performClose:) withObject:self afterDelay:[[NSAnimationContext currentContext] duration]];
This is an old (but still popular) question with obsolete answer.
The right way to wait for animator finished is using special NSAnimationContext's class method with completionHanler like this:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
// Start some animations here.
[[window animator] setAlphaValue:0.0];
} completionHandler:^{
// This block will be invoked when all of the animations started above have completed or been cancelled.
NSLog(#"All done!");
}];
Implicit animations triggered through the animator proxy run on wall time. Get the duration from the current NSAnimationContext and perform delay your cleanup/post-animation operations using that interval.