Multiple NSImages in NSView? - objective-c

Is it possible to draw multiple NSImages in a single NSView?
So far drawing in an NSView subclass is done like so:
- (void)drawRect:(NSRect)dirtyRect
{
[image drawInRect:NSMakeRect(0.0f, 0.0f, 100.0f, 100.0f) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0f];
}
//to change the current image
- (void)newImage:(NSImage *)image_
{
[image release];
image = [image_ retain];
[self setNeedsDisplay:YES];
}
But this is for drawing just one image. Can anyone please help out?
Thanks.

You have to keep references to the images you need to draw.
#interface MyView : NSView
{
NSMutableArray* images;
}
Then, add the images with a suitable API, for example:
#implementation MyView
- (void) addImage: (NSImage *) anImage
{
[images addObject: anImage];
[self setNeedsDisplay: YES];
}
In drawRect: you can iterate through the images and draw all/some of them.

Related

Why my NSBezierPath is not showing?

So i recieve a notification with the data i need to draw my path. This code is from my main app controller:
-(void) handleAdd:(NSNotification *)aNotification{
NSLog(#"x1:%f y1:%f x2:%f y2:%f ",[panelController x1],[panelController y1],[panelController x2],[panelController y2]);
myPath = [[NSBezierPath alloc]init];
[myPath setLineWidth:[panelController grosor]];
[myPath moveToPoint:NSMakePoint([panelController x1],[panelController y1])];
[myPath lineToPoint:NSMakePoint([panelController x2],[panelController y2])];
[[panelController trazado] setStroke];
[myPath stroke];
}
The NSLog is showing me the right data. I have a view created and in that white rectangle is where i want to draw.
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds= [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
}
If i draw in that class the stroke is showing, but how could i draw on that view from the controller? Or should i just receive the notification in the view and draw from that class?
You can't (easily) draw from the controller into the view.
Think of your views drawRect method as a complete set of instructions of what to draw at every refresh.
So right now all you are saying is "fill me with white"
You need to handle all the relevant drawing within the views drawRect: method.
Adjust your controller method to this.
-(void) handleAdd:(NSNotification *)aNotification{
[myViewInstance setNeedsDisplay:YES]; //also myViewInstance.needsDisplay = YES
}
And assuming the view has a reference to the panel controller…
#interface MyView: UIView
//weak as you don't want a reference cycle
#property (weak) MyPanelController *panelController;
#end
#implementation MyView
- (void)drawRect:(NSRect)dirtyRect
{
NSRect bounds= [self bounds];
[[NSColor whiteColor] set];
[NSBezierPath fillRect:bounds];
NSBezierPath *myPath = [[NSBezierPath alloc]init];
[myPath setLineWidth:[self.panelController grosor]];
[myPath moveToPoint:NSMakePoint([self.panelController x1],[self.panelController y1])];
[myPath lineToPoint:NSMakePoint([self.panelController x2],[self.panelController y2])];
[[self.panelController trazado] setStroke];
[myPath stroke];
}
#end
If you want explore your first technique you want to look at at NSView and the lockFocus/unlockFocus methods. The documentation explains why you probably don't want to do this.

Cache NSView Context

I'm trying to store NSView Current Context and draw it later to NSView again. I was wondering what is the fastest and most efficient way to do this?
You can draw your content to an NSImage and just redraw the image later, but you need to invalidate the cache when needed (this depends on what your view does).
Example:
#interface SOCacheView : NSView
#end
#implementation SOCacheView
{
NSImage *_cache;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
if (!_cache) [self prepareImage];
[_cache drawAtPoint:NSZeroPoint fromRect:self.bounds operation:NSCompositeSourceOver fraction:1.0];
}
- (void)prepareImage
{
_cache = [[NSImage alloc] initWithSize:self.bounds.size];
[_cache lockFocus];
// do the drawing here...
[[NSColor blueColor] setFill];
NSRectFill(NSMakeRect(0, 0, NSWidth(self.bounds)/2, NSHeight(self.bounds)));
[[NSColor redColor] setFill];
NSRectFill(NSMakeRect(NSWidth(self.bounds)/2, 0, NSWidth(self.bounds)/2, NSHeight(self.bounds)));
[_cache unlockFocus];
}
To invalidate the cached drawing, just set _cache to nil.
Here's a sample project for you to play with 😉

Does it make sense to add ATMHud to tabBarController.view

When i try to add ATMHud to uitableviewcontroller subview it does work but it doesn't disable the scrolling and if the tableview is not on top i can't see the hud view. What i did was added to teh tabBarController.view that works but i want find out if this is a good idea or later on i might have issues with it.
Another question is tabBarController.view frame is that the whole screen or just the bottom part. How come atmhud shows in the middle of the screen?
Thanks in advance!
Yan
============
Found a blog post that shows how to reset self.view and add tableview separately in uitableviewcontroller
UITableViewController and fixed sub views
- (void)viewDidLoad {
[super viewDidLoad];
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[[UIView alloc] initWithFrame:
[UIScreen mainScreen].applicationFrame] autorelease];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(44.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
UIView *fixedBar = [[UIView alloc] initWithFrame:
CGRectMake(0.0, 0.0, self.view.bounds.size.width, 44.0)];
fixedBar.backgroundColor = [UIColor colorWithRed:
0.0 green:1.0 blue:0.0 alpha:0.7];
[self.view addSubview:fixedBar];
[fixedBar release];
}
After this when add hud to self.view you will be able to disable the tableview on the bottom.
Let me know if this a good way to setup the tableview
The problem with using the tab bar is that the hud is now modal, and the user cannot change the tab.
It sounds like the tableview is not your primary viewm, as it can get "covered up". If its not the primary view, then add the ATMHud to self.view. If the tableView is the same as self.view, then add a new transparent view to it, then add the HUD to that view.
The tabBarController.view is the view that hosts the tabbed views - if you want to see its size (or frame) log it using NSStringFromCGRect(self.tabBarController.frame);
EDIT: I just did a test, the ATMHud DOES block the UI. All I can think of is that you have not inserted it where you need to (at the top of current view's subviews.) I have a demo project where I do this:
hud = [[ATMHud alloc] initWithDelegate:self];
[self.view addSubview:hud.view];
[hud setCaption:#"Howdie"];
[hud setActivity:YES];
[hud show];
[hud hideAfter:5];
A button under the hud is not active - in fact nothing in the view is active (probably the Nav Bar would be live though)
If you want an ARCified and field tested version, you can grab it here
EDIT2: The solution to your problem is below. Note that ATMHud blocks clicks from getting to the table, and the code below stops the scrolling:
- (void)hudWillAppear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = NO;
}
- (void)hudDidDisappear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = YES;
}
Dump the views:
#import <QuartzCore/QuartzCore.h>
#import "UIView+Utilities.h"
#interface UIView (Utilities_Private)
+ (void)appendView:(UIView *)v toStr:(NSMutableString *)str;
#end
#implementation UIView (Utilities_Private)
+ (void)appendView:(UIView *)a toStr:(NSMutableString *)str
{
[str appendFormat:#" %#: frame=%# bounds=%# layerFrame=%# tag=%d userInteraction=%d alpha=%f hidden=%d\n",
NSStringFromClass([a class]),
NSStringFromCGRect(a.frame),
NSStringFromCGRect(a.bounds),
NSStringFromCGRect(a.layer.frame),
a.tag,
a.userInteractionEnabled,
a.alpha,
a.isHidden
];
}
#end
#implementation UIView (Utilities)
+ (void)dumpSuperviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
while(v) {
[self appendView:v toStr:str];
v = v.superview;
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
+ (void)dumpSubviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
if(v) [self appendView:v toStr:str];
for(UIView *a in v.subviews) {
[self appendView:a toStr:str];
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
#end

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:

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