Am fooling around with this question a couple of days now but no progress. What i want to do is quite simple i think:
I have an image of 320x60 which i use in the plain TableView which works oke as those cells take up the entire width (320) of the screen. The grouped cells in a TableView are 300 wide and have insets/margins left of 10 on the left and the right.
Can i somehow remove those insets/margins and let the grouped cell be 320 wide? I tried setting the content inset left to -10. That does "remove" the left margin but then it's still only 300 wide. Also tried editing the XML of the storyboard (I'm working with iOS 5 - Storyboards) but no joy.
This similar question here got answered as no it's not possible, hopfully something changed in 2+ years!:
Adjust cell width in grouped UITableView
PS i want to alter the width as the background images contain nice shadows, I've read that exesive use of shadows could mean performance issues. Also the shadow's are 5px extra around the border so that would mean -10px wide if I use the standard width.
Help much appreciated!
An untidy solution is to make the table view 340 pixels wide, and 10 pixels off the left edge of the screen.
A solution that involves changing properties of private classes is to make a UITableViewCell subclass, and override its layoutSubviews method. When I log the subviews, I find these:
"<UIGroupTableViewCellBackground: 0x95246b0; frame = (9 0; 302 45); autoresize = W; layer = <CALayer: 0x95226b0>>",
"<UITableViewCellContentView: 0x92332d0; frame = (10 0; 300 43); layer = <CALayer: 0x9233310>>",
"<UIView: 0x95248c0; frame = (10 0; 300 1); layer = <CALayer: 0x951f140>>"
What happens if we take those subviews and fill the entire bounds available?
- (void)layoutSubviews;
{
// By default the cell bounds fill the table width, but its subviews (containing the opaque background and cell borders) are drawn with padding.
CGRect bounds = [self bounds];
// Make sure any standard layout happens.
[super layoutSubviews];
// Debugging output.
NSLog(#"Subviews = %#", [self subviews]);
for (UIView *subview in [self subviews])
{
// Override the subview to make it fill the available width.
CGRect frame = [subview frame];
frame.origin.x = bounds.origin.x;
frame.size.width = bounds.size.width;
[subview setFrame:frame];
}
}
At this particular moment, on the iOS 5.1 simulator, this works. Now, some future version of iOS may restructure these classes, causing this method to catastrophically mangle the table layout. Your app could be rejected for changing the properties of UITableViewCellContentView... even though you're only modifying its frame. So, how much do you need to have your cells fill the table width?
You can the UITableView's Leading and Trailing Space constraints in the Size Inspector which is accessible via the Storyboard. I'm not sure when this was added, but setting the Leading Space Constraint to -10 and the Trailing Space Constraint to 10 will make the cells full width.
Related
How do you handle centering and scrolling content in a UIScrollView when the dimensions are both larger and smaller than the containing scroll view?
I have been trying to set values in layoutSubviews to handle the initial centering of the content, while still allowing for scrolling.
If the content is smaller in both dimensions, I can just set the frame and the image is properly centered for all rotations and orientations. Setting the contentInset will also work. contentOffset does not seem to work.
If the content is larger in both dimensions, I can set contentOffset for the initial display, and not modify it again to support scrolling.
What do I do if I have an image with one dimension larger, and the other smaller, than the scroll view?
contentOffset uses a CGPoint, and contentInset uses UIEdgeInsets (top, left, bottom, right). I have tried mixing positive and negative, since one dimension needs to be moved in and the other out, but haven't gotten anything to work.
My next thought is to resize the scroll view (and modify constraints I suppose) so that the content is never smaller than the container and use contentOffset.
I would really like to have a single approach that will work regardless of larger or smaller dimensions.
What is the best solution (a solution) to this problem?
After stepping back from this, getting a better understanding of UIScrollView, and rethinking, I have solved my problem.
For starters, layoutSubviews is the wrong way to go, at least for what I need.
Trying to resize the UIScrollView and update constraints seemed more trouble than it was worth.
For whatever reason, it didn't initially occur to me than I could use both contentOffset and contentInset at the same time (thought it was either/or for some reason), but that was my exact solution.
CGRect rectContent = CGRectMake(0, 0, image.size.width, image.size.height);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:rectContent];
self.scrollView.contentSize = rectContent.size;
CGFloat fOffsetWidth = (rectContent.size.width < self.scrollView.bounds.size.width) ? (self.scrollView.bounds.size.width - rectContent.size.width)/2 : 0;
CGFloat fOffsetHeight = (rectContent.size.height < self.scrollView.bounds.size.height) ? (self.scrollView.bounds.size.height - rectContent.size.height)/2 : 0;
self.scrollView.contentInset = UIEdgeInsetsMake(fOffsetHeight, fOffsetWidth, fOffsetHeight, fOffsetWidth);
self.scrollView.contentOffset = CGPointMake((rectContent.size.width - self.scrollView.bounds.size.width)/2, (rectContent.size.height - self.scrollView.bounds.size.height)/2);
imageView.image = image;
[self.scrollView addSubview:imageView];
All image dimension possibilities (larger/smaller, one/both) are centered in the scroll view, and a larger image dimension is scrollable while a smaller dimension remains centered.
Perfect!
So after compiling an app on XCode 6, I noticed a strange bug that happens only when running on iOS 8:
The UITableView takes the wrong inner dimensions after updating its frame.
Now I'll try to explain the exact situation:
We have a UITableView rotated on its side, which basically makes a horizontal UITableView. It happens through tableView.transform = CGAffineTransformMakeRotation(-M_PI / 2);.
Now after setting the transform, and then settings its frame - everything is fine.
But of course the system in most cases sends the parent another frame change because it needs to set the parent to the real sizes and not the XIB sizes or any initialization size. In that moment - when I relayout the subviews, including the table view - everything goes wrong.
Actually the frame of the table view is simply set to the bounds of the containing view, but then the inner scrollview (In iOS 8 the UITableView has another UIScrollView inside it, called UITableViewWrapperView. As UITableView is a UIScrollView by itself, I can't figure out why they needed another one...) takes a "height" which equals the parent width. And "height" is actually the width property, only rotated.
Now we can easily estimate the they have a bug with relating the width of the inner UIScrollView to the actual width of the parent UITableView, which could possibly be by reading the .frame.size.width instead of the .bounds.size.width.
But the strange thing is that when investigating the frame of the subviews of the UITableView- it seems that they are all good! So it must be a rendering problem somewhere.
So we are left with a horizontal table which has a blank gap on top, because the "height" of the cells is 320 instead of 568, while the "width" of the cells is fine, set to 320.
I'll be very happy to hear from other people experiencing this problem (Or from Apple), but I have finally found a solution and posting it here with the question, for future reference for me and for others.
So the change that made it behave, was instead of doing this:
- (void)layoutSubviews
{
tableView.frame = self.bounds;
}
I have reset the transform, set the frame to the bounds which the UITableView would expect locally after the transform, and then set the transform and set the correct frame. This is a bit confusing, but here it goes:
- (void)layoutSubviews
{
if (UIDevice.currentDevice.systemVersion.floatValue >= 8.f)
{
// iOS 8 layout bug! Table's "height" taken from "width" after changing frame. But then if we cancel transform, set width/height to the final width/height, and rotate it and set to the virtual width/height - it works!
CGRect rotatedFrame = self.bounds,
unrotatedFrame = rotatedFrame;
unrotatedFrame.size.width = rotatedFrame.size.height;
unrotatedFrame.size.height = rotatedFrame.size.width;
tableView.transform = CGAffineTransformIdentity;
tableView.frame = unrotatedFrame;
tableView.transform = CGAffineTransformMakeRotation(-M_PI / 2);
tableView.frame = rotatedFrame;
}
else
{
tableView.frame = self.bounds;
}
}
This appears to be a new problem with iOS8. When you want to rotate an object it no longer appears to rotate around the upper left corner of the object's frame.
Apple docs for iOS8 state that "an object is rotated about it's center point". So when a vertical UITableView is rotated 90 degrees, it may disappear from view because the center point may be off the visible area. In order to make the table appear as if it was rotated about the upper left corner of the table, you must now also translate the frame by an amount equal to the difference between the frame width and frame height.
It's important to note you need to concatenate the transforms in order to get the desired result, like the following:
First create a 90 degree rotation transform:
CGAffineTransform xform_rotate = CGAffineTransformMakeRotation(-M_PI * 0.5);
Then create a translation amount variable equal to the difference between table width and height:
float translateAmount = (camThumbsTableView.frame.size.height/2)-(camThumbsTableView.frame.size.width/2);
Then concatenate the original rotation transform with the translation:
CGAffineTransform xform_total = CGAffineTransformTranslate(xform_rotate, translateAmount, translateAmount);
When done, you can now transform your tableView as follows:
self.camThumbsTableView.transform = xform_total;
This will have the effect of both rotating and translating your tableView such that it now appears to have been rotated around the upper left corner of the tableView instead of about the center point.
I have a UILabel functioning as a title in my UITableViewCell. It displays fine the first time it is used, but each time it is redisplayed the width shrinks. All my layout is done in IB, nothing in code at the moment.
[EDIT]
The left hand side of the label is pinned to the (barely visible) box that says "New Words". The right hand side is pinned to the superview. Height is set to 44px.
I am able to fix this by using the following code in layoutSubviews. I don't like it at all though since it involves setting the frame. I would like to understand the underlying problem.
[super layoutSubviews];
CGRect frame = self.titleLabel.frame;
CGSize size = self.titleLabel.intrinsicContentSize;
frame.size.width = size.width;
frame.size.height = 44;
self.titleLabel.frame = frame;
[super layoutSubviews];
Here are a few screenshots of the problem:
First time:
Second time:
Third+ time:
I am implementing the horizontal UITableView by rotating it by 90 degrees
horizontalShopsTableViewController.view.transform = CGAffineTransformMakeRotation(-M_PI/2);
and then rotating its cells back by 90 degrees:
cell.contentView.transform = CGAffineTransformMakeRotation(M_PI/2);
I want the table to have the flexible width. If I do not set it to be flexible, everything works fine after the orientation change. However if I do, the contents of the cells are misplaced and disappear. The flag "Autoresize Subviews" of the table view is false.
Any ideas what could be the causing problem or what alternatives could be used?
Ok, the problem was that since the width of the tableview was flexible, after the rotation the width of the cells (which is actual the height) were increased too. I overcame the problem by subclassing the UITableView and overriding the layoutSubviews method, so it looks like this now:
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView* child in [self subviews]) {
CGRect frame1 = child.frame;
if ([child isKindOfClass:[UITableViewCell class]]) {
frame1.size.width = 120;
child.frame = frame1;
}
}
}
Anyway, I still don't understand why the uitableview was resizing its cells if I explicitly set the Autoresize Subviews to false.
uuuuh... what are you trying to achieve? the autoresizing system doesn't respect any transforms on views of course. Probably you have to resize it manually.
But can't you do what you want to achieve with a standard scrollview!? It's not the best idea to rotate UI components with transforms, as you are getting other problems.
I have a UIScrollView that contains several dynamically resizing subviews. I can resize and layout the subviews just fine, but when I set the content size of the scroll view itself, the bottom subviews are clipped. Is there some reason why a scroll view's content size height should be larger than the sum of the heights of the views it contains?
Here's my situation in more detail:
I have a superview containing a UIScrollView containing several subviews. In the superview's layoutSubviews method, I calculated the needed size of each subview, then set the frames so the subviews are tiled vertically down the screen with a bit of space between them. When done, I set the height of the UIScrollView's content size to be the end of the last subview (origin.y + size.height). In theory, this means the bottom of the scroll view's content area should exactly line up with the bottom of the last subview.
But it doesn't. Instead, a nice chunk of the last subview is clipped. It's still there - if I scroll down I can see the remaining portion during the "bounce". The problem is even worse in landscape mode - a much larger portion of the bottom subview simply isn't visible.
The subviews are all being arranged and positioned properly. The problem is that the UIScrollView's contentSize seems to need to be significantly larger than the sum of the heights of the subviews (plus the space between them). This doesn't make any sense to me. Furthermore, the amount the size is "off" varies - I reuse this view several times with different subviews, and they're all off by a different amount. Therefore, simply adding a constant to the content view height won't help.
What is causing the content size (or my height calculations) to not function correctly?
Code:
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat width = self.bounds.size.width - [self subviewLeftMargin] - [self subviewRightMargin]; // All subviews have same width as parent view
CGFloat x = [self subviewLeftMargin]; // All subviews should start at the far left of the view
CGFloat y = [self spaceBetweenSubviews]; // Running tally of the y coordinate for the next view
/* Adjust the subviews */
for(UIView *view in self.theVariousSubviews) {
/* Resize the view with the desired width, then let it size its height as needed */
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, width, view.frame.size.height);
CGSize newSize = [view sizeThatFits:view.frame.size];
/* Set the origin */
//The subviews are positioned correctly, so this doesn't seem to be a problem
view.frame = CGRectMake(x, y, newSize.width, newSize.height);
/* Have the view refresh its own layout */
[view setNeedsLayout];
/* Update the y value for the next subview */
y += newSize.height + [self spaceBetweenSubviews];
}
/* Resize the scroll view to ensure it fits all of the content */
CGFloat scrollViewHeight = y;
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, scrollViewHeight);
//Content size is set to the same total height of all subviews and spacing, yet it is too small. Why?
}
hi it seems to me that your calculation and resizing timing is wrong.
Without the missing code for the layout change I could not fully understand the problem.
What strikes me is that you are assigning view.frame twice and between the new calculation you intercept the process with sublayouting which might change some of the values your calculation is depending on.
I could only advice you to separate the calculation from layouting and not invoke methods while you are calculating. To bring light into it you should either drop a sample app with the missing calculation or for yourself add some NSLog statement showing you the frame origin size of any subview and the contentOffset for the scrollview.
On my experiences the scrollview is working properly in general so I would expect a bug within your code.