How to add animated icon to OS X status bar? - cocoa

I want to put an icon in Mac OS status bar as part of my cocoa application. What I do right now is:
NSStatusBar *bar = [NSStatusBar systemStatusBar];
sbItem = [bar statusItemWithLength:NSVariableStatusItemLength];
[sbItem retain];
[sbItem setImage:[NSImage imageNamed:#"Taski_bar_icon.png"]];
[sbItem setHighlightMode:YES];
[sbItem setAction:#selector(stopStart)];
but if I want the icon to be animated (3-4 frames), how do I do it?

You'll need to repeatedly call -setImage: on your NSStatusItem, passing in a different image each time. The easiest way to do this would be with an NSTimer and an instance variable to store the current frame of the animation.
Something like this:
/*
assume these instance variables are defined:
NSInteger currentFrame;
NSTimer* animTimer;
*/
- (void)startAnimating
{
currentFrame = 0;
animTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:#selector(updateImage:) userInfo:nil repeats:YES];
}
- (void)stopAnimating
{
[animTimer invalidate];
}
- (void)updateImage:(NSTimer*)timer
{
//get the image for the current frame
NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:#"image%d",currentFrame]];
[statusBarItem setImage:image];
currentFrame++;
if (currentFrame % 4 == 0) {
currentFrame = 0;
}
}

I re-wrote Rob's solution so that I can reuse it:
I have number of frames 9 and all the images name has last digit as frame number so that I can reset the image each time to animate the icon.
//IntervalAnimator.h
#import <Foundation/Foundation.h>
#protocol IntervalAnimatorDelegate <NSObject>
- (void)onUpdate;
#end
#interface IntervalAnimator : NSObject
{
NSInteger numberOfFrames;
NSInteger currentFrame;
__unsafe_unretained id <IntervalAnimatorDelegate> delegate;
}
#property(assign) id <IntervalAnimatorDelegate> delegate;
#property (nonatomic) NSInteger numberOfFrames;
#property (nonatomic) NSInteger currentFrame;
- (void)startAnimating;
- (void)stopAnimating;
#end
#import "IntervalAnimator.h"
#interface IntervalAnimator()
{
NSTimer* animTimer;
}
#end
#implementation IntervalAnimator
#synthesize numberOfFrames;
#synthesize delegate;
#synthesize currentFrame;
- (void)startAnimating
{
currentFrame = -1;
animTimer = [NSTimer scheduledTimerWithTimeInterval:0.50 target:delegate selector:#selector(onUpdate) userInfo:nil repeats:YES];
}
- (void)stopAnimating
{
[animTimer invalidate];
}
#end
How to use:
Conform to protocol in your StatusMenu class
//create IntervalAnimator object
animator = [[IntervalAnimator alloc] init];
[animator setDelegate:self];
[animator setNumberOfFrames:9];
[animator startAnimating];
Implement protocol method
-(void)onUpdate {
[animator setCurrentFrame:([animator currentFrame] + 1)%[animator numberOfFrames]];
NSImage* image = [NSImage imageNamed:[NSString stringWithFormat:#"icon%ld", (long)[animator currentFrame]]];
[statusItem setImage:image];
}

Just had to do something similar recently in a simple project, so I'm posting my personal version written in Swift:
class StatusBarIconAnimationUtils: NSObject {
private var currentFrame = 0
private var animTimer : Timer
private var statusBarItem: NSStatusItem!
private var imageNamePattern: String!
private var imageCount : Int!
init(statusBarItem: NSStatusItem!, imageNamePattern: String, imageCount: Int) {
self.animTimer = Timer.init()
self.statusBarItem = statusBarItem
self.imageNamePattern = imageNamePattern
self.imageCount = imageCount
super.init()
}
func startAnimating() {
stopAnimating()
currentFrame = 0
animTimer = Timer.scheduledTimer(timeInterval: 5.0 / 30.0, target: self, selector: #selector(self.updateImage(_:)), userInfo: nil, repeats: true)
}
func stopAnimating() {
animTimer.invalidate()
setImage(frameCount: 0)
}
#objc private func updateImage(_ timer: Timer?) {
setImage(frameCount: currentFrame)
currentFrame += 1
if currentFrame % imageCount == 0 {
currentFrame = 0
}
}
private func setImage(frameCount: Int) {
let imagePath = "\(imageNamePattern!)\(frameCount)"
print("Switching image to: \(imagePath)")
let image = NSImage(named: NSImage.Name(imagePath))
image?.isTemplate = true // best for dark mode
DispatchQueue.main.async {
self.statusBarItem.button?.image = image
}
}
}
Usage:
private let statusItem =
NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Assuming we have a set of images with names: statusAnimatedIcon0, ..., statusAnimatedIcon6
private lazy var iconAnimation =
StatusBarIconAnimationUtils.init(statusBarItem: statusItem, imageNamePattern: "statusAnimatedIcon",
imageCount: 7)
private func startAnimation() {
iconAnimation.startAnimating()
}
private func stopAnimating() {
iconAnimation.stopAnimating()
}

Related

How make [UIView animateWithDuration: ..] work in application porting by apportable?

On portal application by apportable I need to make some animation (move/scale/change alpha) of UIView *object via call:
[UIView
animateWithDuration:1.f
delay:0.5f
options:UIViewAnimationOptionAllowUserInteraction
animations:^(void)
{
myview.center = moveTo;
myview.transform = transformTo;
myview.alpha = alphaTo;
}
completion:^(BOOL finished)
{
[self animationFinished];
}];
For now it's only make delay, then execute animation code & completion code immediately.
Thank you for answer.
But I need animation "today", so I make next class.
It's work not good enoght, but it's much better then nothing.
Maybe for one it will be helpful
AOTAnimate.h
//
// AOTAnimate.h
//
// Created by Andrei Bakulin on 18/11/2013.
//
#import <Foundation/Foundation.h>
#interface AOTAnimate : NSObject
{
UIView *view;
NSInteger animationTicksLeft;
CGFloat scaleX;
CGFloat scaleY;
CGPoint moveDelta;
CGSize scaleCurrent;
CGSize scaleDelta;
CGFloat alphaDelta;
void (^completeAction)();
}
#property (nonatomic, assign) CGFloat duration;
#property (nonatomic, assign) CGFloat delay;
#property (nonatomic, assign) CGFloat frequency;
#property (nonatomic, assign) UIViewAnimationOptions options;
#property (nonatomic, assign) CGPoint moveFrom;
#property (nonatomic, assign) CGAffineTransform transformFrom;
#property (nonatomic, assign) CGFloat alphaFrom;
#property (nonatomic, assign) CGPoint moveTo;
#property (nonatomic, assign) CGAffineTransform transformTo;
#property (nonatomic, assign) CGFloat alphaTo;
+ (AOTAnimate*)makeAnimationOnView:(UIView*)view_ duration:(CGFloat)duration_;
+ (AOTAnimate*)makeAnimationOnView:(UIView*)view_ duration:(CGFloat)duration_ delay:(CGFloat)delay_;
- (void)run;
- (void)runWithCompleteAction:(void (^)(void))complete_;
#end
AOTAnimate.m
//
// AOTAnimate.m
//
// Created by Andrei Bakulin on 18/11/2013.
//
#import "AOTAnimate.h"
#implementation AOTAnimate
#synthesize duration, delay, frequency, options;
#synthesize moveFrom, transformFrom, alphaFrom;
#synthesize moveTo, transformTo, alphaTo;
+ (AOTAnimate*)makeAnimationOnView:(UIView*)view_ duration:(CGFloat)duration_
{
return [self makeAnimationOnView:view_ duration:duration_ delay:0.f];
}
+ (AOTAnimate*)makeAnimationOnView:(UIView*)view_ duration:(CGFloat)duration_ delay:(CGFloat)delay_
{
return [[AOTAnimate alloc] initWithView:view_ duration:(CGFloat)duration_ delay:(CGFloat)delay_];
}
//----------------------------------
- (void)dealloc
{
[view release];
if( completeAction )
Block_release(completeAction);
[super dealloc];
}
- (id)initWithView:(UIView*)view_ duration:(CGFloat)duration_ delay:(CGFloat)delay_
{
self = [super init];
if (self)
{
view = [view_ retain];
duration = duration_;
delay = delay_;
frequency = 0.025f;
options = UIViewAnimationOptionAllowUserInteraction;
moveFrom = view.center;
transformFrom = view.transform;
alphaFrom = view.alpha;
moveTo = view.center;
transformTo = view.transform;
alphaTo = view.alpha;
}
return self;
}
//----------------------------------
#pragma mark - Run animation
- (void)run
{
[self runWithCompleteAction:nil];
}
- (void)runWithCompleteAction:(void (^)(void))complete_
{
view.center = moveFrom;
view.transform = transformFrom;
view.alpha = alphaFrom;
#ifndef ANDROID
[UIView
animateWithDuration:duration
delay:delay
options:options
animations:^(void)
{
view.center = moveTo;
view.transform = transformTo;
view.alpha = alphaTo;
}
completion:^(BOOL finished)
{
if( complete_ )
complete_();
}];
#else
if( duration <= 0.f )
{
[self doAnimationComplete];
return;
}
animationTicksLeft = ceil( duration / frequency );
if( animationTicksLeft == 0 )
{
[self doAnimationComplete];
return;
}
moveDelta = CGPointMake( (moveTo.x-moveFrom.x)/animationTicksLeft, (moveTo.y-moveFrom.y)/animationTicksLeft );
alphaDelta = (alphaTo-alphaFrom)/animationTicksLeft;
CGSize scaleFrom = CGSizeMake( [self scaleX:transformFrom], [self scaleY:transformFrom] );
CGSize scaleTo = CGSizeMake( [self scaleX:transformTo], [self scaleY:transformTo] );
scaleDelta = CGSizeMake((scaleTo.width - scaleFrom.width)/animationTicksLeft,
(scaleTo.height - scaleFrom.height)/animationTicksLeft );
scaleCurrent = scaleFrom;
if( complete_ )
{
completeAction = Block_copy(complete_);
}
[self performSelector:#selector(doAnimationTick) withObject:nil afterDelay:delay];
#endif
}
//----------------------------------
#pragma mark - Manual animation
#ifdef ANDROID
- (void)doAnimationTick
{
if( CGPointEqualToPoint( moveDelta, CGPointZero ) == NO )
{
view.center = CGPointMake( view.center.x + moveDelta.x, view.center.y + moveDelta.y );
}
if( CGSizeEqualToSize( scaleDelta, CGSizeZero) == NO )
{
view.transform = CGAffineTransformMakeScale( scaleCurrent.width, scaleCurrent.height );
scaleCurrent.width += scaleDelta.width;
scaleCurrent.height += scaleDelta.height;
}
if( alphaDelta != 0.f )
{
view.alpha = view.alpha + alphaDelta;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - -
animationTicksLeft--;
if( animationTicksLeft > 0 )
{
[self performSelector:#selector(doAnimationTick) withObject:nil afterDelay:frequency];
}
else
{
[self doAnimationComplete];
}
}
- (void)doAnimationComplete
{
view.center = moveTo;
view.transform = transformTo;
view.alpha = alphaTo;
if( completeAction )
completeAction();
}
//----------------------------------
#pragma mark - Helpers
- (CGFloat)scaleX:(CGAffineTransform)t
{
return sqrt(t.a * t.a + t.c * t.c);
}
- (CGFloat)scaleY:(CGAffineTransform)t
{
return sqrt(t.b * t.b + t.d * t.d);
}
#endif
#end
Use like this:
UIView *someview;
AOTAnimate *animate = [AOTAnimate makeAnimationOnView:someview duration:1.f delay:0.5f];
// allow to assign - animate.moveFrom / .tranfromFrom / alphaFrom properties,
// but by default they are copy from UIView* object
animate.moveTo = CGPointMake( 100, 200 ); // new point where need to move
animate.transformTo = CGAffineTransformScale( CGAffineTransformIdentity, 1.5f, 1.5f );
animate.alphaTo = 0.5f;
[animate runWithCompleteAction:^{
NSLog(#"Animation done..);
}];
If this method will run on iOS device - it'll use normal [UIView animateWithDuration:...] method
PS: This class do only "move" from one center point to another. Transform use only to scale object (not move). Alpha on my 2 test devices not supported, but maybe some where it does.
Animations do not work on the current version of Apportable's UIKit. We have fully functioning animations coming in the next version of UIKit, though. We will be releasing that once we are satisfied with the quality and coverage.

how to show UI objects code in xcode?

in one part of my project I need to create some UI objects programmatically, can I just customize my UI Objects like labels,... visually in storyboard then simply copy/paste generated code relevant to that object?
I searched in xcode menu but I couldn't find this but once I saw it in a tutorial in youtube.
Thanks in Advance
Yes you can customize the UI Classes or any other class, Like I have customize UILabel Class as UILabelExtended
UILabelExtended.h
#import <Foundation/Foundation.h>
/* **********************************************************************************************
This class inherit the class UILabel and extend the features of UILabel.
********************************************************************************************** */
#interface UILabelExtended : UILabel {
__unsafe_unretained id customDelegate;
id objectInfo;
SEL selector;
}
#property (nonatomic,assign) SEL selector;;
#property (nonatomic,assign) id customDelegate;
#property (nonatomic,retain) id objectInfo;
#end
#interface UILabel(UILabelCategory)
- (void)setHeightOfLabel;
- (void)setWidthOfLabel;
- (void)setHeightOfLabelWithMaxHeight:(float)maxHeight;
- (void)setWidthOfLabelWithMaxWidth:(float)maxWidth ;
#end
UILabelExtended.m
#import "UILabelExtended.h"
#implementation UILabelExtended
#synthesize selector,customDelegate, objectInfo;
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(self.selector)
if([self.customDelegate respondsToSelector:self.selector]) {
[self.customDelegate performSelector:self.selector withObject:self];
return;
}
}
- (void)dealloc {
self.customDelegate = nil;
self.selector = NULL;
self.objectInfo = nil;
}
#end
#implementation UILabel(UILabelCategory)
- (void)setHeightOfLabel {
UILabel* label = self;
//get the height of label content
CGFloat height = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(label.bounds.size.width, 99999) lineBreakMode:NSLineBreakByWordWrapping].height;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
frame.size.height = height;
}
else {
frame.size.height = 0;
}
label.frame = frame;
}
- (void)setWidthOfLabel {
UILabel* label = self;
//get the height of label content
CGFloat width = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(99999, label.bounds.size.height) lineBreakMode:NSLineBreakByWordWrapping].width;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
frame.size.width = width+5;
}
else {
frame.size.width = 0;
}
label.frame = frame;
}
- (void)setHeightOfLabelWithMaxHeight:(float)maxHeight {
UILabel* label = self;
//get the height of label content
CGFloat height = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(label.bounds.size.width, maxHeight) lineBreakMode:NSLineBreakByWordWrapping].height;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
if (height > maxHeight) {
frame.size.height = maxHeight;
}
else {
frame.size.height = height;
}
}
else {
frame.size.height = 0;
}
label.frame = frame;
}
- (void)setWidthOfLabelWithMaxWidth:(float)maxWidth {
UILabel* label = self;
//get the height of label content
CGFloat width = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(99999, label.bounds.size.height) lineBreakMode:NSLineBreakByWordWrapping].width;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
if (width > maxWidth) {
frame.size.width = maxWidth;
}
else {
frame.size.width = width;
}
}
else {
frame.size.width = 0;
}
label.frame = frame;
}
#end

NSImageView animations

I am new to Mac development, Do we have any methods like
imagev = [NSArray arrayWithObjects
I need some thing like what we do in iOS want to do in mac,
imageVie.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],[UIImage imageNamed:#"2.png"],
[UIImage imageNamed:#"3.png"],[UIImage imageNamed:#"4.png"],
[UIImage imageNamed:#"5.png"],[UIImage imageNamed:#"6.png"],
[UIImage imageNamed:#"7.png"] ,nil];
In iPhone, How can i animate
Regards
I found someone using a Core Animation approach to this issue which was close enough for me. I modified it slightly. You need to #import QuartzCore;
- (void)awakeFromNib
{
CALayer *layer = [CALayer layer];
NSMutableArray *spinnerImages = [NSMutableArray arrayWithCapacity:30u];
for (NSUInteger i = 0; i < 30; ++i)
{
NSString *imageName = [NSString stringWithFormat:#"spinner%#", #(i)];
[spinnerImages addObject:[NSImage imageNamed:imageName]];
}
self.spinnerImages = spinnerImages;
layer.frame = self.imageView.bounds;
[self.imageView setLayer:layer]; // This view is just a container for the layer. Its frame can be managed by a xib.
self.imageView.wantsLayer = YES;
self.spinnerLayer = layer;
}
Then you can animate it like this:
- (void)stopAnimating
{
if ([self.layer.animationKeys containsObject:kAnimationKey])
{
[self.layer removeAnimationForKey:kAnimationKey];
}
}
- (void)startAnimating
{
if ([self.layer.animationKeys containsObject:kAnimationKey])
{
return;
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:kAnimationKey];
[animation setCalculationMode:kCAAnimationDiscrete];
[animation setDuration:1.0f];
[animation setRepeatCount:HUGE_VALF];
[animation setValues:self.spinnerImages];
[self.spinnerLayer addAnimation:animation forKey:kAnimationKey];
}
Building on the great answer by Ben Flynn.
In Swift 3:
// This needs to happen around init.
// NSView (and who inherit from it) does not come with a layer.
// self.layer will be nil at init.
self.layer = CALayer()
self.wantsLayer = true
let layer = CALayer()
let keyPath = "contents" // (I did not find a constant for that key.)
let frameAnimation = CAKeyframeAnimation(keyPath: keyPath)
frameAnimation.calculationMode = kCAAnimationDiscrete
// Duration
// This is the duration of one cycle
let durationOfAnimation: CFTimeInterval = 2.5
frameAnimation.duration = durationOfAnimation
frameAnimation.repeatCount = HUGE// can also use Float.infinity
let imageSeq: [NSImage] = imageSequance // Your images to animate
frameAnimation.values = imageSeq
// Sadly, there are no layout constraints on CALayer.
// If your view will be resized while animating, you'll need to override
// 'func layout()' and calculate aspect ratio if needs be
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)
self.layer?.addSublayer(layer)
If the view is expected to be resized:
Remove these lines:
let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
layer.frame = layerRect
layer.bounds = layerRect
And call self.needsLayout = true after adding the sublayer. This will cause the layout() to be called.
//let layerRect = CGRect(origin: CGPoint.zero, size: self.frame.size)
//layer.frame = layerRect
//layer.bounds = layerRect
layer.add(frameAnimation, forKey: keyPath)
self.layer?.addSublayer(layer)
self.needsLayout = true
Lastly, override layout():
override func layout() {
super.layout()
var layerFrame = CGRect(origin: CGPoint.zero, size: self.frame.size)
self.myLayer.frame = layerFrame
self.myLayer.bounds = // Adjust ratio as needed.
}
Cocoa doesn't have anything like animatedImageWithImages:duration:. Images in Cocoa can vary in space (different resolutions) and color depth, but not time; a single image is always a static image, never animated.
(There might be an exception for animated GIFs, but GIFs can't display more than 255 or 256 colors per frame, and do not support partial transparency. Moreover, I haven't tried creating or displaying GIFs using the NSImage or CGImage machinery.)
What you'll need to do is create not an image, but a movie. Add images to the movie, varying each one's duration to achieve the playback speed you want. Then, display your movie in a movie view, optionally with the controller hidden.
Swift 4.2
I couldn’t get the previous answers get to work until I added a beginTime. Since Swift 3 some constants changed too. So I’ve converted the solution to Swift 4.2. Also, I thought it would be handy to create it as a CALayer extention:
extension CALayer {
static func image(sequence: [NSImage], duration: CFTimeInterval? = nil, frame: CGRect? = nil) -> CALayer {
let layer = CALayer()
if let f = frame { layer.frame = f }
layer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
let keyPath = "contents"
let keyFrameAnimation = CAKeyframeAnimation(keyPath: keyPath)
keyFrameAnimation.values = sequence
keyFrameAnimation.calculationMode = .discrete
keyFrameAnimation.fillMode = .forwards
keyFrameAnimation.duration = duration ?? CFTimeInterval(sequence.count / 18)
keyFrameAnimation.repeatCount = Float.infinity
keyFrameAnimation.autoreverses = false
keyFrameAnimation.isRemovedOnCompletion = false
keyFrameAnimation.beginTime = 0.0
layer.add(keyFrameAnimation, forKey: keyPath)
return layer
}
}
Use it like
let sequenceLayer = CALayer.image(sequence: imageSequence, duration: 0.55, frame: yourView.bounds)
#interface AnimatedNSImage : NSImage
#property (nonatomic, retain) IBInspectable NSImageView *delegate;
#property (nonatomic, readonly) NSArray *frames;
#property (nonatomic, readonly) CGFloat duration;
- (instancetype) initWithImages:(NSArray*)frames duration:(CGFloat)duration;
#end
And in the .m file...
#interface AnimatedNSImage () {
NSTimer *_scheduledTimer;
NSArray *_frames;
NSUInteger _frameIndex;
CGFloat _duration;
}
#end
#implementation AnimatedNSImage
#synthesize delegate;
- (NSArray *) frames
{
return _frames;
}
- (CGImageRef) CGImage
{
if (_frames && _frames.count >0) {
NSImage *_frame = _frames[_frameIndex];
return _frame.CGImage;
}
return nil;
}
- (NSArray<NSImageRep *> *) representations
{
NSImage *_frame = _frames[_frameIndex];
return _frame.representations;
}
- (CGFloat) duration
{
return _duration;
}
- (void) __showNextFrame:(id)sender
{
_frameIndex = (_frameIndex + 1) % _frames.count;
if (delegate) {
[delegate setNeedsDisplay:YES];
}
}
- (NSSize) size
{
if (_frames && _frames.count >0) {
NSImage *_frame = _frames[_frameIndex];
return _frame.size;
}
return CGSizeZero;
}
- (void) setup
{
_scheduledTimer = [NSTimer scheduledTimerWithTimeInterval:_duration target:self selector:#selector(__showNextFrame:) userInfo:nil repeats:YES];
}
- (void) dealloc
{
[_scheduledTimer invalidate];
_scheduledTimer = nil;
}
- (instancetype) initWithImages:(NSArray *)frames duration:(CGFloat)duration
{
self = [super init];
if (self) {
_frames = frames;
_duration = duration / 100.0f;
[self setup];
}
return self;
}
#end
Note that you will have to assign the delegate (to the NSImageView) in order to invoke a refresh..
An example....
IBOutlet NSImageView *_testGifView;
AnimatedNSImage *_animatedImage = [NSImage animatedImageWithAnimatedGIFURL:[NSURL URLWithString:#"https://media.giphy.com/media/B2zB8mHrHHUXS57Cuz/giphy.gif"]];
_testGifView.image = _animatedImage;
_animatedImage.delegate = _testGifView;
The scheduled timer of course can be adjusted as required as the input time is in centiseconds (as opposed to minutes).

Cocos2d populate layer based on menu in a different layer

I'm working on a species ID app and would like to populate a layer with sprites based on which animal you select on the main layer. I've made each animal a menu item, and can get my info layer to appear when pressing the button, but how can I set it up so the layer shows the right data depending on which animal you select? The info layer is not a full screen layer, but rather an overlaying layer that only fills about 75% of the screen, which is why I'm going with a layer rather than a scene. I know I can create a new layer for each animal (approx 50) and code it so each button calls its own layer, but I think populating based on which button is pressed would make for cleaner code. If flamingoButton is pressed, sprite is filled with flamingo.png and label is populated with flamingo information. How do I get my info layer to listen to the buttons on the main layer?
MainLayer.m code:
-(id) init
{
if( (self=[super init]))
{
CCMenuItemImage *flamingoButton = [CCMenuItemImage itemFromNormalImage:#"Explore-sign.png" selectedImage:#"Explore-sign.png" target:self selector:#selector(showSecondLayer:)];
flamingoButton.position = CGPointMake(0, 60);
flamingoButton.tag = 101;
CCMenu *menu = [CCMenu menuWithItems:flamingoButton, nil];
[self addChild:menu];
}
return self;
}
-(void) showSecondLayer: (id) sender
{
CCMenuItemImage *item = (CCMenuItemImage *) sender;
int itemID = item.tag;
secondLayer = [SecondLayer node];
secondLayer.position = CGPointMake(0, 700);
[self addChild:secondLayer];
CCMoveTo *moveLayer = [CCMoveTo actionWithDuration:1.0 position:CGPointMake(0, 0)];
[secondLayer runAction:moveLayer];
}
SecondLayer.m (the info layer)
-(id) init
{
if( (self=[super init]))
{
//Change this sprite image based on button from main layer. I don't have it coded in yet, but I understand the concept of putting a variable in the file string using %# or %d
CCSprite *infoCard = [CCSprite spriteWithFile:#"species1.png"];
infoCard.anchorPoint = CGPointMake(0.5, 0);
infoCard.position = CGPointMake(512, 0);
[self addChild:infoCard];
}
return self;
}
Ok, this might work:
//MainLayer:
-(id) init
{
if( (self=[super init]))
{
CCMenuItem *flamingoButton = [CCMenuItemImage itemFromNormalImage:#"Explore-sign.png"
selectedImage:#"Explore-sign.png"
target:self
selector:#selector(showSecondLayer:)];
flamingoButton.position = ccp(0, 60);
flamingoButton.tag = 1;
CCMenu *menu = [CCMenu menuWithItems:flamingoButton, nil];
[self addChild:menu];
}
return self;
}
-(void) showSecondLayer: (CCMenuItem*) sender
{
secondLayer = [SecondLayer layerWithTag:[sender tag]];
secondLayer.position = ccp(0, 700);
[self addChild:secondLayer];
CCMoveTo *moveLayer = [CCMoveTo actionWithDuration:1.0 position:ccp(0, 0)];
[secondLayer runAction:moveLayer];
}
//Second Layer.h
+(id)layerWithTag:(NSInteger)aTag;
-(id) initWithTag:(NSInteger)aTag;
//Second Layer.m:
+(id)layerWithTag:(NSInteger)aTag {
return [[[SecondLayer alloc] initWithTag:aTag] autorelease];
}
-(id) initWithTag:(NSInteger)aTag
{
if( (self=[super init]))
{
//Change this sprite image based on button from main layer. I don't have it coded in yet, but I understand the concept of putting a variable in the file string using %# or %d
CCSprite *infoCard = [CCSprite spriteWithFile:[NSString stringWithFormat:#"species%d.png", aTag]];
infoCard.anchorPoint = ccp(0.5, 0);
infoCard.position = ccp(512, 0);
[self addChild:infoCard];
}
return self;
}
EDIT:
Even though the previous solution works, it's not intuitive, and I feel I am breaking some OOP concepts. Most importantly, it is only useable given that your info about the animal can be retrieved using a single int! .. Using it this way is a BIT better, it's totally up to you to decide:
Ehm, so, I would suggest you set up an Entity Class first:
//AnimalResources.h
#import "Blahblahblah"
//Give it a good name, I was always bad at Science:
#interface AnimalResources {
//load all your properties:
NSString* info;
CCSprite* sprite;
...
}
//set the properties as needed:
//Make sure you properly manage this!! It is retained!
#property (nonatomic, retain) CCSprite* sprite;
...
//method prototype (signature.. am not sure)
//Now, we shall build on the fact that it will be easy for you to map an integer to the right resources:
+(id)animalResourcesWithTag:(NSInteger)aTag;
-(id)initAnimalResourcesWithTag:(NSInteger)aTag;
//AnimalResources.m:'
#synthesize sprite, ... ;
+(id)animalResourcesWithTag:(NSInteger)aTag {
[[[AnimalResources alloc] initAnimalResourcesWithTag:aTag] autorelease];
}
-(id)initAnimalResourcesWithTag:(NSInteger)aTag {
if ((self = [super init])) {
//use tag to retrieve the resources:
//might use the stringFormat + %d approach, or have a dictionary/array plist, that maps an int to a dictionary of resource keys.
//string way of doing things:
self.sprite = [CCSprite spriteWithFile:[NSString stringWithFormat:#"species%d.png", aTag]];
...
//Dictionary: dict/array is an NSDictionary/NSArray read from disk sometime. Don't read it here, since it
//will read the file from disk many times if you do --> BAD. I could explain a rough way to do that if you
//need help
animalDict = [dict objectForKey:[NSString stringWithFormat:#"species%d.png", aTag]];
//OR...
animalDict = [array objectAtIndex:aTag];
//better to have #"spriteNameKey" defined in a macro somewhere: #define kAnimalResourceKeySprite #"SpriteKey"
self.sprite = [CCSprite spriteWithFile:[animalDict objectForKey:#"SpriteNameKey"]];
....
}
return self;
}
Phew! Then .. you guessed it!
-(void) showSecondLayer: (CCMenuItem*) sender
{
secondLayer = [SecondLayer layerWithAnimalResources:[AnimalResources animalResourcesWithTag:[sender tag]]];
secondLayer.position = ccp(0, 700);
[self addChild:secondLayer];
CCMoveTo *moveLayer = [CCMoveTo actionWithDuration:1.0 position:ccp(0, 0)];
[secondLayer runAction:moveLayer];
}
//Second Layer.h
+(id)layerWithAnimalResources:(AnimalResources*)resource;
-(id)initWithAnimalResources:(AnimalResources*)resource;
//Second Layer.m:
+(id)layerWithAnimalResources:(AnimalResources*)resource {
return [[[SecondLayer alloc] initWithAnimalResources:aTag] autorelease];
}
-(id) initWithAnimalResources:(AnimalResources*)resource
{
if( (self=[super init]))
{
//Change this sprite image based on button from main layer. I don't have it coded in yet, but I understand the concept of putting a variable in the file string using %# or %d
CCSprite *infoCard = [resource sprite];
infoCard.anchorPoint = ccp(0.5, 0);
infoCard.position = ccp(512, 0);
[self addChild:infoCard];
}
return self;
}
Give each menu item a unique id. In the method which you invoke on the tap of the button, you can reference the id of the sender. Use this id to populate the new layer with the unique information.
- (void) buttonPressed: (id) sender
{
MenuItem* item = (MenuItem*) sender;
int itemID = item.tag;
// Get unique data based on itemID and add new layer
}
EDIT: Per your code updates
-(void) showSecondLayer: (id) sender
{
CCMenuItemImage *item = (CCMenuItemImage *) sender;
int itemID = item.tag;
secondLayer = [SecondLayer node];
[secondLayer setItem: itemID]; // ADDED
secondLayer.position = CGPointMake(0, 700);
[self addChild:secondLayer];
CCMoveTo *moveLayer = [CCMoveTo actionWithDuration:1.0 position:CGPointMake(0, 0)];
[secondLayer runAction:moveLayer];
}
SecondLayer.m (the info layer)
-(id) init
{
if( (self=[super init]))
{
// Removed
}
return self;
}
-(void) setItem: (int) item
{
CCSprite *infoCard = [CCSprite spriteWithFile:[NSString stringWithFormat:#"species%d", item]];
infoCard.anchorPoint = CGPointMake(0.5, 0);
infoCard.position = CGPointMake(512, 0);
[self addChild:infoCard];
}

subclassing TTTableMessageItemCell, anything wrong?

I subclassing TTTableMessageItemCell, got EXC_BAD_ACCESS runtime error. Anythign wrong?
CustomTTTableSubtitleItemCell.h
#import "Three20/Three20.h"
#interface CustomTTTableSubtitleItemCell : TTTableMessageItemCell {
TTButton *_rightButton;
}
#end
CustomTTTableSubtitleItemCell.m
#import "CustomTTTableSubtitleItemCell.h"
#import "CustomTTTableSubtitleItem.h"
#import "XYDefaultStyleSheet.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
static CGFloat kHPadding = 10;
static CGFloat kVPadding = 15;
#interface ButtonStyleSheet : TTDefaultStyleSheet
#end
#implementation ButtonStyleSheet
- (TTStyle*)blueToolbarButton:(UIControlState)state {
TTShape* shape = [TTRoundedRectangleShape shapeWithRadius:4.5];
UIColor* tintColor = RGBCOLOR(30, 110, 255);
return [TTSTYLESHEET toolbarButtonForState:state shape:shape tintColor:tintColor font:nil];
}
#end
#implementation CustomTTTableSubtitleItemCell
+ (CGFloat)tableView:(UITableView*)tableView rowHeightForItem:(id)item {
CustomTTTableSubtitleItem* captionedItem = item;
CGFloat maxWidth = tableView.width - kHPadding*2;
CGSize titleSize = [captionedItem.title sizeWithFont:TTSTYLEVAR(myTitleFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
lineBreakMode:UILineBreakModeWordWrap];
CGSize textSize = [captionedItem.text sizeWithFont:TTSTYLEVAR(myHeadingFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
lineBreakMode:UILineBreakModeWordWrap];
CGSize subtextSize = [captionedItem.caption sizeWithFont:TTSTYLEVAR(mySubtextFont)
constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX) lineBreakMode:UILineBreakModeWordWrap];
return kVPadding*2 + titleSize.height + textSize.height + subtextSize.height + kVPadding;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)identifier {
if (self = [super initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:identifier]) {
_item = nil;
[TTStyleSheet setGlobalStyleSheet:[[[ButtonStyleSheet alloc] init] autorelease]];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.detailTextLabel sizeToFit];
self.detailTextLabel.top = kVPadding;
self.textLabel.height = self.detailTextLabel.height;
//_rightButton.frame = CGRectMake(20, self.detailTextLabel.bottom + kVPadding, kImageWidth, kImageHeight);
//_rightButton.alpha = !self.showingDeleteConfirmation;
[_rightButton sizeToFit];
_rightButton.left = self.contentView.width - (_timestampLabel.width + kHPadding);
_rightButton.top = self.height/2;
}
- (id)object {
return _item;
}
- (void)setObject:(id)object {
if (_item != object) {
[super setObject:object];
CustomTTTableSubtitleItem* item = object;
//self.textLabel.textColor = TTSTYLEVAR(myHeadingColor);
// self.textLabel.font = TTSTYLEVAR(myHeadingFont);
// self.textLabel.textAlignment = UITextAlignmentRight;
// self.textLabel.contentMode = UIViewContentModeCenter;
// self.textLabel.lineBreakMode = UILineBreakModeWordWrap;
// self.textLabel.numberOfLines = 0;
//
// self.detailTextLabel.textColor = TTSTYLEVAR(mySubtextColor);
// self.detailTextLabel.font = TTSTYLEVAR(mySubtextFont);
// self.detailTextLabel.textAlignment = UITextAlignmentLeft;
// self.detailTextLabel.contentMode = UIViewContentModeTop;
// self.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
_rightButton = [TTButton
buttonWithStyle:#"blueToolbarButton:" title:item.rightButtonTitle];
}
}
- (void)dealloc {
TT_RELEASE_SAFELY(_rightButton);
[super dealloc];
}
#end
You're creating the TTButton using autorelease, while you're releasing it in your dealloc function. So both the release pool and the dealloc are trying to release your _rightButton TTButton.
in your header file, try adding:
#property (nonatomic, readonly, retain) TTButton* rightButton;
And then create the TTButton using his get function in your source file:
///////////////////////////////////////////////////////////////////////////////////////////////////
- (TTButton*)rightButton {
if (!_rightButton) {
_rightButton = [[TTButton
buttonWithStyle:#"blueToolbarButton:" title:item.rightButtonTitle] retain];
[self.contentView addSubview:rightButton];
}
return rightButton;
}
When using the rightButton, make sure to use self.rightBotton and not _rightButton, such as in the layout function (Because you need to object to be created).
self.rightButton.frame = CGRectMake(20, self.detailTextLabel.bottom + kVPadding, kImageWidth, kImageHeight);
I suggest opening the Three20UI/TTTableMessageItemCell.h & source file and trying to copy the behavior of one of the elements. That's what I did.

Resources