I've been trying to figure out how to have a UIImageView with a next and previous button which when clicked will go to the next or previous image in the array. So far, here's what I have.
In my .h file I have declared:
IBOutlet UIImageView *imageView;
NSArray *imageArray;
And I also added:
-(IBAction) next;
In the .m file I have:
-(void) viewDidLoad;
{
imageArray = [[NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],
[UIImage imageNamed:#"2.png"],
nil] retain];
}
Now here is where I'm struggling. I have the IBAction defined as follows in my .m:
-(IBAction)next
{
if (currentImage + 1 == [imageArray count])
{
currentImage = 0;
}
UIImage *img = [imageArray objectAtIndex:currentImage];
[imageView setImage:img];
currentImage++;
}
My problem is that I do not know where to define the currentImage index integer or how to define it. Is it in the header? The implementation? And how exactly do I declare it?
To be honest I'm not even 100% sure the code I currently have is right, although I think if I can define the index, it will work.
Any advice?
This is how I would do it. (I've changed the name of a few instance variables: currentImage sounds like it could be a pointer to an image (UIImage *) rather than just an integer value; adding Index to the end makes that more clear. It may be obvious now that it's an integer, but when you revisit this code (or other code you write) in a month, it may be less obvious; or maybe that's just me)...
MDSlideshowController.h:
#import <UIKit/UIKit.h>
#class MDBilboBaggins;
#interface MDSlideshowController : NSObject {
IBOutlet UIImageView *imageView;
NSArray *imageArray;
NSUInteger currentImageIndex;
BOOL someOtherVariable;
MDBilboBaggins *bilboBaggins;
// keep adding more instance variables as needed
}
- (IBAction)previous:(id)sender;
- (IBAction)next:(id)sender;
#end
MDSlideshowController.m:
#import "MDSlideshowController.h"
#import "MDBilboBaggins.h"
// you could perhaps define currentImageIndex here, but see notes below:
// NSUInteger currentImageIndex = 0;
#implementation MDSlideshowController
// `currentImageIndex` is automatically initialized to 0 during init
// `someOtherVariable` is automatically initialized to 0 (NO) during init
-(void) viewDidLoad
{
imageArray = [[NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],
[UIImage imageNamed:#"2.png"],
nil] retain];
[imageView setImage:[imageArray
objectAtIndex:currentImageIndex]];
}
- (IBAction)previous:(id)sender {
if (currentImageIndex == 0) {
currentImageIndex = [imageArray count] - 1;
} else {
currentImageIndex--;
}
[imageView setImage:[imageArray objectAtIndex:currentImageIndex]];
}
- (IBAction)next:(id)sender {
if (currentImageIndex + 1 >= [imageArray count]) {
currentImageIndex = 0;
} else {
currentImageIndex++;
}
[imageView setImage:[imageArray objectAtIndex:currentImageIndex]];
}
#end
Basically, you put instance variables right underneath the ones you've already defined. They can be of almost any type. You can use the types Cocoa Touch knows about, or classes you make yourself. In this example, I said that there was a special class named MDBilboBaggins by using the #class MDBilboBaggins statement. Then, I add the #import "MDBilboBaggins.h" part in the .m file: this can help speed up compile times.
As I mentioned in the comment, you could perhaps define the currentImageIndex variable inside the .m file, however, it would be a static variable that is common to, and shared, by all instances of the class. This can be useful in some situations, but create issues in others. For example, imagine we have 2 slideshow controllers, each with different images created and showing slides in 2 different windows. If they were modifying a shared currentImageIndex variable, they'd mess each other up if you would switch between them and start clicking Previous and Next indiscriminately. That's why in this case it might make more sense to just make it an instance variable by defining it other your other instance variables in the .h file.
[EDIT] Regarding the :(id)sender parameter: in this example, it wasn't used at all, I generally do it out of habit, since in some circumstances, it can save a lot of code, and simplify things dramatically. For example, say you had 9 different buttons and you wanted each button to load a specific image (or perform a specific operation). Now, you could define 9 separate methods like - (IBAction)button1Clicked;, or you could do it the easy way and just define a single - (IBAction)loadImage:(id)sender method. In your nib file, you would give each button a different tag (an NSInteger value), like 1 − 9. Then in your single method you could do this:
- (IBAction)loadImage:(id)sender {
NSInteger buttonTag = [(NSButton *)sender tag];
[imageView setImage:[UIImage imageNamed:
[NSString stringWithFormat:#"image%ld.png",buttonTag]]];
}
In this case, sender is the object that sends the message, which would be the button that was clicked on. Though admittedly a contrived example, by providing that one additional parameter, I probably saved 100 lines of needless code and complexity of having 9 separate methods.
[EDIT #2] Replaced the one pseudo-coded (written in Safari) next: method with actual code from one of my apps that I know works.
Hope this helps...
you need to declare the currentindex in the header like so:
NSInteger currentImage;
This way the value is saved throughout the views lifetime
Related
EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")
I want to have an static UIImage so I could access it from different classes. I've tried this way, but didn't worked:
Made Constans.h file with:
static UIImage *myImage;
And after that I import this header where it's needed. I thought that at this moment myImage was static and any changes made on this object would be visible everywhere. But it looks like every class is working on it's own myImage instance. Is there any way to have such static UIImage?
Edit:
Property in AppDelegate works fine. I have now static UIImage, but still I don't have effect I was expecting.
I have an UIImageView in ViewController. I load an image to my delegate.myImage and after I do:
delegate.myImage = [UIImage imageNamed:#"blah.png"];
myImageView.image = delegate.myImage;
Image is loaded, but after I want to change it in AppDelegate, but when I change myImage this way:
delegate.myImage = [UIImage imageNamed:#"blah2.png"];
nothing change in myImageView. It's like myImageView.image = delegate.myImage copied memory address of myImage so after if I change reference of myImage it's not affecting myImageView.image. I wanted to have an UIImage that after any changes it would also affect myImageView.
Is there other way than having an reference to myImageView in AppDelegate?
Rather than making an explicitly application-wide image, just use [UIImage imageNamed:]. This handles caching of the image for you! Whereever you need to use the image, just access it like so:
[UIImage imageNamed:#"imageName.png"]
Note: this will cause there to be a single copy of the image in memory. You can't unload it -- but newer versions of iOS may unload it behind the scenes upon low memory conditions.
See also the API docs for [UIImage imageNamed:].
Btw, imageNamed is often used for small images that get used multiple times -- e.g. table cell images -- but there's no reason to not use it on large images if you genuinely want a static app-wide image.
The keyword static makes a variable local to the compilation unit where it id defined. This means you can safely have the same symbol defined in multiple .c files; all those declarations will not collide and each file will have its own private symbol.
Put simply, if you really want to define a global variable that is accessed by any part of your program, you do not need the static keyword. In this case, though, the "trick" is declaring the variable in a header file (that you include everywhere the global should be visible) like this:
extern UIImage *myImage;
and then provide a definition for that variable in one single place (.c file) without the static keyword. The extern keyword tells the compiler that the definition for that symbol is not found inside of the current compilation unit (.c file), but in a different one.
Now, as many others have pointed out, you could better do that by means of a singleton, although it is usually recognized that using a singleton to mask a global variable is usually a way to mask a design problem.
That's a C problem (not specifically related to Objective-C or iOS)
The static keyword make the variable sticky inside its compilation unit.
When you #include (or #import in ObjC) a header, that's like if its content were copied & pasted into the file that includes it. As a reminder, ".h" files are not compiled (they are just included in ".m" files that themselves compile)
So that works exactly the same way as if you were typing the same lines of code you have in your .h in any file that #include it.
This explains why in each of your source files that #include "Constants.h" they each see a different instance of the myImage variable.
Instead you should:
Use the Singleton Pattern, that is specifically made for such cases
Or use the static keyword in an implementation file ("Constants.m") to make this variable sticky inside the compilation unit of this "Constants.m" file
I highly recommand to go with the Singleton Pattern for such cases anyway. More info on Singleton Pattern here in the Apple official DevPedia
You can create a #property (nonatomic, retain) UIImage *image; in your app delegate and in every class you want to use the image you can create AppDelegate *delegate=(AppDelegate *)[[UIApplication sharedApplication] delegate]; and then access to the UIImage from the delegate object like this :
[imageView setImage:[delegate image]];
Or you can use a class like this :
header file
#interface Data : NSObject
#property (nonatomic, strong) UIImage *image;
+ (Data *)sharedInstance;
+ (id)allocWithZone:(NSZone*)zone;
- (id)init;
- (id)copyWithZone:(NSZone *)zone;
#end
implementation file
#implementation Data
#synthesize image;
static Data *sharedInstance=nil;
+ (Data *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#end
Then, you have to import Data.h in every class you want and then use :
UIImageView *imageView=[[UIImageView alloc] init];
[imageView setImage:[[Data sharedInstance] image]];
This works great for me :)
Use the singleton pattern.
If your code is ARC follow this link http://lukeredpath.co.uk/blog/a-note-on-objective-c-singletons.html
In iPhone the AppDelegate class Acts a Static Class. so you can do the same thing which you have done in Constant.h in the YourAppDelegate Class. But dont use Static Keyword.
I am not very Sure but thinks it will work. :)
You can use UIImage category as example to get this picture.
In your .h file just add your static method.
#import <UIKit/UIKit.h>
#interface UIImage (StaticImage)
+(UIImage *)staticImage;
#end
And in your .m file do following steps:
#import "UIImage+StaticImage.h"
//This is your static image
static UIImage *myStaticImage;
#implementation UIImage (StaticImage)
+(void)initialize{
//Important to add this condition, because this method will be called for every
//child class of UIImage class
if (self == [UIImage class]){
myStaticImage = [[UIImage alloc] init];
}
}
+(UIImage *)staticImage{
//Just return your existing static image
return myStaticImage;
}
#end
Happy November to all,
Well I tried Xcode Build and analyze on my project, and it showed some unusual leaks, which I couldn't quite accept with my knowledge of Objective C.
So I decided to put up a test project and ask here..
MemoryTestController.h
#interface MemoryTestController : UIViewController{
UIImageView *tstImageView;
}
#property(nonatomic,retain) UIImageView *tstImageView;
#end
MemoryTestController.m
#implementation MemoryTestController
#synthesize tstImageView;
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release];
}
-(void)dealloc{
[tstImageView release];
[super dealloc];
}
#end
When I try Build and analyze, clang static analyzer say
Potential leak of an object at line xx
And the culprit line is
self.tstImageView = [[UIImageView alloc]initWithFrame:<SomeFrame>];
I think I am releasing once for every time I am allocing/retaining. Am I missing something, or Static analyzer has some bugs?
EDIT : Is there any leak there?
Well I run the above project using Leak tool in instrument..It didn't show any leak even though I tried many times..Whom should I believe? Static analyzer or Leak instrument?
your problem is how you release it:
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release]; // << here
}
you should do it this way:
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
imageView.image = [UIImage imageNamed:#"SomeImage.png"];
self.tstImageView = imageView;
[imageView release];
[self.view addSubview:self.tstImageView];
}
The checker is correct because it cannot assume that the variable is identical to the one you set. Therefore, the form you use in the OP could introduce a reference count imbalance because the ivar's value may not be what you assigned to it by the time you message release upon the ivar.
These cases are not likely for a UIImageView, and quite unlikely in the context of your program, but these examples should give you an idea as to why the checker assumes that object->ivar associations shall not be trusted:
Between creation of the image view and the message to release it via the ivar, you have:
self.tstImageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
1) assignment of the image view via the setter
2) access of the image view via the getter
3) direct access of the ivar, when adding to self.view
the setter may have taken a copied or used a cached value. UIImageView is a bad example, but the checker does not know how types are generally passed around - even if it did, it would (at times) make unsafe assumptions.
the simplest example would be:
- (void)setName:(NSString *)inName {
NSString * prev = name;
if (inName == prev) return;
if (0 == [inName count]) name = #"";
else name = [inName copy];
[prev release];
}
the value held by the ivar could change in the meantime. not likely an issue in this case, but let's say that adding the image view as the subview could end up calling back and altering self in the process/effect of adding the subview, and replacing or removing the image view you passed. In that case, the variable view you passed would leak and the view it replaced it with would have a negative imbalance.
Neither of those are likely to happen in your example, but it does happen in real world programs, and the checker is correctly evaluating based on locality, not property (the checker can't assume much of what happens inside a method call). It also encourages one good idiomatic style in this case.
EDIT : Is there any leak there?
Well I run the above project using
Leak tool in instrument..It didn't shown any leak even though I tried
it many times..Whom should I believe? Static analyzer or Leak
instrument?
The static analyzer says there is a potential leak because it is unable to guarantee the reference/allocation it follows is correctly retained/released. You can guarantee that reference counting is correct and please the static analyzer by changing you program to look like I wrote it in my example.
The way you have written it has made it impossible for the analyzer to follow the reference.
If you have no leaks and no zombies, then there is not a leak. But the solution is easy to fix - and programs have a way of changing during development. It's much easier to use the form I posted so it is easier for the toolset and for you to verify the program is correct. The static analyzer is not always correct, but you should adjust your programs to please it because static analysis is very useful. The program I posted is also easier for a human to understand and confirm that it is correct.
when you declare a property with retain like this
#property(nonatomic,retain) UIImageView *tstImageView;
a setter is added that will incr the retainCount when you assign to the property. When you do as below the object you created has already a retainCount == 1
self.tstImageView = [[UIImageView alloc]
initWithFrame:<SomeFrame>];
so the tstImageView object has 2 in retainCount.
do instead
UIImageView* view = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView = view;
[view release];
then, although unrelated to your leak when you release it write like this instead
self.tstImageView = nil;
since the setter will then will properly set the retainCount
I am using a Nib as a template for several buttons. It seemed to work fine, they each have their own independent state. However when I went to release the buttons I would crash in the dealloc. Here is the code...
mSoundBtns = new cSoundButton*[mNumSounds];
for(unsigned int i = 0 ; i < mNumSounds; ++i) {
mSoundBtns[i] = nil;
}
for(unsigned int s = 0; s < mNumSounds; ++s) {
[[NSBundle mainBundle] loadNibNamed:#"InstanceSoundButton" owner:self options:nil];
//Auto Loads via Outlet into 'soundNib'
mSoundBtns[s] = soundNib;
soundNib = nil;
uint32 count = mSoundBtns[s].retainCount;
NSLog(#"Last Count: %d", count);
}
for(unsigned int j = 0; j < mNumSounds; ++j) {
[mSoundBtns[j] release]; //**** Crash here on 7th (of 8) release
mSoundBtns[j] = nil;
}
Header:
#interface cLocationContext {
...
cSoundButton** mSoundBtns;
IBOutlet cSoundButton* soundNib;
}
#property (nonatomic, assign) IBOutlet cSoundButton* soundNib;
#end
The Nib is very simple, it just include a parent view and a child view of a custom view type.
cSoundButton simply keeps track of a name and a boolean state Mute or Not. Here is the dealloc
- (void)dealloc {
delete[] mSoundTag;
// Call the inherited implementation
[super dealloc]; //****Crashes in here
}
The crash is inside the call to super dealloc, in UIButton -> UIButtonContent dealloc. I assume I am doing something poor with my memory management like deallocing twice but I can't spot where.
Is what I am doing by loading the nib multiple times legal?
You have to retain the button as soon as you load it from the NIB. If you don't, you are not allowed to release it later, and you won't be able to access the button once your code returns control to the runloop (when the autorelease pool is drained).
PS: Wouldn't it be easier to use a Cocoa collection (NSMutableArray) to store the references to the buttons? Your code looks too complicated to me.
It will greatly simplify your memory management if you use your property and use an NSArray to store the button instances.
[[NSBundle mainBundle] loadNibNamed:#"InstanceSoundButton" owner:self options:nil];
//Auto Loads via Outlet into 'soundNib'
[mSoundBtns addObject:self.soundNib];
self.soundNib = nil;
Later, when it's time to release
[mSoundBtns release];
Keep in mind that when you're using properties you've got to reference them through self. The following two lines are exactly equivalent:
self.soundNib = something;
[self setSoundNib:something];
When you set soundNib = nil you are setting the variable soundNib to nothing, losing the reference to the button you loaded. If you hadn't added the pointer to an array and released it later you'd be leaking everything. Technically the way you're doing it might work... but don't do it that way. Using proper NSArrays and properties will make this whole process significantly easier and more maintainable.
UPDATED Scroll down to see the question re-asked more clearly....
If I had the name of a particular UIImageView (IBOutlet) stored in a variable, how can I use it to change the image that is displayed. I tried this, but it does not work.
I'm still new to iphone programming, so any help would be appreciated.
NSString *TmpImage = #"0.png";
NSString *Tst = #"si1_1_2";
TmpImage = #"1.png";
UIImage *sampleimage = [[UIImage imageNamed:TmpImage] retain];
((UIImageView *) (Tst)).image = sampleimage; // This is the line in question
[sampleimage release];
RESTATED:
I have a bunch of images on the screen.... UIImageView *s1, *s2 ,*s3 etc up to *s10
Now suppose I want to update the image each displays to the same image.
Rather than doing
s1.image = sampleimage;
s2.image = sampleimage;
:
s10.image = sampleimage;
How could i write a for loop to go from 1 to 10 and then use
the loop var as part of the line that updates the image.
Something like this.
for ( i = 1; i <- 10; ++i )
s(i).image = sample; // I know that does not work
Basic question is how do I incorporate the variable as part of the statement to access the image? Don't get hung up on my example. The main question is how to use a variable as part of the access to some element/object.
Bottom Line... If I can build the name of a UIImageView into a NSString object, How can I then use that NSString object to manipulate the UIImageView.
Thanks!
Ugh! Your line in question:
((UIImageView *) (Tst)).image = sampleimage;
is casting a string pointer as a UIImageView pointer - you're basically saying that your pointer to a string is actually a pointer to a UIImageView! It will compile (because the compiler will accept your assertion happily) but will of course crash on running.
You need to declare a variable of type UIImageView. This can then hold whichever view you want to set the image of. So your code could look like the following:
NSString *TmpImage = #"0.png";
UIImageView *myImageView;
If (someCondition == YES) {
myImageView = si1_1_2; //Assuming this is the name of your UIImageView
} else {
myImageView = si1_1_3; //etc
}
UIImage *sampleimage = [UIImage imageNamed:TmpImage]; //no need to retain it
myImageView.image = sampleImage;
Hopefully this makes sense!
Edit: I should add, why are you trying to have multiple UIImageViews? Because a UIImageView's image can be changed at any time (and in fact can hold many), would it not be better to have merely one UIImageView and just change the image in it?