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

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.

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>

drawRect is not drawing over a UI element

TL/DR version: I'm trying to draw a line OVER the UISlider, not under it.
I'm overriding the UISlider's drawRect method in order to draw lines upon it. For some reason, the sliders progress bar overlaps the line I draw, and it appears like so:
You can BARELY see the marks, hence my need to draw differently. This is the code I wrote to draw said lines:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 20);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue + 175, 45);
CGContextAddLineToPoint(context, x.floatValue + 175, rect.size.height - 50);
CGContextStrokePath(context);
}
}
I figured that would work just fine, but nope. So, I did some research on this website, and the conclusion that I can to is like I need to override layoutSubViews.
Perfect, so I added [super layoutSubviews]; right in the beginning on my method;
Like so:
- (void)drawRect:(CGRect)rect {
[super layoutSubviews];
... // all other code
}
No help. As you can tell from the code, I'm setting the line color as [UIColor whiteColor], yet it appears so hard to see, even when I override layoutSubViews. I would like it to look like this instead:
NOTE- For anyone trying to do this or similar, as rmaddy stated below:
"Overriding the drawRect: method of a control provided by Apple is a
really bad idea. It's fragile and likely to break in some iOS update."
I'm doing this as a quick patch, and it's NOT best practice to do this if you don't need to.
It just happens so that the drawing you do are behind the minimum and maximum track image view inside UISlider. You can do it so that you create a separate view, say suppose LineView, and then add it to your UISlider, bring this subView to the top most zIndex and do line drawing to this view. See the implementation I have done to get more idea about how you can achieve.
#interface LineView: UIView
#property (nonatomic, strong) NSArray *lines;
#end
#implementation LineView
- (void)setLines:(NSArray *)lines
{
_lines = lines;
[self setNeedsDisplay];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.opaque = YES;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
// disable the touch as UISlider has thumb view as a separate view which actually needs to handle touch events
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return NO;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
const CGFloat lineWidth = 5;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, lineWidth);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue , self.bounds.size.height);
CGContextAddLineToPoint(context, x.floatValue + 20, self.bounds.size.height);
CGContextStrokePath(context);
}
}
#end
#interface MySlider : UISlider
#property (nonatomic, strong) UIView *lineView;
#property (nonatomic, strong) NSArray *lines;
#end
#implementation MySlider
- (void)setLines:(NSArray *)lines
{
self.lineView.lines = lines;
}
- (NSArray *)lines
{
return self.lineView.lines;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.lineView = [[LineView alloc] initWithFrame:self.bounds];
[self addSubview:self.lineView];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self bringSubviewToFront:self.lineView];
}
#end
And set it from external interface.
mySlider.lines = #[#0, #50, #100, #160, #180, #250];
And here is the result,
The UI elements are subviews of your view. drawRect fills in the contents of the view itself, but its subviews draw on top of the view. If you want to draw over the view and its subviews, you'll need to add your own subviews to the slider, or embed the whole slider in a separate parent view, that has its own subviews on top of the slider.
You can see the view hierarchy of the slider in the debugger, using the recursiveDescription method:
(lldb) po [slider recursiveDescription]
<UISlider: 0x7fd7e3e43b60; frame = (0 0; 100 100); opaque = NO; layer = <CALayer: 0x7fd7e3e42460>; value: 0.000000>
| <UIView: 0x7fd7e7077e20; frame = (16 49; 82 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd7e7077f30>>
| | <UIImageView: 0x7fd7e70765e0; frame = (-14 0; 96 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd7e7076310>>
| <UIImageView: 0x7fd7e7077b40; frame = (2 49; 14 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd7e70767d0>>
| <UIImageView: 0x7fd7e3d1d880; frame = (0 34; 31 31); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd7e3d1d9c0>>
| | <UIImageView: 0x7fd7e3d1d400; frame = (-13 -6.5; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd7e3d1ba50>>
You can hide and unhide views in the debugger to see which views are responsible for what in the slider. One of the two point tall views will be the blue bar behind the slider thumb. The other will be the bar in front of the slider thumb. The 31x31 image view is the thumb. Anything you draw with drawRect: is going to go into the top level slider view itself (in the example, the view with address 0x7fd7e3e43b60). Then all the subviews draw on top of that.

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

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.

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.

Objective-C I can access self.view but not self.view.frame

I can access and show self.view and see the frame in the log but when I try to access self.view.frame I get null. Below is the log output of
NSLog(#"Show self.view:%#",self.view);
NSLog(#"Show self.view.frame:%#",self.view.frame);
-
2010-03-28 11:08:43.373 vivmed_CD_Tab[20356:207] Show self.view:<UITableView: 0x4001600; frame = (0 0; 320 583); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x3b21270>>
2010-03-28 11:08:43.373 vivmed_CD_Tab[20356:207] Show self.view.frame:(null)
Can anyone explain why self.view.frame is null but self.view shows a frame? My goal is to change the frame size.
Cheers,
Grant
One problem in the code you've posted is that you're printing the frame (a CGRect) as an object, which won't work. Try:
NSLog(#"Show self.view.frame: %u", self.view.frame); // as a pointer
NSLog(#"Show self.view.frame: %#", NSStringFromCGRect(self.view.frame)); // as a string