I am writing a simple application that consists of 2 UIImageViews and 2 UIButtons. The UIImageViews are placed on top of the UIButtons.
I have 1 UIImage that appears on a random UIImageView and disappears after 2 seconds. The user then has to tap on the button they think the image appeared on.
I shuffle the UIImageView array and use the first element (index 0) for the image to be displayed on. When shuffling I keep track of which element (i.e from which index. lets say from index "n") was placed on index 0 of the array.
However, when a button is pressed i compare the id of the button pressed with the id of the button with index n. (because that is the index of the random UIImageView).
But for some reason it is not working. :(
Here is my code: (i didn't upload the .h file. it just contains declarations.)
The code below doesn't produce any error/warning messages. It just doesn't output the result I want.
#interface ViewController ()
#property (strong, nonatomic) NSMutableArray* tempButton;
#property (strong, nonatomic) NSMutableArray *tempViews;
#property (strong, nonatomic) UIImage *myImage;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_myImage = [UIImage imageNamed:#"hello"];
_tempButton = [[NSMutableArray alloc] initWithObjects:_button1, _button2, nil];
_tempViews = [[NSMutableArray alloc]initWithObjects:_image1, _image2, nil];
[self displayonView];
}
-(void)displayToFindSymbol{
[_toFindImage setImage:_myImage];
_toFindImage.contentMode = UIViewContentModeScaleAspectFit;
}
- (IBAction)pressed:(id)sender {
UIButton *result = (UIButton *)sender;
if (result == _tempButton[_correctButton]) {
NSLog (#"Correct");
}
else if (result != _tempButton[_correctButton]){
NSLog (#"Incorrect");
}
}
-(NSMutableArray *)shuffleViews{
NSUInteger count = _tempViews.count;
int n;
for (int i=count-1; i>=0; --i) {
n = arc4random() % (i + 1);
[_tempViews exchangeObjectAtIndex:n withObjectAtIndex:i];
}
_correctButton = n;
return _tempViews;
}
-(void)displayonView{
NSMutableArray *tempArray;
tempArray = [self shuffleViews]; // shuffle views
_correctImage = [tempArray objectAtIndex:0];
_correctImage.contentMode = UIViewContentModeScaleAspectFit;
[_correctImage setImage:_myImage];
NSTimer* myTImer;
myTImer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(hideLabel) userInfo:nil repeats:NO];
}
-(void) hideLabel{
_correctImage.hidden = YES;
for (UIButton *each in self.tempButton) {
each.enabled = YES;
}
[self displayToFindSymbol];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
You could either have each button call a different method as the selector, or assign a different tag to each button and inspect it in a shared method.
Add all imageviews in an array and also all buttons in an array.
when setting image to random image view store its index and use the index later on to get the corresponding button that should have been clicked.
Pseudo code
NSArray a = [NSArray arrayWithObjects:imgVie1,ImgView2...,nil]
// create second array with buttons
int randomIndex = arc4random() % a.count;
UIImageView *imgView = [a objectAtIndex:randomIndex];
//set image and hide later
UIButton *correctButton = [secondarray objectAtIndex:randomIndex];
Related
In my ViewController I have added A UISegmentedControl to preform different task for a different selection of the Segmented Control. Its a cards matching game.
And everything seems to work just fine, except that the Segmented Control is not reacting to the selection.., I created a switch to do something in case of "twoCardGame" and in case of "threeCardGame".
From what I understand it would be good to define those variables with enum, which I did in the top part of the controller, but it seems like i'm missing something in it..
Sorry if its not so directed, but my controller is pretty short and simple, would appreciate if you can tell me what am I doing wrong in term of the UISegmentedControl.
Here it is:
#import "CardGameViewController.h"
#import "PlayingCardsDeck.h"
#import "CardMatchingGame.h"
enum CardGame {
twoCardGame,
threeCardGame
};
#interface CardGameViewController ()
#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 (weak, nonatomic) IBOutlet UISegmentedControl *numberOfCardsToPlayWith;
#end
#implementation CardGameViewController
//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]];
_game.numberOfCardsToPlayWith = [self selectNumberOfCardsToPlayWith];
}
return _game;
}
//creating a setter for the IBOutletCollection cardButtons
-(void)setCardButtons:(NSArray *)cardButtons {
_cardButtons = cardButtons;
[self updateUI];
}
- (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.isUnplayable;
cardButton.alpha = card.isUnplayable ? 0.3 : 1.0;
}
self.scoreCounter.text = [NSString stringWithFormat:#"Score: %d", self.game.score];
}
//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]];;
[self updateUI];
}
//sending an alert if the user clicked on new game button
- (IBAction)newGame:(UIButton *)sender {
UIAlertView* mes=[[UIAlertView alloc] initWithTitle:#"Think about it for a sec..?" message:#"This will start a new game" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil];
[mes show];
}
- (NSUInteger)selectNumberOfCardsToPlayWith {
switch (self.numberOfCardsToPlayWith.selectedSegmentIndex) {
case twoCardGame:
return 2;
case threeCardGame:
return 3;
default:
return 2;
}
[self updateUI];
}
//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.game = nil;
for (UIButton *button in self.cardButtons) {
Card *card = [self.game cardAtIndex:[self.cardButtons indexOfObject:button]];
card.isUnplayable = NO;
card.isFaceUp = NO;
button.alpha = 1;
}
self.notificationLabel.text = nil;
[self updateUI];
}
}
#end
Well, I don't see any addTarget: calls to segmented control. So you probably set them in Interface Builder. Check IB connections. If everything in IB seems ok -- try to addTarget: programmatically.
I think you'd be better off creating a selector and adding it to the segmented control target like so:
[segmentedControl addTarget:self
action:#selector(selectNumberOfCardsToPlayWith:)
forControlEvents:UIControlEventValueChanged];
- (NSUInteger)selectNumberOfCardsToPlayWith:(UISegmentedControl *)control {
switch (control.selectedSegmentIndex) {
case twoCardGame:
return 2;
case threeCardGame:
return 3;
default:
return 2;
}
[self updateUI];
}
This should work fine. Using similar code myself currently.
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?
I have read almost every post I can find on this site. I have found some extremely useful posts, but have not quite found how to solve my problem. I am trying to create a springboardish image viewer, were a user can slide the next image into place, but stop halfway though and go back. I have read on numerous posts that a PanGestureRecognizer is the way to go, but have been having a very difficult time with it.
In Storyboard, I created my ViewController and added a PanGestureRecognizer to the ViewController and added an action.
Here is my .h:
#interface PanViewController : UIViewController
- (IBAction)PanGesture:(UIPanGestureRecognizer *)recognizer;
#end
Here is my .m:
#interface PanViewController ()
#property (nonatomic, retain) NSArray *PhotoBundle;
#property (nonatomic, retain) NSMutableArray *PhotoArray;
#property (nonatomic, retain) NSMutableArray *ImgViewArray;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//set i to the correct range if needed.
if (i > 0 && 1 <= _PhotoArray.count) {
}
else i = 0;
//create photo bundle from directory
_PhotoBundle = [[NSBundle mainBundle] pathsForResourcesOfType:#".jpg"inDirectory:#"Otter_Images"];
//create array from bundle of paths.
_PhotoArray = [[NSMutableArray alloc] initWithCapacity:_PhotoBundle.count];
for (NSString* path in _PhotoBundle)
{
[_PhotoArray addObject:[UIImage imageWithContentsOfFile:path]];
}
//create initial image view
UIImage *currentImage = [[UIImage alloc] init];
currentImage = [_PhotoArray objectAtIndex:i];
UIImageView *currentIV = [[UIImageView alloc]initWithFrame:CGRectMake(100,100, 100, 100)];
[currentIV setImage:currentImage];
[self.view addSubview:currentIV];
//increment i to progress through array.
i++;
}
- (IBAction)PanGesture:(UIPanGestureRecognizer *)recognizer {
[self GestureDirection:recognizer];
}
- (void)GestureDirection:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint direction = [gestureRecognizer velocityInView:self.view];
if(direction.x > 0)
{
NSLog(#"gesture went right");
UIImage *nextImg = [[UIImage alloc] init];
nextImg = [_PhotoArray objectAtIndex:i];
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(100,100, 100, 100)];
UIImageView *nextIV = [[UIImageView alloc] initWithFrame:CGRectMake(100,100, 100, 100)];
[nextIV setImage:nextImg];
[newView addSubview:nextIV];
self.view = newView;
}
else
{
NSLog(#"gesture went left");
}
}
It shows the next image on the pan, but then there is no PanGestureRecongizer on the new View that is loaded in. I thought that when I added the PANGesture in Storyboard, it would make it available to all views in the ViewController.
This may be a separate question, but how do I get the animation to work on the PanGesture? I was incorrectly (obviously) under the impression that a Pan would do the slide in effect.
Thank you all for your help!!
So like Tom Irving said, I used a UIScrollView with paging. I did play around with creating a new gesture recognizer each time and adding paging, but it was really complicated and I was never able to get it to work.
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
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