NSMutableArray objects being lost - objective-c

I hope this makes sense...I have an NSMutableArray that I'm attempting to store multiple UIScrollView's in. Each UIScrollView is going to have multiple images and the ultimate goal is to be able to allow the user to swipe vertically for categories (each instance of UIScrollView) and horizontally for each image in that category (each instance of UIImageView). For now, until I get this code to work, I'm just creating 1 UIScrollView with 1 image and I'm adding that to scrollViewManager. This seems to add everything correctly, but when I leave this function, game over. My Array is empty. I don't understand. Am I supposed to do some sort of deep copy when I add to the Array so it doesn't get destroyed. Perhaps I'll figure it out when I wake up tomorrow, but for now, I'd like to break everything in sight. Thanks, in advance!
EDIT: Sorry for lack of details, I posted this rather late. My project is using ARC, so I'm not releasing scrollView anywhere. Here is the declaration for scrollViewManager in my header file:
#property (nonatomic, retain) NSMutableArray *scrollViewManager;
Here is the code I use to retrieve my scrollView:
UIScrollView *test = (UIScrollView*) [self.scrollViewManager objectAtIndex: 0];
And here is the code to initialize the scrollView and array:
scrollViewManager = [[NSMutableArray alloc] initWithCapacity: 1];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
[scrollView setBackgroundColor:[UIColor blackColor]];
[scrollView setCanCancelContentTouches:NO];
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
NSString *imageName = #"pic1.png";//[NSString stringWithFormat:#"pic%d.jpg", i+1];
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = 400;
rect.size.width = 300;
imageView.frame = rect;
imageView.tag = 1; // tag our images for later use when we place them in serial fashion
[scrollView addSubview:imageView];
[self.scrollViewManager addObject: scrollView];

There are some things that are kind of vague. For example:
Are you using ARC, if not and you are releasing your scrollView anywhere?
Are you using the auto-create-synthesize thing?
What type of property is your scrollViewManager (weak, strong, etc).
If you are creating your ivars automatically when defining a property, your ivar should be called _scrollViewManager instead of scrollViewManager.
The use of self.scrollViewManager is correct when using this method.
I always do this kind of stuff the other way around. When I have a property like this:
#property (nonatomic, strong) NSMutableArray* myArray;
I always initialize them using the self keyword:
self.myArray = [NSMutableArray new];
And when modifying it, I always use the generated ivar:
[_myArray addObject:myFineObject];
I think this is something I kept using when moving from non-ARC to ARC. Not sure if it still the way to go, but it works like expected.

Related

Identify UIImages Individually

I've got an app where multiple UIImages can be added to the view. Those images can then be dragged around the screen. How can I check which image has been dragged and then save that image's coordinates to a file and no other UIImage in the same view. I need a way of tagging each UIImage if possible to separate them out and identify them each individually. Hopefully this makes sense!
This is how I'm adding each UIImage to the view:
CGRect imageFrame = CGRectMake(activeView.center.x - 50, activeView.center.y - 50, 200, 200);
imageResizableView = [[SPUserResizableView alloc] initWithFrame:imageFrame];
UIImage *image = [UIImage imageNamed:#"galaxy.jpg"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageResizableView.contentView = imageView;
imageResizableView.delegate = self;
[activeView addSubview:imageResizableView];
You can use the tag property on UIVIew.
As per mentioned in the official doc:
tag An integer that you can use to identify view objects in your application.
#property(nonatomic) NSInteger tag Discussion The default value is 0. You can set the value of this tag and use that value to identify the view later.
UIImageViews are UIView subclasses, so have a tag property. Set that for each image, then when you want that one image [self.subViews viewWithTag:number]; to find it.
EDIT: If I understand this, there are many UIImageViews, but multiple imageViews may show the same image.
Assuming that, then you partition the tag's 32 bits to say 16 bits upper as the unique imageView number, and the lower 16 are the image number. You will then need to iterate through all subviews looking for the image. You can use macros to make this easier:
#define TAG_NUMBER(imageViewNum, imageNum) ((imageViewNum << 16) | imageNum)
#define IMAGE_FROM_TAG(tag) (tag & 0xFFFF)
etc
when you want to find all imageviews showing that image:
for(UIVIew *view in self.subviews) {
if(![view isKindOf:[UIIMageView class]]) continue;
int imageNum = IMAGE_FROM(view.tag);
if(imageNum == theOneIwant) {
save frame of "view"
}
}
If integer tags are good enough, then use the tag property which already exists for UIViews.
However, if you want something more, you can use obj_setAssociatedObject to add a tagName property to UIView.
#interface UIView (tagName)
#property (nonatomic, copy) NSString *tagName;
#end
--
#import <objc/runtime.h>
#implementation UIView (tagName)
static char tagNameKey[1];
- (NSString*)tagName {
return objc_getAssociatedObject(self, tagNameKey);
}
- (void)setTagName:(NSString *)tagName {
objc_setAssociatedObject(self, tagNameKey, tagName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
#end
which can then be used like so...
NSString *imageName = #"galaxy.jpg";
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.tagName = imageName;
and later...
NSString *imageName = imageView.tagName;
EDIT
Of course, you can add whatever you want, for example to mimic viewWithTag
- (UIView*)viewWithTagName:(NSString *)tagName {
if ([self.tagName isEqualToString:tagName]) {
return self;
}
UIView *result = nil;
for (UIView *view in self.subviews) {
if ((result = [view viewWithTagName:tagName]) != nil) {
return result;
}
}
return nil;
}

array crashing at index which is in bounds?

Can anyone help me out with explaining why my array is crashing.
Basically I have two buttons which change the image in the imageView.
h:
#interface CaseStudySecondPageViewController : UIViewController
{
//scroller and back and forward buttons for custom control.
__weak IBOutlet UIScrollView *scroller;
__weak IBOutlet UIButton *back;
__weak IBOutlet UIButton *forward;
//app delegate to return the selected case study from the other controller.
AppDelegate *del;
//variables for displaying the case studies
NSArray *myImageArray;
NSInteger localSelctorInt;
}
//setup a IBOutlet to allow the image to be changed.
#property (weak, nonatomic) IBOutlet UIImageView *logoImage;
#end
m:
update method:
-(void)Updater
{
[logoImage setImage:[myImageArray objectAtIndex:localSelctorInt]];
}
previous and next buttons:
//returns to previous image if back button is clicked.
-(void) back:(id)sender
{
if (localSelctorInt > 0)
{
localSelctorInt--;
}
else
{
localSelctorInt = 0;
}
[self Updater];
}
//returns to next image if forward button is clicked. increase 7 if array size changes.
//1 removed from int as in starts at 1 and array starts at 0.
-(void) forward:(id)sender
{
if (localSelctorInt < 7)
{
localSelctorInt++;
}
else
{
localSelctorInt = 7;
}
[self Updater];
}
and finally my image array is declared in:
- (void)viewDidLoad
{
[super viewDidLoad];
//setup the delegate to allow access to the int which was modified on the page before.
del = [[UIApplication sharedApplication] delegate];
//assign a local variable to the int from the previous page.
localSelctorInt = *(del.selectorInt);
//create back and forward buttons as UIButtons first.
[back addTarget:self action:#selector(back: ) forControlEvents:UIControlEventTouchUpInside];
[forward addTarget:self action:#selector(forward:) forControlEvents:UIControlEventTouchUpInside];
//pass the ui buttons into ui bar items.
UIBarButtonItem *UIBack = [[UIBarButtonItem alloc] initWithCustomView:back];
UIBarButtonItem *UIForward = [[UIBarButtonItem alloc] initWithCustomView:forward];
//add these to an array (notice item"s").
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:UIForward, UIBack, nil];
//initialize the array with all of the images for the case studies logos.
myImageArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"Logo_Arm.png"],
[UIImage imageNamed:#"Logo_Fife"],
[UIImage imageNamed:#"Logo_Findel.png"],
[UIImage imageNamed:#"Logo_BirkBeck.png"],
[UIImage imageNamed:#"Logo_NHS_Dudley.png"],
[UIImage imageNamed:#"Logo_NHS_Kensignton.png"],
[UIImage imageNamed:#"Logo_Yorkshire_Water.png"],
[UIImage imageNamed:#"Logo_Uni_Hertfordshire.png"],
nil];
//call update method once to load the first image selected from the previous page.
[self Updater];
}
now my app crashes when it trys to index the 4th image (3rd in the array):
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 ..
2]'
It's probably something really simple, but id appreciate your help, cheers.
If you look at your exception, it says that your array contains 3 objects only ( ... bounds [0..2] ... ). So, my guess is that your fourth image doesn't exist ... Check spelling of [UIImage imageNamed:#"Logo_BirkBeck.png"]. Does this image really exist? It returns nil, list of objects is nil terminated, so, your array does contain 3 images only instead of 8 images.
Side Note: Don't use magic constants like localSelctorInt < 7. Always rely on real number of elements in array (_myImageArray.count). It's a way to hell ...
2nd Side Note: Don't declare ivars without underscore prefix. Do use something like this _myImageArray for ivar name.
I guess its due to not allocating the array
myImageArray = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"Logo_Arm.png"],
[UIImage imageNamed:#"Logo_Fife.png"],
[UIImage imageNamed:#"Logo_Findel.png"],
[UIImage imageNamed:#"Logo_BirkBeck.png"],
[UIImage imageNamed:#"Logo_NHS_Dudley.png"],
[UIImage imageNamed:#"Logo_NHS_Kensignton.png"],
[UIImage imageNamed:#"Logo_Yorkshire_Water.png"],
[UIImage imageNamed:#"Logo_Uni_Hertfordshire.png"],
nil];

Subclass of UIPageControl refresh only after uiscrollview move, not before

the problem I've met today is with my subclass of UIPageControl. When I initialize it, the frame (specifically the origin) and image of dots stays default, which is the problem, since I want it to change right after initialization. However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images.
What could be the problem?
Code:
CustomPageControl.m
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
activeImage = [UIImage imageNamed:#"doton.png"];
inactiveImage = [UIImage imageNamed:#"dotoff.png"];
return self;
}
- (void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView *dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
[dot setFrame:CGRectMake(i * 13.5, 1.5, 17, 17)];
}
}
- (void)setCurrentPage:(NSInteger)currentPage
{
[super setCurrentPage:currentPage];
[self updateDots];
}
#end
ChoosingView.m - init part
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 160, 300)];
[scrollView setBackgroundColor:[UIColor clearColor]];
[scrollView setDelaysContentTouches:NO];
[scrollView setCanCancelContentTouches:YES];
[scrollView setClipsToBounds:NO];
[scrollView setScrollEnabled:YES];
[scrollView setPagingEnabled:YES];
[scrollView setShowsHorizontalScrollIndicator:NO];
[scrollView setShowsVerticalScrollIndicator:NO];
pageControl = [[CustomPageControl alloc] initWithFrame:CGRectMake(200, 300, 80, 20)];
[pageControl setBackgroundColor:[UIColor clearColor]];
pageControl.numberOfPages = 6;
[pageControl setCurrentPage:0];
the last line is when I would expect the UIPageControl to refresh, however that does not happen.
Does this happen with the standard UIPageControl implementation?
Your problem states that your objects subViews (eg the UIImageViews) rects/size are not initialising to your desired size/position.
I implemented this code in my project with a nib rather than programmatically and I needed to call -(void)updateDots to set it as its initial condition was the standard dots..
I dont see how the UIScrollView has any bearing impact on this unless somehow its linked to your -(void)updateDots function (E.g. your setting the currentIndex of your custom page control). You state, "However, when I move with scrollView (as in "touch and move") after initialization, they (the dots) somehow jump to the right position with correct images."
Because they "jump to the right position with correct images" it means that your -(void)updateDots function must be getting called. I dont see any other explanation.
Also your iteration loop assumes that all the UIViews in your .subViews array are UIImageViews, although fairly safe to assume this, I would check to see if the UIView is a UIImageView with reflection.

iOS (Re)Use a NIB as a Template

I have created a nib file with different views. I was planning to use this nib file as a template, much like in web (i.e. html). So create a new instance of this nib fill it with actual data, create another new instance of nib & fill with actual data etc...
But when I try to do this, nib instance is not behaving the same. Only one gets created. Am I missing something? Aren't nib files supposed to be used this way?
Here's how I tried to use my nib file -
int yCoord = 0;
for(int i=0; i<[resultSet count]; i++)
{
[[NSBundle mainBundle] loadNibNamed:#"msgView" owner:self options:nil];
UIView *tmpMsgView = [[UIView alloc] initWithFrame:CGRectMake(0, yCoord, msgView.view.frame.size.width, msgView.view.frame.size.height)];
tmpMsgView = msgView.view;
UILabel *senderLabel = (UILabel *)[tmpMsgView viewWithTag:1];
[senderLabel setText:#"trial"];
[self.view addSubview:tmpMsgView];
[tmpMsgView release];
yCoord += msg.view.frame.size.height;
}
this msgView is hooked up to the nib file (through IB) and the owner of that nib file too is defined as this viewController class.
Your code is adding the same instance over and over again, not a new instance each time.
UIView *testView = [[UIView alloc] init];
testView.backgroundColor = [UIColor blueColor];
for (int i=0; i<5; i++) {
testView.frame = CGRectMake(0.0, (i+1)*40.0, 200.0, 20.0);
[self.window addSubview:testView];
}
[testView release];
and
for (int i=0; i<5; i++) {
UIView *testView = [[UIView alloc] init];
testView.backgroundColor = [UIColor blueColor];
testView.frame = CGRectMake(0.0, (i+1)*40.0, 200.0, 20.0);
[self.window addSubview:testView];
[testView release];
}
are two very different things.
the first actually makes very little sense and results in a single blue bar,
the second makes much more sense and results in 5 blue bars -- I think this is what you want.
look at the following, it will create 5 rectangles with all different colors -- this is to illustrate that each is rectangle is its seperate instance loaded from a nib:
for (int i=0; i<5; i++) {
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"aView" owner:self options:nil];
UIView *nibView = [nibObjects objectAtIndex:0];
nibView.backgroundColor = [UIColor colorWithRed:(i+1)*0.14 green:(i+1)*0.11 blue:200.0 alpha:1.0];
nibView.frame = CGRectMake(0.0, (i+1)*40.0, 200.0, 20.0);
[self.window addSubview:nibView];
}
now, if we assign our nibObjects array only once before the loop, we again have the same issue in that we add the same instance over and over again, which will result in one rectangle only:
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"aView" owner:self options:nil];
for (int i=0; i<5; i++) {
UIView *nibView = [nibObjects objectAtIndex:0];
nibView.backgroundColor = [UIColor colorWithRed:(i+1)*0.14 green:(i+1)*0.11 blue:200.0 alpha:1.0];
nibView.frame = CGRectMake(0.0, (i+1)*40.0, 200.0, 20.0);
[self.window addSubview:nibView];
}
hope this helps
You have memory management issues in your code.
UIView *tmpMsgView = [[UIView alloc] initWithFrame:CGRectMake(0, yCoord, msgView.view.frame.size.width, msgView.view.frame.size.height)];
You alloc/init a UIView and on the next line you store some other view in the variable. You lose the pointer to the UIView and therefore you leak its memory.
Whether the code using senderLabel will work depends on what you defined in the nib file.
In [tmpMsgView release]; you're most probably releasing something you do not own (the pointer no longer points to the UIView you alloc/inited). The issue here depends on how you declare msgView. If you are releasing an object you do not own, it might get deallocated and that might be the reason you do not see it in your app.
What you are trying to do is fine - this is what is routinely done for loading table view cells, for example.
If I do this I have a single outlet from the view controller (which is set as files owner which you have done). This will be the topmost object in the nib, eg a UIView. All of your other Nib components are just subviews of this, you can identify with tags or, if your topmost view is a custom view, that can have outlets which are connected appropriately.
When you do loadNibNamed this will set your outlet ivar to the topmost object. You then assign this to a method level variable, and set your ivar back to nil. You can then do what you like with this before adding it as a subview to something else. Next time you load the nib, you get another fresh version.
Hope that makes sense, let me know if I can add anything else. The problem in your code is that you are doing things with msgView.view instead of the top level object.

Subview remove and add in objective c/iphone

I have a UIViewController class where I create a UIView instance.
And then I initialize (means fill) it with 25 subviews each containing an image (1, 2, ..., 25). Then after clicking 5 times in these image I called a function where I used
for(UIView *subview in [contentView subviews]) {
[subview removeFromSuperview];//ContentView name of my view
}
to remove the previously added subview. And then I use the same approch to
add 25 new subviews (image 1,2,3,....25). But this time no subview is added.
Can someone plz give me full code of adding & removing subview.
I have used the following code when I first add subview
//create main window
contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
contentView.backgroundColor = [UIColor blackColor];
self.view = contentView;
[contentView release];
//adding 25 subview 1st time
int a=0;
int b=0;
for (int i = 0; i < 26; i++)
{
CGRect dragRect = CGRectMake(0.0f, 0.0f, x, y);
dragRect.origin = CGPointMake(a,b);
DragView *dragger = [[DragView alloc] initWithFrame:dragRect];
NSString *Flower = [[NSArray arrayWithObjects:#"1.png", #"2.png", #"3.png",#"4.png", #"5.png", #"6.png",#"7.png",#"8.png", #"9.png",#"10.png", #"11.png", #"12.png",#"13.png",#"14.png",#"15.png",#"16.png",#"17.png",#"18.png",#"19.png",#"20.png",#"21.png",#"22.png",#"23.png",#"24.png",#"25.png",#"26.png", nil] objectAtIndex:i];
[dragger setImage:[UIImage imageNamed:Flower]];
[dragger setUserInteractionEnabled:YES];
[self.view addSubview:dragger];
[dragger release];
a+=10;
b+=10;
}
//then removing 25 subview
//adding 25 subview 2nd times
I used the same approch to add the second time as first time, but the problem is that when I remove 25 subview and then add 25 subview, these subview are not added/shown, the view remain same. I am tired with these problem. plz someone help me.
The problem might be the way you remove the old views. You are modifying an array while iterating over it, which does not work. Try this code to remove the old views:
UIView* subview;
while ((subview = [[contentView subviews] lastObject]) != nil)
[subview removeFromSuperview];
A nice one liner is:
[view.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
Looking at your code, I can suggest the following changes. There's one line in your for loop which is terribly inefficient:
NSString *Flower = [[NSArray arrayWithObjects:#"1.png", #"2.png", #"3.png",#"4.png", #"5.png", #"6.png",#"7.png",#"8.png", #"9.png",#"10.png", #"11.png", #"12.png",#"13.png",#"14.png",#"15.png",#"16.png",#"17.png",#"18.png",#"19.png",#"20.png",#"21.png",#"22.png",#"23.png",#"24.png",#"25.png",#"26.png", nil] objectAtIndex:i];
[dragger setImage:[UIImage imageNamed:Flower]];
Either take the Flower initialisation out of the the for loop (to only create the array once) or do the following:
[dragger setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d.png", i]]];
The code itself looks like it should work though. If you add 26 subviews then remove the 26 subviews then add the 26 subviews in exactly the same way then it should display as you'd expect.