Modal view controller is slow to appear - cocoa-touch

I'm having a hard time debugging an issue when presenting a modal view controller. I'm seeing a pause of between 0.5 seconds and 1 second between viewWillAppear being called and viewDidAppear being called on the presented (table view) controller. I tried replacing this with a bare bones table view controller to see if the issue was in the controller calling presentModalController, and it appeared quickly as expected.
I've peppered both controllers with NSLog statements in an attempt to diagnose the issue, but can't narrow it down further than a delay between viewWillAppear and viewDidAppear.
Short of rewriting the controller line by line, what's the best way for me to find out where the issue is? Are there any usual suspects here I should be aware of?
Edit : update with problematic code
The table view is displaying 2 cells, each containing a text field.
I have UITextField properties for each of the 2 text fields
#property (nonatomic, retain) IBOutlet UITextField *itemTextField;
and assign text fields to these properties as follows :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
if (indexPath.row == 0) {
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
textField.delegate = self;
cell.textLabel.text = #"Item";
textField.placeholder = #"Enter item name";
textField.keyboardType = UIKeyboardTypeDefault;
textField.returnKeyType = UIReturnKeyNext;
self.itemTextField = textField;
[cell addSubview:textField];
[textField release];
}
}
return cell;
}
I've left out the second row, but the code is the same.
If I comment out
self.itemTextField = textField;
the view loads as expected, but uncommented causes the slight delay I've been seeing. Should I be initialising this somewhere else rather than in cellForRowAtIndexPath? I'm a bit stumped.

Use Time Profiler in Instruments to see which is the offending code. Also note that excessive logging itself can cause noticeable speed degradation. Likely cases are costly methods to provide data to your table view, custom heights perhaps? Or loading of content from a network synchronously.

Got the same problem. Just fixed it with barrym's comment. Just move the becaomeFirstResponder code to viewDidAppear

Related

Updating subviews in cells on a UITableView

I'm developing an application in iPad 6.0 using Storyboards.
Let me first explain my goal. I'm trying to achieve a Master-Detail (SplitViewController-like) View Controller using 2 UITableViewControllers.
The first UITableView("Master"), let's call this HeaderTableView, as the name implies, lists down the Headers for the...
...Second UITableView("Detail"), let's call this the EncodingTableView, which contains a programmatically changing CustomTableViewCell (subviews contained within each cell may be a UITextField, UIButton or UISwitch).
See EncodingTableView.m
- (void)updateEncodingFields:(NSArray *)uiViewList
{
// Add logic for determining the kind of UIView to display in self.tableView
// Finally, notify that a change in data has been made (not working)
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *encodingFieldsTableId = #"encodingFieldsTableId";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:encodingFieldsTableId];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
}
// Change text in textView property of CustomTableViewCell
cell.encodingFieldTitle.text = uiViewList.title;
// added methods for determining what are to be added to [cell.contentView addSubView:]
// data used here is from the array in updateEncodingFields:
}
My HeaderTableView.m, contains the didSelectRowAtIndexPath to update the EncodingTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![selectedIndexPath isEqual:indexPath]) {
selectedIndexPath = indexPath;
[self updateDataFieldTableViewForIndexPath:indexPath];
}
}
- (void)updateDataFieldTableViewForIndexPath:(NSIndexPath *)indexPath {
[self.encodingTableView updateEncodingFields:self.uiViewList];
}
Question
- Data is all ok but why doesn't EncodingTableView "redraw"ing the fields? My
suspicion is that reusing cells has something to do with this but I just can't figure out why.
Screenshots on the result:
Initial Selection in HeaderTableView
Second Selection in HeaderTableView
What I've tried :
I kept seeing suggestions such as [UITableView setNeedsDisplay],
[UITableView reloadData] and [UITableView setNeedsLayout] but none of
them worked.
Removing the reuse of tableViewCells works fine but this causes parts of my
CustomTableView.encodingFieldTitle to disappear. Not to mention that this might cause performance issues if I were to drop reusing cells.
Restrictions:
I know that a good idea is to use a SplitViewController but this is just a subpart of my app (hence not the RootViewController).
Finally, thanks for reading such a long post. ;)
It looks like you are most likely adding subviews inside tableView:cellForRowAtIndexPath:.
The issue is that if you use cell reuse then are not always starting from a blank slate inside tableView:cellForRowAtIndexPath: instead you can possibly be given a cell back that has already been configured once. This is what you are seeing, a cell that has previously had labels added to it is handed back to you and then you add some more labels over the top.
There are a few way to deal with this:
(My preferred option) Create a subview of UITableViewCell with these extra sub views available as properties.
Ensure the cell setup is only done once
A great place to do this is when you actually create a cell when one does not already exist e.g. inside the if (cell) check
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
// add subview's here and give them some way to be referenced later
// one way of doing it is with the tag property (YUK)
UILabel *subView = [[UILabel alloc] initWithframe:someFrame];
subView.tag = 1;
[cell.contentView addSubview:subView];
}
UILabel *label = (id)[cell.contentView viewWithTag:1];
label.text = #"some value";
One problem i can see in your code is that the cell identifiers used are different in tableView cellForRowAtIndxPath function.
While dequeueing you are using this identifier - > "encodingFieldsTableId"
&
while creating a cell you are using this identifier - > "dataFieldUiGroupTableId".
Ideally these two identifiers should be same !!!
Try adding,
cell.encodingFieldTitle.text = nil;
Before if(cell == nil)
So that whenever your cellForRowAtIndexPath method is called, the string already present in the cell you are going to reuse will get deleted and the new text in uiViewList.title will be displayed.

UITableView is lagging a cell containing a UITextView is displayed the first time

I'm running into issues with a UITableView that lags while scrolling when specific cells will move to superview.
I've written my own IPFormKit for an easy way to create beautiful input forms with different kind of inputViews without having to re-code everything manually for each form field / cell.
I've got a UITableViewController that initializes my IPFormKit and its fields.
The - (UITableViewCell *) cellForRowAtIndexPath:(NSIndexPath)indexPath; loads the dequeued custom cells (called IPFormTableViewCell) and assigns the IPFormField to each cell.
The custom UITableViewCell (IPFormTableViewCell) creates all (possibly) required inputViews (UITextField, UITextView, CustomUILabel) with a CGRectZero on initialization.
The matching inputView depending on the IPFormField's type (that was already inited as an iVar of the cell) is resized and added as a subview to the cell.contentView within.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath)indexPath
For UITextField and CustomUILabel this works flawlessly, but when the inputView is a UITextView, the scrolling of the UITableView lags (slightly) noticable when this cell will be displayed for the first time.
When the cell will be displayed again later after scrolling a bit (even if the cell was reused and thus the UITextView removed and readded), there is no lag and scrolling is super smooth for those cells.
I'm running out of ideas what the reason for this lag could be.
Any idea is appreciated.
PS: The lag is noticable on both, iPhone 4 and iPhone 4S and is of almost exactly the same duration (so it should not be CPU related)
UITableViewController.m:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"IPFormFieldCell";
// Get Form Field for indexPath
IPFormField *formField = [self.form fieldAtIndexPath:indexPath];
IPTableViewCell *cell = (IPTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[IPTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.backgroundView = nil;
cell.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]];
cell.selectedBackgroundView = nil;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[cell assignFormField:formField];
return cell;
}
IPFormTableViewCell.m:
- (void) assignFormField:(IPFormField *)field:
- (void) assignFormField:(IPFormField *)field {
if (formField != nil) {
formField.inputView = nil; // unlink old field
}
self.formField = field;
// Change Field Label
[fieldLabel setText:[field label]];
// Add an Input View to the Field
UIView *labelView = nil;
UIView *inputView = nil;
switch (formField.type) {
case IPFormFieldTypeTextField:
{
labelView = fieldLabel;
UITextField *textField = inputTextField;
textField.delegate = (IPFormTextField *)formField;
textField.inputAccessoryView = [formField.form inputAccessoryView];
textField.placeholder = [self.formField stringFromValue:self.formField.defaultValue];
textField.keyboardType = [(IPFormTextField *)formField keyboardType];
if (self.formField.value == nil || [[self.formField stringFromValue:self.formField.value] isEqualToString:[self.formField stringFromValue:self.formField.defaultValue]]) {
textField.clearsOnBeginEditing = YES;
} else {
textField.text = [self.formField stringFromValue:self.formField.value];
textField.clearsOnBeginEditing = NO;
}
inputView = textField;
break;
}
case IPFormFieldTypeTextArea:
{
UITextView *textView = inputTextView;
textView.delegate = (IPFormTextArea *)formField;
textView.inputAccessoryView = [formField.form inputAccessoryView];
if (self.formField.value == nil || ![[self.formField stringFromValue:self.formField.value] length] > 0) {
textView.text = [self.formField stringFromValue:self.formField.defaultValue];
} else {
textView.text = [self.formField stringFromValue:self.formField.value];
}
inputView = textView;
break;
}
default:
break;
}
self.leftItem = labelView;
self.rightItem = inputView;
if (leftItem != nil) {
[self.contentView addSubview:leftItem];
}
if (rightItem != nil) {
[self.contentView addSubview:rightItem];
}
formField.inputView = rightItem;
}
Apparently, cellForRowAtIndexPath: of my dataSource made use of a field's property, that was set as #property (nonatomic, copy) instead of #property (nonatomic, readonly).
Now that I've fixed it, the scrolling isn't lagging anymore.
As I guessed, your problem here is with your custom controls. Yes, you are reusing the cell, but this doesn't give anything in your case, as every time you request for the cell, you are creating new custom control for each cell. My advise, you can create and keep your custom controls as an instance variables, and when required return them without many if-elses, or, you could create custom cells for your two cases, and keep them dequeued with different cell identifiers and reuse them. Good Luck!

UITableView numberOfRowsInSection Will Not Load More than One Row

So I have a UITableView which should loop through a single NSMutableArray and use each of them as row labels. Currently the only way I can get this to run is with 0 or 1 rows, 2 or higher throws out an error saying the array index is off. I tried NSLog to output my array and can confirm it's reading all the Strings.
// table methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Set up the cell...
NSString *cellValue = [harvestRecipeList objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
return cell;
}
The array code is stored in the exact same file (MasterViewController.m) which I added below.
- (void)viewDidLoad
{
[super viewDidLoad];
harvestRecipeList = [[NSMutableArray alloc] init];
[harvestRecipeList addObject:#"Ice Cream"];
[harvestRecipeList addObject:#"Walnut Cake"];
[harvestRecipeList addObject:#"Cookies"];
[harvestRecipeList addObject:#"Salad"];
[harvestRecipeList addObject:#"Grilled Fish"];
//Set the title
self.navigationItem.title = #"BTN Recipes";
}
I would love any help on this it's been bugging me. I was using [harvestRecipeList count] but this throws the same array index error. And as I mentioned I can get the app to run perfectly fine with 0 or 1 rows - thanks in advance for any help!
EDIT: here is the error I'm getting in the output window after building:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
EDIT2: included below my property setup for harvestRecipeList
//MasterViewController.h
#interface MasterViewController : UITableViewController {
NSMutableArray *harvestRecipeList;
}
#property (nonatomic, retain) NSMutableArray *harvestRecipeList;
// and also my MasterViewController.m
#synthesize harvestRecipeList;
EDIT3
here's my source code zipped for this project. It's called treehouse, just a testing name for now but you can dl from my cloudapp here.
Updated Solution:
So I have checked your code and found the problem. Do the following:
Go into your storyboard and select the Master Table View (click where it says Static Content)
Click on the Attributes Inspector (looks like a downward arrow sort of) and change the content from Static Cells to Dynamic Prototypes
Click on the prototype cell and type Cell into Identifier field (this is what you are using as a cell ID
Also change the Accessory from None to Disclosure Indicator
In your tableView:numberOfRowsInSection return self.harvestRecipeList.count
In tableView:cellForRowAtIndexPath: you can remove the following two lines (as they are provided by the Storyboard):
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
Recreate your Push segue from your Master Cell to your Detail View Controller
It should all now work fine - and I've tested that it works. The basic problem was you had specified Static Cells rather than Dynamic Prototypes and the rest of the instructions are just mopping up. The NSRangeException was caused by only having a single cell so that was all that was displayed.
Hope this helps.
Previous Solution:
So, a few comments but, first, if you've updated your code can you post an update?
Your harvestRecipeList that you add objects to in ViewDidLoad does not appear to be the same harvestRecipeList that you synthesised - it will be local to the method so your instance variable will always be nil. You should always use self.harvestRecipeList - do this everywhere. This could easily explain your NSRangeException. Also see #4 below.
In your tableView:numberOfRowsInSection: you should return self.harvestRecipeList.count
If you are using the iOS5 SDK, you do not need to check if cell == nil as dequeueReusableCellWithIdentifier: is guaranteed to return non-nil cell. You do need to check if you are on the iOS4 SDK.
Change your #synthesize harvestRecipeList; to #synthesize harvestRecipeList = _harvestRecipeList; as this will assist with #1 and checking you are accessing the ivar.
Try #1 & #2 as a minimum and then post an update on the problems you are having. Hope this helps.
I examined the code you put in the ZIP file. I immediately noticed your using the iOS 5 Storyboard feature. I haven't got Xcode 4 nor the iOS 5 SDK, so I could not test that part of your application.
However, I went on and coded your Storyboard part by hand. I have tested your MasterViewController solely and found no errors. I added in the AppDelegate this method to replace the Storyboard automatical features and just show the view controller where you think the error is coming from.
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MasterViewController *myVC;
_window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] applicationFrame]];
myVC = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
[_window setAutoresizesSubviews:YES];
[_window addSubview:myVC.view];
[_window makeKeyAndVisible];
return YES;
}
To prove your MasterViewController.m contains no error, I add this screenshot:
Conclusion: Your error is to be found somewhere else, probably in your Storyboard file. However I have never used that new functionality so I cannot help you with that. I suggest you review your Storyboard and put all attention to that file.
Okay, if your log messages display an array with five objects right before you try to query the second, and you application gives you an NSRangeException the bug is definitely not to be found in the code you show us.
Try to find it by placing various logs before and after any -[NSArray objectAtIndex:] and see which log doesn't come through after the call. There's your error.
Remember you can use
NSLog(#"%s", __PRETTY_FUNCTION__);
to show where you log message is coming from. Their also exists a line and file macro, but normally the function macro should help you enough.
Example:
NSLog(#"%s Before", __PRETTY_FUNCTION__);
[myArray objectAtIndex:anIndex];
NSLog(#"%s After", __PRETTY_FUNCTION__);
If your second log message doesn't come through, then you'll have found your error.
It is always best to use the setter and getter methods for your instance variables. It takes care of a lot of problems. My guess is that is your problem. So anywhere you want to use harvestRecipeList use self.harvestRecipeList
It would be useful to know what your property declaration is for harvestRecipeList

UITableViewCell losses repeated content in other cell

I've a UITableView which when a cell has the same content that other, this content only appear in the las cell drawed. My custom cell adds an UIView property to add dynamic subviews from other class.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"cell";
CollectionCell *cell = (CollectionCell *)[tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (!cell) {
cell = [[[CollectionCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier]
autorelease];
}
[cell setCollectionView:/* Generated view in other class */];
return cell;
}
The concrete problem is:
My dynamic view is composed by, for example, 2 UILabels:
if label 1 is a title, the title is unique for each row -> No problem, renders fine.
if label 2 is a category, indexes from 0 to 5 have same category -> Only row at index 5 shows category label.
I can't create this labels in cell instantiation and add as subview because the cell content is all dynamic.
Thanks for your time and help.
UPDATE:
I can't create this labels in cell instantiation and add as subview because the cell content is all dynamic.
I'm going to explain it in detail:
The content and UI controls added to collectionView property can be differentes each execution. In one execution collectionView could have an UIImageView and a UILabel, and next execution it has 2 UILabels (for example). This is why I can't create something like this
if (!cell) {
cell = [[[CollectionCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier]
autorelease];
UILabel *foo = [[UILabel alloc] initWithFrame:SomeFrame];
[foo setTag:101];
[cell.collectionView addSubview:foo];
}
UILabel *foo = [cell.collectionView subviewWithTag:101];
[foo setTitle:#"This content is dynamic"];
Thanks!
Update 2:
Appears to be a problem with custom UILabel subclass. If I use original UILabel to show strings works fine.
You are not supposed to add subviews outside the block-
if (!cell) {
cell = [[[CollectionCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier]
autorelease];
}
your subviews should only be added inside this block (the first time the reusable cell is created).
everything that happens outside (after) this 'if' block happens multiple times as you scroll your table up and down so that's where you edit the added subviews (only after the whole 'if block, outside it).
See my answer here

UITableView "cellForRowAtIndexPath" method gets called twice on a swipe abruptly

I think many of us has faced this problem on UITableView delegate method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath which gets called twice.
In my application I transforming the tableView. The code is:
CGAffineTransform transform = CGAffineTransformMakeRotation(-M_PI/2);
theTableView.transform = transform;
theTableView.rowHeight = self.bounds.size.width;
theTableView.frame = self.bounds;
Now inside the delegate method I am doing a couple of things:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
modelRef.currentCellAtIndexPathRow = indexPath.row;
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier frame:self.bounds] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
modelRef.currentPageIndex = (indexPath.row + 1);
[cell showPage];
NSLog(#" visible cell %i ",[[tableView visibleCells] count]);
return cell;
}
At a time 1 cell is visible, but first time when the application launches. The log shows visible cells 0.
Many a times this particular delegate method gets called twice abruptly.
How can I solve this?
I think an immediate fix is just to set a flag which changes the first time it is hit, so then you ignore the second call. It's probably not the perfect solution, and I can't tell you why it gets hit twice - but this will work. (I have experienced exactly the same behavior when I implemented an Apple delegate from the UIWebView class)
EDIT:
Create a BOOL member in the class header, then in the init set the value to be YES. So if the BOOL is called mbIsFirstCall for example, in your delegate method, do the following:
if (mbIsFirstCall)
{
// do your processing, then the line below
mbIsFirstCall = NO;
}
else
{
// you don't need this else, but just for clarity it is here.
// you should only end up inside here when this method is hit the second time, so we ignore it.
}