NSView* subClass change color animated - objective-c

I am on OSX, XCode 8.3.3, Objective-C.
I have subclassed NSView* to set a custom background color:
.h
#interface SHViewWhiteBackground : NSView
#property NSColor* backgroundColor;
#end
.m
#implementation SHViewWhiteBackground
- (void)drawRect:(NSRect)dirtyRect {
NSColor* fillColor = [NSColor whiteColor];
if (self.backgroundColor)
fillColor = self.backgroundColor;
[fillColor setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
#end
Now i can call
[view setBackgroundColor:[NSColor someColor]];
[view setNeedsDisplay:YES];
to change the color. I was wondering if there is a way to animate that change?

I ended up using layer backed views and the pop framework for animation.

Related

Change NSGradient colors in OSX 10.7 and 10.8

I have a NSView subclass named OneView with the following code:
#import "OneView.h"
#interface OneView ()
#property (strong, nonatomic) NSGradient *gradient;
#end
#implementation OneView
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSColor *top = [NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0];
NSColor *btm = [NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:1.0];
self.gradient = [[NSGradient alloc] initWithStartingColor:top endingColor:btm];
[self.gradient drawInRect:self.bounds angle:270];
}
# pragma mark - Public
- (void)changeGradient {
self.gradient = nil;
NSColor *top = [NSColor colorWithCalibratedRed:0.0 green:1.0 blue:0.0 alpha:1.0];
NSColor *btm = [NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:1.0];
self.gradient = [[NSGradient alloc] initWithStartingColor:top endingColor:btm];
[self.gradient drawInRect:self.bounds angle:270];
[self setNeedsDisplay:YES];
}
#end
In my AppDelegate (or could be any other class), I am trying to change the colors of the gradient by calling the changeGradient method of the OneView class:
#import "AppDelegate.h"
#import "OneView.h"
#interface AppDelegate ()
#property (weak, nonatomic) IBOutlet OneView *oneView;
#end
#implementation AppDelegate
- (IBAction)changeGradient:(id)sender {
[self.oneView changeGradient];
}
#end
When the view is first loaded the gradient is initialized as expected but I am unable to change the gradient from the IBAction method. I have achieved this using layer backed views but I am trying to find a way that doesn't rely on layers for backwards compatibility.
Any thoughts on why the IBAction is not changing the gradient?
The problem is self.gradient = [[NSGradient alloc] initWithStartingColor:top endingColor:btm]; inside drawRect: function.
Change it to if (!self.gradient) { self.gradient = [[NSGradient alloc] initWithStartingColor:top endingColor:btm]; } will fix the issue.
By the way, you shouldn't create gradient inside your drawRect: method. It will hurt the performance. In that case, should put the initialization in awakeFromNib method.
Your drawRect: method is always going to draw the same thing inside it.
You shouldn't try to draw outside of drawRect:
You really only need to set the gradient in viewWillDraw: if the gradient is nil.
You should probably redesign to have the gradient's colors be properties of the view as well.
Then use KVO to observe and respond to any color change by calling setNeedsDisplay:YES
If you need more I can post after back at computer.

Objective C, rounded corner custom window?

I have a subclass of NSWindow to customize one of my windows for my app.
I have everything set, but I am not sure how to make the corners round.
Currently, my window is a transparent rectangular window with some buttons, labels, and a textfield in it.
The class includes:
#import "TransparentRoundRectWindow.h"
#implementation TransparentRoundRectWindow
-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self) {
[self setAlphaValue:0.75];
[self setOpaque:YES];
[self setHasShadow:YES];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
-(BOOL)canBecomeKeyWindow
{
return YES;
}
I just need to make the corners round now. I tried searching for similar situations and saw some of them explaining to override the drawRect method but I couldn't get them to work.
How could I do this?
(I'm using Mac OS X Lion)
Thanks in advance.
You need to set to Your window Opaque to NO. And subclass Your window's view.
Window subclass:
-(id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
{
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
if (self) {
[self setOpaque:NO];
[self setHasShadow:YES];
[self setBackgroundColor:[NSColor clearColor]];
}
return self;
}
-(BOOL)canBecomeKeyWindow
{
return YES;
}
Window's view subclass:
- (void)drawRect:(NSRect)rect
{
NSBezierPath * path;
path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:8 yRadius:8];
[[NSColor colorWithCalibratedRed:0 green:0 blue:0 alpha:0.75] set];
[path fill];
}
Result:
More explanation how to do this:
Create new NSView class and paste "Window's view subclass" code which I wrote in it. Then go to Your window's view.
Here is window's view click on it:
Go to the Identity inspector and set class to your created class:

NSScrollview with NSGradient

I have a nsscroll view in my application and i made a subclass of nsscrollview to add a nsgradient but it doesn't work this is my code in my implementation file:
#import "scrollview.h"
#implementation scrollview
#synthesize startingColor;
#synthesize endingColor;
#synthesize angle;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
[self setStartingColor:[NSColor colorWithCalibratedRed:0.941 green:0.941 blue:0.941 alpha:1]];
[self setEndingColor:[NSColor colorWithCalibratedRed:0.6588 green:0.6588 blue:0.6588 alpha:1]];
[self setAngle:90];
}
return self;
}
- (void)drawRect:(NSRect)rect {
NSBezierPath* roundRectPath = [NSBezierPath bezierPathWithRoundedRect: [self bounds] xRadius:10 yRadius:10];
[roundRectPath addClip];
if (endingColor == nil || [startingColor isEqual:endingColor]) {
// Fill view with a standard background color
[startingColor set];
NSRectFill(rect);
}
else {
// Fill view with a top-down gradient
// from startingColor to endingColor
NSGradient* aGradient = [[NSGradient alloc]
initWithStartingColor:startingColor
endingColor:endingColor];
[aGradient drawInRect:[self bounds] angle:angle];
}
}
The first step is to create a custom NSView subclass that draws a gradient:
GradientBackgroundView.h:
#interface GradientBackgroundView : NSView
{}
#end
GradientBackgroundView.m:
#import "GradientBackgroundView.h"
#implementation GradientBackgroundView
- (void) drawRect:(NSRect)dirtyRect
{
NSGradient *gradient = [[[NSGradient alloc] initWithStartingColor:[NSColor redColor] endingColor:[NSColor greenColor]] autorelease];
[gradient drawInRect:[self bounds] angle:90];
}
#end
The next step is to make the scroll view's document view an instance of this class (instead of plain NSView).
In IB, double-click your scroll view, and in the Identity pane set the Class to GradientBackgroundView.
From this point on, things are handled pretty much in the standard way. You can add subviews to the document view, resize it, etc. Here's a screenshot:

Couldn't UIToolBar be transparent?

I try the following code, but it doesn't work.
[helloToolbar setBackgroundColor:[UIColor clearColor]];
To make a completely transparent toolbar, use the method described here. In a nutshell, create a new TransparentToolbar class that inherits from UIToolbar, and use that in place of UIToolbar.
TransarentToolbar.h
#interface TransparentToolbar : UIToolbar
#end
TransarentToolbar.m
#implementation TransparentToolbar
// Override draw rect to avoid
// background coloring
- (void)drawRect:(CGRect)rect {
// do nothing in here
}
// Set properties to make background
// translucent.
- (void) applyTranslucentBackground
{
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.translucent = YES;
}
// Override init.
- (id) init
{
self = [super init];
[self applyTranslucentBackground];
return self;
}
// Override initWithFrame.
- (id) initWithFrame:(CGRect) frame
{
self = [super initWithFrame:frame];
[self applyTranslucentBackground];
return self;
}
#end
(code from the blog post linked above)
In iOS 5, simply call setBackgroundImage and pass a transparent image.
Here's how I do it (I dynamically generate transparent image):
CGRect rect = CGRectMake(0, 0, 1, 1);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
CGContextFillRect(context, rect);
UIImage *transparentImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[toolbar setBackgroundImage:transparentImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
The best you can do is using
[helloToolbar setBarStyle:UIBarStyleBlack];
[helloToolbar setTranslucent:YES];
This will get you a black but translucent toolbar.
Transparent (iOS 5.0):
[toolbar setBackgroundImage:[[UIImage alloc] init] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
Translucent:
[toolbar setBarStyle:UIBarStyleBlack];
[toolbar setTranslucent:YES];
A cumulative solution for all devices, from oldest iOS 3.0 (iPhone 1) to newest iOS 6.1 (iPad mini).
#implementation UIToolbar (Extension)
- (void)drawRect:(CGRect)rect
{
if (CGColorGetAlpha(self.backgroundColor.CGColor) > 0.f)
{
[super drawRect:rect];
}
}
- (void)setTransparent
{
//iOS3+
self.backgroundColor = [UIColor clearColor];
//iOS5+
if ([self respondsToSelector:#selector(setBackgroundImage:forToolbarPosition:barMetrics:)])
{
[self setBackgroundImage:[[UIImage new] autorelease] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
}
//iOS6+
if ([self respondsToSelector:#selector(setShadowImage:forToolbarPosition:)])
{
[self setShadowImage:[[UIImage new] autorelease] forToolbarPosition:UIToolbarPositionAny];
}
}
#end
When you want a transparent toolbar, call setTransparent on it.
When you want a non-transparent toolbar, set a backgroundColor of your choice or add an imageView by yourself.
Another solution would be to define a category for UIToolbar:
#implementation UIToolbar(Transparent)
-(void)drawRect:(CGRect)rect {
// do nothing in here
}
#end
In the IB set the toolbar as Black Translucent and non opaque.
We've just noticed that overriding drawRect doesn't work anymore with iOS 4.3. It's not called anymore (edit: seems to be only in Simulator). Instead drawLayer:inContext: is called.
A great solution was posted here
Now you can set each UIToolbar object transparent, by setting its tintColor to [UIColor clearColor] :)
With iOS 5 the following works:
UIToolbar *bar = [[UIToolbar alloc] initWithFrame:CGRectZero];
if (bar.subviews.count > 0)
[[[bar subviews] objectAtIndex:0] removeFromSuperview];
This is because the background is now a subview. This code is safe even with new iterations of iOS, but it may stop working. This is not private API usage, your app is safe to submit to the store.
Make sure you remove the backgroundView before adding any UIBarButtonItems to the bar. Or my code will not work.
I just tested the following with iOS 4.3 on simulator and phone, seems to work fine. Subclass UIToolbar, provide one method:
- (void)drawRect:(CGRect)rect
{
[[UIColor colorWithWhite:0 alpha:0.6f] set]; // or clearColor etc
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
}
toolbar.barStyle = UIBarStyleBlackTranslucent;
This works in iOS5.1 with pretty minimal effort. I am matching up the size, as only the background will have the same frame size as the toolbar itself. You could use other criteria, of course.
Enjoy.
Create a subclass of UIToolbar as follows:
.h:
#import <UIKit/UIKit.h>
#interface UIClearToolbar : UIToolbar
#end
.m:
#import "UIClearToolbar.h"
#implementation UIClearToolbar
- (void)layoutSubviews {
// super has already laid out the subviews before this call is made.
[self.subviews enumerateObjectsUsingBlock:^(UIView* obj, NSUInteger idx, BOOL *stop) {
if (CGSizeEqualToSize(self.frame.size, obj.frame.size) ||
self.frame.size.width <= obj.frame.size.width) { // on device, the background is BIGGER than the toolbar.) {
[obj removeFromSuperview];
*stop = YES;
}
}];
}
#end
Thanks #morais for your solution - here's the code translated to MonoTouch:
public class TransparentToolbar : UIToolbar
{
public TransparentToolbar()
{
init();
}
public TransparentToolbar(RectangleF frame) : base(frame)
{
init();
}
void init()
{
BackgroundColor=UIColor.Clear;
Opaque=false;
Translucent=true;
}
public override void Draw(RectangleF rect)
{
}
}

Help with stock ticker style scrolling using Core Animation

I'm looking for some guidance on the best way to implement stock ticker style right-to-left scrolling of CALayers in Core Animation on OSX. I'm pretty new to Cocoa and don't know the best way to implement this.
I have a continuous stream of news items and stock details that I turn into CALayers (made up of 1 image and a CATextLayer) and I want to animate the CALayers from right to left of my custom view.
The news and stock information is constantly updating so I would like to add 1 item at a time to the view, scroll it right to left until the right-most point of the CALayer is showing, then add another CALayer to the view and start scrolling that as well. I would like to do this dynamic updating instead of taking a big snapshot of all my data, turning it into a big horizontal CALayer and scrolling that.
I'm looking for guidance on how to achieve this sort of effect -
do I manually animate each new CALayer along a path in the view? Or should I be using CAScrollLayer to achieve this effect?
Many thanks
Glen.
I'd do this with Quartz Composer and put a QCView in your app. It's a lot less work than you imagine.
You perhaps don't need to customize anything to do this. Here self is an object of class extending UIScrollView and with a UILabel to display the text.
////
//Header
#import <UIKit/UIKit.h>
#interface TickerScrollView : UIScrollView
- (void)displayText:(NSString *)string;
- (void)clearTicker;
#property (nonatomic, retain, readwrite) UILabel *textLabel;
#end
//////
//Implementation
#implementation TickerScrollView
#synthesize textLabel;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
[self setFrame: frame];
[self setBounces: NO];
[self setShowsVerticalScrollIndicator:NO];
[self setShowsHorizontalScrollIndicator:NO];
[self setBackgroundColor:[UIColor greenColor]];
[self initialiseTextLabel];
}
return self;
}
- (void)initialiseTextLabel {
textLabel = [[UILabel alloc] initWithFrame:self.bounds];
[textLabel setTextAlignment:UITextAlignmentLeft];
[textLabel setNumberOfLines:1];
[textLabel sizeToFit];
[self addSubview:textLabel];
[self setScrollEnabled:YES];
}
- (void)displayText:(NSString *)string {
[self clearTicker];
[textLabel setText:string];
[textLabel sizeToFit];
[self beginAnimation];
}
- (void)clearTicker {
[textLabel setText:#""];
[textLabel sizeToFit];
CGPoint origin = CGPointMake(0, 0);
[self setContentOffset:origin];
}
- (void)beginAnimation {
CGFloat text_width = textLabel.frame.size.width;
CGFloat display_width = self.frame.size.width;
if ( text_width > display_width ) {
CGPoint origin = CGPointMake(0, 0);
[self setContentOffset:origin];
CGPoint terminal_origin = CGPointMake(textLabel.frame.size.width - self.frame.size.width, textLabel.frame.origin.y);
float duration = (text_width - display_width)/40;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelay:1.0];
[UIView setAnimationDuration:duration];
[self setContentOffset:terminal_origin];
[UIView commitAnimations];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (void)dealloc {
[textLabel release];
[super dealloc];
}
#end