Draggable CALayer - objective-c

Any way of making a CALayer draggable by the user? If so, how?
(In Cocoa - Mac)

Layers can not receive mouse events themselves. You would have to do the event handling in the view or view controller containing the layer.
If a mouseDragged: event originates on a layer (see -[CALayer hitTest:] and -[CALayer containsPoint:] to test this), adjust the layer's position accordingly. You will probably want to disable implicit animations to have the layer follow the mouse pointer immediately (rather than lagging a little behind because of the animation of the position property):
[CATransaction begin];
[CATransaction setDisableActions:YES];
layer.position = ...;
[CATransaction commit];

I tried creating a window through code and adding the CALayer to it, but I don't get why it's not showing.
NSRect rect = NSZeroRect;
rect.size = NSMakeSize( SSRandomFloatBetween( 300.0, 200.0 ), SSRandomFloatBetween( 300.0, 200.0 ));
NSWindow *newWin = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSWindowBackingLocationDefault defer:YES];
[newWin setBackgroundColor: [NSColor clearColor]];
[newWin setOpaque:NO];
[newWin setIgnoresMouseEvents:NO];
[newWin setMovableByWindowBackground:YES];
[newWin makeKeyAndOrderFront:self];
[[newWin contentView] setWantsLayer:YES];
NSRect contentFrame = [[newWin contentView] frame];
CALayer *newWinLayer = [CALayer layer];
newWinLayer.frame = NSRectToCGRect(contentFrame);
layer.backgroundColor=CGColorCreateGenericGray(0.0f, 0.5f);
layer.borderColor=CGColorCreateGenericGray(0.756f, 0.5f);
layer.borderWidth=5.0;
// Calculate random origin point
rect.origin = SSRandomPointForSizeWithinRect( rect.size, [window frame] );
// Set the layer frame to our random rectangle.
layer.frame = NSRectToCGRect(rect);
layer.cornerRadius = 25.0f;
[newWinLayer addSublayer:layer];
Window is linked to a big window, with a semi-transparent (black filled) window that is resized to fill the screen.
I've made the window draggable, but why isn't the CALayer in the window showing?

Related

How to draw an fullscreen overlay and text+text field over it?

Is there a way to draw fullscreen overlay, and then draw a label + textfield over it?
What I already did:
When user clicks button, screen should be covered by overlay.
in the AppDelegate.m button action method I wrote:
NSRect frame = [[NSScreen mainScreen] frame];
self.mainWindow = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
[self.mainWindow setAcceptsMouseMovedEvents:YES];
[self.mainWindow setOpaque:NO];
[self.mainWindow setLevel:CGShieldingWindowLevel()];
[self.mainWindow setBackgroundColor:[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.7]];
[self.mainWindow orderFrontRegardless];
NSApplicationPresentationOptions options = NSApplicationPresentationDisableProcessSwitching + NSApplicationPresentationHideDock + NSApplicationPresentationDisableForceQuit + NSApplicationPresentationDisableSessionTermination + NSApplicationPresentationDisableHideApplication;
[NSApp setPresentationOptions:options];
It's working nice, but without any animations & I really don't know how to draw objects (labels/textfields) over it?
Either, should I move this method into another class?
as for animation, there are many ways.. Id do
mainWindow.alphaValue = 0;
[mainWindow orderFrontRegardless];
[[mainWindow animator] setAlphaValue:1.0];
as for the label, just add a NSTextField with a HUGE NSFont, as a subview
NSTextField *label = ...
[[mainWindow contentView] addSubview:label];
p.s. just like with the label, you can add any other views :)

Overlay not scrolling with UITableView - iOS

I have a UITableView class that uses the following methods to call a loading overlay when going to the next screen. The problem is that this loading screen does not scroll with the list... So if you scroll a little bit and click on something, the loading screen doesn't show (because it's at the top). How can I get the loading screen to stay on top of the UITableView at all times? Please know that each UITableView is contained within a UINavBar, which is contained within a UITabBar. Here is my code:
-(void)createLoadScreen
{
CGRect screenRect = [self.view bounds];
CGRect overlayFrame = CGRectMake(0.0, 0.0, screenRect.size.width, screenRect.size.height);
overlay = [[UIView alloc] initWithFrame:overlayFrame];
overlay.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.7];
CGRect frame = CGRectMake(screenRect.size.width / 2 - 25.0, screenRect.size.height / 2 - 70, 25.0, 25.0);
loading = [[UIActivityIndicatorView alloc] initWithFrame:frame];
[loading setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
[loading sizeToFit];
loading.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin);
loading.hidesWhenStopped = YES;
[overlay addSubview:loading];
}
-(void)showActivityIndicator
{
[self.view addSubview:overlay];
[loading startAnimating];
}
-(void)removeActivityIndicator {
[loading stopAnimating];
[overlay removeFromSuperview];
}
Are you using a UITableViewController?
You should be able to fix your issue by adding the overlay view to the table's superview instead of the table view itself. This way your overlay is actually above and separate from your scrolling table view
-(void)showActivityIndicator
{
[[self.view superview] addSubview:overlay];
[loading startAnimating];
}

How to get UIView to overlay UIScrollView as a footer

I have a UIScrollView with an image that needs to be scalable. I want to have a "footer" with a black (opaque) background and white text. I wanted to have it be fixed as a footer. It will be opaque so you can see the image behind it.
I created a containing UIView for the scrollview and footer. I can get the scrollview to be smaller than the app frame and have a footer at the bottom filling in the extra space, but obviously I can't see the image behind the footer.
I also tried putting the UIScrollView and UIView (footer) inside the container and positioning them accordingly, but in this case I can't even see the footer. Any ideas?
Code I've gotten so far (executed in viewDidLoad of view controller):
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
UIView* view = [[UIView alloc] initWithFrame:appFrame];
view.backgroundColor = [UIColor blackColor];
CGRect scrollViewFrame = CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height - 100);
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:scrollViewFrame];
[scrollView setCanCancelContentTouches:NO];
scrollView.clipsToBounds = YES;
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[scrollView addSubview:imageView];
[scrollView setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
scrollView.minimumZoomScale = 0.7;
scrollView.maximumZoomScale = 5;
scrollView.delegate = self;
[scrollView setScrollEnabled:YES];
UIView* textView = [[UIView alloc] initWithFrame:CGRectMake(0.0, scrollViewFrame.size.height, appFrame.size.width, 100)];
// Red for testing purposes
textView.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5];
[view addSubview:scrollView];
[view addSubview:textView];
self.view = view;
I assume that with "image" you mean that there is an UIImage in the scroll view, and that you want to see it through a half transparent footer view.
In this case you should not put the views beneath each other, but the footer view on top of the scroll view. You can insure it is on top by calling bringSubviewToFront or using [containerView insertSubview:footer atIndex:0]; instead of addSubview:.
Another caveat might be that you call your view view. There might be some unpredictable behavior about which view it is, so perhaps it is better to call it something else and then assign it to self.view.
I have plenty of UIScrollViews with a small non-scroll view at the bottom or the top. In most cases I don't have any overlap, and they just sit side-by-side. I create them all in InterfaceBuilder, and assign them to IBOutlets.

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.

CALayer not showing

In trying to make a draggable sort of CALayer ( Question on Stack Overflow here ) I tried creating a window through code and adding a CALayer to it, but I don't get why it's not showing.
NSRect rect = NSZeroRect;
rect.size = NSMakeSize( SSRandomFloatBetween( 300.0, 200.0 ), SSRandomFloatBetween( 300.0, 200.0 ));
NSWindow *newWin = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSWindowBackingLocationDefault defer:YES];
[newWin setBackgroundColor: [NSColor clearColor]];
[newWin setOpaque:NO];
[newWin setIgnoresMouseEvents:NO];
[newWin setMovableByWindowBackground:YES];
[newWin makeKeyAndOrderFront:self];
[[newWin contentView] setWantsLayer:YES];
NSRect contentFrame = [[newWin contentView] frame];
CALayer *newWinLayer = [CALayer layer];
newWinLayer.frame = NSRectToCGRect(contentFrame);
layer.backgroundColor=CGColorCreateGenericGray(0.0f, 0.5f);
layer.borderColor=CGColorCreateGenericGray(0.756f, 0.5f);
layer.borderWidth=5.0;
// Calculate random origin point
rect.origin = SSRandomPointForSizeWithinRect( rect.size, [window frame] );
// Set the layer frame to our random rectangle.
layer.frame = NSRectToCGRect(rect);
layer.cornerRadius = 25.0f;
[newWinLayer addSublayer:layer];
"Window" is linked to a big window, with a semi-transparent (black filled) window that is resized to fill the screen.
I've made the window draggable, but why isn't the CALayer in the window showing?
NSRect rect = NSZeroRect;
rect.size = NSMakeSize( SSRandomFloatBetween( 300.0, 200.0 ),
SSRandomFloatBetween( 300.0, 200.0 ));
NSWindow *newWin = [[NSWindow alloc] initWithContentRect:rect
styleMask:NSBorderlessWindowMask
backing:NSWindowBackingLocationDefault defer:YES];
[newWin setBackgroundColor:[NSColor clearColor]];
[newWin setOpaque:NO];
[newWin setIgnoresMouseEvents:NO];
[newWin setMovableByWindowBackground:YES];
[newWin makeKeyAndOrderFront:self];
// you don't want to do this yet
// [[newWin contentView] setWantsLayer:YES];
NSRect contentFrame = [[newWin contentView] frame];
CALayer *newWinLayer = [CALayer layer];
newWinLayer.frame = NSRectToCGRect(contentFrame);
Line break, also a memory-managment issue:
// NOTE: remember that the following 2 *Create* methods return
// results that need to be released, unless you're using Garbage-Collection
// Also, I'm guessing that `layer` is created somewhere?
CALayer *layer = [CALayer layer];
CGColorRef backgroundCol = CGColorCreateGenericGray(0.0f, 0.5f);
CGColorRef borderCol = CGColorCreateGenericGray(0.756f, 0.5f);
layer.backgroundColor=backgroundCol;
layer.borderColor=borderCol;
CGColorRelease(backgroundCol); CGColorRelease(borderCol);
layer.borderWidth=5.0;
// Calculate random origin point
rect.origin = SSRandomPointForSizeWithinRect( rect.size, [window frame] );
// Set the layer frame to our random rectangle.
layer.frame = NSRectToCGRect(rect);
layer.cornerRadius = 25.0f;
[newWinLayer addSublayer:layer];
NSView *view = [newWin contentView];
// the order of the following 2 methods is critical:
[view setLayer:newWinLayer];
[view setWantsLayer:YES];
See NSView's documentation for setWantsLayer:
Discussion
The order that
setWantsLayer: and setLayer: are
called is important, it makes the
distinction between a layer-backed
view, and a layer-hosting view.
A layer-backed view is a view that is
backed by a Core Animation layer. Any
drawing done by the view is the cached
in the backing layer. You configured a
layer-backed view by simply invoking
setWantsLayer: with a value of YES.
The view class will automatically
create the a backing layer for you,
and you use the view class’s drawing
mechanisms. When using layer-backed
views you should never interact
directly with the layer.
A layer-hosting view is a view that
contains a Core Animation layer that
you intend to manipulate directly. You
create a layer-hosting view by
instantiating an instance of a Core
Animation layer class and setting that
layer using the view’s setLayer:
method. After doing so, you then
invoke setWantsLayer: with a value of
YES. When using a layer-hosting view
you should not rely on the view for
drawing, nor should you add subviews
to the layer-hosting view.
I believe the way you were doing it would be trying to create a layer-backed view, where you shouldn't be trying to interact with the view's underlying layer like you were. What you want is the layer-hosting variety.