Animate NSSplitview and window size - objective-c

What I'm trying to achieve is that when the user press a button the window grows and the extra space is taken by a panel of NSSplitview that uncollapses and grows to fill the space.
I can easily animate the window resize and the splitview growing independently but when I try to put the two animation together one inevitably happens before the other or by steps. First the window partly resize, then the splitview resize follow, then the window finish resizing and finally the splitview finishes as well. Any ides on why that might be happening?
Here is the code I use:
- (IBAction)helpButtonPressed:(id)sender
{
if ([sender isKindOfClass:[NSMenuItem class]]) [sender setState:![sender state]];
NSWindow *window = [[[self windowControllers] objectAtIndex:0] window];
NSRect oldFrame = [window frame];
CGFloat windowWidthAdd;
if ([sender state]) windowWidthAdd = HELP_WIDTH; else windowWidthAdd = -HELP_WIDTH;
NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y,oldFrame.size.width+windowWidthAdd, oldFrame.size.height);
[[NSAnimationContext currentContext] setDuration:0.3f];
[NSAnimationContext beginGrouping];
[[window animator] setFrame:newFrame display:YES];
if ([sender state]) [self uncollapseRightView]; else [self collapseRightView];
[NSAnimationContext endGrouping];
}
-(void)collapseRightView
{
NSView *right = [[self.splitView subviews] objectAtIndex:1];
NSView *left = [[self.splitView subviews] objectAtIndex:0];
NSRect leftFrame = [left frame];
NSRect overallFrame = [self.splitView frame];
[right setHidden:YES];
[[left animator] setFrameSize:NSMakeSize(overallFrame.size.width,leftFrame.size.height)];
}
-(void)uncollapseRightView
{
NSView *left = [[self.splitView subviews] objectAtIndex:0];
NSView *right = [[self.splitView subviews] objectAtIndex:1];
[right setHidden:NO];
CGFloat dividerThickness = [self.splitView dividerThickness];
// get the different frames
NSRect leftFrame = [left frame];
// Adjust left frame size
leftFrame.size.width = (leftFrame.size.width-HELP_WIDTH-dividerThickness);
NSRect rightFrame = [right frame];
rightFrame.origin.x = leftFrame.size.width + dividerThickness;
rightFrame.size.width = HELP_WIDTH;
[[left animator] setFrameSize:leftFrame.size];
[[right animator] setFrame:rightFrame];
}

If you are looking a little closer at the class reference of NSSplitView, you will recognize it conforms to NSAnimatablePropertyContainer. That means NSSplitView will provide you with an "animating proxy of itself". If you call animator on your NSSplitView you'll get this proxy, on which you should be able to change properties in an animated fashion.
For adjusting the animation duration and timing function, use NSAnimationContext the same way you already did.
Last but not least: Did you recognize NSSplitView's minPossiblePositionOfDividerAtIndex: and maxPossiblePositionOfDividerAtIndex:?

Related

NSToolbar with custom views

Hi so I've got a problem that i can't really figure out what the cause is. I made an app so far that has a NSToolbar that has custom views in it so that you can switch between views on the toolbar (just like safari's preference window toolbar views). I was using CCodings tutorial on youtube but i have the problem of the views moving by themselves, for example i click on view 1 and when i click on view 2 view one changes and the view moves up so that you can only see half of when i put on the view, I'm using Xcode 6 and OS X 10.10 so i don't know if that would be one cause but i thank you in advance if you can help me, heres the MainWindowController.m file:
Also the views should be setup correctly
-(NSRect)newFrameForNewContentView:(NSView*)view {
NSWindow *window = [self window];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
-(NSView *)viewForTag:(int)tag {
NSView *view = nil;
switch (tag) {
case 0:
view = smallView;
break;
case 1:
view = mediumView;
break;
case 2: default:
view = largeView;
break;
}
return view;
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)item {
if ([item tag] == currentViewTag) return NO;
else return YES;
}
-(void)awakeFromNib {
[[self window] setContentSize:[smallView frame].size];
[[[self window] contentView] addSubview:smallView];
[[[self window] contentView] setWantsLayer:YES];
}
-(IBAction)switchView:(id)sender {
int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
[[NSAnimationContext currentContext] setDuration:1.0];
[[[[self window] contentView] animator] replaceSubview:previousView with:view];
[[[self window] animator] setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
Seems that you also need to change NSWidnow frame apart from contenView.
Also as for me for switching views by click events its better to use NSTabView.

gap between keyboard and textview during animation

I want to adjust the height of a textview when the keyboard appears. In iOS 7 this can be done by adjusting the NSLayoutConstraint between the textview and the bottomLayoutGuide of the view controller.
That works fine with the code below except for one detail. During the animation the textview runs ahead of the keyboard and a wide gap appears. Similar during the keyboardWillHide method.
The cause for this "bug" is probably because the keyboard starts from the very bottom of the screen while the textview starts higher up due to the height of the toolbar.
Any suggestions on how to fix this?
-(void) keyboardWillShow:(NSNotification *) notification
{
//update constraints
CGRect keyboardFrame = [[[notification userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedKeyboardFrame = [[self view] convertRect:keyboardFrame
fromView:nil];
CGRect toolbarFrame = [[[self navigationController] toolbar] frame];
CGRect convertedToolbarFrame = [[self view] convertRect:toolbarFrame
fromView:nil];
CGFloat toolbarAdjustment = (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) ? CGRectGetHeight(convertedToolbarFrame) :CGRectGetWidth(convertedToolbarFrame);
[[_textView bottomSpaceConstraint] setConstant:CGRectGetHeight(convertedKeyboardFrame) - toolbarAdjustment];
//animate change
for (UIView *view in [[self view] subviews])
{
[view setNeedsUpdateConstraints];
}
[UIView animateWithDuration:[[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]
delay:0 //0.2 as possible hack, otherwise a gap appears between keyboard and textview
options:[[[notification userInfo] valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]
animations:^{
for (UIView *view in [[self view] subviews])
{
[view layoutIfNeeded];
}
}
completion:NULL];
}
The delay is probably caused by calling layoutIfNeeded repeatedly in a loop.
In the animation block, just send layoutIfNeeded to the root view, i.e., self.view. Sending layoutIfNeeded to the root view will take care of the entire view hierarchy. So get rid of the loop.
I question if the call to setNeedsUpdateConstraints is necessary; if it is, it only needs to be sent to the root view.
Also, try eliminating the animation options parameter
options:0

Switching between custom views using Toolbar

I have a single MainMenu.xib file and I have a window by the name "Preferences"
Also I have four different Custom Views with different Heights. and a Toolbar on that window.
I have linked every toolbar item to the -(void) switchViews:(id)sender; function. The views are linked and are showing up and are also changing but the problem is that if I change from one view to another and then back or to another, the content i.e. buttons, labels, checkboxes on the views keep moving upwards, the window still keeps on changing to right heights, but if I switch enough times the content would disappear from the view.. Any kind of help would be useful.
http://imgur.com/bV3NHa0,zgJCacA
http://imgur.com/bV3NHa0,zgJCacA#1
// FOR CHANGING VIEWS IN THE PREFERENCES WINDOW
- (IBAction)switchView:(id)sender {
long int tag = [sender tag];
NSView *view = [self viewForTag:tag];
NSView *previousView = [self viewForTag:currentViewTag];
currentViewTag = tag;
NSRect newFrame = [self newFrameForNewContentView:view];
[NSAnimationContext beginGrouping];
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
[[NSAnimationContext currentContext] setDuration:1.0];
}
[[[[self preferences] contentView] animator] replaceSubview:previousView with:view];
[[[self preferences] animator] setFrame:newFrame display:YES];
[NSAnimationContext endGrouping];
}
- (NSView *)viewForTag:(long int)tag {
NSView *view = nil;
switch (tag) {
case 0:
view = generalView;
break;
case 1:
view = timesView;
break;
case 2:
view = menubarView;
break;
case 3: default: view = aboutView;
break;
}
return view;
}
- (NSRect)newFrameForNewContentView:(NSView *)view {
NSWindow *window = [self preferences];
NSRect newFrameRect = [window frameRectForContentRect:[view frame]];
NSRect oldFrameRect = [window frame];
NSSize newSize = newFrameRect.size;
NSSize oldSize = oldFrameRect.size;
NSRect frame = [window frame];
frame.size = newSize;
frame.origin.y -= (newSize.height - oldSize.height);
return frame;
}
with the problem you're describing and glancing at your code i would start to suspect this line...
frame.origin.y -= (newSize.height - oldSize.height);

Can't get [[id Animator] setAlphaValue: 0.0] to work

I'm trying to animate an opacity fade out + frame size change of an NSImageView . The frameSize works smoothly but for some reason the alpha value just jumps directly to it's final value at the beginning of the animation. I've tried back layering the superview and I'm running out of ideas here. Here is my code sample ->
AABLCLogo -> NSImageView IBOutlet
speed -> 1.0f
//setting target frame for image view
NSRect newFrame = [AABLCLogo frame];
newFrame.origin.x = newFrame.origin.x + (newFrame.size.width*0.1);
newFrame.origin.y = newFrame.origin.y + (newFrame.size.height*0.1);
newFrame.size.width = newFrame.size.width * 0.8;
newFrame.size.height = newFrame.size.height * 0.8;
//backing layer for super view
[[AABLCLogo superview] setWantsLayer:YES];
//animating image view
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration: speed];
[[AABLCLogo animator] setFrame: newFrame];
[[AABLCLogo animator] setAlphaValue: 0.0];
[NSAnimationContext endGrouping];
//XCode 4 delay snippet
double delayInSeconds = speed;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime,
dispatch_get_main_queue(),
^(void) {
//code executes after ''speed'' seconds
[[AABLCLogo superview] setWantsLayer:NO];
[[self view] removeFromSuperview];
});
I suspect this is an issue with animating on the main thread. In most cases, if you want to animate two things simultaneously, you'll need to use NSViewAnimation. Use this code as a guide, it resizes the window while fading out part of it's content.
NSRect newWindowFrame = NSMakeRect(self.window.frame.origin.x, self.window.frame.origin.y, 640, self.window.frame.size.height);
NSMutableArray *viewAnimations = [NSMutableArray array];
NSDictionary *windowSizeDict = [NSDictionary dictionaryWithObjectsAndKeys:self.window, NSViewAnimationTargetKey, [NSValue valueWithRect:newWindowFrame], NSViewAnimationEndFrameKey, nil];
[viewAnimations addObject:windowSizeDict];
NSDictionary *animateOutDict = [NSDictionary dictionaryWithObjectsAndKeys:self.dataSidebarController.containerView, NSViewAnimationTargetKey, NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil];
[viewAnimations addObject:animateOutDict];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:viewAnimations];
[animation setDuration:0.35];
[animation setAnimationBlockingMode:NSAnimationNonblocking];
[animation setDelegate:self];
[animation startAnimation];
You want to send setWantsLayer:YES to the view you are animating, not its superview.
NSView's setAlphaValue: only works an a layer-backed or layer-hosting instance, since it forwards the value to the opacity property of NSView's layer.
For the full story, read the "Discussion" of NSView's setAlphaValue: in Apple's documentation.
I believe the layer you request is not create until the next time the view is drawn.
Thus you either need to force display of the superview or wait until the next iteration of the run loop before adding on animations.

How can I make the NSWindow setFrame: .. animated:YES] function animated down instead of up?

When I call my [window setFrame: frame animated:YES] to enlarge the window it works great but it animates up and it goes behind the status bar. How can I make the window animate down instead?
Just decrease the y coordinate the same distance you increase the height.
CGFloat amountToIncreaseWidth = 100, amountToIncreaseHeight = 100;
NSRect oldFrame = [window frame];
oldFrame.size.width += amountToIncreaseWidth;
oldFrame.size.height += amountToIncreaseHeight;
oldFrame.origin.y -= amountToIncreaseHeight;
[window setFrame:oldFrame animated:YES];
I couldn't find an easy way to do this so I wrote an animation function that would resize the window and move it at the same time thus giving the appearance that it's animating down.
- (IBAction)startAnimations:(id)sender{
NSViewAnimation *theAnim;
NSRect firstViewFrame;
NSRect newViewFrame;
NSMutableDictionary* firstViewDict;
{
firstViewDict = [NSMutableDictionary dictionaryWithCapacity:3];
firstViewFrame = [window frame];
[firstViewDict setObject:window forKey:NSViewAnimationTargetKey];
[firstViewDict setObject:[NSValue valueWithRect:firstViewFrame]
forKey:NSViewAnimationStartFrameKey];
newViewFrame = firstViewFrame;
newViewFrame.size.height += 100;
newViewFrame.origin.y -= 100;
[firstViewDict setObject:[NSValue valueWithRect:newViewFrame]
forKey:NSViewAnimationEndFrameKey];
}
theAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray
arrayWithObjects:firstViewDict, nil]];
[theAnim setDuration:1.0];
[theAnim startAnimation];
[theAnim release];
}