For my iPad app, I am programmatically creating several UIImage views that I display on the screen. The code looks basically like this:
for(ModelObject *model in ModelsList){
//create a UIImage view from the model object
UIImageView *icon = [[UIImageView alloc] initWithFrame:model.icon_frame];
icon.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:model.icon_path ofType:#"png"]];
//add the imageview to a mutable array to keep track of them
[myImageViews addObject:icon];
// add the view as a subview
[self.view addSubview:icon];
}
So now I have a bunch of icons displayed on the screen. But I would like to intercept touch events from the UIImageViews that I created programmatically, so that it calls some other method, preferably with an argument containing the sender's id or some other distinguishing information that I can use to determine which UIImageView was touched.
What is the best practices way of accomplishing this?
I am new to iOS so recommended reading would also be greatly appreciated.
Please provide any applicable feedback, as I have no idea if this is common practice or even a decent way of doing things...
So basically what I did was keep a dictionary of ids that maps between the view object and the model objects, and then I look up the id of the sending view and find the appropriate model object, (I will then use that model object to load another view)
The code looks like this:
// in header
#property(nonatomic, retain) NSMutableDictionary *icons_to_models
// after creating a UIButton called icon
[icon setBackgroundImage:model.image forState:UIControlStateNormal];
[icon addTarget:self action:#selector(tappedIcon:) forControlEvents:UIControlEventTouchUpInside];
NSNumber *key = [NSNumber numberWithUnsignedInt:[icon hash]];
[icons_to_models setObject:model forKey:key];
...
//match sender to the icon that was pressed and
-(void)tappedIcon:(id)sender{
NSNumber *key = [NSNumber numberWithUnsignedInt:[sender hash]];
ModelObject *model = [icons_to_models objectForKey:key];
NSLog(#"tapped on: %#", model.name);
}
Related
I have an outlet called myImage for an imageView that is set up with an animation of 5 images. With a Tap Gesture Recognizer on it, I want to have the users tap on the correct image as it quickly passes to move onto the next level.
There is an error in my code and this does not work:
- (void)viewDidLoad {
[super viewDidLoad];
self.myImage.animationImages =
[NSArray arrayWithObjects:
[UIImage imageNamed:#"examplePic1.png"],
[UIImage imageNamed:#"examplePic2.png"],
[UIImage imageNamed:#"correctPic.png"],
[UIImage imageNamed:#"examplePic4.png"],
[UIImage imageNamed:#"examplePic5.png"],
nil];
[self.myImage setAnimationRepeatCount:0];
self.myImage.animationDuration = 1;
[self.myImage startAnimating];
}
- (IBAction)TapGestureRecognizer:(id)sender {
if (self.myImage.image = [UIImage imageNamed:#"correctPic.png"]) {
// Launch next level
}
else {
// Vibrate Device and subtract points from score
}
}
You have a fundamental misconception in your code about objects and equality. I assume that you meant "==" in your TapGestureRecognizer method. However, even if you changed the = to == would not give you what you want.
The expressions like
[UIImage imageNamed:#"examplePic1.png"]
call a class method of UIImage that creates a new UIImage object. You have created 5 of these objects, put them into an array, an set the animationImage array of your UIImage object to these images. These objects are all different objects from myView.image: I can tell because I can see from your code that you have assigned 5 new objects to the animationObjects array. Your == test cannot ever work because you are comparing self.image to a brand new UIImage object.
What you are trying to do is determine which image the user touched. There is no method in UIImageView that tells you which animation UIImage is currently showing. You are assuming that the image property of the view will be changed as the animation images are displayed, but there is no reason for this assumption to be true: it is certainly not stated in the documentation. Even if you corrected your code (by doing something like this):
if(self.image == [self.animationImages objectAtIndex:2]){
}
your code would not be correct.
If I were trying to do what you want, I would create a UIView with 5 UIImageView as subview, each with their own gesture recognizer. I would use the frame property of each subview to do my animation.
Looks like the options are to determine which image "should" be showing based upon the startTime of the animation.
Determine which of its animationImages a UIImageView is displaying?
Or, just use a timer to set the currently displayed image.
Detect which image was clicked in UIImageView with animationImages
I have a simple application with only one button.
When user clicks on it, he can take a pictures, click "Done" in the camera and then can save it and see it on the ScrollView.
However, when he is taking another picture and clicking "Done" in the camera, the new picture overwrites the previous one and he can see only the picture that he has taken.
I want to save all these pictures and then if the user takes 4-5 pictures, he can see all of them by scrolling them.
Here is my code, im not sure why it is not working.
- (IBAction)Camera:(id)sender {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:picker animated:YES completion:NULL];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
NSMutableArray *images = [NSMutableArray arrayWithCapacity:[info count]];
for (UIView *v in [_scrollView subviews]) {
[v removeFromSuperview];
}
[self dismissViewControllerAnimated:YES completion:nil];
UIImage* image=[info valueForKey:#"UIImagePickerControllerEditedImage"];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
UIImageView *imageview = [[UIImageView alloc] initWithImage:image];
[_scrollView addSubview:imageview];
[images addObject:image];
self.chosenImages = images;
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
[_scrollView setPagingEnabled:YES];
}
And in header i have an array of chosenImages
#property (nonatomic, copy) NSArray *chosenImages;
There are lots of things wrong with your code. Every time the user picks an image, you remove all the subviews from your scroll view, then add just the single selected image.
You have an array chosenImages which you might use to hold all the selected images, but you replace it with a new array, images, which only contains the single newly selected image.
You allocate images as a mutable array with enough capacity for the count of the info dictionary you get in didFinishPickingMediaWithInfo, but there's no point to doing that. The count of that dictionary is going to be the number of key/value pairs in the dictionary, which does not reflect the number of images the user selected (I believe the number of images the user selected will always be 1, ignoring the different formats like the original image and the (possible) edited image.)
I would suggest leaving the previous images in your scroll view, tracking the number and placement of those images, and simply adding a new image to the scroll view after the user picks.
You might think about using a UIPageViewController in page scroll mode rather than a raw scroll view. It manages paged scrolling much better than a scrollview will. There is a sample app from Apple called PhotoScroller (link) that even supports tiled rendering of the images, pinch-to-zoom, and panning around an image. It would be pretty easy to stitch that code into your app for handling these images. (You might not decide to deal with the tiled images - I adapted the PhotoScroller code, with tiled image support, into a client project, and generating the image tiles is a fair amount of work.)
Assuming that self.chosenImages is of NSMutableArray type, try this
[self.chosenImages addObjectsFromArray:images]
instead of
self.chosenImages = images;
and if it isn't mutable, you can always set it like so:
NSMutableArray *newArray = [self.chosenImages mutableCopy];
newArray = [newArray addObjectsFromArray:images];
self.chosenImages = [newArray copy];
I have a view with 10 buttons and I want to use information from an array to set how many of the buttons are visible and what their title is.
The buttons are named option1BTN...option10BTN. The array has different data and size depending on what the user selects and I want to buttons to also reflect the changes.
The code below shows a for-do loop, that sets which button is visible and the button title
for (int i=0; i == [optionsArray count]; i++) {
self.option1BTN.hidden = NO;
[self.option1BTN setTitle:[optionsArray objectAtIndex:i] forState:UIControlStateNormal];
}
How can I change the button name (programatically) in the loop so that depending on the size of the array it changes to option1BTN then option2BTN...optionXBTN and so on?
NSArray *buttons = #[self.option1BTN, self.option2BTN]; // add all the buttons here
for (int i=0; i < buttons.count; i++) {
UIButton *button = buttons[i];
button.hidden = NO;
[button setTitle:[optionsArray objectAtIndex:i] forState:UIControlStateNormal];
// the previous line can be re-written as
//[button setTitle:optionsArray[i] forState:UIControlStateNormal];
}
The difference between this approach and using an IBOutletCollection (as Jeff suggests in his answer) is that the outlet collection does not guarantee the order of its items. If the order is important to you, you need to specify it yourself, like in my code snippet above.
You have two main options, one is to use an IBOutletCollection (if your buttons are created in Interface Builder). Each UIButton will be stored in the collection and you can retrieve the right button in the loop from the collection.
Example:
#property (nonatomic, strong) IBOutletCollection(UIButton) NSArray *buttons;
In IB, you will need to link each button to the IBOutletCollection. Then in your code you will be able to do:
for (int i = 0; i < optionsArray.count; i++) {
UIButton *tempButton = self.buttons[i];
tempButton.hidden = NO;
[tempButton setTitle:[optionsArray objectAtIndex:i] forState:UIControlStateNormal];
tempButton.hidden = NO;
}
The ordering of an IBOutletCollection is not always guaranteed so you may need to use a combination of IBOutletCollection and view tagging (see end of post). For more information on IBOutletCollection's see NSHipster's article
Second option is if your buttons are created programmatically, store each button in an NSMutableArray as you create it:
#property (nonatomic, strong) NSMutableArray *buttons;
UIButton *myButton;
// Do something...
[self.buttons addObject:option1BTN];
Then use the same loop as before.
A third (not recommended option), would be to tag each UIButton and then you can use the viewWithTag: method to retrieve the button. E.g. using the above code again but getting the button with:
UIButton *tempButton = [self.view viewWithTag:i];
If this is a noob question, I apologize in advance. I just want to clarify how the relationship between the objects inside an array relates to the actual object. For example:
UILabel *labelOne = //init stuff
[self.view addSubview: labelOne];
[labelArray addObject: labelOne];
Now if I change the property of the object inside the array like so:
[[labelArray objectAtIndex:0] setText:#"NEW STRING"];
Am I updating just what is inside the array or will it also update my initial UILabel and therefore reflect the change on the view? Are there any situations where the results might differ? (i.e. releasing initial UILabel with object still in the array, etc.)
The array stores a strong reference to the object, but doesn't copy it. So there is only ever one label. So the code you showed will update the label's text.
It will change the UILabel you have on screen since NSArray only holds references to objects, it doesn't make copies. So whatever labels you are putting into the array are just references to what you've already created.
Yes it will update your initial UILabel and therefore reflect the change on the UIView as reference of UILabel is stored in array.
IT will update your initial UILabel as NSArray is holding the reference of original UILabel and not there copies
Your labelArray will contain pointers to each adding object. And it increment retain counter for each adding object. So [[labelArray objectAtIndex:0] setText:#"NEW STRING"]; will change labelOne.text too, because you reference to one UILabel object.
UILabel *labelOne = //init stuff
[self.view addSubview: labelOne];
[labelArray addObject: labelOne];
[[labelArray objectAtIndex:0] setText:#"NEW STRING"];
The label which you add to labelArray is the same object as initial labelOne.
But if you what to avoid such behavior you can add a copy of labelOne to labelArray by using copy method.
[labelArray addObject: [labelOne copy]];
This will give you a copy of labelOne object in you labelArray. And this copy will not be changed after you change label text.
So after you get labelOne from array the label will be in initial state.
Yes this will change the value of the label text displayed on the view. See here I have simulate the scenario..
Code....
UILabel *labelOne = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, 200, 20)];
labelOne.text = #"Dilip";
[self.view addSubview: labelOne];
NSMutableArray *labelArray =[[NSMutableArray alloc] init];
[labelArray addObject: labelOne];
[[labelArray objectAtIndex:0] setText:#"Hi Dilip"];
And here is the output..
You can see that instead of text "Dilip" there is displayed text is "Hi Dilip".
The labelArray stores only the reference of the labelOne. so if you change anything in the labelArray affect the labelOne.
Here's my code:
if([pantallas objectForKey:par]){
UIView *vista= [[UIView alloc] initWithFrame:self.Botones.frame];
vista.backgroundColor= [UIColor brownColor];
CGSize la= CGSizeMake(50,60);
int cuantos= [part2 count];
NSArray *arr= [COLGenerales tileN:cuantos RectsOfSize:la intoSpaceOf:vista.frame withMaxPerRow:5 spaceVertical:10 spaceHorizontal:10];
for(int j=0; j<cuantos; j++){
UIButton *bot= [[UIButton alloc] initWithFrame:[[arr objectAtIndex:j] CGRectValue]];
bot.tag=j;
bot.titleLabel.text=par;
bot.titleLabel.hidden=true;
bot.imageView.image = [UIImage imageNamed:[[part2 allKeys] objectAtIndex:j]];
[bot addTarget:self action:#selector(registrar:) forControlEvents:UIControlEventTouchDown];
[vista addSubview:bot];
}
[pantallas setObject:vista forKey:par];
self.Botones= vista;
}else{
self.Botones= [pantallas objectForKey:par];
}
Botones is a simple view embedded into the view this class controls (first initiated by the Nib file), the class method of COLGenerales returns an array of CGRects coded as NSValues, and registrar: is a local method.
Everything gets properly set (I've thoroughly checked this with the debugger). The view gets successfully created, set, and added to the dictionary.
However, I absolutely never get the actual screen to change. I even included the background color change just to check if it isn't some kind of problem with the buttons. Nothing. Any suggested solution to this?
A property that is an IBOutlet does not have an intrinsic connection to the view hierarchy—it only makes it possible to populate that property from a xib. When you set self.Botones, you'll need to do something like the following:
[self.Botones removeFromSuperview];
self.Botones = newValue;
[self.BotonesSuperview addSubview:self.Botones];
If you update self.Botones in many places, and you always want the change reflected on-screen, you could add this into a setter implementation:
-(void)setBotones:(UIView*)newValue {
if (newValue != _Botones) {
[_Botones removeFromSuperview];
_Botones = newValue;
[self.BotonesSuperview addSubview:_Botones];
}
}
I recommend using a UINavigation controller that houses these two views.
You can reference this link Swapping between UIViews in one UIViewController
Basically, you create one view, removeSubview for the first and then add the second one with addSubview!
[view1 removeFromSuperview];
[self.view addSubview: view2];
Other reference sources:
An easy, clean way to switch/swap views?
How to animate View swap on simple View iPhone App?
Hopefully this helps!