I am trying to build an augmented reality app using the CoreMotion framework. I have tried to go off of Apple's pARk sample code project, but it only works in portrait mode. I need it to work in landscape. When switched to landscape mode the subviews in the overlay view move in the opposite directions and at the wrong rate (they either move too fast or too slow across screen)
I have read other postings that provide two solutions:
Create a reference attitude and apply the inverse of that attitude to the current attitude, as suggested in the CoreMotion Tea Pot Example.
Rotate the quaternion representation of the attitude 90 degrees
I do not think that the first will work because my augmented reality app requires that it be referenced to true north.
I also do not understand the math required to do the second.
Any suggestions on how to accomplish this complex problem I welcome.
How far are you now in your augmented reality app? I think beginning by taking a look at PARk from Apple is harsh. You need to have some advanced mathematical understanding. But if you do, why not!
you can take a look at this repository, this an augmented reality project working on Portrait and Landscape mode. Here is how the rotation is handled:
- (void)deviceOrientationDidChange:(NSNotification *)notification {
prevHeading = HEADING_NOT_SET;
[self currentDeviceOrientation];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
// Later we may handle the Orientation of Faceup to show a Map. For now let's ignore it.
if (orientation != UIDeviceOrientationUnknown && orientation != UIDeviceOrientationFaceUp && orientation != UIDeviceOrientationFaceDown) {
CGAffineTransform transform = CGAffineTransformMakeRotation(degreesToRadian(0));
CGRect bounds = [[UIScreen mainScreen] bounds];
switch (orientation) {
case UIDeviceOrientationLandscapeLeft:
transform = CGAffineTransformMakeRotation(degreesToRadian(90));
bounds.size.width = [[UIScreen mainScreen] bounds].size.height;
bounds.size.height = [[UIScreen mainScreen] bounds].size.width;
break;
case UIDeviceOrientationLandscapeRight:
transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
bounds.size.width = [[UIScreen mainScreen] bounds].size.height;
bounds.size.height = [[UIScreen mainScreen] bounds].size.width;
break;
case UIDeviceOrientationPortraitUpsideDown:
transform = CGAffineTransformMakeRotation(degreesToRadian(180));
break;
default:
break;
}
[displayView setTransform:CGAffineTransformIdentity];
[displayView setTransform: transform];
[displayView setBounds:bounds];
degreeRange = [self displayView].bounds.size.width / ADJUST_BY;
}
}
You have to rotate all your annotations and then your overlay view after the device rotation!
If you're really stuck, and are willing to use another toolkit, try iPhone-AR-Toolkit. It works in both portrait and landscape.
Related
I am working on an app which places labels of cities on top of the cameraView for augmented reality. When I open the camera I hide the controls and navigationBar and toolBar.
I use the following code I found online to resize the camera view to cover the whole screen. I only have an iPhone 7 in landscape mode. When I use this the height of the screen is 320 (instead of 375 that it should be) and the length is 677. How can I make it so the screen size is the actual size of the phone screen and how do I change it for each different iPhone? I can't use it in simulator to see since you can't use augmented reality in simulator.
picker = [[UIImagePickerController alloc] init];
picker.allowsEditing = NO;
picker.sourceType = UIImagePickerControllerSourceTypeCamera ;
picker.showsCameraControls = NO;
self.picker.navigationBarHidden = YES;
self.picker.toolbarHidden = YES;
if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
CGSize screenSize = [[UIScreen mainScreen] bounds].size; // 320 x 568
float scale = screenSize.width / screenSize.height*5/3; // screen height divided by the pickerController height ... or: 568 / ( 320*4/3 )
CGAffineTransform translate=CGAffineTransformMakeTranslation(0,(screenSize.height - screenSize.width*4/3)*0.5);
CGAffineTransform fullScreen=CGAffineTransformMakeScale(scale, scale);
picker.cameraViewTransform =CGAffineTransformConcat(fullScreen, translate);
I don't understand the logic behind this and think it is originally for an iPhone SE. If there is a tutorial that explains this or if someone can explain it to me I would really appreciate it.
Thanks
Currently I am working on an iPhone application. I find some difficulties in fixing the UI layouts.
Is there a tool thats measures the pixels given in my application? Something like a ruler in Microsoft Word or Photoshop around my application which allows me to find the measurements in my app.
Thanks
If I understand correctly, you'r looking for a way to better understand the current (and desired) positions of the UI elements in InterfaceBuilder (whether you're working with xib files or storyboards).
If that's the case, there are two things you need to know:
If you select any object on the screen and press the ALT key then drag the mouse cursor (without pressing the mouse button), you'll see the distance between that element and any other element that you're interested in (the one your cursor is currently pointing to). This makes the positioning of elements on the screen very straight forward and is relatively similar to the rulers you described.
In addition to the previous tool, you have the Size Inspector tab on the top right corner of InterfaceBuilder, in which you can see the sizes and positions (in points, not pixels) of the current element you're editing. The display of this tab changes whether you're using auto-layout or autoresizing masks.
I hope this helps. Good luck!
Screen height and width macros:
#define SCREEN_WIDTH ((([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) || ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)) ? [[UIScreen mainScreen] bounds].size.width : [[UIScreen mainScreen] bounds].size.height)
#define SCREEN_HEIGHT ((([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) || ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)) ? [[UIScreen mainScreen] bounds].size.height : [[UIScreen mainScreen] bounds].size.width)
these macros give SCREEN_HEIGHT and SCREEN_WIDTH whenever you use them for the current Iphone or Ipad device
If you want the scale then you can check the scale like so:
[UIScreen mainScreen].scale;
Point coordinates multiplied by screen scale = Pixel size, so for an Iphone 6+ this is how this would work:
SCREEN_WIDTH * 3 = Pixel width
SCREEN_HEIGHT * 3 = Pixel height
My question is less of how to fix the issue and more of why is this happening:
Starting in iOS 8.3, when displaying videos on a second screen (through either AirPlay or Lightning -> HDMI) the screen is rotated by 90ยบ. This isn't a problem on previous versions of iOS or when the app is launched in portrait instead of landscape.
I've created a workaround by checking for iOS version and screen rotation and then rotating the view for the second window. In case anyone else has this problem, here's my solution:
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.3f) {
CGFloat width = (_externalWindow.frame.size.width > _externalWindow.frame.size.height) ? _externalWindow.frame.size.width : _externalWindow.frame.size.height;
CGFloat height = (_externalWindow.frame.size.width < _externalWindow.frame.size.height) ? _externalWindow.frame.size.width : _externalWindow.frame.size.height;
CGRect rotatedFrame = CGRectMake(0.0f, 0.0f, width, height);
_externalWindow.frame = rotatedFrame;
if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft && [[UIDevice currentDevice].systemVersion floatValue] < 9.0f) {
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_2 * 3);
_externalWindow.transform = transform;
} else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight && [[UIDevice currentDevice].systemVersion floatValue] < 9.0f) {
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_2);
_externalWindow.transform = transform;
}
}
Edit: tested this on iOS 9 and found an interesting problem that's similar to the previous problem. The orientation was displaying correctly but the frame was still rotated so only part of the content was showing. I adjusted my solution to make sure the window frame is always oriented as widescreen.
Just so you know, I was able to solve this by only allowing portrait on the VC:
-(NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
In many situation need to rotate the controller and is not working.
Right now I have the inverse of the problem: it is rotating, and I want to disable.
In that ViewController I have this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown);
}
but it is auto-rotating, because not this UIViewController is asked, but his parent in UI tree. Maybe that is the problem. The root controller must return Yes for all cases, because there are a few other UIViewControllers on the stack, which has / must have Portait / Landscape support.
I can't / don't want to touch other parts, because ... there are several reasons, for eg: the app is huge, with lot of know bugs and I don't want to make 1 and test it for 1 week, other is the deadline.
Please don't suggest it shouldn't be like this and must rewritten. I know.
How to deal with this controller to force Portait ?
Please read the bolded text too: can't force the whole app to support only Portait for 1 view controller, there are many on stack!
Try marking the app's supported Interface orientations in the properties file to only being portrait. But then of course in that function you just return YES on view controllers that you want to allow rotation. But then when you push it back in the stack the other views should be portrait.
detect the Landscape rotation and rotate to Portait:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
UIInterfaceOrientation appOrientation = [UIApplication sharedApplication].statusBarOrientation;
float width = self.view.bounds.size.width;
float height = self.view.bounds.size.height;
//NSLog(#"width %3.0f, height: %3.0f", width, height);
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait || fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// if is rotated from Portait:
if((appOrientation == UIInterfaceOrientationLandscapeLeft || appOrientation == UIInterfaceOrientationLandscapeRight)){
// to Landscape:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, -(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
else {
// it is rotated from Landscape:
if((appOrientation == UIInterfaceOrientationPortrait || appOrientation == UIInterfaceOrientationPortraitUpsideDown)){
// to Portrait:
CGAffineTransform transform = self.view.transform;
transform = CGAffineTransformRotate(transform, +(M_PI / 2.0));
self.view.transform = transform;
[self.view setBounds:CGRectMake(0, 0, height, width)];
}
}
}
it isn't the best programming paradigm, but it does the trick.
Somebody write similar like tis to accept his answer, or write a better method, if you can!
How do you execute multiple CGAffineTransform operations (in animation blocks) without keeping track of every operation executed?
The translation operation doesn't take x,y coordinates but instead values to shift by. So unless you know where you are currently translated to, say at "location 2," how do you know what values to shift by to get to "location 3?"
For example:
A UIView's frame is at (0, 0) - Position 1. I set the transform to translate to (768, 0) and rotate -90 degrees - Position 2. Some time passes and now I want to move to (768, 1024) and rotate another -90 degrees - Position 3.
How do I know what to translate by to move from Position 2 to Position 3?
In context, I'm trying to achieve an iPad view like the following:
a UIView that takes up the entire screen
a UIToolbar that takes up the top edge and is on top of the UIView
when the iPad rotates, the UIView stays with the device, but the toolbar will rotate so that it is always on the top edge of the screen.
I am using CGAffineTransform translate and rotate to move the toolbar. Works great, except when I rotate the iPad multiple times. The first translate/rotate will work perfect. The following transforms will be off because I don't know the correct values to shift by.
UPDATE:
It looks like if I take the current translation (tx, ty) values in the UIView.transform struct and use the difference between them and the new location, it works. However, if the view has been rotated, this does not work. The tx and ty values can be flipped because of the previous rotation. I'm not sure how to handle that.
UPDATE 2:
Doing some research, I've found that I can get the original, unrotated points from tx, ty by getting the abs value of the points and possibly swapping x and y if the UIView is perpendicular. Now I am stuck figuring out how to correctly apply the next set of transforms in the right order. It seems no matter how I concat them, the UIView ends up in the wrong place. It just seems like this is all too complicated and there must be an easier way.
The answer is, apparently, you don't track the transforms.
So the way to rotate the toolbar around the screen is by not concatenating a rotate and translate transform. Instead, create a rotate transform and set the frame in the animation block. Further, based on the new UIInterfaceOrientation, set the degrees to rotate based on the compass values of 0, -90, -180, -270. Also, set the frame size base on the same locations.
So:
CGPoint portrait = CGPointMake(0, 0);
CGPoint landscapeLeft = CGPointMake(768 - 44, 0);
CGPoint landscapeRight = CGPointMake(0, 0);
CGPoint upsideDown = CGPointMake(0, 1024 - 44);
CGSize portraitSize = CGSizeMake(768, 44);
CGSize landscapeLeftSize = CGSizeMake(44, 1024);
CGSize landscapeRightSize = CGSizeMake(44, 1024);
CGSize upsideDownSize = CGSizeMake(768, 44);
CGFloat rotation;
CGRect newLocation;
switch(orientation) {
case UIDeviceOrientationPortrait:
NSLog(#"Changing to Portrait");
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
case UIDeviceOrientationLandscapeRight:
NSLog(#"Changing to Landscape Right");
newLocation.origin = landscapeRight;
newLocation.size = landscapeRightSize;
rotation = -90.0;
break;
case UIDeviceOrientationLandscapeLeft:
NSLog(#"Changing to Landscape Left");
newLocation.origin = landscapeLeft;
newLocation.size = landscapeLeftSize;
rotation = -270.0;
break;
case UIDeviceOrientationPortraitUpsideDown:
NSLog(#"Changing to Upside Down");
newLocation.origin = upsideDown;
newLocation.size = upsideDownSize;
rotation = -180.0;
break;
default:
NSLog(#"Unknown orientation: %d", orientation);
newLocation.origin = portrait;
newLocation.size = portraitSize;
rotation = 0.0;
break;
}
CGRect frame = newLocation;
CGAffineTransform transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rotation));
if(lastOrientation) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationBeginsFromCurrentState:YES];
}
toolbar.transform = transform;
toolbar.frame = frame;
// Commit the changes
if(lastOrientation) {
[UIView commitAnimations];
}
lastOrientation = orientation;
This works beautifully. However, an unexpected problem is that UI elements that iOS shows on your behalf are not oriented correctly. I.e., modal windows and popovers all keep the same orientation as the underlying UIView. That problem renders this whole thing moot.