UIView UIScrollview - frames, bounds, center confusions - ios

I am putting a UIImageView inside a UIScrollView, and trying to control the image so that it is centred on the scrollview after a zoom. and I am not sure the best way to do this.
The apple docs tell us NOT to use the frame property: "Warning If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored." So I am attempting using the following in a UIViewController subclass whose xib contains a scrollView and contained imageView:
scrollView.bounds =
CGRectMake
(scrollView.contentSize.width/2 - scrollView.center.x,
scrollView.contentSize.height/2 - scrollView.center.y,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
containedView.center =
CGPointMake
(containedView.bounds.size.width*scrollView.zoomScale/2,
containedView.bounds.size.height*scrollView.zoomScale/2);
This works accurately where the width and height of the containedView is larger than that of the scrollView and sets the views so that subsequent scrolling will take you exactly to the edges of the containedView. However when either dimension of the image is smaller than the scrollView width and height the image is magnetically attracted to the top left corner of the screen. In the iPad Simulator (only) when the images is shrunk to the size of minimumZoom it does lock on to the centre of the screen. The magnetic attraction is very smooth as if something in the UI is overriding my code after the image has been centred. It looks a bit like a CALayer contentsGravity ( kCAGravityTopLeft ) thing, maybe?
Apple contradict their own advice in their code sample, photoScroller (in a subclass of UIScrollView):
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
imageView.frame = frameToCenter;
This method does a better job of centring when the image is smaller, but when I try this on my project it introduces some kind of inconsistencies. For example, with scrollView.bounces = NO, a horizontal image whose height is smaller than the height of the scrollView but whose width is larger (so it can be scrolled from left to right) will scroll further to the left than it should (when scrolling to the right it stops correctly at the edge of the image, although if scrollView.bounces = YES it then bounces in from the edge so the image is always cropped on the left) When the image is larger in both dimensions than its containing scrollview this issue accentuates and the whole result feels broken, which is unsurprising given Apple's documented advice.
I have scoured the forums and can't find much comment on this. Am I missing something really obvious?

You don't appear to be using the transform property, so you can ignore that warning about not using the frame property when using the transform property. Go ahead and use the frame property, just like Apple (and the rest of us) do.

Related

Adjust content scale and offset on resize of UIScrollView

I have UI which hast two states of layouts (besides portrait-landscape). In each state some other part of UI is exposed (is larger).
Problem is that UIScrollView with some UIImageView is resized on those states changes, and in each state different part of image is shown since scale and offset remains unchanged.
Is there some nice way to update this scale and offset values so more or less same part of image is shown for a large and small sized UIScrollView?
How about the UIScrollView method
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
Rect is a rectangle in the coordinate space of the view returned by viewForZoomingInScrollView:.
Determine what rectangle of the zoomed view is being shown.
Change your views so that the UIScrollView bounds are changed.
Do the zoomToRect to show the same content, scaled as necessary.
Without having compiled and run this, it should be approximately...
CGSize rectSize;
rectSize.origin = scrollview.contentOffset;
rectSize.width = scrollview.bounds.size.width * scrollview.zoomScale;
rectSize.height = scrollview.bounds.size.height * scrollview.zoomScale;
// Do whatever makes the views change
[scrollView zoomToRect:rectSize animated:whateverYouLike];

Take a screenshot of UIScrollView, starting at contentOffset point?

I looked at this code here, which lets you take a screenshot of either the top visible portion of a UIScrollView or of the whole UIScrollView:
Getting a screenshot of a UIScrollView, including offscreen parts
What I want is to take a screenshot of just the portion from contentOffset onwards, as the UIScrollView can be quite long and thus it would take a while to take a screenshot of it. Is it possible to do this?
The solution you mentioned basically resize the scroll view to match the size of its content, and therefore the entire content will be rendered without a scroll and then captured.
If you want to apply the same solution, you can resize the scroll view to match the size of the visible content + the size of remaining content til the end.
So try to replace those lines:
_scrollView.contentOffset = CGPointZero;
_scrollView.frame = CGRectMake(0, 0,
_scrollView.contentSize.width,
_scrollView.contentSize.height);
With:
//_scrollView.contentOffset = CGPointZero; // Don't change the offset
_scrollView.frame = CGRectMake(0, 0,
_scrollView.contentSize.width,
_scrollView.contentSize.height - _scrollView.contentOffset);
I had asked similar question before Taking screenshot of Part of Screen
You can take screenshot of what the user is seeing in terms of 320 x 480 but you cannot take screenshot of a portion of the screen. X and Y coordinates have absolutely no effect on where the screenshot starts/stops.

Instead of sizeToFit what should I use

I'm working on an app where the main view fills up the whole screen. This main view contains subviews that you could think of as sprites. They wander around using their own code. The logic of how they wander around could have them wandering off screen. What I'd like to do is to resize the main view so that it encloses all the subviews. It's really not the view size that is changing, though. It is the scaling that is changing. Kind of like zooming out of a google maps view. How do I do that? It will be a gesture recognizer that triggers this.
You should do this using a three-level view hierarchy:
top-level view
scaled view
sprite view 0
sprite view 1
sprite view 2
etc.
Set the bounds of the scaled view to encompass the frames of all of the sprite views. Then set the transform of the scaled view so that it fits in the top-level view.
- (void)updateBoundsAndTransformOfView:(UIView *)scaledView {
CGRect scaledBounds = CGRectNull;
for (UIView *spriteView in scaledView.subviews) {
scaledBounds = CGRectUnion(scaledBounds, spriteView.frame);
}
scaledView.bounds = scaledBounds;
// Now scaledView's size is large enough to contain all of its subviews,
// and scaledView's coordinate system origin is at the left edge of the
// leftmost sprite and the top edge of the topmost sprite.
UIView *topLevelView = scaledView.superview;
CGRect topLevelBounds = topLevelView.bounds;
CGFloat xScale =topLevelBounds.size.width / scaledBounds.size.width;
CGFloat yScale = topLevelBounds.size.height / scaledBounds.size.height;
CGFloat scale = MIN(xScale, yScale);
scaledView.transform = CGAffineTransformMakeScale(scale, scale);
scaledView.center = CGPointMake(CGRectGetMidX(topLevelBounds), CGRectGetMidY(topLevelBounds));
}

UIKeyboard's Frame is 20 pixels off

I need someone to explain this to me as it is not making any sense.
When getting the UIKeyboards frame from the userInfo using UIKeyboardFrameEndUserInfoKey and doing the math so that a view would appear to be stacked on top of the keyboard, I need to make a difference of 20 pixels.
The math:
CGRect frame = view.frame;
CGPoint origin = frame.origin;
origin.x = kbFrame.origin.x;
origin.y = kbFrame.origin.y - view.frame.size.height - 20;
frame.origin = origin;
view.frame = frame;
I thought it must be the status bar, but here's the kicker, I'm developing on a retina display and so the status bar is 40 pixels in height not 20.
I then added a the conversion from view to view
CGRect kbFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIWindow * window = [UIApplication sharedApplication].windows[0];
kbFrame = [self.view convertRect:kbFrame fromView:window];
and that seems to fixed of it; that is I can remove the 20 pixels difference.
What really throws me is when using UIKeyboardFrameBeginUserInfoKey, the keyboard's frame is at the bottom of the screen, which is correct, but the end frame causes me to encode a 20 pixel difference. When I add the conversion code in, it puts the keyboard 20 pixels up and so gets rid of the difference. What the hell is going on?
The thing you are describing is the natural and wanted behaviour.
The thing is that the keyboard coordinate are in the Window Coordinate system.
Your view is probably not in Window Coordinate system. So you always need to do a conversion between coordinate system to be able to use it correctly.
What really throws me is when using UIKeyboardFrameBeginUserInfoKey, the keyboard's frame is at the bottom of the screen, which is correct,
I would disagree, you are probably 20 points lower than what you think you are (if you are setting it in your View coordinate system), but since that is off the screen you don't notice the offset.
Every View have it's own coordinate system that is inside it's bounds. And the frame of a view is expressed in it's parent coordinate system. That can also lead to some confusion if we don't understand the why and necessity of this difference.
I hope this will help you.
NOTE on Retina Display and Measurement :
On iOS you never deal with Pixel in your code (only when preparing your assets) you always deal with Point. So the Status bar is always 20 points, on Retina or not.

iOS - squish an image vertically

I have a round image that I want to "squish" vertically so that it looks more like a horizontal line, then expand it back to the original shape. I thought this would work by setting the layer's anchor point to the center and then animating the frame via UIViewAnimation with the height of the frame = 1.
[self.imageToSquish.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
CGRect newFrame = CGRectMake(self.imageToSquish.frame.origin.x, self.imageToSquish.frame.origin.y, self.imageToSquish.frame.size.width, 1 );
[UIView animateWithDuration:3
animations:^{self.imageToSquish.frame = newFrame;}
completion:nil];
But the image shrinks toward the top instead of around the center.
You’re giving it a frame that has its origin—at the top left—in the same position as it started. You probably want to do something more like this, adding half the image’s height:
CGRect newFrame = CGRectMake(self.imageToSquish.frame.origin.x, self.imageToSquish.frame.origin.y + self.imageToSquish.frame.size.height / 2, self.imageToSquish.frame.size.width, 1);
Alternatively—and more efficiently—you could set the image view’s transform property to, say, CGAffineTransformMakeScale(1, 0.01) instead of messing with its frame. That’ll be centered on the middle of the image, and you can easily undo it by setting the transform to CGAffineTransformIdentity.

Resources