how to create an action for a clicked image from imageviewanimation - objective-c

[SOLVED - SEE THE ANSWER]
i want to have three images grouping as animation, and i can be able to click any of these three images to define an action. In the other words, i'd like to know the ways to create separate action/ event for each image in the animation
that will be great if you know there's a way to do with UIButton instead of UIImageview
here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Load images
NSArray *imageNames = #[#"hello.png",#"bye.png",#"helloagain.png"];
NSMutableArray *images = [[NSMutableArray alloc] init];
for (int i = 0; i < imageNames.count; i++) {
[images addObject:[UIImage imageNamed:[imageNames objectAtIndex:i]]];
}
// Normal Animation
UIImageView *animationImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60, 95, 86, 90)];
animationImageView.animationImages = images;
animationImageView.animationDuration = 1.5;
[self.view addSubview:animationImageView];
[animationImageView startAnimating];
animationImageView.userInteractionEnabled = TRUE;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(bannerTapped:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[animationImageView addGestureRecognizer:singleTap];
}
- (void)bannerTapped:(UIGestureRecognizer *)gestureRecognizer {
//action here
}

You don't want to use an animating image view to do this, since you can't get the image from the image view when you click on it. Instead, use a timer to switch the images. Something like this should work,
#interface ViewController ()
#property (strong,nonatomic) NSMutableArray *images;
#property (strong,nonatomic) UIImageView *animationImageView;
#end
#implementation ViewController {
NSInteger counter;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *imageNames = #[#"hello.png",#"bye.png",#"helloagain.png"];
self.images = [[NSMutableArray alloc] init];
for (int i = 0; i < imageNames.count; i++) {
[self.images addObject:[UIImage imageNamed:[imageNames objectAtIndex:i]]];
}
self.animationImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60, 95, 86, 90)];
self.animationImageView.image = self.images[0];
[self.view addSubview:self.animationImageView];
self.animationImageView.userInteractionEnabled = TRUE;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(bannerTapped:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self.animationImageView addGestureRecognizer:singleTap];
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(changeImage:) userInfo:nil repeats:YES];
}
-(void)changeImage:(NSTimer *) timer {
if (counter == self.images.count - 1 ) {
counter = 0;
}else {
counter ++;
}
self.animationImageView.image = self.images[counter];
}
- (void)bannerTapped:(UIGestureRecognizer *)gestureRecognizer {
UIImage *img = self.animationImageView.image;
NSInteger indx = [self.images indexOfObject:img];
NSLog(#"The index is: %ld", indx);
// use indx value in a switch statement or if-else block to choose your action
}
After Edit
If you want different times between images, I would use a different approach. Instead of using a timer, just call the following method to start the animation,
-(void)changeImage {
if (counter == self.images.count - 1 ) {
counter = 0;
}else {
counter ++;
}
self.animationImageView.image = self.images[counter];
if (counter == 1) {
[self performSelector:#selector(changeImage) withObject:nil afterDelay:2];
}else{
[self performSelector:#selector(changeImage) withObject:nil afterDelay:1];
}
}
This would give you a 2 second delay between the 2nd and 3rd image, but 1 second between the others.

My proposed hacky solution assuming you know number of images, and animation time.
1. Schedule a timer for imageCount/animationTime milliseconds.
2. In the timer's callback, increment the above property on the UIImageView
3. In -bannerTapped use that index to decide what event to throw corresponding to the image currently being animated.
-(void)setup {
// Load images
NSArray *imageNames = #[#"happy",#"sad",#"popeye_village"];
NSMutableArray *images = [[NSMutableArray alloc] init];
for (int i = 0; i < imageNames.count; i++) {
[images addObject:[UIImage imageNamed:[imageNames objectAtIndex:i]]];
}
// Normal Animation
imageview.frame = CGRectMake(60, 95, 86, 90);
imageview.animationImages = images;
imageview.animationDuration = 6;
[imageview startAnimating];
imageview.userInteractionEnabled = TRUE;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(bannerTapped:)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[imageview addGestureRecognizer:singleTap];
[NSTimer scheduledTimerWithTimeInterval:((double)imageview.animationDuration / images.count)
target:self
selector:#selector(updateAnimatingImage)
userInfo:nil
repeats:YES];
}
- (void)bannerTapped:(UIGestureRecognizer *)gestureRecognizer
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"Clicked on Image index %li", imageview.indexOfAnimatingImage] message:#"" delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
[alert show];
}
-(void)updateAnimatingImage
{
imageview.indexOfAnimatingImage = ++imageview.indexOfAnimatingImage % (imageview.animationImages.count);
NSLog(#"index currently showing is %li",imageview.indexOfAnimatingImage);
}
While very similar to the above solution, this still uses UIImageView's animationImages.
Here is my fork of your project in case you want to play with a working solution.

Related

Objective-C append image thumbnails onto the end of a view

I'm trying to append images onto a view and have them display a thumbnail in a nice neat row. The issue I'm having is that it appears all images simply get added on top on each other in the corner as only the last image in the array is dispayed.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.notesField.delegate = self;
NSString *userName = ((ontracAppDelegate*)[UIApplication sharedApplication].delegate).userName;
self.textField.text = [NSString stringWithFormat:#"Date Created: %# \nCreated By: %#",[NSDate date], userName];
if (self.noteText != nil) {
self.notesField.text = self.noteText;
self.notesField.editable = NO;
self.textLabel.hidden = TRUE;
}
[self.view resignFirstResponder];
if ([self.imageArray count] != 0) {
for (UIImage *image in imageArray) {
NSLog(#"Image %#", image);
[self addImageToScreen:image];
}
}
NSLog(#"imageArray %#", imageArray);
NSLog(#"self.textField.text %#", self.textField.text );
NSLog(#"self.notesField.text %#", self.notesField.text);
}
-(void) addImageToScreen:(UIImage*) image;
{
int adjustHeight = 0;
int adjustWidth = 10;
int imagesInARow = 7;
int imageHeight = 75;
int imageWidth = 75;
int count = [self.imageArray count];
self.numberOfPhotosLabel.text = [NSString stringWithFormat:#"%i",count];
if (count > 1 && count < imagesInARow) {
adjustHeight = 0;
adjustWidth = (20 * [self.imageArray indexOfObject:image]) + ([self.imageArray indexOfObject:image] * imageWidth);
}
UIButton* container = [[UIButton alloc] init ];
CGRect frame = CGRectMake(adjustWidth, 5, imageWidth, imageHeight);
[container setTag:[self.imageArray indexOfObject:image]];
[container setImage:image forState:UIControlStateNormal];
[container setFrame:frame];
[container addTarget:self action:#selector(displayFullScreenImage:) forControlEvents:UIControlEventAllTouchEvents];
UIStackView *photosView = [[UIStackView alloc] initWithFrame:frame];
container.frame = photosView.bounds;
[photosView addSubview:container];
[self.view addSubview:photosView];
}
Use addArrangedSubview of UIStackView instead of addSubview.
And you should add all the Buttons to the same UIStackView. So, add a property for the UIStackView to your class, initialize it in viewDidLoad (or in the storyboard) and add the buttons to that stack view in addImageToScreen.

UIScrollView with UIImageview and gestures using transitionFromView acting strange

I made a view which holds a UIScrollview:
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10, 65, 300, 188)];
//BackViews will hold the Back Image
BackViews = [[NSMutableArray alloc] init];
for (int i=0; i<BigPictures.count; i++) {
[BackViews addObject:[NSNull null]];
}
FrontViews = [[NSMutableArray alloc] initWithCapacity:BigPictures.count];
[self.pageControl setNumberOfPages:BigPictures.count];
Then I add several UIImageviews containing images:
//BigPictures holds objects of type UIImage
for (int i = 0; i < BigPictures.count; i++) {
UIImageView *ImageView = [[UIImageView alloc] initWithImage:[BigPictures objectAtIndex:i]];
ImageView.frame = [self.scrollView bounds];
[ImageView setFrame:CGRectMake(self.scrollView.frame.size.width * i, ImageView.frame.origin.y, ImageView.frame.size.width, ImageView.frame.size.height)];
//this saves the FrontView for later (flip)
[FrontViews insertObject:ImageView atIndex:i];
[self.scrollView addSubview:test];
}
// Detect Single Taps on ScrollView
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flip)];
[self.scrollView addGestureRecognizer:tap];
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * BigPictures.count, self.scrollView.frame.size.height);
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
Ok so far so good. Now the method which does the flipImage part:
- (void)flip {
int currentPage = self.pageControl.currentPage;
UIView *Back = nil;
if ([BackViews objectAtIndex:currentPage] == [NSNull null]) {
//CreateBackView is just creating an UIView with text in it.
Back = [self CreateBackView];
[BackViews replaceObjectAtIndex:currentPage withObject:Back];
[UIView transitionFromView:[[self.scrollView subviews] objectAtIndex:currentPage] toView:[BackViews objectAtIndex:currentPage] duration:0.8 options:UIViewAnimationOptionTransitionFlipFromLeft completion:NULL];
} else {
[UIView transitionFromView:[[self.scrollView subviews] objectAtIndex:currentPage] toView:[FrontViews objectAtIndex:currentPage] duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight completion:NULL];
[BackViews replaceObjectAtIndex:currentPage withObject:[NSNull null]];
}
[self.view addSubview:Back];
[self rebuildScrollView];
}
This is what rebuildScrollView does:
- (void)rebuildScrollView
{
for (UIView *subview in self.scrollView.subviews) {
[subview removeFromSuperview];
}
for (int i = 0; i < BigPictures.count; i++) {
if ([BackViews objectAtIndex:i] == [NSNull null]) {
[self.scrollView addSubview:[FrontViews objectAtIndex:i]];
} else {
[self.scrollView addSubview:[BackViews objectAtIndex:i]];
}
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * BigPictures.count, self.scrollView.frame.size.height);
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
}
So the behavior is the following:
If I click on the first image (1 of 3 in scrollview) the effect is the way I want it, meaning the frontimage turns around and shows the empty (white) back with some text in the middle
If I click on the second image, the image turns but the back is completely empty showing the grey background of the window. If I scroll to the other images, the still show the front image (as expected)
Now I click on the third image and its the same as 1) great.
Current layout is now [BackView, Nothing, Backview)
Lets run that again. But now I click on the last image and its the same as 2) :(
Any ideas whats going wrong here?
EDIT: Some new findings. I doubled the amount of pictures and this is how Front and Backviews are placed (after flipping each one). P = Picture & B = Backview.
P_1(B_1) - actually the only correct one
P_2(empty - should be B_2)
P_3(B_2 - should be B_3)
P_4(empty - should be B_4)
P_5(B_3 - should be B_5)
P_6(empty - should be B_6)
Did a complete rebuild and now it works. Really strange because I used the exact same code.

Activity Indicator while loading images inside UIScrollView

I have UIScrollView that contains images from the server.
I should put Activity Indicator while the image is currently loading.
UIScrollView contains dynamic number of images from the server.
May I know how can I add activity indicators on each page while the image is loading and remove once image is loaded.
Here's my code to retrieve images:
NSDictionary *items = [NSDictionary dictionaryWithObject:dictInfo forKey:#"images"];
imageList = [items objectForKey:#"images"];
NSArray *img = [imageList objectForKey:#"list"];
NSInteger imgCount = [img count];
buttonArray = [[NSMutableArray alloc] init];
for (int i=0; i<imgCount; i++) {
NSDictionary *imgDict = [img objectAtIndex:i];
// REQUEST FOR IMAGES
NSString *imgPath = [imgDict objectForKey:#"image_slot"];
NSString *imgURL = imgPath;
__block ASIHTTPRequest *requestImage = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imgURL]];
[requestImage setCompletionBlock:^{
imgView = [UIImage imageWithData:[requestImage responseData]];
if (imgURL.length) {
[pendingRequests removeObjectForKey:imgURL];
}
scrollView.userInteractionEnabled = YES;
scrollView.exclusiveTouch = YES;
scrollView.canCancelContentTouches = YES;
scrollView.delaysContentTouches = YES;
scrollView.bounces = NO;
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
SWTUIButton *imgBtn = [[SWTUIButton alloc] initWithFrame:frame];
imgBtn.url = [requestImage.userInfo objectForKey:#"rURL"];
[imgBtn setImage:imgView forState:UIControlStateNormal];
imgBtn.backgroundColor = [UIColor clearColor];
[imgBtn addTarget:self action:#selector(buttonpushed:) forControlEvents:UIControlEventTouchUpInside];
[scrollView addSubview:imgBtn];
[buttonArray addObject:imgBtn];
[imgBtn release];
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * img.count, self.scrollView.frame.size.height);
}];
I highly suggest you use NINetworkImageView from https://github.com/jverkoey/nimbus project.
It's very light and useful.
It has a delegate method to let you know when an image is loaded.
What you basically need to do is:
1. create an NINetworkImageView for each page, just like you do with UIImageView, and call set
NINetworkImageView* networkImageView = [[[NINetworkImageView alloc] initWithImage:initialImage]
autorelease];
networkImageView.delegate = self;
networkImageView.contentMode = UIViewContentModeCenter;
[networkImageView setPathToNetworkImage:
#"http://farm3.static.flickr.com/2484/3929945380_deef6f4962_z.jpg"
forDisplaySize: CGSizeMake(kImageDimensions, kImageDimensions)];
https://github.com/jverkoey/nimbus/tree/master/examples/photos/NetworkPhotoAlbums
the add the indicator to the networkImageView as a subview.
implement the delegate as follows:
-(void)networkImageView:(NINetworkImageView *)imageView didLoadImage:(UIImage *)image {
[imageView removeAllSubviews];
}
the end result would be a much smaller code for doing the same thing.

Why this brings about SIGABRT error?

I have some sources below.
- (void)Button:(UIButton *)button {
NSString *imageName = ((UIButton *)[self.view viewWithTag:button.tag]).titleLabel.text;
}
- (void)viewDidLoad {
NSMutableArray *_array = [[NSMutableArray alloc] init];
NSInteger iCount = [_array count];
for (i = 0; iCount > i; i++) {
UIButton *btn = [[UIButton alloc] init];
btn.titleLabel.text = [[_array objectAtIndex:i] objectForKey:#"FILE"];
btn.tag = i;
[btn addTarget:self action:#selector(Button:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
[btn release];
}
When I access Button method with 0 index tag, I get SIGABRT error.
What can I do ?
Read your console output properly, by default every view has the tag '0', so it can be crashed because it is taking some other view besides of uibutton, and may be that view don't not have the titleLabel property.Because it is the property of UIButton.

Cocoa Touch - Looping Help

I need help with some programming logic... I need to loop this method to display the math problem in my labels then sleep(5) and then loop again. Anything I've tried ends of freezing the program. PLEASE help! I've been tearing my hair out trying everything I know!
EDIT: I edited the code to this, After 3 seconds it fades away the label displaying the problem but then it crashed and the debugger displays 2010-08-06 10:43:27.776 Reactor [13444:207] modifying layer that is being finalized - 0x5c13500
//quickfire problems
-(void)simpleMath{ //"Tap when the answer is X"
isTimeToTap = NO;
int problemSelector = arc4random() % 4;
NSArray *mathProblems = [[NSArray alloc] initWithObjects:#"6 - 1",#"2 + 3",#"3 x 2",#"3 x 1",#"2 x 4",nil]; //correct index 2
NSArray *mathAnswers = [[NSArray alloc] initWithObjects:#"5",#"5",#"6",#"3",#"8",nil]; //correct index 2
if ([mathAnswers objectAtIndex:problemSelector] == #"6") {
isTimeToTap = YES;
}
if (ranBefore == NO) { //create labels
//tell when to tap
tapTellerTop.text = #"Tap when the answer is 6!";
tapTellerBottom.text = #"Tap when the answer is 6!";
//make bottom label
mathDisplayBottom = [[UILabel alloc] initWithFrame:CGRectMake(15, 250, 242, 92)];
mathDisplayBottom.font = [UIFont fontWithName:#"Helvetica" size: 96.0];
mathDisplayBottom.textColor = [UIColor whiteColor];
mathDisplayBottom.backgroundColor = [UIColor clearColor];
[self.view addSubview: mathDisplayBottom];
//make top label
mathDisplayTop = [[UILabel alloc] initWithFrame:CGRectMake(55, 120, 242, 92)];
mathDisplayTop.font = [UIFont fontWithName:#"Helvetica" size: 96.0];
mathDisplayTop.textColor = [UIColor whiteColor];
mathDisplayTop.backgroundColor = [UIColor clearColor];
[self.view addSubview: mathDisplayTop];
//rotate top label
mathDisplayTop.transform = CGAffineTransformMakeRotation(180.0 /180.0 * M_PI);
}
//if ran before just update the text
mathDisplayBottom.text = [mathProblems objectAtIndex:problemSelector];
mathDisplayTop.text = [mathProblems objectAtIndex:problemSelector];
ranBefore = YES; //if its here. its been ran.
//run timer wait for (3) then loop again until userTaps = YES
[self performSelector:#selector(simpleMath) withObject:nil afterDelay:3.0f];
[mathProblems release];
[mathAnswers release];
[mathDisplayBottom release];
[mathDisplayTop release];
}
Sleep will stop your application from running or responding at all. Here is a quick partial example of a view controller you could use. This assumes you wire up the tap button and only uses one of the labels, etc. but you can play around with it. Also, this won't deal with memory issues or anything, so you'd add support for that.
But, once this view controller is created and the view installed, the math problem will update every 5 seconds. If the user presses the button and the answer is valid, we log a success message, otherwise we log fail message.
#interface myMathController : UIViewController {
NSArray* mathProblems;
NSIndexSet* validIndexes;
NSUInteger currentIndex;
NSTimer* timer;
}
#property(nonatomic,assign) IBOutlet UILabel* mathDisplayLabel;
- (void)updateProblem:(NSTimer*)timer;
- (IBAction)userTap;
#end
#implementation myMathController
- (void)viewDidLoad
{
[super viewDidLoad];
mathProblems = [[NSArray alloc] initWithObjects:#"6 - 1",#"2 + 3",#"3 x 2",#"3 x 1",#"2 x 4",nil];
// Add all valid answers
NSMutableIndexSet* tempIndexes = [[NSMutableIndexSet alloc] init];
[tempIndexes addIndex:2];
validIndexs = [tempIndexes copy];
[tempIndexes release];
timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:5.0 target:self selector:#selector(updateProblem:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)updateProblem:(NSTimer*)timer
{
currentIndex = arc4random() % [mathProblems count];
[mathDisplayLabel setText:[mathProblems objectAtIndex:currentIndex]];
}
- (void)userTap
{
if( [validIndexes containsIndex:currentIndex] ) {
NSLog(#"This user is SMART!");
} else {
NSLog(#"This user needs more work!");
}
}
- (void)dealloc
{
[timer invalidate];
[timer release];
[mathProblems release];
[validIndexes release];
[super dealloc];
}
#end
You shouldn't sleep(), ever. To invoke a function repeatedly, call:
[NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(doStuff:)
userInfo:nil repeats:YES];
then define the function:
-(void)doStuff:(NSTimer*)timer
{
// stuff
if ( iAmDone ) [timer invalidate];
}
OR if you want to fire another call, after 5 seconds, you could call
if ( !iAmDone ) [self performSelector:#selector(simpleMath) afterDelay:5];
at the end of simpleMath.