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.
Related
I have a problem. I'm working on making a game. As part of my game I need images to be rotated and then moved in the direction of the rotated angle inside a game loop (using an NSTimer). In essence I'm trying to create the effect of launching a projectile. The code works fine when moving in perpendicular directions such as 0, 90, 180, 270, and 360 degrees, but any other angle and the image starts to glitch out. The object on the screen maintains its correct bounds and contents, but the actual displayed image disappears. Does anybody know what the problem is or someway I could get around it? If needed, I can make and post a video of my problem so you can see what I'm talking about.
Here is a sample of the code I'm using. The "background" variable is just a UIImageView:
angle = 60;
background.transform = CGAffineTransformRotate(object.transform, angle*M_PI/180); //converts degrees to radians and rotates the image
background.frame = CGRectMake( background.frame.origin.x + cos(angle*m_PI/180)*32; background.frame.origin.y -sin(angle*M_PI/180)*32, background.frame.size.width, background.frame.size.height); //moves the image in the direction of the angle
For starters, there is a semicolon after the x origin in your CGRect instead of a comma. Was that just a typo?
The UIView documentation for frame states:
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
Changes to this property can be animated. However, if the transform
property contains a non-identity transform, the value of the frame
property is undefined and should not be modified. In that case, you
can reposition the view using the center property and adjust the size
using the bounds property instead.
So there you have it, you should not be trying to change the frame when setting a custom transform. You are only trying to adjust the position of the view anyway so just modify your code to adjust center instead of the origin coordinates.
To change the size, you can use the bounds.
CGRect bounds = myView.bounds;
bounds.size.width = whatever;
bounds.size.height = whatever;
myView.bounds = bounds;
I'm using UIKit Dynamics to push a UIView off screen, similar to how Tweetbot performs it in their image overlay.
I use a UIPanGestureRecognizer, and when they end the gesture, if they exceed the velocity threshold it goes offscreen.
[self.animator removeBehavior:self.panAttachmentBehavior];
CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
if (fabs(velocity.y) > 100) {
self.pushBehavior = [[UIPushBehavior alloc] initWithItems:#[self.scrollView] mode:UIPushBehaviorModeInstantaneous];
[self.pushBehavior setTargetOffsetFromCenter:centerOffset forItem:self.scrollView];
self.pushBehavior.active = YES;
self.pushBehavior.action = ^{
CGPoint lowestPoint = CGPointMake(CGRectGetMinX(self.imageView.bounds), CGRectGetMaxY(self.imageView.bounds));
CGPoint convertedPoint = [self.imageView convertPoint:lowestPoint toView:self.view];
if (!CGRectIntersectsRect(self.view.bounds, self.imageView.frame)) {
NSLog(#"outside");
}
};
CGFloat area = CGRectGetWidth(self.scrollView.bounds) * CGRectGetHeight(self.scrollView.bounds);
CGFloat UIKitNewtonScaling = 5000000.0;
CGFloat scaling = area / UIKitNewtonScaling;
CGVector pushDirection = CGVectorMake(velocity.x * scaling, velocity.y * scaling);
self.pushBehavior.pushDirection = pushDirection;
[self.animator addBehavior:self.pushBehavior];
}
I'm having an immense amount of trouble detecting when my view actually completely disappears from the screen.
My view is setup rather simply. It's a UIScrollView with a UIImageView within it. Both are just within a UIViewController. I move the UIScrollView with the pan gesture, but want to detect when the image view is off screen.
In the action block I can monitor the view as it moves, and I've tried two methods:
1. Each time the action block is called, find the lowest point in y for the image view. Convert that to the view controller's reference point, and I was just trying to see when the y value of the converted point was less than 0 (negative) for when I "threw" the view upward. (This means the lowest point in the view has crossed into negative y values for the view controller's reference point, which is above the visible area of the view controller.)
This worked okay, except the x value I gave to lowestPoint really messes everything up. If I choose the minimum X, that is the furthest to the left, it will only tell me when the bottom left corner of the UIView has gone off screen. Often times as the view can be rotating depending on where the user pushes from, the bottom right may go off screen after the left, making it detect it too early. If I choose the middle X, it will only tell me when the middle bottom has gone off, etc. I can't seem to figure out how to tell it "just get me the absolute lowest y value.
2. I tried CGRectIntersectsRect as shown in the code above, and it never says it's outside, even seconds after it went shooting outside of any visible area.
What am I doing wrong? How should I be detecting it no longer being visible?
If you take a look on UIDynamicItem protocol properties, you can see they are center, bounds and transform. So UIDynamicAnimator actually modifies only these three properties. I'm not really sure what happens with the frame during the Dynamics animations, but from my experience I can tell it's value inside the action block is not always reliable. Maybe it's because the frame is actually being calculated by CALayer based on center, transform and bounds, as described in this excellent blog post.
But you for sure can make use of center and bounds in the action block. The following code worked for me in a case similar to yours:
CGPoint parentCenter = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
self.pushBehavior.action = ^{
CGFloat dx = self.imageView.center.x - parentCenter.x;
CGFloat dy = self.imageView.center.y - parentCenter.y;
CGFloat distance = sqrtf(dx * dx + dy * dy);
if(distance > MIN(parentCenter.y + CGRectGetHeight(self.imageView.bounds), parentCenter.x + CGRectGetWidth(self.imageView.bounds))) {
NSLog(#"Off screen!");
}
};
I understand that in order to get the x or y coordinates of a UI Element you can use something like button.center.y or self.view.frame.size.height to get the height of your UIView). But, if I understand all this correctly, all of this is within the bounds of a UIView.
How do you obtain the same information in terms of the iPhone screen itself?
Maybe something like:
screen.view.frame.size.height
Thanks in advance.
EDIT: My UIElements are on a UIScrollView.
Convert the point from your view's coordinate system to the enclosing window's coordinate system, and then convert that point from the window to the screen.
Use -[UIView convertPoint:toView:] and -[UIWindow convertPoint:toWindow:]
UIWindow* myWindow = myView.window;
CGPoint pointInWindow = [myView convertPoint:pointInMyView toView:myWindow];
// note: toView:nil also works
CGPoint pointInScreen = [myWindow convertPoint:pointInWindow toWindow:nil];
By the way, an important point: view properties like center and frame are relative to the view's superview's coordinate system. So, to convert a view's center to the screen:
CGPoint pointInWindow = [myView.superview convertPoint:myView.center toView:nil];
CGPoint pointInScreen = [myView.window convertPoint:pointInWindow toWindow:nil];
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.
I would like to be able to inspect a UIView instance that may or may not have been rotated from its original orientation (by the user rotating the device for instance) and determine what the "true" width and height are. "true" here meaning, if a view on a portrait-oriented iPad was 768x1024 before rotation, after being turned sideways I would calculate that the new width was 1024 and the new height was 768.
It appears that if I apply the view's transform to its frame property like this:
CGRect rotated = CGRectApplyAffineTransform([myview frame], [myview transform);
I get the desired result. Apple's documentation however states that UIView::frame is undefined if the transform for the view is not the identity transform, so maybe it's not a good idea to rely on this calculation?
view.bounds will return the rect you want
view.frame will return a rect with the transfrom applied to the bounds along with position.
Well due to lack of input, I'm going to go with my solution of using:
CGRect rotated = CGRectApplyAffineTransform([myview frame], [myview transform);
To get the properly oriented bounding. If somebody has another solution or can confirm this is safe I will award the answer to them instead.