I have an application which is based on animations. After hours of trying and searching the most effective animation processor (which would have low delay and the best memory management) I found out the amazing PNGAnimator (http://www.modejong.com/iPhone/). Funny thing is that I am actually using JPEGs (due to size), but that is not the problem.
The problem is that the class itself is a subclass of UIViewController and when I add it to my rootViewController the new view controller overlaps it - so all the interface elements (buttons...) are hidden under it.
That is why I would like to somehow convert the code to be a subclass of UIView. (So I can add the buttons over it) I tried converting the code myself, however the application would crash on an animation call. Could you please tell me what should I change in the code to so it will act as an UIView?
For example, I tried to change it in .h file to UIView and then in .m file, changing the references self.view to just self. Then in my root view controller adding it as an subview, but that does not show up and crashes either.
Here is the .h file:
//
// ImageAnimatorViewController.h
// PNGAnimatorDemo
//
// Created by Moses DeJong on 2/5/09.
//
#import <UIKit/UIKit.h>
#define ImageAnimator15FPS (1.0/15)
#define ImageAnimator12FPS (1.0/12)
#define ImageAnimator25FPS (1.0/24)
#define ImageAnimator18FPS (1.0/18)
#define ImageAnimatorDidStartNotification #"ImageAnimatorDidStartNotification"
#define ImageAnimatorDidStopNotification #"ImageAnimatorDidStopNotification"
#class AVAudioPlayer;
#interface ImageAnimatorViewController : UIViewController {
#public
NSArray *animationURLs;
NSTimeInterval animationFrameDuration;
NSInteger animationNumFrames;
NSInteger animationRepeatCount;
UIImageOrientation animationOrientation;
NSURL *animationAudioURL;
AVAudioPlayer *avAudioPlayer;
#private
UIImageView *imageView;
NSArray *animationData;
NSTimer *animationTimer;
NSInteger animationStep;
NSTimeInterval animationDuration;
NSTimeInterval lastReportedTime;
}
// public properties
#property (nonatomic, copy) NSArray *animationURLs;
#property (nonatomic, assign) NSTimeInterval animationFrameDuration;
#property (nonatomic, readonly) NSInteger animationNumFrames;
#property (nonatomic, assign) NSInteger animationRepeatCount;
#property (nonatomic, assign) UIImageOrientation animationOrientation;
#property (nonatomic, retain) NSURL *animationAudioURL;
#property (nonatomic, retain) AVAudioPlayer *avAudioPlayer;
#property (nonatomic, assign) CGRect viewCGRect;
// private properties
#property (nonatomic, retain) UIImageView *imageView;
#property (nonatomic, copy) NSArray *animationData;
#property (nonatomic, retain) NSTimer *animationTimer;
#property (nonatomic, assign) NSInteger animationStep;
#property (nonatomic, assign) NSTimeInterval animationDuration;
+ (ImageAnimatorViewController*) imageAnimatorViewController;
- (void) startAnimating;
- (void) stopAnimating;
- (BOOL) isAnimating;
- (void) animationShowFrame: (NSInteger) frame;
+ (NSArray*) arrayWithNumberedNames:(NSString*)filenamePrefix
rangeStart:(NSInteger)rangeStart
rangeEnd:(NSInteger)rangeEnd
suffixFormat:(NSString*)suffixFormat;
+ (NSArray*) arrayWithResourcePrefixedURLs:(NSArray*)inNumberedNames;
#end
And here the .m file:
//
// ImageAnimatorViewController.m
// PNGAnimatorDemo
//
// Created by Moses DeJong on 2/5/09.
//
#import "ImageAnimatorViewController.h"
#import <QuartzCore/QuartzCore.h>
#import <AVFoundation/AVAudioPlayer.h>
#implementation ImageAnimatorViewController
#synthesize animationURLs, animationFrameDuration, animationNumFrames, animationRepeatCount,
imageView, animationData, animationTimer, animationStep, animationDuration, animationOrientation, viewCGRect;
#synthesize animationAudioURL, avAudioPlayer;
- (void)dealloc {
// This object can't be deallocated while animating, this could
// only happen if user code incorrectly dropped the last ref.
NSAssert([self isAnimating] == FALSE, #"dealloc while still animating");
self.animationURLs = nil;
self.imageView = nil;
self.animationData = nil;
self.animationTimer = nil;
[super dealloc];
}
+ (ImageAnimatorViewController*) imageAnimatorViewController
{
return [[[ImageAnimatorViewController alloc] init] autorelease];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *myView = [[UIView alloc] initWithFrame:viewCGRect];
[myView autorelease];
self.view = myView;
/*UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[myView autorelease];
self.view = myView;
// FIXME: Additional Supported Orientations
if (animationOrientation == UIImageOrientationUp) {
// No-op
} else if (animationOrientation == UIImageOrientationLeft) {
// 90 deg CCW
//[self rotateToLandscape];
} else if (animationOrientation == UIImageOrientationRight) {
// 90 deg CW
//[self rotateToLandscapeRight];
} else {
NSAssert(FALSE,#"Unsupported animationOrientation");
}
*/
// Foreground animation images
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:self.view.frame];
[myImageView autorelease];
self.imageView = myImageView;
// Animation data should have already been loaded into memory as a result of
// setting the animationURLs property
NSAssert(animationURLs, #"animationURLs was not defined");
NSAssert([animationURLs count] > 1, #"animationURLs must include at least 2 urls");
NSAssert(animationFrameDuration, #"animationFrameDuration was not defined");
// Load animationData by reading from animationURLs
NSMutableDictionary *dataDict = [NSMutableDictionary dictionaryWithCapacity:[animationURLs count]];
NSMutableArray *muArray = [NSMutableArray arrayWithCapacity:[animationURLs count]];
for ( NSURL* aURL in animationURLs ) {
NSString *urlKey = aURL.path;
NSData *dataForKey = [dataDict objectForKey:urlKey];
if (dataForKey == nil) {
dataForKey = [NSData dataWithContentsOfURL:aURL];
NSAssert(dataForKey, #"dataForKey");
[dataDict setObject:dataForKey forKey:urlKey];
}
[muArray addObject:dataForKey];
}
self.animationData = [NSArray arrayWithArray:muArray];
int numFrames = [animationURLs count];
float duration = animationFrameDuration * numFrames;
self->animationNumFrames = numFrames;
self.animationDuration = duration;
[self.view addSubview:imageView];
// Display first frame of image animation
self.animationStep = 0;
[self animationShowFrame: animationStep];
self.animationStep = animationStep + 1;
if (animationAudioURL != nil) {
AVAudioPlayer *avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:animationAudioURL
error:nil];
[avPlayer autorelease];
NSAssert(avPlayer, #"AVAudioPlayer could not be allocated");
self.avAudioPlayer = avPlayer;
[avAudioPlayer prepareToPlay];
}
}
// Create an array of file/resource names with the given filename prefix,
// the file names will have an integer appended in the range indicated
// by the rangeStart and rangeEnd arguments. The suffixFormat argument
// is a format string like "%02i.png", it must format an integer value
// into a string that is appended to the file/resource string.
//
// For example: [createNumberedNames:#"Image" rangeStart:1 rangeEnd:3 rangeFormat:#"%02i.png"]
//
// returns: {"Image01.png", "Image02.png", "Image03.png"}
+ (NSArray*) arrayWithNumberedNames:(NSString*)filenamePrefix
rangeStart:(NSInteger)rangeStart
rangeEnd:(NSInteger)rangeEnd
suffixFormat:(NSString*)suffixFormat
{
NSMutableArray *numberedNames = [[NSMutableArray alloc] initWithCapacity:40];
for (int i = rangeStart; i <= rangeEnd; i++) {
NSString *suffix = [NSString stringWithFormat:suffixFormat, i];
NSString *filename = [NSString stringWithFormat:#"%#%#", filenamePrefix, suffix];
[numberedNames addObject:filename];
}
NSArray *newArray = [NSArray arrayWithArray:numberedNames];
[numberedNames release];
return newArray;
}
// Given an array of resource names (as returned by arrayWithNumberedNames)
// create a new array that contains these resource names prefixed as
// resource paths and wrapped in a NSURL object.
+ (NSArray*) arrayWithResourcePrefixedURLs:(NSArray*)inNumberedNames
{
NSMutableArray *URLs = [[NSMutableArray alloc] initWithCapacity:[inNumberedNames count]];
NSBundle* appBundle = [NSBundle mainBundle];
for ( NSString* path in inNumberedNames ) {
NSString* resPath = [appBundle pathForResource:path ofType:nil];
NSURL* aURL = [NSURL fileURLWithPath:resPath];
[URLs addObject:aURL];
}
NSArray *newArray = [NSArray arrayWithArray:URLs];
[URLs release];
return newArray;
}
// Invoke this method to start the animation
- (void) startAnimating
{
self.animationTimer = [NSTimer timerWithTimeInterval: animationFrameDuration
target: self
selector: #selector(animationTimerCallback:)
userInfo: NULL
repeats: TRUE];
[[NSRunLoop currentRunLoop] addTimer: animationTimer forMode: NSDefaultRunLoopMode];
animationStep = 0;
if (avAudioPlayer != nil)
[avAudioPlayer play];
// Send notification to object(s) that regestered interest in a start action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStartNotification
object:self];
}
// Invoke this method to stop the animation, note that this method must not
// invoke other methods and it must cancel any pending callbacks since
// it could be invoked in a low-memory situation or when the object
// is being deallocated. Invoking this method will not generate a
// animation stopped notification, that callback is only invoked when
// the animation reaches the end normally.
- (void) stopAnimating
{
if (![self isAnimating])
return;
[animationTimer invalidate];
self.animationTimer = nil;
animationStep = animationNumFrames - 1;
[self animationShowFrame: animationStep];
if (avAudioPlayer != nil) {
[avAudioPlayer stop];
avAudioPlayer.currentTime = 0.0;
self->lastReportedTime = 0.0;
}
// Send notification to object(s) that regestered interest in a stop action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStopNotification
object:self];
}
- (BOOL) isAnimating
{
return (animationTimer != nil);
}
// Invoked at framerate interval to implement the animation
- (void) animationTimerCallback: (NSTimer *)timer {
if (![self isAnimating])
return;
NSTimeInterval currentTime;
NSUInteger frameNow;
if (avAudioPlayer == nil) {
self.animationStep += 1;
// currentTime = animationStep * animationFrameDuration;
frameNow = animationStep;
} else {
currentTime = avAudioPlayer.currentTime;
frameNow = (NSInteger) (currentTime / animationFrameDuration);
}
// Limit the range of frameNow to [0, SIZE-1]
if (frameNow == 0) {
frameNow = 0;
} else if (frameNow >= animationNumFrames) {
frameNow = animationNumFrames - 1;
}
[self animationShowFrame: frameNow];
// animationStep = frameNow + 1;
if (animationStep >= animationNumFrames) {
[self stopAnimating];
// Continue to loop animation until loop counter reaches 0
if (animationRepeatCount > 0) {
self.animationRepeatCount = animationRepeatCount - 1;
[self startAnimating];
}
}
}
// Display the given animation frame, in the range [1 to N]
// where N is the largest frame number.
- (void) animationShowFrame: (NSInteger) frame {
if ((frame >= animationNumFrames) || (frame < 0))
return;
NSData *data = [animationData objectAtIndex:frame];
UIImage *img = [UIImage imageWithData:data];
imageView.image = img;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.animationRepeatCount = 0;
[self stopAnimating];
}
#end
Thank you very much for any advice!
Not sure why you got downvoted. I think your issue is that the UIViewController subclass has the loadView function implemented, and depends on it to build the UI. In the case of a view controller, this function is called automatically to build the view if it is not loaded from a .xib. You can try calling this functional manually after you create your object, and it should do pretty much the same thing.
Keep in mind that the shouldAutorotateToInterfaceOrientation
function is also a UIViewController function and will never be called on a UIView
Related
I am learning Cocos2D from the Ray Wenderlich book in my spare time but also making changes to it if it does not explain how to do something that I could see myself needing in the future. I am currently stuck because of my very limited knowledge.
I wish to use the GamePlayLayer Class to randomly change the character state of the TBT class.
I have checked each State and they all work (checked by making them the spawn state) so the problem is most likely in the gamePlayLayer.m file where I have attempted to make my own method.
I seem to be able to get it to change its state to kStateThrowing but it changes straight back (in 0.3 of a second!) to kStateIdle before the animation plays and has the following output...
2013-11-21 13:45:07.938 Spaceviking[981:12c03] CHANGING STATE TBT!!!!!!
2013-11-21 13:45:07.939 Spaceviking[981:12c03] TBT->Changing State to throwing
2013-11-21 13:45:07.967 Spaceviking[981:12c03] TBT Going to Idle
2013-11-21 13:45:07.970 Spaceviking[981:12c03] TBT->changing state to idle
Any help with this would be greatly appreciated.
Code listed below...
// CommonProtocols.h
#ifndef SpaceViking_CommonProtocols_h
#define SpaceViking_CommonProtocols_h
typedef enum {
kDirectionLeft,
kDirectionRight
}
PhaserDirection;
typedef enum {
kStatespawning,
kStateIdle,
kStateWalking,
kStateIdleTilt,
kStatebackTilting,
kStateforwardTilting,
kStateAttacking,
kStateTakingDamage,
kStateThrowing,
kStateLosingALife,
kStateDead,
kStateTravelling,
kStateRotating,
kStatetest
}
CharacterStates; //1
typedef enum {
kObjectTypeNone,
kPowerUpTypeHealth,
kPowerTypeMallet,
kEnemyType1BT,
kEnemyType2BT,
kEnemyType3BT,
kEnemyTypePhaser,
kVikingType,
} GameObjectType;
#protocol GamePlayLayerDelegate
-(void)createObjectOfType:(GameObjectType)objectType
withHealth:(int)initialHealth
atLocation:(CGPoint)spawnLocation
withZValue:(int)ZValue;
-(void)createPhaserWithDirection: (PhaserDirection)phaserDirection
andPosition:(CGPoint)spawnPosition;
#endif
// GamePlayLayer.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "CCLayer.h"
#import "SneakyJoystick.h"
#import "SneakyButton.h"
#import "SneakyButtonSkinnedBase.h"
#import "SneakyJoystickSkinnedBase.h"
#import "Constants.h"
#import "CommonProtocols.h"
#import "TBT.h"
#interface GamePlayLayer : CCLayer <GamePlayLayerDelegate> {
CCSprite *vikingSprite;
SneakyJoystick *leftJoystick;
SneakyButton *jumpButton;
SneakyButton *attackButton;
CCSpriteBatchNode *sceneSpriteBatchNode;
}
#end
------------------------------------------------
// GamePlayLayer.m
#import "GamePlayLayer.h"
#implementation GamePlayLayer
-(void) dealloc {
[leftJoystick release];
[jumpButton release];
[attackButton release];
[super dealloc];
}
--DELETED BELOW 2 METHODS AS NOT NEEDED FOR THIS QUESTION--
-(void)initJoystickAndButtons {
}
-(void)applyJoystick:(SneakyJoystick *)aJoystick toNode: (CCNode *)tempNode forTimeDelta:(float)deltaTime
{
}
-(void) update:(ccTime)deltaTime {
CCArray *listOfGameObjects =
[sceneSpriteBatchNode children];
for (GameCharacter *tempChar in listOfGameObjects) {
[tempChar updateStateWithDeltaTime:deltaTime andListOfGameObjects:listOfGameObjects];
}
}
-(void) createObjectOfType: (GameObjectType)objectType
withHealth:(int)initialHealth atLocation:(CGPoint)spawnLocation withZValue:(int)ZValue {
if (objectType == kEnemyType1BT) {
CCLOG(#"creating 1BT");
TBT *tBT = [[TBT alloc] initWithSpriteFrameName:#"BT_anim_1.png"];
[tBT setCharacterHealth:initialHealth];
[tBT setPosition:spawnLocation];
[sceneSpriteBatchNode addChild:tBT
z:ZValue
tag:k1BTTagValue];
[tBT release];
}
}
--BELOW IS THE METHOD I HAVE CREATED TO CHANGE THE STATE--
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
int x = (arc4random() % 3);
TBT *tBT = [[TBT alloc]
initWithSpriteFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"BT_anim_1.png"]];
if (x>0) {
CCLOG(#"CHANGING STATE TBT!!!!!!");
[tBT changeState:kStateThrowing];
[tBT setDelegate:self];
[tBT release];
}
}
-(id)init {
self = [super init];
if (self !=nil) {
CGSize screenSize = [CCDirector sharedDirector]. winSize;
self.TouchEnabled = YES;
srandom(time(NULL));
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:#"scene1atlas.plist"]; // 1
sceneSpriteBatchNode =
[CCSpriteBatchNode batchNodeWithFile:#"scene1atlas.png"]; // 2
} else {
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:#"scene1atlasiPhone.plist"]; // 1
sceneSpriteBatchNode =
[CCSpriteBatchNode batchNodeWithFile:#"scene1atlasiPhone.png"];
}
[self addChild:sceneSpriteBatchNode z:0];
[self initJoystickAndButtons];
BC *viking = [[BC alloc]
initWithSpriteFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"BCmoving_anim_1.png"]];
[viking setJumpButton:jumpButton];
[viking setAttackButton:attackButton];
[viking setPosition:ccp(screenSize.width * 0.19f,
screenSize.height * 0.19f)];
[viking setCharacterHealth:3];
[sceneSpriteBatchNode
addChild:viking
z:kVikingSpriteZValue
tag:kVikingSpriteTagValue];
[self createObjectOfType:kEnemyType1BT withHealth:3 atLocation:ccp(screenSize.width * 0.0439f, screenSize.height * 0.822f) withZValue:10];
---THE BELOW SELECTOR IS WHAT I HAVE USED TO DO A TIMED STATE CHANGE (METHOD ABOVE)--
[self schedule:#selector(changeState:) interval:1.0f];
[self scheduleUpdate];
}
return self;
}
#end
// TBT.h
#import <Foundation/Foundation.h>
#import "GameCharacter.h"
#interface TBT : GameCharacter {
//This is for the required animations
CCAnimation *tiltingAnim;
CCAnimation *transmittingAnim;
CCAnimation *loseLifeAnim;
CCAnimation *throwingAnim;
CCAnimation *afterThrowingAnim;
CCAnimation *shootPhaserAnim;
GameCharacter *vikingCharacter;
id <GamePlayLayerDelegate> delegate;
}
#property (nonatomic,assign) id <GamePlayLayerDelegate> delegate;
#property (nonatomic, retain) CCAnimation *tiltingAnim;
#property (nonatomic, retain) CCAnimation *transmittingAnim;
#property (nonatomic, retain) CCAnimation *loseLifeAnim;
#property (nonatomic, retain) CCAnimation *throwingAnim;
#property (nonatomic,retain) CCAnimation *afterThrowingAnim;
#property (nonatomic,retain) CCAnimation *shootPhaserAnim;
-(void)initAnimations;
-(void)changeState:(CharacterStates)newState;
#end
// TBT.m
#import "TBT.h"
#implementation TBT
#synthesize delegate;
#synthesize tiltingAnim;
#synthesize transmittingAnim;
#synthesize loseLifeAnim;
#synthesize throwingAnim;
#synthesize afterThrowingAnim;
#synthesize shootPhaserAnim;
-(void) dealloc {
delegate = nil;
[tiltingAnim release];
[transmittingAnim release];
[loseLifeAnim release];
[throwingAnim release];
[afterThrowingAnim release];
[shootPhaserAnim release];
[super dealloc];
}
--BELOW METHOD NOT NEEDED FOR THIS QUESTION
-(void)shootPhaser {}
-(CGRect)TBTboundingBox {
CGRect tbtBoundingBox = [self boundingBox];
float xOffset;
float xCropAmount = tbtBoundingBox.size.width * 0.5482f;
float yCropAmount = tbtBoundingBox.size.height * 0.1f;
tbtBoundingBox =
CGRectMake(tbtBoundingBox.origin.x + xOffset,
tbtBoundingBox.origin.y,
tbtBoundingBox.size.width - xCropAmount,
tbtBoundingBox.size.height - yCropAmount);
return tbtBoundingBox;
}
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
id action = nil;
[self setCharacterState:newState];
switch (newState) {
case kStatespawning:
CCLOG(#"TBT->Changing State to Spwaning");
[self setDisplayFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"BT_anim_1.png"]];
break;
case kStateIdle:
CCLOG(#"TBT->schaning state to idle");
[self setDisplayFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"BT_anim_1.png"]];
break;
case kStateThrowing:
CCLOG(#"TBT->Changing State to throwing");
action = [CCSequence actions:
[CCAnimate actionWithAnimation:throwingAnim
restoreOriginalFrame:NO],
[CCDelayTime actionWithDuration:1.0f],
//[CCAnimate actionWithAnimation:shootPhaserAnim
// restoreOriginalFrame:NO],
[CCCallFunc actionWithTarget:self
selector:#selector(shootPhaser)],
[CCAnimate actionWithAnimation:afterThrowingAnim
restoreOriginalFrame:NO],
[CCDelayTime actionWithDuration:0.1f],
nil];
break;
case kStateLosingALife:
CCLOG(#"TBT->Losing a life");
break;
case kStateDead:
CCLOG(#"TBT->changing state to dead");
action = [CCAnimate actionWithAnimation:loseLifeAnim];
break;
default:
CCLOG(#"unhandled state %d in TBT", newState);
break;
}
if (action !=nil) {
[self runAction:action];
}
}
-(void)updateStateWithDeltaTime: (ccTime)deltaTime andListOfGameObjects:(CCArray*)listOfGameObjects {
if (characterState == kStateDead)
return;
vikingCharacter =
(GameCharacter*)[[self parent]
getChildByTag:kVikingSpriteTagValue];
CGRect vikingBoudingBox =
[vikingCharacter adjustedBoundingBox];
CharacterStates vikingState = [vikingCharacter characterState];
if ((vikingState == kStateAttacking) && (CGRectIntersectsRect ([self adjustedBoundingBox], vikingBoudingBox))) {
if (characterState != kStateTakingDamage) {
[self changeState:kStateTakingDamage];
return;
}
}
if ((([self numberOfRunningActions] == 0) && (characterState != kStateDead)) ) {
CCLOG(#"TBT Going to Idle");
[self changeState:kStateIdle];
return;
}
}
-(void)initAnimations {
[self setTransmittingAnim:[self loadPlistForAnimationWithName:#"transmittingAnim" andClassName:NSStringFromClass([self class])]];
[self setThrowingAnim:[self loadPlistForAnimationWithName:#"throwingAnim" andClassName:NSStringFromClass([self class])]];
[self setAfterThrowingAnim:[self loadPlistForAnimationWithName:#"afterThrowingAnim" andClassName:NSStringFromClass([self class])]];
}
-(id) initWithSpriteFrameName:(NSString*)frameName{
if ((self=[super init])) {
if ((self = [super initWithSpriteFrameName:frameName])) {
CCLOG(#"### TBT initialized");
[self initAnimations];
characterHealth = 3.0f;
gameObjectType = kEnemyType1BT;
[self changeState:kStatespawning];
}
}
return self;
}
#end
My guess is you are not adding the TBT object as a child to anything (i.e. CCScene/CCNode/CCSprite). Try adding it to "sceneSpriteBatchNode" like in the createObjectOfType: method.
[sceneSpriteBatchNode addChild:tBT
z:ZValue
tag:k1BTTagValue];
In my card matching game:
-I have a method that checking cards that were flipped in certain index. It's basically the whole logic in the app.
-I have another method that checks the matching.
Now, I created a switch button in my view controller that will tell the controller that the user changed the mode for "3" cards instead of the basic mode (2 cards).
My issue is, how do i tell the controller to check in the matching method if there is more than 2 matches..it's driving me crazy please try to help me figure this out.
I also have in the controller an updateUI method that making cards that are matched fade away so I need to make sure it behaves the same.
The following code show's the flipCardAtIndex method, matching method & view controller in the same order:
CardMatchingGame.m (the last method is flipCardAtIndex):
#import "CardMatchingGame.h"
#import "PlayingCardsDeck.h"
#interface CardMatchingGame()
#property (readwrite, nonatomic) int score;
#property (strong, nonatomic) NSMutableArray *cards;
#property (strong, nonatomic) NSString *notification;
#end
#implementation CardMatchingGame
-(NSMutableArray *) cards {
if (!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
-(id)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck {
self = [super init];
if (self) {
for (int i = 0; i < count; i++) {
Card *card = [deck drawRandonCard];
if (!card) {
self = nil;
} else {
self.cards[i] = card;
}
}
}
return self;
}
-(Card *) cardAtIndex:(NSUInteger)index {
return (index < self.cards.count) ? self.cards[index] : nil;
}
#define FLIP_COST 1
#define MISMATCH_PENALTY 2
#define BONUS 4
-(void) flipCardAtIndex:(NSUInteger)index {
Card *card = [self cardAtIndex:index];
if (!card.isUnplayable) {
if (!card.isFaceUp) {
for (Card *otherCard in self.cards) {
if (otherCard.isFaceUp && !otherCard.isUnplayable) {
NSMutableArray *myCards = [[NSMutableArray alloc] init];
[myCards addObject:otherCard];
int matchScore = [card match:myCards];
if (matchScore) {
otherCard.unplayble = YES;
card.unplayble = YES;
self.notification = [NSString stringWithFormat:#"%# & %# match!", card.contents, otherCard.contents];
self.score += matchScore * BONUS;
} else {
otherCard.faceUp = NO;
self.score -= MISMATCH_PENALTY;
self.notification = [NSString stringWithFormat:#"%# did not matched to %#", card.contents, otherCard.contents];
}
break;
}
}
self.score -= FLIP_COST;
}
card.faceUp = !card.isFaceUp;
}
}
#end
PlayingCards.m (Only the first method, matching method) :
#import "PlayingCards.h"
#implementation PlayingCards
#synthesize suit = _suit;
//overriding the :match method of cards to give different acore if its only a suit match or a number match
-(int)match:(NSArray *)cardToMatch {
int score = 0;
for (int i = 0; i < cardToMatch.count; i++) {
PlayingCards *nextCard = cardToMatch[i];
if ([nextCard.suit isEqualToString:self.suit]) {
score += 1;
} else if (nextCard.rank == self.rank) {
score += 4;
}
}
return score;
}
My view controller (the last method is the one for the switch button) :
#import "CardGameViewController.h"
#import "PlayingCardsDeck.h"
#import "CardMatchingGame.h"
#interface CardGameViewController ()
#property (weak, nonatomic) IBOutlet UILabel *flipsLabel;
#property (weak, nonatomic) IBOutlet UILabel *notificationLabel;
#property (weak, nonatomic) IBOutlet UILabel *scoreCounter;
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
#property (strong, nonatomic) CardMatchingGame *game;
#property (nonatomic) int flipsCount;
#property (nonatomic) NSNumber *mode;
//#property (weak, nonatomic) IBOutlet UISwitch *mySwitch;
#property (weak, nonatomic) IBOutlet UISwitch *mySwitch;
#end
#implementation CardGameViewController
#synthesize mode = _mode;
//creating the getter method that creates a new card game.
-(CardMatchingGame *) game {
if (!_game) _game = [[CardMatchingGame alloc] initWithCardCount:self.cardButtons.count usingDeck:[[PlayingCardsDeck alloc] init]];
return _game;
}
//creating a setter for the IBOutletCollection cardButtons
-(void) setCardButtons:(NSArray *)cardButtons {
_cardButtons = cardButtons;
[self updateUI];
}
//creating the setter for the flipCount property. Whick is setting the flipsLabel to the right text and adding the number of counts.
-(void) setFlipsCount:(int)flipsCount {
_flipsCount = flipsCount;
self.flipsLabel.text = [NSString stringWithFormat:#"Flips: %d", self.flipsCount];
}
-(void) updateUI {
for (UIButton *cardButton in self.cardButtons) {
Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:cardButton]];
[cardButton setTitle:card.contents forState:UIControlStateSelected];
[cardButton setTitle:card.contents forState:UIControlStateSelected|UIControlStateDisabled];
cardButton.selected = card.isFaceUp;
cardButton.enabled = !card.unplayble;
if (card.unplayble) {
cardButton.alpha = 0.1;
}
//updating the score
self.scoreCounter.text = [NSString stringWithFormat:#"Score: %d", self.game.score];
//if notification in CardMatchingGame.m is no nil, it will be presented
if (self.game.notification) {
self.notificationLabel.text = self.game.notification;
}
}
}
//Here I created a method to flipCards when the card is selected, and give the user a random card from the deck each time he flips the card. After each flip i'm incrementing the flipCount setter by one.
- (IBAction)flipCard:(UIButton *)sender {
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender] forMode:self.mode];
self.flipsCount++;
[self updateUI];
}
//sending an alert if the user clicked on new game button
- (IBAction)newGame:(UIButton *)sender {
UIAlertView* mes=[[UIAlertView alloc] initWithTitle:#"Are you sure..?" message:#"This will start a new game" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil];
[mes show];
}
//preforming an action according to the user choice for the alert yes/no to start a new game
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [alertView cancelButtonIndex]) {
self.flipsCount = 0;
self.game = nil;
for (UIButton *button in self.cardButtons) {
Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:button]];
card.unplayble = NO;
card.faceUp = NO;
button.alpha = 1;
}
self.notificationLabel.text = nil;
[self updateUI];
}
}
-(void) setMode:(NSNumber *)mode {
mode = _mode;
}
-(void) switchValueChange:(id)sender {
UISwitch *Switch = (UISwitch *) sender;
NSNumber *twoCards = [NSNumber numberWithInt:2];
NSNumber *threeCards = [NSNumber numberWithInt:3];
if (Switch.on) {
self.mode = twoCards;
}
else
{
self.mode = threeCards;
}
}
- (void)viewDidLoad
{
UISwitch *mySwitch;
[super viewDidLoad];
[mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
[self updateUI];
}
#end
Actually, getting the switch value is the easy part. You can get set a referencing outlet from your switch to your viewController (CardGameViewController), and in your view controller's viewDidLoad method, add the method to listen for changes to switch's value:
[mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
Add a new property called NSNumber *mode in your CardGameViewController and synthesize it.
Now you can update the "mode" (which I believe could be an instance variable) in switchValueChanged method:
- (void)switchValueChange:(id)sender
{
UISwitch *switch = (UISwitch *) sender;
if (sender.on)
self.mode = 2;
else
self.mode = 3;
}
If my assumption is right, then by "mode" you mean how many cards to match, is that correct? 2 means, that atleat 2 cards should be same when faced up, and 3 means, that 3 cards should match in suit or number.
Start by changing the match method in your PlayingCards to something like this (accepting another parameter named mode) (You might have to update the same method in its parent class too):
//overriding the :match method of cards to give different acore if its only a suit match or a number match
-(int)match:(NSArray *)cardToMatch forMode:(NSNumber *) mode{
int score = 0;
int cardsMatched = 0;
for (int i = 0; i < cardToMatch.count; i++) {
PlayingCards *nextCard = cardToMatch[i];
if ([nextCard.suit isEqualToString:self.suit]) {
score += 1;
cardsMatched++;
} else if (nextCard.rank == self.rank) {
score += 4;
cardsMatched++;
}
if (cardsMatched >= [mode intValue])
break;
}
return score;
}
Now, in your CardMatchingGame.m method, change the flipCardAtIndex method to this (accepting another parameter named mode):
-(void) flipCardAtIndex:(NSUInteger)index forMode (NSNumber *mode) {
Card *card = [self cardAtIndex:index];
if (!card.isUnplayable) {
if (!card.isFaceUp) {
NSMutableArray *myFaceUpCards = [[NSMutableArray alloc] init];
// UPDATED: Loop through all the cards that are faced up and add them to an array first
for (Card *otherCard in self.cards) {
if (otherCard.isFaceUp && !otherCard.isUnplayable && orderCard != card) {
[myCards addObject:otherCard];
}
// UPDATED: Now call the method to do the match. The match method now takes an extra parameter
int matchScore = [card match:myCards forMode:mode];
if (matchScore) {
otherCard.unplayble = YES;
card.unplayble = YES;
self.notification = [NSString stringWithFormat:#"%# & %# match!", card.contents, otherCard.contents];
self.score += matchScore * BONUS;
} else {
otherCard.faceUp = NO;
self.score -= MISMATCH_PENALTY;
self.notification = [NSString stringWithFormat:#"%# did not matched to %#", card.contents, otherCard.contents];
}
}
self.score -= FLIP_COST;
}
card.faceUp = !card.isFaceUp;
}
}
Finally, change call to
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender]];
in
- (IBAction)flipCard:(UIButton *)sender
method of CardGameViewController to
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender] forMode:self.mode];
Take a look and see if that makes sense.
Remove #property (weak, nonatomic) IBOutlet UISwitch *mySwitch; from your .m class.
Instead, go to your storyboard, click on the controller that has the switch, then click on the Assistant Editor button on top right (looks like a face). It will open up CardGameViewController.h. Now right click on the switch view on the storyoard, and click and drag from New Referencing Outlet to CardViewController.h. This is how you reference your switch into your controller.
Now that you have the switch variable in the interface file, go to your implementation file (CardGameViewController.m) and synthesize the variable:
#synthesize mySwitch = _mySwitch;
Now, change your viewDidLoad method to this:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.mySwitch addTarget:self action:#selector(switchValueChange:) forControlEvents:UIControlEventValueChanged];
[self updateUI];
}
Also remove setMode method. If you are synthesizing mode variable, then you don't need it.
Give it a try now. You know how to debug in xcode using breakpoints?
Bear with me on this one.
I have an iphone application. It is a questionnaire application. There are several types of question, some have a slider, some have text input etc. I have developed a view controller for each type of question.
Two example types of question controllers are: TextInputQuestionViewController and SliderQuestionViewController.
I have a rootViewcontroller named QuestionnaireViewController. This is defined as follows:
#import <UIKit/UIKit.h>
#import "JSONKit.h";
#import "dbConnector.h"
#import "SliderQuestionViewController.h";
#import "TextInputQuestionViewController.h";
#import "MainMenuProtocol.h";
#interface QuestionnaireViewController : UIViewController {
NSDictionary* questions;
NSMutableArray* questionArray;
NSMutableArray* answerArray;
dbConnector* db;
SliderQuestionViewController* currQ; //need to create a generic var
TextInputQuestionViewController* currQ;
NSInteger currQNum;
NSString* qaTitle;
NSString* secId;
id<MainMenuProtocol>delegate;
}
#property(nonatomic, retain) NSDictionary* questions;
#property(nonatomic, retain) NSMutableArray* questionArray;
#property(nonatomic, retain) NSMutableArray* answerArray;
#property(nonatomic, retain) dbConnector* db;
#property(nonatomic, retain) SliderQuestionViewController* currQ;
#property(nonatomic, retain) TextInputQuestionViewController* currTI;
#property(nonatomic) NSInteger currQNum;
#property(nonatomic, retain) NSString* qaTitle;
#property(nonatomic, retain) NSString* secId;
#property(nonatomic, retain) id <MainMenuProtocol> delegate;
-(void) setQuestions;
-(void) startQuestion:(NSInteger)index isLast:(BOOL)last;
-(void) loadQuestions;
-(void) initialise;
-(void) finishQuestionnaire:(id)sender;
-(void) switchViews:(id)sender;
#end
#import "QuestionnaireViewController.h"
#import "dbConnector.h"
#import "ASIHTTPRequest.h"
#import "JSONKit.h";
#import "Answer.h";
#implementation QuestionnaireViewController
#synthesize questions, questionArray, db, currQ, currQNum, answerArray, qaTitle, secId, delegate;
-(void)viewDidLoad{
[self initialise];
answerArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
self.title = qaTitle; //set to whatever section is
}
-(void) initialise {
currQNum = 0;
[self loadQuestions];
UIBarButtonItem *anotherButton = [[UIBarButtonItem alloc] initWithTitle:#"Start" style:UIBarButtonItemStylePlain target:self action:#selector(switchViews:)];
self.navigationItem.rightBarButtonItem = anotherButton;
}
-(void) loadQuestions {
db = [[dbConnector alloc]init];
//code to initialise view
[db getQuestions:secId from:#"http://dev.speechlink.co.uk/David/get_questions.php" respondToDelegate:self];
}
//called when questions finished loading
//stores dictionary of questions
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
NSString *json = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSDictionary *qs = [json objectFromJSONString];
self.questions = qs;
[json release];
[qs release];
[self setQuestions];
}
//assigns JSON to question objects
-(void) setQuestions {
questionArray = [[NSMutableArray alloc] init];
for (NSDictionary *q in self.questions) {
/* Create Question object and populate it */
id question;
if([[q objectForKey:#"type"] isEqualToString:#"Slider"]){
question = [[SliderQuestionViewController alloc]init];
//set min max values
}else if([[q objectForKey:#"type"] isEqualToString:#"Option"]){
}else if([[q objectForKey:#"type"] isEqualToString:#"TextInput"]){
question = [[TextInputQuestionViewController alloc]init];
}else if([[q objectForKey:#"type"] isEqualToString:#"ImagePicker"]){
}else{
//comments
}
//if else to create appropriate view controller - NEED to identify question type
[question setQuestionId:[q objectForKey:#"questionId"] withTitle:[q objectForKey:#"question"] number:[q objectForKey:#"questionNumber"] section:[q objectForKey:#"sectionId"] questionType: [q objectForKey:#"type"]];
/* Add it to question (mutable) array */
[questionArray addObject:question];
[question release];
}
}
-(void) startQuestion:(NSInteger)index isLast:(BOOL)last{
//currQ = [[QuestionViewController alloc]init];
currQ = [questionArray objectAtIndex:index];
//push currQ onto navigationcontroller stack
[self.navigationController pushViewController:currQ animated:YES];
[currQ addButton:self isLast: last];
}
//pushes new view onto navigation controller stack
-(void) switchViews:(id)sender{
Answer* ans = currQ.question.answer;
ans.questionId = currQ.question.qId;
ans.entryId = #"1";//temporary;
if(currQNum < [questionArray count] - 1){
if(currQNum > 0){
//if else for different input types
NSString* qt = currQ.question.qType;
if([qt isEqualToString:#"Slider"]){
ans.answer = currQ.sliderLabel.text;
}else if([qt isEqualToString:#"Option"]){
}else if([qt isEqualToString:#"TextInput"]){
//NSLog(#"%#", currQ.inputAnswer);
ans.answer = currQ.inputAnswer.text;
}else if([qt isEqualToString:#"ImagePicker"]){
}else{
}
[answerArray addObject: ans];
[ans release];
}
[self startQuestion:currQNum isLast:FALSE];
currQNum++;
}else{
ans.answer = currQ.sliderLabel.text;
[answerArray addObject: ans];
//store data temporarily - section finished
[self startQuestion:currQNum isLast:TRUE];
currQNum++;
}
[ans release];
}
-(void) finishQuestionnaire:(id)sender{
//go back to main manual
//if else statement for answers
NSString* answ = currQ.sliderLabel.text;
[answerArray addObject: answ];
[delegate finishedSection:answerArray section:secId];
[answ release];
[self.navigationController popToRootViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.questions = nil;
self.currQ = nil;
[super viewDidUnload];
}
//hide back button in navigation bar
- (void) viewWillAppear:(BOOL)animated{
self.navigationItem.hidesBackButton = YES;
}
- (void)dealloc {
[currQ release];
[db release];
[questionArray release];
[questions release];
[super dealloc];
}
#end
the problematic lines with the above are in the switchViews function. I need to make the answer equal to the specific input component in that question view (slider value, text input value). So I need to make currQ a type that can be instantiated using any view controller.
I therefore need a generic variable to hold the current question. currQ holds the current question, but at the moment is of type SliderQuestionViewController. I tried to change this to id, but it throws a load of "Request For member...not a structure of union" and also a load of misassigned pointer issues.
Let me know if you need more code.
This reads like you want a pointer for a UIViewController, so just use that as the type. Then you can cast it down to whatever subclass you like later. For example:
-(void)myAction:(UIViewController *)vc {
SpecialViewController *svc = (SpecialViewController *)vc;
...
}
In your case, declare
UIViewController* currQ;
and then cast it as needed in the implementation to access the different properties and methods of your two subclasses.
If you're looking for a 'generic' variable, you should be able to use id. Make sure you don't define the type as id*, the asterisk should not be present.
A better idea, though, is to create a superclass for your question viewcontrollers. Create a superclass called QuestionViewController that inherits from UIViewController and have the Slider and TextInput (and any others) inherit from QuestionViewController. Then you can define your variable as: QuestionViewController* currQ; You can put any common functionality in that superclass as well and eliminate duplication.
Edit:
Thanks #BlackFrog. I think I'm nearer now, but the values are still not get getting through...
The values are set as shown by logs within [progressController updateProgressSummary:...] but are nil when I log them in progressUpdate initWithProgressUpdate:.... as shown below.
I'm slightly confused over which property is used the one set for progressUpdate or the ones set for each of the 3 components of progressUpdate. I have changed the 3 individual properties from assign to retain as suggested and have also tried doing the same with the overall progressUpdate property too (not shown here).
progressController.h
......
#property (nonatomic, assign) ProgressUpdate *progressUpdate;
progressController.m
// Ask delegate to update and display Progress text
-(void) updateProgressSummary:(NSString *)summary detail:(NSString *)detail percentComplete:(NSNumber *)complete {
// These report the proper values
DLog(#"Reporting Summary - %s", [summary UTF8String]);
DLog(#"Reporting Detail - %s", [detail UTF8String]);
DLog(#"Reporting Complete - %i", [complete intValue]);
if (summary != nil)
self.progressUpdate.summaryText = summary;
self.progressUpdate.detailText = detail;
self.progressUpdate.percentComplete = complete;
ProgressUpdate *progressUpdateForIssue = [[ProgressUpdate alloc] initWithProgressUpdate:progressUpdate];
[self.delegate performSelectorOnMainThread:#selector(displayProgress:) withObject:progressUpdateForIssue waitUntilDone:NO];
[progressUpdateForIssue release];
}
But then a few milliseconds later...., inside the object....they're nil.
progressUpdate.h
.....
#property (nonatomic, retain) NSString *summaryText;
#property (nonatomic, retain) NSString *detailText;
#property (nonatomic, retain) NSNumber *percentComplete;
progressUpdate.m
-(id) initWithProgressUpdate:(ProgressUpdate *)update {
if ((self = [super init])) {
summaryText = [update.summaryText copy];
detailText = [update.detailText copy];
percentComplete = [[NSNumber alloc] initWithFloat:[update.percentComplete floatValue]];
}
// These report nil values
DLog(#"Reporting in progUpdate summaryText - %s", [summaryText UTF8String]);
DLog(#"Reporting in progUpdate detailText - %s", [detailText UTF8String]);
DLog(#"Reporting in progUpdate percentComplete - %i", [percentComplete intValue]);
return self;
}
end of update
I need some help with passing data in a custom class from one thread to another. Its there before the pass but then disappears upon arrival. I've tried everything I know, but to no avail.
My background thread calls ProgressController and passes it details of the current progress. That in turn does performSelectorOnMainThread on ProgressController's delegate (the view controller) to display the details.
It was all working fine when I was passing through a single NSString, but I need to pass two strings and a number and as performSelectorOnMainThread can only pass one object, I have encapsulated these in a custom object - ProgressUpdate.
The data gets through to ProgressController correctly but is null by the time that it appears in the View Controller. I know this as I've put NSLogs in various places.
I wonder if its to do with:
multithreading and custom objects
the fact that ProgressController is a singleton, which is why I have then alloc'd a new ProgressUpdate each time its called, but that has not helped.
Any ideas welcome. For clarity, the code is below.
ProgressUpdate.h
#import <Foundation/Foundation.h>
#interface ProgressUpdate : NSObject {
NSString *summaryText;
NSString *detailText;
NSNumber *percentComplete;
}
#property (nonatomic, assign) NSString *summaryText;
#property (nonatomic, assign) NSString *detailText;
#property (nonatomic, assign) NSNumber *percentComplete;
-(id) initWith:(ProgressUpdate *)update;
#end
ProgressUpdate.m
#import "ProgressUpdate.h"
#implementation ProgressUpdate
#synthesize summaryText, detailText, percentComplete;
-(id) initWith:(ProgressUpdate *)update {
self = [super init];
self.summaryText = update.summaryText;
self.detailText = update.detailText;
self.percentComplete = update.percentComplete;
return self;
}
#end
ProgressController.m
static ProgressController *sharedInstance;
+ (ProgressController *)sharedInstance {
#synchronized(self) {
if (!sharedInstance)
[[ProgressController alloc] init];
}
return sharedInstance;
}
+(id)alloc {
#synchronized(self) {
NSAssert(sharedInstance == nil, NSLocalizedString(#"Attempted to allocate a second instance of a singleton ProgressController.", #"Attempted to allocate a second instance of a singleton ProgressController."));
sharedInstance = [super alloc];
}
return sharedInstance;
}
-(id) init {
if (self = [super init]) {
[self open];
}
return self;
}
.........
// Ask delegate to update and display Progress text
-(void) updateProgressSummary:(NSString *)summary detail:(NSString *)detail percentComplete:(NSNumber *)complete {
if (summary != nil)
self.progressUpdate.summaryText = summary;
self.progressUpdate.detailText = detail;
self.progressUpdate.percentComplete = complete;
ProgressUpdate *progressUpdateForIssue = [[ProgressUpdate alloc] initWith:progressUpdate];
[self.delegate performSelectorOnMainThread:#selector(displayProgress:) withObject:progressUpdateForIssue waitUntilDone:NO];
[progressUpdateForIssue release];
}
RootViewController.m
// Delegate method to display specific text in Progress label
- (void) displayProgress:(ProgressUpdate *)update {
[progressSummaryLabel setText:update.summaryText];
[progressDetailLabel setText:update.detailText];
[progressBar setProgress:[update.percentComplete intValue]];
[progressView setNeedsDisplay];
}
In the init method, you are only assigning the ivars and not retaining them in the new object.
Redo your init method as the following:
-(id) initWithProgressUpdate:(ProgressUpdate *)update {
if ((self = [super init])) {
summaryText = [update.summaryText copy];
detailText = [update.detailText copy];
percentComplete = [[NSNumber alloc] initWithFloat:[update.percentComplete floatValue];
}
return self;
}
Couple of points:
You should not use accessor in the init method
Rename your init method to be a lot clear
In the #property, change the assign to retain
Try removing the statement '[progressUpdateForIssue release];' in the method
'-(void) updateProgressSummary:(NSString *)summary detail:(NSString *)detail percentComplete:(NSNumber *)complete '.
Also change the property attribute from 'assign' to 'retain' in your class ProgressUpdate.
You could release those properties in the dealloc method .
Good luck.
Is there a way to manually set low-level still-camera settings like shutter speed, aperture, or ISO in iOS4 on the iPhone 4? I don't think it exists in the official SDK but perhaps someone has found some private APIs to allow this?
I find my iPhone 4 camera to be unusable because even in fairly decent lighting, it always insists on shooting at the slowest 1/15th a second shutter speed causing motion blur if the subject is moving at all.
Thanks!
Not directly. Please file a bug report.
Yes, there may be private APIs available, but that's of limited use.
Try this, I could be useful for you:
#interface MyViewController ()
#property (nonatomic, retain) IBOutlet UIImageView *imageView;
#property (nonatomic, retain) IBOutlet UIToolbar *myToolbar;
#property (nonatomic, retain) OverlayViewController *overlayViewController;
#property (nonatomic, retain) NSMutableArray *capturedImages;
// toolbar buttons
- (IBAction)photoLibraryAction:(id)sender;
- (IBAction)cameraAction:(id)sender;
#end
#implementation MyViewController
- (void)viewDidLoad
{
self.overlayViewController =
[[[OverlayViewController alloc] initWithNibName:#"OverlayViewController" bundle:nil] autorelease];
// as a delegate we will be notified when pictures are taken and when to dismiss the image picker
self.overlayViewController.delegate = self;
self.capturedImages = [NSMutableArray array];
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
// camera is not on this device, don't show the camera button
NSMutableArray *toolbarItems = [NSMutableArray arrayWithCapacity:self.myToolbar.items.count];
[toolbarItems addObjectsFromArray:self.myToolbar.items];
[toolbarItems removeObjectAtIndex:2];
[self.myToolbar setItems:toolbarItems animated:NO];
}
}
- (void)viewDidUnload
{
self.imageView = nil;
self.myToolbar = nil;
self.overlayViewController = nil;
self.capturedImages = nil;
}
- (void)dealloc
{
[_imageView release];
[_myToolbar release];
[_overlayViewController release];
[_capturedImages release];
[super dealloc];
}
- (void)showImagePicker:(UIImagePickerControllerSourceType)sourceType
{
if (self.imageView.isAnimating)
[self.imageView stopAnimating];
if (self.capturedImages.count > 0)
[self.capturedImages removeAllObjects];
if ([UIImagePickerController isSourceTypeAvailable:sourceType])
{
[self.overlayViewController setupImagePicker:sourceType];
[self presentModalViewController:self.overlayViewController.imagePickerController animated:YES];
}
}
- (IBAction)photoLibraryAction:(id)sender
{
[self showImagePicker:UIImagePickerControllerSourceTypePhotoLibrary];
}
- (IBAction)cameraAction:(id)sender
{
[self showImagePicker:UIImagePickerControllerSourceTypeCamera];
}
// as a delegate we are being told a picture was taken
- (void)didTakePicture:(UIImage *)picture
{
[self.capturedImages addObject:picture];
}
// as a delegate we are told to finished with the camera
- (void)didFinishWithCamera
{
[self dismissModalViewControllerAnimated:YES];
if ([self.capturedImages count] > 0)
{
if ([self.capturedImages count] == 1)
{
// we took a single shot
[self.imageView setImage:[self.capturedImages objectAtIndex:0]];
}
else
{
// we took multiple shots, use the list of images for animation
self.imageView.animationImages = self.capturedImages;
if (self.capturedImages.count > 0)
// we are done with the image list until next time
[self.capturedImages removeAllObjects];
self.imageView.animationDuration = 5.0; // show each captured photo for 5 seconds
self.imageView.animationRepeatCount = 0; // animate forever (show all photos)
[self.imageView startAnimating];
}
}
}
#end