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!
Related
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 an UIScrollview that is zoomable, the subview is one UIView (viewTexto) that contains an UILabel inside (messageLabel).
This is the code
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollViewtmp{
return viewTexto;
}
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollViewtmp withView:(UIView *)view atScale:(float)scale{
messageLabel.contentScaleFactor=scale;
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, messageLabel.frame.origin.y + messageLabel.frame.size.heightt)];
}
With this code I can zoom, and the text is not blurry, there is no horizontal scroll but the size of the UILabel continues to be too large, so it is cut. I need that the width of the UILabel adopts to the scrollView width again as at the beginning.
I have read any question about UIScrollViews in SO and having found exactly what I need.
OK, so finally I found the answer as Ismael suggested I had to adjust the width, the problem was to find the equation.
The way the scaling works and the width of the scrollview's subviews is not obvious at the beginning.
Once you scale a UIView in a scrollview, and you want to have the same width for it, you have to divide the width of the element by the scale.
That is to say that if your initial width was 600, once you scale you can think that the width has changed and you only have to resize it again to 600, but it is not true. 600 is going to be multiplied by the scale automatically. So the right answer would be to resize it to 600 / scale.
Here we divide.
Now the code:
Everything happens in the method:
-(void)scrollViewDidEndZooming:(UIScrollView *)scrollViewtmp withView:(UIView *)view atScale:(float)scale{}
The first thing was to get rid of the blurry fonts:
messageLabel.contentScaleFactor=scale;
In another method I saved the initial width of the UILabel messageLabel (inside scrollview), I called the variable "initialWidth", that was 600. This is important, because I started using the current messageLabel width once in the scrollViewDidEndZooming method.
To readjust the width of the subviews of the scrollview to 600, we only have to divide the initial width by the zoomscale, and readjust the label:
[messageLabel setFrame:CGRectMake(0,0,(initialWidth/scale), messageLabel.frame.size.height)];
[messageLabel sizeToFit];
At this point, we have a scrollview that can be zoomed, with a Label that readjust the text to the initial width of the scrollview, we don't have horizontal scrollbars, but we have a problem, the vertical scrollbars have a wrong height: we can only scroll a part of the text.
This was a difficult second problem to solve.
If you pass the messageLabel height to the contentsize, curiously it seems that it doesn'work, and even if I get a height multiplied by the scale in my NSLogs, it does not change the height of the scrolling, as if internally it was divided again. For example, if the initial height was 500, after scaling by 2, I get 1000 height, if I pass this value to the ContentSize it remains the same, as if it was divided again by 2.
So the solution was this time to multiply by the scale.
We only have to add this line:
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, (messageLabel.frame.origin.y+messageLabel.frame.size.height)*scale)];
As it is seen, the difficult part was to understand this mess with dividing or multiplying by the scale.
After you zoom, the contentSize of a UIScrollView increases proportionally (originalContentSize * zoomScale), so when you adjust it again, it will become smaller
Example:
Your scrollView.frame is (0, 0, 100, 100), and your contentSize is (100, 100).
After you zoom to 2.0, your contentSize will be (200, 200).
You are then putting it at (100, 100) manually, which equals to a contentSize of (50, 50) without zoom.
Since (contentSize.width <= scrollView.frame.size.width), there will be no horizontal scrolling, and the right-most part of your label is not visible.
Try not adjusting the contentSize. The scrolling will be possible then, and the user can reach the whole of the label
Edit: Re-reading your question, if you want the label to adjust and become visible entirely after the zoom, what you have to do in addition to adjusting the contentSize, is adjusting the messageLabel's frame. The label should wrap itself properly
Scroll view did not set content size if I set it using a dynamic variable i.e.
scrollView.contentSize=CGSizeMake(320,scrollView.frame.origin.y+120);
the scroll view size remains same after calling above method.
But if I set it using a number (integer or float) it gets changed but it gives a very strange effect on scrolling. Picture of effect is attached.
scrollView.contentSize=CGSizeMake(320,1000);
This is not allowing me to post image anyways a strange effect appears.
How I can get rid of it?
Please tell Whats going wrong or what I am missing?
Now i have got rannking upto 11 the image for effect is given here.
Before Increasing Content Size:
After Increasing Content Size on Scrolling follwing effect appears:
Select the Scroll View and view its Size Inspector window. Observe that its size is 320 , 713 points (in my case). If you do not see the same
size as what I have, this is a good time to adjust the size so that it is the same as mine. You will
need to use this value in your code
(void)viewDidLoad {
scrollView.frame = CGRectMake(0, 0, 320, 460);
[scrollView setContentSize:CGSizeMake(320, 713)];
[super viewDidLoad];
}
i think you should use frame.height to achieve your goal
the content size must be greater then frame size of scroll view for scroll
and you are using y origin of scroll view so may be that's the problem.
scrollView.contentSize=CGSizeMake(320,scrollView.frame.size.height+120);
In this line:
scrollView.contentSize=CGSizeMake(320,scrollView.frame.origin.y+120);
You are setting the scroll view's contentsize's height to scrollView.frame.origin.y+120.
Means, if your scroll view's frame is x=40, y=40, width = 500, height = 500. Then the scrollView.frame.origin.y+120 will be equal to 40 + 120 = 160. Which is less than your scrollview's height. So your scroll view won't scroll to up or down.
In the second line:
scrollView.contentSize=CGSizeMake(320,1000);
You are scroll view's content size's height to 1000. That is greater than the scroll view's height so it'll scroll to up and down.
Finally.......
The issue was scroll view increases width of scroll view indicator which shows on the screnn while scrolling. You have two options to get rid of this.
Disable vertical or horizotnal indicators which one causing problem, but this may cause some problem with scroll view content size in my case it caused a problem last cell was not being viewed.
2.Let it appear but make it out of screen by the following code snippet.
[myScrollView setScrollIndicatorInsets:UIEdgeInsetsMake(0.0, 0.0, 0, 320.0)];
this is working perfectly but scroll indicator is gone out of screen.
I am implementing an app with a trackbar which itself is a view and it needs to display the minimum and maximum values (of the variable which is associated with the bar) so I have added two labels to the top left and top right of it. Think of something like this without an enabled slider:
I would like to be able to shrink or magnify this view with pinch gesture and the below code does work fine :
-(void) handlePinch:(UIPinchGestureRecognizer *)gr
{
//Shrinking
if(gr.scale < 1)
{
//Get screen width
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
//If the view's would be size is smaller than half the screen's size then don't do anything
//Otherwise shrink the view
if(self.frame.size.width * gr.scale >= screenWidth / 2)
{
//Only scale on x axis, y axes stays the same
self.transform = CGAffineTransformScale(self.transform, gr.scale, 1);
}
}
//Magnifying
else if (gr.scale > 1)
{
//Only scale on x axis, y axes stays the same
self.transform = CGAffineTransformScale(self.transform, gr.scale, 1);
}
//Set scale amount back to 1
gr.scale = 1;
}
The problem is when the view is shrank the labels on top left and right are also shrank and their font size gets smaller. Since I scale the view only horizontally this looks bizarre. I want to set the labels' size constant and shrink everything else inside the view.
I have tried to assign a new frame rectangle with the original labels' sizes after shrinking but it didn't work.Do you have any tips?
edit : Setting minimumFontSize property did not work either (I don't try minimumScaleFactor because I'm still using ios 5 sdk)
I think this behavior is actually controlled by the view you have your label on - it's not controlled by the label itself. There is a autoresizesSubviews property of UIView that defaults to YES, and that makes anything inside a view shrink when the UIView shrinks. Since your pinch gesture is actually attached to the view behind the label and not the label itself, your pinch is shrinking the view, then the view shrinks the label. Try setting autoresizesSubviews = NO, and let me know if that works for you.
I found the solution in separating my trackbar from the view that shall support pinch gesture. It was bad design to include trackbar in it beforehand, check your ui design before putting effort on more complicated stuff.
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.