I am showing employee records as rows in a screen
I want the option to remove these and show a new set of records when I click a button
The structure / organization of my code is as follows :
In My controller .h
NSMutableArray *employeeScrollViewArray;
IBOutlet UIScrollView *employeeScrollViewItems;
Here I have a UIScrollView - employeeScrollViewItems - which is populated with rows shown on the UI
I also store these rows in an array - employeeScrollViewArray
Initialization and release of objects in MyController.m file is as shown:
In the method
- (void)viewDidLoad {
employeeScrollViewArray=[[NSMutableArray alloc]init];
I am releasing this mutable array in the method :
- (void)dealloc {
[employeeScrollViewArray release];
when the view loads I populate this array with objects from a user Defined class in a method :
EmployeeDetail.m file
EmployeeDetail *empDetail=[[EmployeeDetail alloc]init];
empDetail.delegate=self;
[[NSBundle mainBundle] loadNibNamed:#"EmployeeDetail" owner:empDetail options:nil];
[self.employeeScrollViewItems addSubview:empDetail.view];
[employeeScrollViewArray addObject:empDetail];
The details of the "h" file foe EmployeeDetail.h is :
#interface EmployeeDetail : UIViewController
UIView *view;
This is the "view" that is populated with data and shown on the screen
I now want to cleanup /remove these objects
So I tried things like this : ( which is then causing the exception )
int yyy = [employeeScrollViewArray count];
for(int xxx=yyy-1;xxx>=0;xxx--){
if ([[[employeeScrollViewItems subviews]objectAtIndex:xxx ]isKindOfClass:[UIView class]]) {
[[[employeeScrollViewItems subviews ]objectAtIndex:xxx] removeFromSuperview];
}
[employeeScrollViewArray removeObjectAtIndex:xxx];
}//for
This is the only way that I am able to remove these objects
If I try to remove them separately I get this error
However even after I remove the items and code passes - it still throws this error
If I remove this code then things work - but then I cannot remove these objects
Please help !
I don't know where the error is coming from, but I have some general advice:
You should release the object after adding it to the array.
[employeeScrollViewArray addObject:empDetail];
[empDetail release];
The array's entries will be retained by the array itself. In the cleanup method (dealloc) now just release the array: [employeeScrollViewArray release]. That will also release all contained objects.
Mostly it is not necessary to cleanup the view hierachy. However, if you want to remove all subviews of employeeScrollViewItems, I'd do this:
for (UIView* subview in [employeeScrollViewItems subviews]) {
[subview removeFromSuperview];
}
Related
I have two UITableView objects that both have an array named sectionHeaders that is used as a reference object to provide, no surprise, the header names for a given section in the table. The first table view has it as an NSMutableArray and adds the headers dynamically as they are needed. The second view uses an NSArray and creates the array using the #[item1, item2, ...] shorthand. Both objects are declared as global instance variables.
The first table view creates the second and pushes it onto the navigation controller stack. The headers appear correctly the first time the former view is displayed, and they always display correctly whenever the latter view is displayed, but when I hit back to return to the first view, the section headers match those of the second view. This problem is immediately resolved by changing one of the two variable names, but I would much rather understand why the problem is happening in the first place.
Please let me know if any more information would be helpful. I have no concept of what might be causing this, so I'm not entirely sure what information could be useful.
Edit: Here's a simplified version of the way the two objects are set up and interact.
CalendarViewController.m:
NSMutableArray *sectionHeaders;
#implementation CalendarViewController
-(id) initWithStyle:(UITableViewStyle)style {
self = [super initWithStyle:style];
sectionHeaders = [[NSMutableArray alloc] init];
}
//this is the method that adds items to the sectionHeaders object with irrelevant information excluded
-(void) distributeEvents {
[self.tableView beginUpdates];
if(condition1) {
if(![sectionHeaders containsObject:#"Today"]) {
[headers addObject:#"Today"];
}
}
else if(condition2) {
if(![sectionHeaders containsObject:#"Next week"]) {
[headers addObject:#"Next week"];
}
}
//et cetera...
}
//the only other time the sectionHeaders object comes up is in
- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return sectionHeaders[section];
}
The second object, an EventViewController, is initialized when a cell is tapped. It is not created with any variables associated with the sectionHeaders object.
EventViewController.m:
NSArray *sectionHeaders;
#implementation EventViewController
//within the init method
sectionHeaders = #[#"What", #"When", #"Where"];
//later...
- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return sectionHeaders[section];
}
That's all the references I make to those objects. I apologize for not having the simplified code in the question to begin with. Hopefully having more context will help.
From your code it appears that both sectionHeaders arrays are defined outside the scope of the corresponding classes and basically are two global variables with same name - in which case linker should have given you a warning.
I suggest you move them inside the class they belong to.
My app crashes some time while removing wait view from screen. Please guide me how can i improve code given below.
The wait view is only called when app is downloading something from server. and when it completed download then i call removeWaitView method.
Exception Type: NSGenericException
Reason: Collection was mutated while being enumerated.
+(void) removeWaitView:(UIView *) view{
NSLog(#"Shared->removeWaitView:");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil) {
[temp removeFromSuperview];
}
}
my waitview adding code is
+(void) showWaitViewInView:(UIView *)view withText:(NSString *)text{
NSLog(#"Shared->showWaitViewWithtag");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil)
{
return;
}
//width 110 height 40
WaitViewByIqbal *waitView=[[WaitViewByIqbal alloc] initWithFrame:CGRectMake(0,0,90,35)];
waitView.center=CGPointMake(view.frame.size.width/2,(view.frame.size. height/2) -15);
waitView.tag=kWaitViewTag; // waitView.waitLabel.text=text;
[view addSubview:waitView];
[waitView release];
}
The exception is pretty clear - a collection (in this case something like an array) is being modified while it is also being enumerated.
In this specific case we are talking about array of layers, or better said, instances of UIView which are all backed up by layers.
The modifications happen when you are calling removeFromSuperview or addSubview. The enumeration can happen anytime during redrawing.
My guess is - you are not calling your methods from the main thread and the main thread is currently redrawing, so you'll get a racing condition.
Solution: call these methods only from the main thread.
You should get a copy of the array, so you don't risk the chance of it being modified while your code is enumerating it. Of course this leaves the chance that a new item may be added as a result of the mutation, thus your enumeration will miss that one array item. Here is an example specific to the title of your question:
[[viewLayer.sublayers copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CALayer * subLayer = obj;
if(subLayer == theLayerToBeDeleted){
[subLayer removeFromSuperlayer];
}
}];
It's possible that you're adding or removing a the waitView while iterating through the waitView's siblings (it's superView's subviews). Check to see what methods call removeWaitView and showWaitInView to make sure the calling methods don't call the show/remove wait view methods from within a for loop iterating the wait view's siblings (the waitView's superview's subviews).
To fix issue just start from last element
NSMutableArray* Items = [[NSMutableArray alloc] initWithArray:[_ScrollList.documentView subviews]];
while ([Items count]>0) {
NSView *v = [Items lastObject];
[v removeFromSuperview];
[Items removeLastObject];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[VenueManager searchNear:#"Orlando"
onLoad:^(NSArray *objects) {
self.locationObjects = objects;
[self.tableView reloadData];
} onError:^(NSError *error) {
NSLog(#"%#", error);
}];
}
This code is in my viewDidLoad method of my UITableViewController class. It is the starting point for using RestKit to parse a JSON file from FourSquare. I was pulling my hair out because i couldn't get the objects to show up in my Table View until i put [self.tableView reloadData];. With out that call the app never even hit my - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) because after the block was done executing locationObjects would be nil.
Before when I was debugging it the self.locationsObjects = objects worked when i was in the block (i am very unfamiliar with blocks by the way). As soon as i was out of the block the debugger would say locationObjects was nil, even though it had said it had 30 objects just like objects did when i had a break point at the assignment statement.
Can some one help me understand what is going on here.
Additional info:
Right now everything is working, or appears to be working my table is populated with the objects request from the JSON document. Originally I was doing this exact same thing in a normal ViewController and trying to set the objects from the block equal to locationObjects. Then using a prepareForSegue method i was trying to pass the locationObjects to the tableViewController in the standard method i have learned from numerous tutorials. I would get a SIGBAT error. The thread would terminate because of an unrecognized selector sent to the table view controller. Through debugging i would find that locationObjects could be nil in the prepareForSegue method. Here is the code from the viewController file.
Also I would get a warning here locationTableViewController.locationObjects = self.locationObjects; saying something about assigning a pointer of type NSArray to strong NSArray, or something like that ( i have since changed a lot attempting to get the code working and deleted some storyboard assets, so i'm not 100% sure of the wording).
#implementation CoffeeShopViewController
#synthesize venueCountLable = _venueCountLable;
#synthesize locationObjects = _locationObjects;
- (void)viewDidLoad
{
[super viewDidLoad];
[VenueManager searchNear:#"Orlando"
onLoad:^(NSArray *objects) {
self.venueCountLable.text = [NSString stringWithFormat:#"%d", objects.count];
self.locationObjects = objects;
} onError:^(NSError *error) {
NSLog(#"%#", error);
}];
}
- (void)viewDidUnload
{
[self setVenueCountLable:nil];
[super viewDidUnload];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"locationTableSegue"])
{
LocationTableViewController *locationTableViewController = segue.destinationViewController;
locationTableViewController.locationObjects = self.locationObjects;
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
Try:
#property (nonatomic, copy) NSArray *locationObjects;'
Edit on why this works:
To be honest, I really don't know the underlying reason for this to work and strong not working.
When I saw the problem, it appeared to me that strong being equivalent of retain - inserting copy instead of strong could secure that locationObjects wouldn't be nullified. Thinking again over it, I suspected that my assumption could be wrong - retain literally meant 'Do not release this object because now there is one more guy holding it.'
That, however, works somewhat differently. See this.
What Malaxeur's answer and comments below tells could possibly apply to NSArray in your example - despite strong ownership to locationObjects, what you are given is a reference to objects NSArray (an NSMutableArray*) instead of copy of it. Once out of scope (block end), it is no longer usable, and ARC claims it. Using copy in turn forces it to create another space in memory just for locationObjects, which would remain forever until you free it up.
I still do not consider this a perfect explanation as I have never understood blocks fully. I would keep this open to everyone who knows better, would fill up as soon as I get something that's useful.
I have made a view in the application that shows a UITableView. It will be full of results (obviously) almost all the time, however when it does not have any results I want to show another view that inform the user about how he/she could populate the table.
I want to design that view in the interfacebuilder. I will have to check in the code whether the datasource is empty or not to toggle between the two different nibs. How do I instantiate and configure a view made in Interfacebuilder?
The easies way to do this is by adding the view in xib normally and make it visible
Design your both views, the table view and the other view, give the tableView a tag of 111 for example and give the otherview another tag 222 for example
Now in viewDidLoad
Get both the views
UIView *noDataView = [self.view viewWithTag:222];
UITableView *tableView = [self.view viewWithTag:111];
//Hide both of them or only the noDataView until you know if you have data from the dataSource or not
Check for your data source
//hasElements do you have any element to show?
if(hasElements)
{
noDatView.hidden = YES;
tableView.hidden = NO;
}
else
{
noDatView.hidden = NO;
tableView.hidden = YES;
}
You can load nib file based on condition.You can write category as follows:
self.view = (UIView *)[self loadNib:#"SecondView" inPlaceholder:self.view];
- (UIView *)viewFromNib:(NSString *)nibName
{
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil];
for (id view in xib) { // have to iterate; index varies
if ([view isKindOfClass:[UIView class]]) return view;
}
return nil;
}
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
UIView *nibView = [self viewFromNib:nibName];
[nibView setFrame:placeholder.frame];
self.view = nibView;
//[self.view insertSubview:nibView aboveSubview:placeholder];
//[placeholder removeFromSuperview];
return nibView;
}
The other answers give you possible technical solutions but I would propose that if you are using the standard Apple design guidelines, you probably don't even need to worry about it. For instance, somewhere on your screen you should have a bar button item with the identifier "Add" (which shows the plus icon). Then rather than giving a long (often poorly localised) description of how to add items, just have a header for an empty section which says "No items" replacing items with whatever pluralised noun is appropriate for your table's items. For example, for an Archery related app I am working on:
Notice how the Edit button is currently disabled too, thus no explanation is needed as the only thing they can do at this point is tap the Add button (screenshots on the Appstore will have shown them what they can expect to see after this point).
General noob questions:
(1) How can I create an NSMutable array in a buttonClicked action that I can add more entries to during subsequent clicks of the same button? I always seem to start over with a new array at every click (the array prints with only 1 entry which is the most recent button's tag in an NSLog statement).
I have about 100 buttons (one for each character in my string called "list") generated by a for-loop earlier in my code, and each has been assigned a tag. They are in a scrollview within the view of my ViewController.
I wish to keep track of how many (and which ones) of the buttons have been clicked with the option of removing those entries if they are clicked a second time.
This is what I have so far:
-(void) buttonClicked:(UIButton *)sender
NSMutableArray * theseButtonsHaveBeenClicked = [[NSMutableArray alloc] initWithCapacity: list.length];
NSNumber *sendNum = [NSNumber numberWithInt:sender.tag];
[theseButtonsHaveBeenClicked addObject:sendNum at index:sender.tag];
NSLog(#"%#",theseButtonsHaveBeenClicked);
}
(2) I have read that I may be able to use a plist dictionary but I don't really understand how I would accomplish that in code since I cant type out the items in the dictionary manually (since I don't know which buttons the user will click). Would this be easier if I somehow loaded and replaced the dictionary in a plist file? And how would I do that?
(3) I also have no idea how I should memory manage this since I need to keep updating the array. autorelease?
Thanks for any help you can provide!
Okay, firstly you are creating a locally scoped array that is being re-initialised on every call to buttonClicked:. The variable should be part of the class init cycle.
You will also be better off with an NSMutableDictionary instead of an NSMutableArray. With a dictionary we don't have to specify capacity and we can use the button's tags as dictionary keys.
Here's what you need to do, these three steps always go together: property/synthesize/release. A good one to remember.
//Add property declaration to .h file
#property (nonatomic, retain) NSMutableDictionary * theseButtonsHaveBeenClicked;
//Add the synthesize directive to the top of .m file
#synthesize theseButtonsHaveBeenClicked;
// Add release call to the dealloc method at the bottom of .m file
- (void) dealloc {
self.theseButtonsHaveBeenClicked = nil; // syntactically equiv to [theseButtonsHaveBeenClicked release] but also nulls the pointer
[super dealloc];
}
Next we create a storage object when the class instance is initialised. Add this to your class's init or viewDidLoad method.
self.theseButtonsHaveBeenClicked = [[NSMutableDictionary alloc] dictionary]; // convenience method for creating a dictionary
And your updated buttonClicked: method should look more like this.
-(void) buttonClicked:(UIButton *)sender {
NSNumber *senderTagAsNum = [NSNumber numberWithInt:sender.tag];
NSString *senderTagAsString = [[NSString alloc] initWithFormat:#"%#",senderTagAsNum];
// this block adds to dict on first click, removes if already in dict
if(![self.theseButtonsHaveBeenClicked objectForKey:senderTagAsString]) {
[self.theseButtonsHaveBeenClicked setValue:senderTagAsNum forKey:senderTagAsString];
} else {
[self.theseButtonsHaveBeenClicked removeObjectForKey:senderTagAsString]; }
[senderTagAsString release];
NSLog(#"%#", self.theseButtonsHaveBeenClicked);
}