UIScrollView Interaction Area does not update with frame/contentSize - ios

I have a UIScrollView which starts with a portrait aspect and which I subsequently (after device rotation) move to another frame which is a landscape aspect.
Accordingly the content view (A UIView inside the UIScrollView) is updated and I set the contentSize of the UIScrollview.
The issue is - after rotation - the UIScrollView has a new frame, new content and new contentSize but the area inside which the user can swipe still behaves as if it was the original frame/content.
Image 1 shows the original aspect / Image 2 shows the new aspect with the odd area available to the user for their swipe gestures.
As you might expect I've fiddled with quite a few of the settings in the scroll view such as the bounds etc but nothing to date has 'fixed' this behaviour.
Any insights into why this behaviour is happening and/or how to set the user interaction area to the frame/bounds of the scroll view would be greatly appreciated.
EDIT2:
There are 3 views in this hierarchy
Lanscape
mainView Frame {0,0,1024,748}
resultsView Frame {633,90,361,677}
scrollView Frame {20,60,340,595}
Portrait
mainView Frame {0,0,532,1240} <--- Whoa Horsy - I expected .... {0,0,768,1004}
resultsView Frame {0,659,768,345}
scrollView Frame {0,70,768,280}
At this point I went into every nib in the project and made sure that autoresizes was turned off and I also tagged every view with a different number so that at the time I was checking the frames I could be sure certain definite positive of what I was looking at.
The top level view in the stack WAS autoresizing and now with that turned off I am getting this for the portrait result..
mainView Frame {0,0,768,1004} ..... YES! WIN.....
resultsView Frame {0,659,768,345}
scrollView Frame {0,70,768,280}
EDIT1:
Setting the resizing mask as comments below reduces the visible size of the scrollview to match the interaction area - so making it seem that the scrollview really does think it's frame is that size only (768 made to look like 468 or so). Increasing the width of the frame does indeed increase the size of the scroll view but there are only 768 pixels for the full width so why should a frame thats 768 wide look like 468 and a frame of 686 look like 568?!

It sounds like you've got a superview of your scroll view which isn't resizing correctly. You could verify this by logging [myScrollView superview] and seeing if that view is resizing as you expect.
If the superview is not resizing, then it won't pass touches down to your scrollview, which explains the behaviour you're seeing. (This also explains why it resizes when you set is autoresizing mask.)

have you tried using the following method on the object?
[_yourView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];

Related

Issues with auto layout on iPhone, align uilabel.center x to superview.center x

I'm using auto layout and size classes with Xcode 7 and am running into an issue with one particular scene. The view has a tableView with a UILabel above it that displays the date. The UILabel is constrained 24 points from the superview's top and centered horizontally on the superview. The user can access a couple of menu views which are presented modally. So far so good.
However, when the modal view is dismissed, the label disappears. I logged its frame, as well as the bounds of the superview, in viewDidAppear: and found that the superview's width was being listed as 1024, putting the label's center at 512, which is offscreen on iPhone. I don't have any code that's manipulating the frame or the constraints, and the tableview (which is constrained to the superviews leading, bottom, and trailing edges and 8 points from the bottom of the UILabel) is appearing normally.
I'm using size classes with auto layout, but I'm not sure what could be causing this issue.
Here's the log in viewDidAppear before opening and dismissing a modal view:
self.view.width is 320.000000; self.dayLabel.center is 160.250000; self.dayLabel.isHidden is 0; self.dayLabel.text is 07/18/2016
Here's the log after opening and dismissing a modal view:
self.view.width is 1024.000000; self.dayLabel.center is 512.000000; self.dayLabel.isHidden is 0; self.dayLabel.text is 07/18/2016
This is happening when using the simulator or a device. Any suggestions or clues as to what could be going on would be incredibly helpful.

Incorrect frame being reported using Autolayout & Size Classes

I've laid out a UIView in my UIViewController. Constrained its height and width, and centered it vertically and horizontally with constraints, but when I log its frame, it reports a location that seems appropriate to the full sizeClass, but not the size of the screen I'm looking at.
For example the default size class UIViewController has a width of 600px, and in that size, my UIView is 150px from the left edge. When I check this distance from the left edge while running in the iphone5 simulator, it still reports 150px.
The weird thing about all of this, is that the UIView itself still appears in the expected (adjusted for screen-size) location.
For posterity: the problem I was having -- getting incorrect values when printing the frame of a view -- was caused by accessing that frame during ViewDidLoad. At this point in the ViewController lifecycle all views may have not been laid out.
Therefore, it is safest to access a UIView property/outlet only after ViewDidLayoutSubviews has been called.
I mean, it's right there in the name of the method: "Your views have been laid out... now do stuff with them."

How does UIScrollView change the frame, but not the bounds? How does UIScrollView work?

I understand the difference between frames and bounds I think, bounds are to the view's local coordinate system, while frames are to the superviews.
With scrollviews however, I'm a little confused.
When I have a UIImageView in a UIScrollView and pinch to zoom it larger, it seems the frame grows larger (width and height) increases the width and height of the corresponding UIImage as well.
However, it seems like the bounds of the UIImageView don't change at all.
Why is this? How do scroll views work in regards to all of this? I've read multiple questions and checked the documentation but this explanation is eluding me.
How does zooming in a UIScrollView affect its contents? Does it just change the frame of the zooming view but somehow not the bounds?
See the explanation in my book:
The scroll view zooms by applying a scaling transform to the scalable view; therefore the frame of the scalable view is scaled as well. Moreover, the scroll view is concerned to make scrolling continue to work correctly: the limits as the user scrolls should continue to match the limits of the content, and commands like scrollRectToVisible:animated: should continue to work the same way for the same values. Therefore, the scroll view automatically scales its own contentSize to match the current zoomScale. (You can actually detect this happening by overriding setContentSize in a UIScrollView subclass: you can see the scroll view adjusting its own content size as you zoom.)
Basically (though that is not quite clear from the above quotation) we have no business concerning ourselves with the frame of the scalable view - or any view - that has a non-identity transform applied to it, which is exactly the case here. That fact is made very clear by the Apple documentation on UIView. Thus you should not be looking at the frame - only the transform. The frame value that your are reading changes purely as a side-effect of the transform change.
As for the bounds of the scalable view - obviously the bounds do not change; that is the whole point of how a transform works. It maintains a constant center and bounds, so that subviews and drawing continue to operate correctly within the frame-of-reference coordinates despite the transform. My book talks you through an understanding of this as well.
On the other hand, the scroll view's own bounds can certainly change their origin, not least because that is exactly and identically what it means for a scroll view to scroll. Scrolling is a change of bounds origin, plain and simple. And this would not be surprising during zooming, because, as I just said, the content size has changed, so the content may be repositioned in order to keep displaying it coherently as you zoom.

Maintaining subview relative location after rotation

My app is built on a UIScrollview with a UIImageView subview and various other subviews. When I rotate to landscape, I change the contentSize of the scroll view and resize the image view proportionally to take advantage of the increased horizontal width. The means the height increases as well to maintain the proportions.
My question is, in the case of the blue subview shown, what do I need to do to reposition it such that it maintains it relative position after rotation, given that it's superview is no longer the same size? I have experimented with convertRect:toView: and converPoint:toView:, but I can't seem to get it quite right.
Are you using auto layout? If so, in many cases, the judicious use of constraints can keep that subview in the right place and right size, even as you go from landscape to portrait. But you'd have to share more details about what else is on this view for us to be more specific.
If not using auto layout, you generally can set the view's autosizing mask so it moves to the correct location for you. But in the case of a scroll view subview, you might have your view controller can respond to viewWillLayoutSubviews, updating the contentSize of the scroll view and the frame of the subview to move, accordingly:
- (void)viewWillLayoutSubviews
{
// update the contentSize of the scroll view for the width of the root view, but I'm assuming the
// height won't change
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, self.scrollView.contentSize.height);
// adjust the frame of the subview you want to move so that it is a certain offset from the bottom
// left corner of the scroll view's `contentSize` (in this case, 10 points from bottom, 10 points from right)
self.subviewToMove.frame = CGRectMake(self.scrollView.contentSize.width - self.subviewToMove.frame.size.width - 10,
self.scrollView.contentSize.height - self.subviewToMove.frame.size.height - 10,
self.subviewToMove.frame.size.width, self.subviewToMove.frame.size.height);
}
The specifics vary based upon details of (a) whether you're using autolayout or not; (b) whether you're creating this subview programmatically or not; and (c) what other content you have in your view and whether the change from portrait to landscape and back results in any change in the vertical height of the scroll view.
To reposition a view, you update its frame. The frame property is of type CGRect, which is a combination of size (CGSize) and origin (CGPoint). If size of your blue view doesn't change, then only origin should be updated.
iOS coordinate system starts from top left corner:
For your blue view you calculate it's origin from the bottom right corner, that is
origin = contentSize - blueViewSize - padding
Do this separately for x and y coordinate, make CGRect with updated origin, and update blue view's frame.
UP: This is how you do it manually, but you can (and better should) let UIKit reposition subviews for you automatically -- learn about autoresizing and autolayout in Xcode's Interface Builder, and Developer manuals

Need to change UIScrollView contentSize when portrait xib is adjusted to landscape

I've got a UIViewController with an iPad xib in portrait orientation. When my iPad is in landscape orientation and I put that view controller into my UISplitViewController's detail pane, it gets automatically resized to fit which is great.
However, when I'm configuring my views in -viewDidLoad, the final size of the views is not yet in landscape orientation/size.
The -didRotateFromInterfaceOrientation: method and the other methods associated with it do not get called when the UIViewController is loaded already in landscape, so when and where am I able to properly set the contentSize of the scroll view and it's contents?
I only want my scroll view to scroll vertically, so I want to make sure that my contentView inside the scrollView is re-fit to the width of the view controller in whatever orientation it is in.
No matter what .frame or .bounds I check, whether it's on my view, my scroll view, or even the detail view controller of my SplitViewController show me what my ACTUAL size is. When in landscape using a UISplitViewController, the left hand side is 320px wide which means the right hand side shouldn't be more than 704px wide, but whenever I check the frames and the bounds of my view and my scrollview, they report as 768px wide which is not correct.
My scrollview is CLEARLY only 704px wide because I can see the scroll indicators correctly.
What am I missing?
Here is my code in -viewDidLoad...
CGSize textSize = [self.purchase.textDescription sizeWithFont:self.labelTextDescription.font constrainedToSize:allowedSize lineBreakMode:UILineBreakModeWordWrap];
CGRect textFrame = self.labelTextDescription.frame;
textFrame.size.height = textSize.height;
self.labelTextDescription.frame = textFrame;
CGRect contentFrame = self.contentView.frame;
contentFrame.size.height += textSize.height;
if (contentFrame.size.width > self.scrollView.frame.size.width) {
contentFrame.size.width = self.scrollView.frame.size.width;
}
self.contentView.frame = contentFrame;
[self.scrollView addSubview:self.contentView];
self.scrollView.contentSize = self.contentView.frame.size;
The proper time to lay out your views and set your scroll view's contentSize is during the layout phase of the run loop. During this phase, UIKit sends the layoutSubviews message to any view that has been marked as needing layout and is in a window. A view is automatically marked as needing layout when various things happen, including when it is first added to a window hierarchy, when it is given a new subview, and when its size changes. You can also manually mark a view as needing layout by sending it the setNeedsLayout message.
By the time a view receives the layoutSubviews message, UIKit has already sent layoutSubviews to any of the view's ancestors (its superview and up) that needed it, and it has already had its frame adjusted based on its autoresizing mask or autolayout constraints, and its own subviews' frames have already been adjusted based on their autoresizing masks or autolayout constraints.
If self.view is already a custom subclass of UIView, the best approach is simply to override layoutSubviews in that class. Put your layout code there, and set the scroll view's contentSize there.
If you're not using a custom subclass, and you don't want to create one, then you can do the layout in your view controller's viewWillLayoutSubviews or viewDidLayoutSubviews method, if you're deployment target is iOS 5.0 or later. You can probably guess when these messages are sent. :)
During autorotation, all of these messages (layoutSubviews, viewWillLayoutSubviews, and viewDidLayoutSubviews) are sent inside the autorotation's animation block, so if you do your layout in one of these methods, you also get the benefit that the changes to your layout will be animated during the autorotation animation.
First of all, if you don't want to get the view resized when rotating check the autoresize mask. If you built the view in the interface builder then check that it's not changing it's size when the superview does:
You can check the current interface orientation of your UIViewController with self.interfaceOrientation. This might help you if you want to set up you view manually.
Furthermore, if you need to adjust the sizes manually then set the desired frames to your views before (or after) the rotation is performed:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
I would recommend query the status bar orientation to get the current rotation in viewDidLoad I would recommend not relying on auto-resizing for scroll views as it tends to get tricky. Check the status bar the resize proportionally based on that.
[UIApplication sharedApplication].statusBarOrientation

Resources