Why the tableHeaderView got removed when I added that to another subView and replace the tableHeaderView? - objective-c

This is the full code of what I want to do (only 2 lines)
[self.headerViewofWholeTable addSubview:self.delegate.tableView.tableHeaderView];
self.delegate.tableView.tableHeaderView= self.headerViewofWholeTable;
Things doesn't work, so I started adding print information
[self.headerViewofWholeTable addSubview:self.delegate.tableView.tableHeaderView];
PO(self.headerViewofWholeTable.subviews);
self.delegate.tableView.tableHeaderView= self.headerViewofWholeTable;
PO(self.headerViewofWholeTable.subviews);
Simple 4 line of codes :D
Result:
self.headerViewofWholeTable.subviews: (
"<UILabel: 0x8bf9770; frame = (50 15; 250 21); text = 'Pull Down to Refresh'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8bf97e0>>",
"<UILabel: 0x8b63aa0; frame = (50 35; 257 20); text = 'last updated'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b63b10>>",
"<UIView: 0x8b63b80; frame = (20 11; 22 54); autoresize = RM+BM; layer = <CALayer: 0x8bf6c40>>",
"<UIImageView: 0x8be9ca0; frame = (0 0; 320 10); autoresize = LM+RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8be9ce0>> - shading-top-Table.png"
)
self.headerViewofWholeTable.subviews: (
"<UILabel: 0x8bf9770; frame = (50 15; 250 21); text = 'Pull Down to Refresh'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8bf97e0>>",
"<UILabel: 0x8b63aa0; frame = (50 35; 257 20); text = 'last updated'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b63b10>>",
"<UIView: 0x8b63b80; frame = (20 11; 22 54); autoresize = RM+BM; layer = <CALayer: 0x8bf6c40>>"
)
So you see,
that self.delegate.tableView.tableHeaderView= self.headerViewofWholeTable; remove the view that's pointed to by self.delegate.tableView.tableHeaderView. However, the view
"<UIImageView: 0x8be9ca0; frame = (0 0; 320 10); autoresize = LM+RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8be9ce0>> - shading-top-Table.png"
should not go away because it's retained by self.headerViewofWholeTable
So can anyone explain what happened?
I can easily circumvent the problem, but I want to know more of what's actually going on.

Most likely the implementation of the UITableView setTableHeaderView method (which you call in the second line) assumes that the current header view is still a subview of the table. So when you try to assign a new header view, the table view first removes the current header from its superview (not knowing it has already been moved to your other view). The table view isn't expecting that its header view has been moved to a new view. So it ends up removing the header view from your headerViewofWholeTable view.

Related

Why Are The UITableView's Cells Being Inserted Above The Table Header?

The question and the screen shots pretty much say it all. The cells are being created from a nib that has been registered with the tables.
Update: I decided to dig in with lldb:
(lldb) po servicesTable.tableHeaderView
nil
(lldb) po characteristicsTable.tableHeaderView
nil
The header views are nil! Does this mean that I am being fooled by what I see on my storyboard? That I have not actually created table headers?
Let's keep digging:
`(lldb) po characteristicsTable!.subviews
▿ 4 elements
- 0 : <UITableViewWrapperView: 0x7f89f4840a00; frame = (0 0; 343 270.5); gestureRecognizers = <NSArray: 0x610000051070>; layer = <CALayer: 0x61000003df00>; contentOffset: {0, 0}; contentSize: {343, 270.5}>
- 1 : <UIImageView: 0x7f89f2d1c6a0; frame = (3 265; 337 2.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x600000224840>>
- 2 : <UIView: 0x7f89f50162a0; frame = (0 100; 343 44); autoresize = RM+BM; layer = <CALayer: 0x618000039e80>>
- 3 : <UIImageView: 0x7f89f2c1c370; frame = (337.5 179.5; 2.5 88); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x6080004241e0>>`
(lldb) po characteristicsTable!.subviews[2].subviews
▿ 2 elements
- 0 : <UILabel: 0x7f89f5016570; frame = (8 8; 327 28); text = 'Characteristics'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x61800008cbc0>>
- 1 : <UIButton: 0x7f89f2d06f40; frame = (313 11; 22 22); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x600000223300>>
Alright, the characteristics table sub view at index 2 is the one that we see labeled as "Header" in the storyboard's document outline (i.e. a view containing a label with the text "Characteristics" and a button).
Lets try this:
(lldb) expression characteristicsTable.tableHeaderView = characteristicsTable.subviews[2]
Voila! The characteristics table's header is positioned properly.
From all of this I am concluding that I do not know how to drag a view onto a table and have it become the table's header. Can anyone advise? (I will continue to experiment)
In order to use the Interface Builder to add a table header to a UITableView, drag a view onto the table view's entry in the document outline. DO NOT drag the view onto the table view's rendering in the storyboard. Jeez! These type of subtleties are darned exasperating.
(lldb) po servicesTable.tableHeaderView
▿ Optional<UIView>

How in the earth [cell.contentView setUserInteractionEnabled: NO]; solves this?

I have the exact same problem
Custom buttons in XIB used as Custom UITableViewCell don't respond to taps (ios7)
Basically buttons in UITableViewCell no longer works.
It used to work fine in IOS 6. Not anymore in IOS 7.
I saw 2 people with the same solution
[cell.contentView setUserInteractionEnabled: NO]
However, the solution doesn't make sense at all.
The buttons are obviously a subview of cell.contentView. If we set userinteractionenabled to no, it should disable user interaction to all the subviews of cell.contentView. So how the hell this work at all?
Actually what have changed between IOS 7 to IOS 6 so that this problem exist in the first place.
Note: It doesn't really solve my problem.
As I suspected, the buttons inside UITableViewCell is indeed a subview of contentView
2013-10-11 13:07:04.946 [15131:a0b] self: <BGCatalogTableCellForCatalog: 0x12e12d20; baseClass = UITableViewCell; frame = (0 0; 320 220); layer = <CALayer: 0x12e12c70>>
2013-10-11 13:07:04.947 [15131:a0b] self.contentView: <UITableViewCellContentView: 0x12e12600; frame = (0 0; 320 220); gestureRecognizers = <NSArray: 0x12e12440>; layer = <CALayer: 0x12e125d0>>
2013-10-11 13:07:04.947 [15131:a0b] self.btnBrochureButton: <UIButton: 0x12e100b0; frame = (0 0; 160 219); opaque = NO; autoresize = RM+TM+BM; layer = <CALayer: 0x12e102c0>>
2013-10-11 13:07:04.948 [15131:a0b] self.btnBrochureButton.superview: <UITableViewCellContentView: 0x12e12600; frame = (0 0; 320 220); gestureRecognizers = <NSArray: 0x12e12440>; layer = <CALayer: 0x12e125d0>>
2013-10-11 13:07:04.948 [15131:a0b] self.btnBrochureButton.superview.superview: <UITableViewCellScrollView: 0x12e12920; frame = (0 0; 320 220); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x12e12840>; layer = <CALayer: 0x12e128f0>; contentOffset: {0, 0}>
2013-10-11 13:07:04.949 [15131:a0b] self.btnBrochureButton.superview.superview.superview: <BGCatalogTableCellForCatalog: 0x12e12d20; baseClass = UITableViewCell; frame = (0 0; 320 220); layer = <CALayer: 0x12e12c70>>
2013-10-11 13:07:04.949 [15131:a0b] <UIButton: 0x12e100b0; frame = (0 0; 160 219); opaque = NO; autoresize = RM+TM+BM; layer = <CALayer: 0x12e102c0>>
So both the btnBrochureButton.superview and self.contentView is 0x12e12600
I got the problem fixed. Starting in IOS 7, contentView is not a direct subview of UITableViewCell. This causes complication.
I'm not sure if this will help you, but I found that my issue was related to the issues described in the posts you referenced, but I also had one more piece of the puzzle. The contentView.userInterActionEnabled = NO didn't work for me, for the same reason you described.
In my app, I'm dynamically resizing the table view cells, changing their heights at runtime based on the content in the cell. This created a separate issue for me where the cell was being resized, but the contentView, for some reason, was not being resized. So my button was visible, but was outside the bounds of the contentView and therefore not available to user interaction.
The way I finally discovered it was by setting the background color of my contentView to a different color, where I finally saw that it was too short. I solved the issue by changing the height for the contentView after I calculated the total height for the parent cell.
I hope this can help you or at least give you something new to check for.

Why the frame of the UILabel in standard UITableViewCell is (0,0,0,0) and What's the Standard Size of UITableViewCell.textLabel?

I want to replace that with a webview so I can display html.
PO(cell.subviews);
PO(cell);
PO(cell.contentView.subviews);
The webview need to have the same frame with the frame of UITableViewCell.
This is what we got:
2013-01-31 12:37:35.204 BadgerNew[3888:c07] cell.subviews: (
"<UITableViewCellContentView: 0xa057140; frame = (0 0; 320 44); layer = <CALayer: 0xa057180>>"
)
2013-01-31 12:37:35.204 BadgerNew[3888:c07] cell: <UITableViewCell: 0xa0c37a0; frame = (0 0; 320 44); text = 'atm'; layer = <CALayer: 0xa0c8530>>
2013-01-31 12:37:35.205 BadgerNew[3888:c07] cell.contentView.subviews: (
"<UILabel: 0xa0d12a0; frame = (0 0; 0 0); text = 'atm'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0xa0571b0>>"
)
Moreover, to further confirm
po [cell textLabel]
(UILabel *) $1 = 0x0a0d12a0 <UILabel: 0xa0d12a0; frame = (0 0; 0 0); text = 'atm'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0xa0571b0>>
What is the final standard frame for normal UITableViewCell then?
I also did
-(void)layoutSubviews
{
PO(self);
PO(self.textLabel);
while (false);
}
and got
2013-02-01 09:36:02.048 BadgerNew[1436:c07] self.textLabel: <UILabel: 0x8923390; frame = (0 0; 0 0); clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x89e8fc0>>
Doing it in
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
while (false);
}
yield the same result
Then I did:
-(void)layoutSubviews
{
[super layoutSubviews];
PO(self);
PO(self.textLabel);
while (false);
}
Again, another 0 frame.
The frames of the subviews within a cell are set after -tableView:cellForRowAtIndexPath.
Make a subclass of UITableViewCell, it's really the best way of handling things like this. You can override -textLabel in your subclass and bend it to your will, or you can override -layoutSubviews and use -textLabel's frame then.
One final warning, mucking around with the internals of an object causes brittle code that could break at any revision of iOS.

Why size to fit doesn't work at all and do nothing in this obvious scenario

Let's examine this line of codes (only 1 actually change anything)
PO(self.containerForFormerHeader);
PO(self.containerForFormerHeader.subviews);
[self.containerForFormerHeader sizeToFit];
PO(self.containerForFormerHeader);
Result:
self.containerForFormerHeader: <UIView: 0x8b669c0; frame = (0 75; 320 0); autoresize = W+H; layer = <CALayer: 0x8b5eb30>>
self.containerForFormerHeader.subviews: (
"<UIImageView: 0xd572210; frame = (0 0; 320 10); autoresize = LM+RM+TM; userInteractionEnabled = NO; layer = <CALayer: 0xd572250>> - shading-top-Table.png"
)
self.containerForFormerHeader: <UIView: 0x8b669c0; frame = (0 75; 320 0); autoresize = W+H; layer = <CALayer: 0x8b5eb30>>
From the result it's obvious that:
The view has a height of 0.
The view has a subview whose height is 10
After sizetofit, nothing change. How come?
I can compute the frame directly however, this bothers me.
When you call the sizeToFit method on a view, it ends up calling the sizeThatFits: method on the view. The default implementation of sizeThatFits: returns the view's current size.
So unless your custom view explicitly overrides the sizeThatFits: method to return an appropriate size to contain its subviews, nothing will change size when you call sizeToFit.
This is all spelled out in the docs for UIView sizeToFit and UIView sizeThatFits:.
Classes like UILabel do implement sizeThatFits: to return an appropriate size.

iOS5, UIScrollView and layoutSubviews behaviour

In my app (code very similar to Apple's PhotoScroller demo from WWDC10), I have a UIScrollView. Within the scroll view I have overwridden layoutSubviews with code like:
- (void) layoutSubviews {
[super layoutSubviews];
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
...
}
In iOS 4.x if the application starts up on landscape mode (user is holding it landscape), then layoutSubviews gets called twice (very quickly). The first time its called, boundsSize has dimensions that indicate its in portrait but immediately afterwards it gets called again and self.bounds.size returns dimensions that indicate the device is in landsacpe and my layout calculations work correctly.
In iOS 5.x, layoutSubviews only gets called once with bounds.size returning dimensions indicating portrait and it doesn't get the second call, so all my calculation code is messed up.
If the user physcially rotates the device then layoutSubviews gets called and works correctly -- so a user starting the app in landscape (draws incorrectly), rotates to portrait (draws correcty) and then rotates back to landscape (now draws correctly).
Its that second "automatic" call of layoutSubviews that I'm missing.
Anybody else notice this or have any advice?
Update:
I did find this in the UIKit release notes for iOS 5, but I'm not sure if it's relevent or even what the impact is of this change.
Rotation callbacks in iOS 5 are not applied to view controllers that are presented over a full screen. What this means is that if your code presents a view controller over another view controller, and then the user subsequently rotates the device to a different orientation, upon dismissal, the underlying controller (i.e. presenting controller) will not receive any rotation callbacks. Note however that the presenting controller will receive aviewWillLayoutSubviews call when it is redisplayed, and the interfaceOrientation property can be queried from this method and used to lay out the controller correctly.
Further updates:
Using [UIView recursiveDescription], to dump the view hierarchies, I got the following output:
iOS 4 (which is working correctly):
Portrait:
2011-12-19 15:57:06.400 stroom[10889:11603] <0x5e7f9b0 stroomViewController.m:(402)> <UIView: 0x6a6f2f0; frame = (0 0; 768 1024); autoresize = W+H; layer = <CALayer: 0x6a659d0>>
| <UIScrollView: 0x5e911e0; frame = (-20 0; 808 1024); clipsToBounds = YES; autoresize = LM+W+RM+TM+H+BM; layer = <CALayer: 0x5e91370>; contentOffset: {0, 0}>
| | <stroomFullScreenPhotoScrollView: 0x5ea1010; baseClass = UIScrollView; frame = (20 0; 768 1024); clipsToBounds = YES; layer = <CALayer: 0x5ea0940>; contentOffset: {0, 0}>
| | | <UIImageView: 0x5ea2600; frame = (0 224; 768 576); transform = [0.75, 0, 0, 0.75, 0, 0]; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5ea0890>>
Landscape:
2011-12-19 15:57:34.522 stroom[10889:11603] <0x5e7f9b0 stroomViewController.m:(402)> <UIView: 0x6d96c30; frame = (0 0; 768 1024); transform = [0, 1, -1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x6d66440>>
| <UIScrollView: 0x5e9eb70; frame = (-27 0; 1078 768); clipsToBounds = YES; autoresize = LM+W+RM+TM+H+BM; layer = <CALayer: 0x5eadde0>; contentOffset: {0, 0}>
| | <stroomFullScreenPhotoScrollView: 0x6dab850; baseClass = UIScrollView; frame = (20 0; 1038 768); clipsToBounds = YES; layer = <CALayer: 0x6dab2e0>; contentOffset: {0, 0}>
| | | <UIImageView: 0x5e97f70; frame = (0 224; 1024 768); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x5e97fa0>>
iOS 5 (which is working incorrectly):
Portrait:
2011-12-19 15:55:59.530 stroom[10850:16103] <0x848b520 stroomViewController.m:(402)> <UIView: 0xa884710; frame = (0 0; 768 1024); autoresize = W+H; layer = <CALayer: 0xa8849a0>>
| <UIScrollView: 0xa883820; frame = (-20 0; 808 1024); clipsToBounds = YES; autoresize = LM+W+RM+TM+H+BM; layer = <CALayer: 0xa883a80>; contentOffset: {0, 0}>
| | <stroomFullScreenPhotoScrollView: 0x8699630; baseClass = UIScrollView; frame = (20 0; 768 1024); clipsToBounds = YES; layer = <CALayer: 0x8699360>; contentOffset: {0, 0}>
| | | <UIImageView: 0x869a7c0; frame = (0 0; 768 576); transform = [0.75, 0, 0, 0.75, 0, 0]; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x869a800>>
Landscape:
2011-12-19 15:56:32.521 stroom[10850:16103] <0x848b520 stroomViewController.m:(402)> <UIView: 0x8498530; frame = (0 0; 768 1024); transform = [0, 1, -1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x8498560>>
| <UIScrollView: 0x849ead0; frame = (-27 0; 1077 768); clipsToBounds = YES; autoresize = LM+W+RM+TM+H+BM; layer = <CALayer: 0x848f390>; contentOffset: {808, 0}>
| | <stroomFullScreenPhotoScrollView: 0x81e4b80; baseClass = UIScrollView; frame = (828 0; 768 1024); clipsToBounds = YES; layer = <CALayer: 0x81e7dc0>; contentOffset: {0, 0}>
| | | <UIImageView: 0x81e5090; frame = (0 0; 768 576); transform = [0.75, 0, 0, 0.75, 0, 0]; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x81e5010>>
From these I can see that the contentOffset of the UIScrollView in iOS 5 looks incorrect and that the landscape frame have incorrect dimensions in iOS 5 where they appear to have correct dimensions in iOS 4.
Eventually, I stumbled upon the solution to this problem for me. I'll provide the answer here as it maybe helpful for others.
The solution for me was to implement the - (void)viewWillAppear:(BOOL)animated method on my view controller. I didn't have one and I had all my setup logic in - (void) viewDidLoad method.
In particular, I implemented the following:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
CGRect bounds = pagingScrollView.bounds; // <=== this was the key
// ... use the bounds in my view setup logic
}
It appears to me, that there has been a change in iOS 5 (from previous versions of the OS). When a view is loaded when a device is in landscape, the bounds property on a view does not reflect the orientation transformation until viewWillAppear. Previously, the bounds value was correct for me in viewDidLoad but moving the calculations and reading of the bounds property out of there and into viewWillAppear did the trick.
All works fine in iOS4 as well, when I did my testing.
Perhaps, I should always have been reading the bounds property in viewWillAppear and it may well be the more correct behavior. However, something has changed in between iOS4 and iOS 5 that I have not been able to find documentation on.
`
I was having similar issue. Somehow, I solved it.
In iOS 5.x, When you create new XIB file, by default, it Orientation is set to Portrait. So, your layoutSubviews only getting called once with bounds.size returning dimensions indicating portrait and it doesn't get the second call.
Just, Do one thing, set your XIB orientation to Landscape in XIB design. So, your code will check orientation for the first time, it will get it as Landscape & your all layouts will be set accordingly.
I'm having a similar issue. I got around it by hardcoding the bounds rect depending on the view controller's interface orientation and possibly flagging the scrollview for setNeedsLayout on viewDidLoad and viewWillAnimateToInterfaceOrientation.