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.
Related
Problem: Scrolling to top again to tableView changes content offset and it hides first tableView cell.
I know there are lot of post on scrolling issue with tableView's scrollView where tableView content offsets changes after scrolling but there solution didn't helped me.
I tried to set content offset to 0 in -(void)layoutSubviews method but doesn't help. Also, this doesn't help.
self.automaticallyAdjustsScrollViewInsets = NO;
self.parentViewController.automaticallyAdjustsScrollViewInsets = NO;
In Detail:
When tableView loads, I see content offset set to {0, 0}
Printing description of $6:
<UITableView: 0x7fca15828400; frame = (0 0; 1920 1080); autoresize = RM+BM; gestureRecognizers = <NSArray: 0x60800024cb40>; layer = <CALayer: 0x60800022c780>; contentOffset: {0, 0}; contentSize: {1920, 1600}>
when I scroll down, and top to again, I see offset content changed.
Printing description of $10:
<UITableView: 0x7fca14880400; frame = (0 0; 1920 1080); autoresize = RM+BM; gestureRecognizers = <NSArray: 0x618000056da0>; layer = <CALayer: 0x618000037920>; contentOffset: {0, 108}; contentSize: {1920, 3754}>
However, I set canFocusRowAtIndexPath to YES, I don't see problem with scrollView but I don't want tableViewCell to focused.
- (BOOL)tableView:(UITableView *)tableView canFocusRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
Any help please?
This issue was because UICollectionViewFlowLayout which I was setting programmatically and messing with content offset every time when tableView's cellForRowIndexpath is getting called.
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>
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.
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.
I'm currently designing an application that aims to make use of vertical UI sliders. The consensus on SO seems to be that, in order to create a vertical UI slider in iOS, you must use a transformation function to rotate the control with UIKit.
When I actually do use one, my slider ends up looking very imprecise and ugly:
Aside from simply starting from scratch with a totally new NSControl built from the ground up to be a vertical slider, does anyone have an idea of how to make a vertical UI slider look less aliased?
Code used to generate slider:
_slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, kWTFSliderHeight, 10)];
_slider.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
[self addSubview:_slider];
Thanks, --Dany.
The problem is that an (untransformed) UISlider ignores the frame height you request and uses a frame height of 23. For example I did this:
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(80, 100, 40, 10)];
[self.view addSubview:slider];
and here's the resulting frame:
(gdb) po [[(id)UIApp keyWindow] recursiveDescription]
<UIWindow: 0x6c1c9a0; frame = (0 0; 320 480); layer = <UIWindowLayer: 0x6c1ca30>>
| <UIView: 0x6c23160; frame = (0 20; 320 460); autoresize = W+H; layer = <CALayer: 0x6c22750>>
| | <UISlider: 0x6c22ad0; frame = (80 100; 40 23); opaque = NO; layer = <CALayer: 0x6c21ae0>; value: 0.000000>
and its center point:
(gdb) p (CGPoint)[0x6c22ad0 center]
$1 = {
x = 100,
y = 111.5
}
Notice that its center is at a half-point coordinate on the Y axis. If you rotate the slider by a right angle like this:
slider.transform = CGAffineTransformMakeRotation(M_PI * 1.5);
its center doesn't change but its frame does:
(gdb) po [[(id)UIApp keyWindow] recursiveDescription]
<UIWindow: 0x6a3a000; frame = (0 0; 320 480); layer = <UIWindowLayer: 0x6a36780>>
| <UIView: 0x6a3c420; frame = (0 20; 320 460); autoresize = W+H; layer = <CALayer: 0x6a3ba10>>
| | <UISlider: 0x6a3bd90; frame = (88.5 91.5; 23 40); transform = [0, -1, 1, 0, 0, 0]; opaque = NO; layer = <CALayer: 0x6a3be20>; value: 0.000000>
...
(gdb) p (CGPoint)[0x6a3bd90 center]
$1 = {
x = 100,
y = 111.5
}
So after a right angle rotation, you need to adjust the center point by a half-point on both the X and Y axes:
CGPoint center = slider.center;
center.x += .5;
center.y += .5;
slider.center = center;
so that the frame will lie on whole point boundaries:
(gdb) po [[(id)UIApp keyWindow] recursiveDescription]
<UIWindow: 0x9960aa0; frame = (0 0; 320 480); layer = <UIWindowLayer: 0x9962970>>
| <UIView: 0x9964680; frame = (0 20; 320 460); autoresize = W+H; layer = <CALayer: 0x9963c70>>
| | <UISlider: 0x9964020; frame = (89 92; 23 40); transform = [0, -1, 1, 0, 0, 0]; opaque = NO; layer = <CALayer: 0x9963010>; value: 0.000000>
Make sure the location of the slider after rotation is on pixels, if it ends up in like 0.3, 0.2 it will be ugly, but if it ends up in 1, 1 it will look normal.
A specific problem I can see is if kWTFSliderHeight is odd. If it is, put it on 0.5, 0 before rotation and check