Custom UITableView headerView disappears after memory warning - iphone

I have a UITableViewController. I create a custom headerView for it's tableView in the loadView method like so:
(void)loadView {
[super loadView];
UIView* containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height * 2 )];
containerView.tag = 'cont';
containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(padding, height, width, height);
... //configure UIButton and events
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"] highlightedImage:[UIImage imageNamed:#"highlight.png"]];
imageView.frame = CGRectMake(0, 0, width, height );
... //configure UIImageView
[containerView addSubview:button];
[containerView addSubview:imageView];
self.tableView.tableHeaderView = containerView;
[imageView release];
[containerView release];
}
None of the other methods (viewDidLoad/Unload, etc) are overloaded.
This controller is hosted in a tab. When I switch to another tab and simulate a memory warning, and then come back to this tab, my UITableView is missing my custon header. All the rows/section are visible as I would expect. Putting a BP in the loadView code above, I see it being invoked when I switch back to the tab after the memory warning, and yet I can't actually see the header.
Any ideas about what I'm missing here?
EDIT: This happens on the device and the simulator. On the device, I just let a memory warning occur by opening a bunch of different apps while mine is in the background.

the loadView method shouldn't call super. Try removing [super loadView]; and that may well solve your problem. The other overridden view methods (viewDidLoad, viewWillAppear etc.) can call super just fine. See the documentation at:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
for confirmation.

Keep containView instead of releasing it. And return it in tableView:viewForHeaderInSection: (UITableViewDelegate method).

I had a similar problem to this, though it doesn't seem to explain the OP's situation based on his code; I just want to put it here in case anyone else comes across this.
I retain my header view in an instance variable. In my viewDidLoad I check to see if this variable is nil, if it is then I create the view, retain and set the instance variable to it and set tableHeaderView to it. The problem is that this condition will only be true once, because my instance variable will never be nil after the first time I create it; however, the table view's tableHeaderView property will be set to nil when there's a memory warning, so there won't be a header view any more, even though I still have the view in an instance variable.
The solution was either to change the check to see if the table view's tableHeaderView's property is nil (and then re-create the header view every time that gets set to nil), or (since in this case I still have the view retained in an instance variable) to move the assignment to tableHeaderView outside of the if-block, thus every time viewDidLoad is run, we will make sure to re-assign the header view to tableHeaderView in case it got set to nil.

What are width and height? Is it possible that they are zero after a memory warning?

that is normal way, iOS use this way to reduce memory, while receive memory issue, then it will free some UI element , that also will invoke method 'didUnLoad' ... you have to free the headerView in the method 'didUnLoad' , set to nil , and then in the method 'didLoad' to create the headerView if it is nil and then add it to tableView as well....
iOS will automatic free some UI elements while face memory warning .

I just ran into a similar issue that was causing my custom header for the uitableview to not be reloaded after receiving a memory warning. my current implementation to create the header is
- (void)viewDidLoad
{
[super viewDidLoad];
if (_tableHeaderView == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"DetailHeaderView" owner:self options:nil];
self.tableView.tableHeaderView = _tableHeaderView;
}
}
and to fix the problem i simply added this line to my didReceiveMemoryWarning
_tableHeaderView = nil
and now it is reloading after a memory warning is sent.
Sorry if this has already been answered but I didn't see a clear cut answer for my problem and figured I'd pop this in here for anyone else in my situation.
Happy Coding

Doing a reloadData on the table view might help

The only problem that I can see by looking at just your loadView is that you are trying to tag your containerView with a string. That should be an NSInteger as far as I know.

This problem is known. For example, if you use the TableViewDelegate tableView:viewForHeaderInSection: it won't work, too.
The problem is iOS and not you. There aren't many questions about the tableHeaderView because nobody use it, and so Apple didn't get any BugTracker entries...
Have a look to a older question: here. And here are other problems.
So I think really it's a SDK bug...

Related

UIImageView still visible after hiding/removing from superview

i hope you will find a solution for my problem, cause i don't have any ideas anymore.
I have a tableview, which has several cells. some cells have another view as subview on their contentView.
This additional view has 2 subviews: 1 UIImageView and 1 UILabel.
Now when i tap an UIButton the UIImageView should be hidden/removed and the UILabel changes it's textColor to white(black before).
The UILabel changes it's textColor but the UIImageView is still visible, even after removing the UIImageView from it's superview.
The Code looks like this.
_adsc_dot_view is the UIImageView
_adsc_text_label is the UILabel
- (void)mc_set_selected:(BOOL)selected {
if (selected) {
_adsc_dot_view.hidden = YES;
_adsc_text_label.textColor = [UIColor whiteColor];
}
else {
_adsc_dot_view.hidden = NO;
_adsc_text_label.textColor = [UIColor blackColor];
}
}
Some check you might find useful for this issue:
1) make sure you create once your UIImageView which referenced as _adsc_dot_view
2) do a debug, mark mc_set_selected with a breakpoint and in the log check the view hierarchy, whether you have the needed number of UIImageView and not more
po [[UIWindow keyWindow] recursiveDescription]
or check this advanced SO answer: I need to inspect the view hierarchy on an iPhone program
3) if you use Interface Builder make sure you have proper type (and not UIImage) and proper reference
You are using UITableView and you add UIImageView and UILabel as a subview in UITableViewCell. So, I think you should reload UITabeView using [self.tableView reloadData]; or [YourTableName reloadData]; after your hide and show UIImageView method. Otherwise you should hide and show UIImageView using UIImageView tag or using UITableViewCell index path.
is the target device on iOS 7? If yes then do try to layoutsubviews of the cell. I had a similar issue where the cell wasn't refreshing on ios 7.
Just reload table view cell after Remove/Hide ImageView.

UIScrollView referenced both by UIViewController's 'view' property and by an outlet

I'm about to add a UIScrollView to my iPhone project and before I implement this functionality I wanted to check if my approach is the right one or if I could be violating some best practice I'm not aware of.
The tutorials I've seen generally involve adding a UIScrollView to an existing UIView and they work from there. However, I was wondering if I could spare the UIView altogether and just add the UIScrollView as the only top-level object in my nib file.
My sample project uses Xcode's View-based Application template:
Project navigator http://img542.imageshack.us/img542/5364/projectnavigator.png
I deleted the UIView top-level object from the original MySampleViewController.xib file and replaced it by adding a UIScrollView object:
Nib placeholders http://img526.imageshack.us/img526/7709/placeholderobjects.png
Now my nib file only shows this object in the canvas:
Canvas http://img233.imageshack.us/img233/4063/scrollview.png
Then I created the link from the UIViewController's view outlet to the UIScrollView.
Now, if I wanted to programmatically manipulate the contents of the UIScrollView I can use this code:
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor greenColor], [UIColor blueColor], nil];
// Solution B: With the following line we avoid creating an extra outlet linking to the UIScrollView top-level object in the nib file
UIScrollView *scrollView = (UIScrollView *)self.view;
for (int i = 0; i < colors.count; i++) {
CGRect frame;
//frame.origin.x = self.scroller.frame.size.width * i; // Solution A: scroller is an IBOutlet UIScrollView *scroller;
frame.origin.x = scrollView.frame.size.width * i; // Solution B
frame.origin.y = 0;
//frame.size = self.scroller.frame.size; // Solution A
frame.size = scrollView.frame.size; // Solution B
UIView *subView = [[UIView alloc] initWithFrame:frame];
subView.backgroundColor = [colors objectAtIndex:i];
//[self.scroller addSubview:subView]; // Solution A
[self.view addSubview:subView]; // Solution B
[subView release];
}
//self.scroller.contentSize = CGSizeMake(self.scroller.frame.size.width * colors.count, self.scroller.frame.size.height); // Solution A
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * colors.count, scrollView.frame.size.height); // Solution B
}
In order to implement Solution A the scroller outlet must be linked to the nib's UIScrollView as well, and the Connections Inspector looks like this:
Connections Inspector http://img228.imageshack.us/img228/8397/connectionsj.png
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
Solution B only involves UIViewController's view outlet linking to the view, and using this line:
UIScrollView *scrollView = (UIScrollView *)self.view;
My questions:
Do I incur in some sort of violation of Apple's design guidelines by using one of these two solutions?
Should I stick to the UIScrollView within a UIView solution?
Is there any other way to implement this?
Thanks!
P.S. Sorry for the syntax highlight, SO didn't recognize the use of the 'objective-c' tag
No I think you are fine either way.
I would, I don't think a UIView has any significant cost, plus what if you want to add a page control? and you don't have to cast the controller's view to a UIScrollView every time you need it.
Looks like you have it under control to me.
Solution A requires an outlet and this means having two connections to the UIScrollView: the UIViewController's own view outlet and MySampleViewController's scroller outlet. Is it legal and/or recommended to have two outlets pointing to the same view?
It standard to have IBOutlets to any view defined in your .nib that you want to access directly from your view controller.
If you don't want two outlets you could give the scroll view a tag then find it like so:
UIScrollView *myScrollView = (UIScrollView *)[self.view viewWithTag:1]
Then you only have the view as an outlet, but I would just add the extra outlet. Just make sure you set them to nil in your viewDidUnload.
Also you don't have to retain the scroll view (if you are even still using retain/release). Since the scroll view is inside your view controller's view it keeps a reference so you can have your scrollview's property by assign or week if your using ARC.
Hope that helps.

Flipping between a standard and grouped style table via segmented control

I'm looking to use a UISegmentedControl to flip between two tables on the same view. One table (the default when the view is loaded) is grouped. The second table should be plain (non-grouped). The tableView is set to Grouped in the xib.
Right now I'm doing a reloadData when flipping between the two sets of data. So of course both "views" are in the grouped style. I know I can't change a tableView's style once set, but I'm looking to mimic that intent.
The best idea I've had is to create a second (plain) tableView in code, then flip between the two as needed. I could either show/hide (keeping them both in memory) or add and remove from the superView.
Both views may result in the user clicking through to additional pushed viewControllers. I want to retain consistency with the chain, including the ability to popToRootViewController from viewControllers further down the chain (the view in question is two or three steps down from the root).
Before I tear up my code with this idea I figure I'd check around to see what others thought.
Thanks!
UPDATE: Getting there. When I want to display the plain table view I have this:
PlainViewController *plainViewController = [[PlainViewController alloc] init];
UIView *plainTableView = plainViewController.view;
plainTableView.frame = CGRectMake(320, 0, 320, 480);
[self.view addSubview:plainViewController.view];
which loads and displays fine. But when PlainViewController's didSelectRowAtIndexPath fires, it fails to load the next view. didSelectRowAtIndexPath is firing (it's logged), but pushViewController does nothing; the next view's viewDidLoad never fires.
I suspect there's a discontinuity in the NavigationController but not quite sure where to fix that.
You can create a separate UIViewController and add its table to the 'main' view.
PlainTableViewController *ctr = [[PlainTableViewController alloc] init];
[self.view addSubview:ctr.view];
[ctr release];
You can also easily animate this process:
PlainTableViewController *ctr = [[PlainTableViewController alloc] init];
UIView *plainTableView = ctr.view;
plainTableView.frame = CGRectMake(320, 0, 320, 480);
[self.view addSubview:plainTableView];
[UIView animateWithDuration:0.3
animations:^{
groupedTableView.frame = CGRectMake(-320, 0, 320, 480);
plainTableView.frame = CGRectMake(0, 0, 320, 480);}
completion:^(BOOL finished){[groupedTableView removeFromSuperView];}
];
[ctr release];
Notice: You don't add it as view controller in your navigation controller so all calls of popToRootViewController should lead to your 'main' viewController. And that's what you want.
Assign your tableViewStyle in something like - (void) refreshTable; then when you tap your segmentedControl, also call [myTable reloadData];
You can use two different views that have table with both the style but with the same data and you can flip that two views on the segment selected index change event
OR
You have to remove the table from its superview and then realloc table with the selected style and add it again to that view.
I think that adding/removing the table views from the view containing the segmented control should work seamlessly.
In order to keep consistency, I think the approach is storing somewhere the information as to which view is selected through the segmented control, so that when you are displaying that view you know which is the current style. This info will be also stored in NSUserDefaults, so that it is persistent across runs.
Simple, every time that the segmented control changes, re-create the UITableView.
Upside: saves you from writing too much code, memory.
Downside: uses a bit more CPU.
But you'd be redrawing the view with reloadData anyway, so it is a very minimal CPU drain to simply recreate the view.
-(void)segmentedControlChanged{
CGRect originalFrame = tableView.frame;
UITableViewStyle tableStyle;
if(selectedSegmentIndex == 0){
tableStyle = UITableViewStylePlain;
} else {
tableStyle = UITableViewStyleGrouped;
}
[tableView removeFromSubview];
tableView = [[UITableView alloc] initWithFrame:originalFrame style:tableStyle];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
}
Note: code written by memory without checking in Xcode.

Why does UIScrollView always scrolls to bottom?

I have seen this question being addressed several times here at SO, e.g Problem with UIScrollView Content Offset, but I´m still not able to solve it.
My iphone app is basically a tab bar controller with navigation bar. I have a tableview controller made programmatically and a DetailViewController that slides in when I tap a cell in my tableview controller.
The DetailViewController is made in IB and has the following hierarchy:
top view => UIScrollView => UIView => UIImage and a UITextField.
My goal is to be able to scroll the image and text field and this works well. The problem is that my UIScrollView always gets positioned at the bottom instead at the top.
After recommendations her at SO, I have made my UIScrollView same size as the top view and instead made the UIView with the max height (1500) of my variable contents.
In ViewDidLoad I set the contentSize for the UIScrollView (as this is not accessible from IB):
- (void)viewDidLoad {
[scrollView setContentSize:CGSizeMake(320, 1500)];
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
NSLog(#"viewDidLoad: contentOffset y: %f",[scrollView contentOffset].y);
}
Specifically setting the contentOffset, I would expect my scrollView to always end up at the top. Instead it always go to the bottom. It looks to me that there is some autoscrolling beyond my control taking place after this method.
My read back of the contentOffset looks OK. It looks to me that there may be some timing related issues as the scrolling result may vary whether animation is YES or NO.
A ugly workaround I have found is by using this delegate method:
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrView {
NSLog(#"Prog. scrolling ended");
[scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
This brings my scrollview to top, but makes it bounce down and up like a yo-yo
Another clue might be that although my instance variables for the IBOutlet are set before I push the view controller, the first time comes up with empty image and textfield:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (!detailViewController) {
detailViewController = [[DayDetailViewController alloc] init];
}
// Pass dictionary for the selected event to next controller
NSDictionary *dict = [eventsDay objectAtIndex:[indexPath row]];
// This method sets values for the image and textfield outlets
[detailViewController setEventDictionary:dict];
// Push it onto the top of the navigation controller´s stack.
[[self navigationController] pushViewController:detailViewController animated:NO];
}
If I set animation to YES, and switch the order of the IBOutlet setting and pushViewController, I can avoid the emptiness upon initialization. Why?
Any help with these matters are highly appreciated, as this is really driving me nuts!
Inspired of Ponchotg´s description of a programmatically approach, I decided to skip interface builder. The result was in some way disappointing: The same problem, with the scrollview ending up in unpredictable positions (mostly at bottom), persisted.
However, I noticed that the scroll offset error was much smaller. I think this is related to the now dynamic (and generally smaller) value of ContentOffset. After some blind experimenting I ended up setting
[textView setScrollEnabled:YES];
This was previously set to NO, as the UITextView is placed inside the scrollview, which should take care of the scrolling. (In my initial question, I have erroneously said it was a UITextField, that was wrong)
With this change my problem disappeared, I was simply not able to get into the situation with scrollview appearing at bottom anymore in the simulator! (At my 3G device I have seen a slight offset appear very seldom, but this is easily fixed with scrollViewDidEndScrollingAnimation delegate described previously ).
I consider this as solved now, but would appreciate if anyone understand why this little detail messes up things?
OK! i have a question before i can give a correct answer.
Why are you using a UIView inside the Scrollview?
You can always only put your UIImageView and UITextField inside the UIScrollView without the UIView
and set the contentSize dynamically depending on the size of the text.
to give you an example how i do it:
int contSize = 0;
imageView.frame = CGRectMake(10, 0, 300, 190);
imageView.image = [UIImage imageNamed:#"yourimage"];
contSize = 190 //or add extra space if you dont want your image and your text to be so close
[textField setScrollEnabled:NO];
textField.font = [UIFont systemFontOfSize:15];
textField.textColor = [UIColor grayColor];
textField.text = #"YOUR TEXT";
[textField setEditable:NO];
textField.frame = CGRectMake(5, contSize, 310, 34);
CGRect frameText = textField.frame;
frameText.size.height = textField.contentSize.height;
textField.frame = frameText;
contSize += (textField.contentSize.height);
[scrollView setScrollEnabled:YES];
[scrolView setContentSize:CGSizeMake(320, contSize)];
In the above example I first create an int to keep track of the ysize of my view then Give settings and the image to my UIImageView and add that number to my int then i give settings and text to my UITextField and then i calculate the size of my text depending on how long is my text and the size of my font, then add that to my int and finally assign the contentSize of my ScrollView to match my int.
That way the size of your scrollview will always match your view, and the scrollView will always be at top.
But if you don't want to do all this, you can allways just:
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
at the end of the code where you set your image and your text, and the NOto avoid the bouncing.
Hope this helps.

iphone, objective c, xcode

I defined a UIView "RowOfThree" inwhich there are 3 labels. i also defined a UIView "Table" inwhich there are numer of objects of type "Row".
the following code is in a method within object "Table":
RowOfThree *rowOfThree = [[RowOfThree alloc] init];
[self addSubview:rowOfThree];
for some reason it doesn't add the view.
i tried defining the labels in "RowOfThree" both in IB and programmatically and it still didn't work.
Thank you.
Typically, a UIView (and the subclasses) are initialized using initWithFrame:. Maybe you did that in your own implementation of init, I don't know, but it may very well be that your view has a frame of {0,0,0,0} and therefore 0 height and 0 width. Set the frame by hand and tell us whether this works.
CGRect newFrame = CGRectMake(0.f, 0.f, 200.f, 40.f);
RowOfThree *rowOfThree = [[RowOfThree alloc] initWithFrame:newFrame];
[self addSubview:rowOfThree];
[rowOfThree release];
SanHalo's answer is most likely correct but in addition, if you're using Interface Builder, you should not be directly initializing views that are defined in the nib. If you do, you have to use initFromNib instead of just init.