Convert scroll view frame to window coordinates - ios

I am trying to convert scroll view coordinates to window coordinates. However the resulting frame seems to be shifted by status bar height, what's confusing is that the height remains the same which is not right.
CGRect visibleBounds = CGRectMake(0, 0, CGRectGetWidth(self.scrollView.frame), CGRectGetHeight(self.scrollView.frame));
CGRect scrollViewFrame = [self.scrollView convertRect:visibleBounds toView:nil];
lldb log:
Printing description of visibleBounds: (CGRect) visibleBounds =
(origin = (x = 0, y = 0), size = (width = 320, height = 568))
Printing description of scrollViewFrame: (CGRect) scrollViewFrame = (origin =
(x = 0, y = 20), size = (width = 320, height = 568))

Turns out scroll view bounds can be used to calculate the frame for scroll view in window coordinates, regardless the fact that I see negative bounds, produced frame will be correct anyway.
[self.scrollView convertRect:self.scrollView.bounds toView:nil];

Related

iOS 13 Animating View Not Changing Frame

I have an app which is compiled in Xcode 10 on iOS 13 simulator. In one view there is a "tray" view which shows from the bottom when tapped, in iOS 12 it works perfectly, in iOS 13, the tap is calling the method, but the changes to the frame are not saving - I have included outputs from the debugger in comments so you can see what the outputs of the frame values are;
- (void) userClickActivityTray: (UITapGestureRecognizer *) gestureRecognizer {
if(self.activityTrayShown) {
/*
(lldb) po self.activityTrayContainerView.frame
(origin = (x = 0, y = 792), size = (width = 414, height = 104))
*/
[self hideActivityTray];
} else {
if (!self.activityTrayViewInitialFrameComputed) {
self.activityTrayViewInitialFrameComputed = YES;
self.activityTrayInitialFrame = self.activityTrayContainerView.frame;
}
/*
(lldb) po self.activityTrayContainerView.frame
(origin = (x = 0, y = 638), size = (width = 414, height = 224))
(origin = (x = 0, y = 638), size = (width = 414, height = 224))
(lldb) po self.activityTrayInitialFrame
(origin = (x = 0, y = 792), size = (width = 414, height = 104))
(origin = (x = 0, y = 792), size = (width = 414, height = 104))
(lldb)
*/
[UIView animateWithDuration:0.25 animations:^{
self.activityTrayContainerView.frame = CGRectMake(self.view.bounds.origin.x,
self.bottomView.frame.origin.y - self.activityTrayViewController.maximumHeight,
self.view.bounds.size.width,
self.activityTrayViewController.maximumHeight);
self.activityTrayBackgroundView.alpha = 1.0;
self.bottomView.alpha = self.dotsProgressView.alpha = 0;
} completion:^(BOOL finished) {
self.activityTrayShown = YES;
/*
(lldb) po self.activityTrayContainerView.frame
(origin = (x = 0, y = 557), size = (width = 414, height = 305))
(origin = (x = 0, y = 792), size = (width = 414, height = 104))
*/
}];
}
}
Layout system in iOS 13 is different, we had the same issue and in our case by switching layout from automatic to Translates Mask Into Constraints fixed the issue.
You can use yourview.layer.frame instead of yourview.frame
It worked for me.
I had the same issue when moving to iOS13.
In my case, the sizing constraints were overriding any change I was making to the frame, which was not the case on iOS12.
For my app to work correctly, I had to also update the NSLayoutContraint just before the "animateWithDuration" block, so that the new frame coordinates stay compatible with the layout constraints.
Hope that helps
only set .translatesAutoresizingMaskIntoConstraints 's View to YES, it worked for me.
Hope that helps

When the UIView's `bounds` origins are increased with positive numbers, why do the subviews shift in the negative direction?

This must be something really simple, and my basic math knowledge may be lacking. This is clear (from this question):
View's frame determines its location in superview. View's bounds
determines its subviews locations. That means, if you change view's
bounds, its location won't be changed, but all of its subviews
location will be changed.
The view controller, after starting a Single View App:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v1 = UIView(frame: CGRect(x: 100, y: 100, width: 200, height: 300))
v1.backgroundColor = UIColor.blue
let v2 = UIView(frame: v1.bounds.insetBy(dx: 50, dy: 50))
v2.backgroundColor = UIColor.red
self.view.addSubview(v1)
v1.addSubview(v2)
}
Checking on the LLDB console, this is completely clear too:
(lldb) p v1.frame
(CGRect) $R0 = (origin = (x = 100, y = 100), size = (width = 200, height = 300))
(lldb) p v1.bounds
(CGRect) $R1 = (origin = (x = 0, y = 0), size = (width = 200, height = 300))
(lldb) p v2.frame
(CGRect) $R2 = (origin = (x = 50, y = 50), size = (width = 100, height = 200))
(lldb) p v2.bounds
(CGRect) $R3 = (origin = (x = 0, y = 0), size = (width = 100, height = 200))
Adding v1.bounds.origin.x += 50 (or v1.bounds.origin.x = 50 for that matter) after v1.addSubview(v2) results in:
(lldb) p v1.frame
(CGRect) $R0 = (origin = (x = 100, y = 100), size = (width = 200, height = 300))
(lldb) p v1.bounds
(CGRect) $R1 = (origin = (x = 50, y = 0), size = (width = 200, height = 300))
(lldb) p v2.frame
(CGRect) $R2 = (origin = (x = 50, y = 50), size = (width = 100, height = 200))
(lldb) p v2.bounds
(CGRect) $R3 = (origin = (x = 0, y = 0), size = (width = 100, height = 200))
The LLDB console output still fits in with my current understanding, but then this is how it is rendered:
Why? Tried to reason about it (see below) and I understand that the views' coordinate systems are relative to each other, but if 50 is added to v1's origin.x, the the subviews' effective frame.origin is supposed to be (x=50+50, y=0).
I found a satisfying answer in Matt Neuburg's Programming iOS 11 book with a similar example:
/* ... */
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
/* ... */
v1.bounds.origin.x += 10
v1.bounds.origin.y += 10
Nothing happens to the superview’s size or position. But the subview
has moved up and to the left so that it is flush with its superview’s
top-left corner. Basically, what we’ve done is to say to the
superview, “Instead of calling the point at your upper left
(0.0,0.0), call that point (10.0,10.0).” Because the subview’s frame
origin is itself at (10.0,10.0), the subview now touches the
superview’s top-left corner. The effect of changing a view’s bounds
origin may seem directionally backward — we increased the superview’s
origin in the positive direction, but the subview moved in the
negative direction — but think of it this way: a view’s bounds origin
point coincides with its frame’s top left.
Therefore it seems modifying the origin is more like a mapping operation than a coordinate system transformation. This would also explain why the results are the same for += 50 and = 50.
By adjusting the bounds' origin.x of v1, you are expanding the origin beyond the visible rectangle. (This is how a UIScrollView works.)
If you instead modify the frame's origin.x, you will, I believe, see results more in line with your expectations.

UITableView same frame regardless of orientation

In the debugger, I get the following output regardless of the orientation...
print self.tableView.frame
(CGRect) $R2 = (origin = (x = 0, y = 0), size = (width = 1000, height = 1000))
Why doesn't the size change depending on the orientation?

Modifying the height and width of UIView after rotation

I have a UIView which is added as a subview to my view controller. I want to rotate the UIView to some arbitrary angle and then modify the width and height of view. I have a UITextField and button to set angles.
-(void) setAngle: (UIButton *) sender
{
angle = [myText.text floatValue];
angle *= (M_PI)/180.0;
self.transform = CGAffineTransformMakeRotation(angle);
}
And two UISliders to modify the height and width accordingly.
-(void)sliderAction1:(id)sender
{
w = (CGFloat)slider1.value;
CGRect myRect= CGRectMake(x, y, w, h);
self.bounds = myRect;
}
-(void)sliderAction2:(id)sender
{
h = (CGFloat)slider2.value;
CGRect myRect= CGRectMake(x, y, w, h);
self.bounds = myRect;
}
I have done my research and found out that after using CGAffineTransformMakeRotation() I can not use setFrame. The problem with "bounds" is that it is modifying the width and height of UIView from both ends. i.e. the origin of UIView is also changing. Is there any way I can extend the width only from one end?
You shouldn't be modifying bounds like that. Typically you do not modify the bounds property.
Try concatenating your transforms using
CGAffineTransformScale (not CGAffineTransformMakeScale). Using "Make" you are effectively resetting the transform matrix.
CGAffineTransformScale(self.rotationTransform, x, y);
-(void)sliderAction1:(id)sender
{
CGFloat w = (CGFloat)slider1.value;
CGRect myRect= CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, w, self.view.frame.size.height);
self.view.frame = myRect;
}
it's working for me changes width from one side. change is very minute cause slider value is in between 0-1 so if multiply w by 100 or 1000 it will reflect in major
use this after multiply
CGRect myRect= CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, w*100, self.view.frame.size.height);

When would a UIView's bounds.origin not be (0, 0)?

When would an UIView's bounds.origin not be (0, 0)?
This paragraph was helpful to me:
IMPORTANT!! Bounds X and Y, the origin, are for moving inside the
View. For eample X+5, moving 5pix to the left of the frame's origin
meaning draw all content within this View to the left 5pix of frame's
origin. It doesn't do anything to itself, it is what being drew on it
that get affected.
But it describes only the case when I had set the value of bounds.origin myself.
In what other cases the value of bounds.origin != (0, 0)?
View's frame determines its location in superview. View's bounds determines its subviews locations. That means, if you change view's bounds, its location won't be changed, but all of its subviews location will be changed.
Positive width and height is like you draw a view from upper-left to bottom-right, while negative value is from bottom-right to upper-left. So
frame1 = CGRectMake(100, 100, -50, -50)
is totally identical with
frame2 = CGRectMake(50, 50, 50, 50).
And in fact, if you init a view with frame1, it will AUTOMATICALLY CHANGED to frame2.
But the bounds.origin of the views are not identical. Bounds.origin indicates the point that you "draw" the view, so all subviews frames will originate at this point.
For example, in Landscape iPhone 6, we have:
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 275, 275)];
leftView.backgroundColor = [UIColor greenColor];
[self.view addSubview:leftView];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(667-50, 375-50, -275, -275)];
rightView.backgroundColor = [UIColor blueColor];
[self.view addSubview:rightView];
And we got:
We will find that rightView's frame is automatically changed to positive value, which is (342, 50, 275, 275), but its bounts.origin = (-275,-275).
And we add subviews:
UIView *leftSubview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
leftSubview.backgroundColor = [UIColor grayColor];
[leftView addSubview:leftSubview];
UIView *rightSubview= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
rightSubview.backgroundColor = [UIColor grayColor];
[rightView addSubview:rightSubview];
So the bounds makes rightView's subview follows the origin which we init rightView.
If we change the bounds of rightView equals to leftView:
rightView.bounds = leftView.bounds;
Then the two subViews location is the same, we lost the information that rightView's width and height are negative.
And we change the bounds of leftView instead of rightView:
CGRect bounds = leftView.bounds;
bounds.origin = CGPointMake(50, 50);
leftView.bounds = bounds;
We can see, its subview's frame.origin is offset by bounds.origin(using minus, not plus).
To conclude:
view.bounds determines all its subview's location(offset by bounds.origin), while bounds will not affect its own location in its superview.
If you init a view with negative width and height, it will automatically changed to positive(which won't change the location), but its bounds.origin indicates the point that you start to "draw" the view.
A UIScrollView's bounds.origin will not be (0, 0) when its contentOffset is not (0, 0).
The bounds.origin will be negative if you initialize a view with negative width/height.
For example, if you did
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(5, 5, -10, -20)];
the frame would be:
origin = {
x = -5,
y = -15
},
size = {
width = 10,
height = 20
}
bounds:
origin = {
x = -10,
y = -20
},
size = {
width = 10,
height = 20
}
center:
x = 0,
y = -5
try it for yourself!
(edited again because I can’t delete my original answer after it was accepted—credit for this goes to ian, who posted a more thorough answer below:)
In most cases this won’t happen. If you initialize your view with a negative width and/or height, you’ll get an origin with a negative X of the width and/or negative Y of the height.

Resources