CAGradientLayer - Doesn't cover the view completely in landscape orientation - iphone

I've used this tutorial to create a gradient background for my app.
It looks beautiful. However there is a problem when I change the orientation.
It looks proper in portrait mode but in landscape orientation the gradient doesn't cover the entire view. I've uploaded a screenshot -
The red is the gradient and the blue part is the default background color which is supposed to be completely covered by the red gradient.
How can I cover entire view? I tried to call the gradient method after detecting rotation change but it didn't work. This is the code I used:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(deviceOrientationDidChangeNotification:)
name:UIDeviceOrientationDidChangeNotification
object:nil];// this is in 'viewWillAppear' method
- (void)deviceOrientationDidChangeNotification:(NSNotification*)note
{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[self addBackground];
}

My guess is the system sends UIDeviceOrientationDidChangeNotification before actually updating the view hierarchy for the landscape layout.
Instead of redoing the gradient in response to UIDeviceOrientationDidChangeNotification, do it in viewDidLayoutSubviews. When your view controller receives viewDidLayoutSubviews, its view's frame has already been modified for the new interface orientation. Something like this should do:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.background.frame = self.view.bounds;
}

Related

Disable taking images in any orientation other than Portrait AVFoundation

I am using AVFoundation to show the camera.
I would like to prevent the camera itself to rotate so the viewer will see the camera only in portrait and the images will be taken only in portrait mode.
I defined Supported Interface Orientation to support portrait only and the view itself is being displayed only in portrait mode, but not the camera - is being rotated with the device orientation
How can I force the AVFoundation camera to be displayed and capture images only in portrait like the UIViewController?
My code to set the camera:
AVCaptureVideoPreviewLayer* lay = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.sess];
UIView *view = [self videoPreviewView];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[lay setFrame:bounds];
if ([lay respondsToSelector:#selector(connection)])
{
if ([lay.connection isVideoOrientationSupported])
{
[lay.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
}
}
[lay setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[viewLayer insertSublayer:lay below:[[viewLayer sublayers] objectAtIndex:0]];
self.previewLayer = lay;
Here is a partial answer based on my understanding of your question (which differs from the other answers you have had).
You have the app locked to portrait orientation. So the status bar is always at the portrait top of the phone regardless of the phone's orientation. This successfully locks your interface, including your AVCapture interface. But you want to also lock the raw image feed from the camera so that the image horizon is always parallel with the status bar.
This will ideally need to be done continuously - so that if you have the camera at a 45degree angle the image will be counter-rotated 45 degrees. Otherwise, most of the time, the image will not be aligned correctly (the alternative is that it is always out of line until your 90degree orientation switch updates, which would swivel the image 90 degrees).
To do this you need to use Core Motion and the accelerometer. You want to get angle of the phone's Y-axis to true vertical and rotate the image accordingly. See here for geometry details:
iPhone orientation -- how do I figure out which way is up?
Using Core Motion, trigger this method from viewDidLoad
- (void)startAccelerometerUpdates {
self.coreMotionManager = [[CMMotionManager alloc] init];
if ([self.coreMotionManager isAccelerometerAvailable] == YES) {
CGFloat updateInterval = 0.1;
// Assign the update interval to the motion manager
[self.coreMotionManager setAccelerometerUpdateInterval:updateInterval];
[self.coreMotionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) {
CGFloat angle = -atan2( accelerometerData.acceleration.x,
accelerometerData.acceleration.y)
+ M_PI ;
CATransform3D rotate = CATransform3DMakeRotation(angle, 0, 0, 1);
self.previewLayer.transform = rotate;
}];
}
}
a b c
phone held (a) portrait; (b) rotated ~30deg; (c) landscape
.
You may find this is a little jumpy, and there is a bit of a lag between the device movement and the view. You can play with the updateInterval, and get in deeper with other Core Motion trickery to dampen the movement. (I have not treated the case of the phone being exactly upside down, and if you hold the camera face down or face up, the result is undefined fixed with updated code/ use of atan2).
Now orientation is reasonably correct, but your image does not fit your view. There is not a lot you can do about this as the format of the raw camera feed is fixed by the physical dimensions of it's sensor array. The workaround is to zoom the image so that you have enough excess image data at all angles to enable you to crop the image to fit the portrait format you want.
Either in Interface Builder:
set your previewLayer's view to square centered on it's superview, with width and height equal to the diagonal of the visible image area (sqrt (width2+height2)
Or in code:
- (void)resizeCameraView
{
CGSize size = self. videoPreviewView.bounds.size;
CGFloat diagonal = sqrt(pow(size.width,2)+pow(size.height,2));
diagonal = 2*ceil(diagonal/2); //rounding
self.videoPreviewView.bounds = (CGRect){0,0,diagonal,diagonal};
}
If you do this in code, resizeCameraView should work if you call it from your viewDidLoad. Make sure that self.videoPreviewView is your IBOutlet reference to the correct view.
Now when you take a photo, you will capture the whole of the 'raw' image data from the camera's array, which will be in landscape format. It will be saved with an orientation flag for display rotation. But what you may want is to save the photo as seen onscreen. This means that you will have to rotate and crop the photo to match your onscreen view before saving it, and remove it's orientation metadata. That's for you to work out (the other part of the 'partial answer'): I suspect you might decide that this whole approach doesn't get you what you want (I think what you'd really like is a camera sensor that hardware-rotates against the rotation of the device to keep the horizon stable).
update
changed startAccelerometerUpdates to get angle from atan2 instead of acos, smoother and takes account of all directions without fiddling
update 2
From your comments, it seems your rotated preview layer is getting stuck? I cannot replicate your error, it must be some other place in your code or settings.
So that you can check with clean code, I have added my solution into Apple's AVCam project, so you can check it against that. Here is what to do:
add the Core Motion framework to AVCam.
In AVCamViewController.m
#import <CoreMotion/CoreMotion.h>
add my startAccelerometerUpdates method
add my resizeCameraView method (stick both of these methods near the top of the class file or you may get confused, there are more than one #implementations in that file)
add the line: [self resizeCameraView]; to viewDidLoad (it can be the first line of the method)
add the property
#property (strong, nonatomic) CMMotionManager* coreMotionManager
to the #interface (it doesn't need to be a property, but my method assumes it exists, so if you don't add it you will have to modify my method instead).
In startAccelerometerUpdates change this line:
self.previewLayer.transform = rotate;
to:
self.captureVideoPreviewLayer.transform = rotate;
also, in the Objects list in AVCamViewController.xib, move the videoPreview View above the ToolBar (otherwise when you enlarge it you cover the controls)
Be sure to disable rotations - for iOS<6.0, that is already true, but for 6.0+ you need to select just portrait in supported orientations in the target summary.
I think that is a complete list of changes I made to AVCam, and the rotation/orientation is all working very well. I suggest you try doing the same. If you can get this to work smoothly, you know there is some other glitch in your code somewhere. If you still find your rotations stick, I would be curious to know more about your hardware and software environment such as which devices are you testing on.
I am compiling on XCode 4.6/OSX10.8.2, and testing on:
- iPhone4S / iOS5.1
- iPhone3G / iOS6.1
- iPad mini / iOS6.1
All results are smooth and accurate.
I guess you need to use this method to restrict the camera rotation.
AVCaptureConnection *videoConnection = [CameraVC connectionWithMediaType:AVMediaTypeVideo fromConnections:[imageCaptureOutput connections]];
if ([videoConnection isVideoOrientationSupported])
{
[videoConnection setVideoOrientation:[UIDevice currentDevice].orientation];
}
Assuming your preview layer is defined as property, can use
[self.previewLayer setOrientation:[[UIDevice currentDevice] orientation]];
In your case you can replace [[UIDevice currentDevice] orientation] by UIInterfaceOrientationPortrait
edited
Try to add the preview layer when you actually need it.
Example
preview = [[self videoPreviewWithFrame:CGRectMake(0, 0, 320, 480)] retain];
[self.view addSubview:preview];
The videoPreviewWithFrame function.
- (UIView *) videoPreviewWithFrame:(CGRect) frame
{
AVCaptureVideoPreviewLayer *tempPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:[self captureSession]];
[tempPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
tempPreviewLayer.frame = frame;
UIView* tempView = [[UIView alloc] init];
[tempView.layer addSublayer:tempPreviewLayer];
tempView.frame = frame;
[tempPreviewLayer autorelease];
[tempView autorelease];
return tempView;
}
Assuming your previewlayer is added to a viewcontroller view. Do this in viewDidLoad :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
and define the selector as:
- (void)orientationChanged:(NSNotification*)notification {
UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
if ([self.previewlayer respondsToSelector:#selector(orientation)]) {
//for iOS5
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
} else {
//for iOS6
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
}
}
Note: put tempPreviewLayer in the property self.previewlayer .
This will force the preview layer to portrait position when the device orientation changes.
EDIT
you can also add this in ur 'shouldAutoRotate` method of the viewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if ([self.previewlayer respondsToSelector:#selector(orientation)]) {
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
} else {
if (interfaceOrientation != UIInterfaceOrientationPortrait) {
self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
}
}
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
for ios6 over ride these two and check.
-(NSUInteger)supportedInterfaceOrientations {
//UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
//return (
//interfaceOrientation == UIInterfaceOrientationLandscapeLeft |
//interfaceOrientation == UIInterfaceOrientationLandscapeRight);
return UIInterfaceOrientationMaskLandscape;//(UIInterfaceOrientationLandscapeLeft | UIInterfaceOrientationLandscapeRight);
}
- (BOOL)shouldAutorotate {
return YES;
}
before return in these two methods apend the code ..and in the notification that i gave, see if its called when you roate the device.

How can I get the correct size of my UIView?

Question 1:
How do I get the correct size of a UIView?
I am creating a CGRect to show some images using tiled layers.
When I'm creating the CGRect, I basically need it to be the exact same size as that of my UIView. This turned out to be quite hard..
When I NSLog() out my mainView.bounds.size.width or my mainView.frame.size.width they are always wrong when in landscape! They always log out the values as if it was in portrait, even though I can see the actual view being wider. And reversing them will also be wrong.
It's not good enough set the width to be the height and vice versa when in landscape, I need the right values.
The only way I've been able to make it look right is to manually put in 1024 for width when in Landscape, and this doesn't always work either, because:
Question 2:
What is the correct way to check if the device is in landscape or not?
I've been using
if([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft || [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight)
but this doesn't always work. If I hold my device in landscape mode when launching, it is correct, but if I make it landscape, then lay the iPad flat down leaving the dashboard as landscape and THEN launch it, then the landscape-splash shows up, but that code think it's in portrait.
That code doesn't work at all for iPad simulator either..
EDIT
For some reason, when I decided to add support for landscape orientation, it wasn't enough to just check the landscape-orientations in the summary-page of the target, I had to actually sub-class my TabBarController and physically tell it to rotate with
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
I shouldn't have to do this.. right? If I create an empty project like that, it doesn't need it.. I don't know why.
Question 1:
Yup, that's right. :)
Question 2:
I just got home and checked my own code. I use:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
to check the orientation of the current interface. Then, you can use something like:
if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
// Do something
} else if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
// Do something else
}
HOWEVER You really should not need to do this if you properly handle rotation events.
Here is my typical way of dealing with rotation when I need to adjust UI element positions in code based on orientation:
#pragma mark - View rotation methods
// Maintain pre-iOS 6 support:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
// Make sure that our subviews get moved on launch:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
[self moveSubviewsToOrientation:orientation duration:0.0];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self moveSubviewsToOrientation:toInterfaceOrientation duration:duration];
}
// Animate the movements
- (void)moveSubviewsToOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
{
[UIView animateWithDuration:duration
animations:^{
[self.tableView reloadData];
if (UIInterfaceOrientationIsPortrait(orientation))
{
[self moveSubviewsToPortrait];
}
else
{
[self moveSubviewsToLandscape];
}
}
completion:NULL];
}
- (void)moveSubviewsToPortrait
{
// Set the frames/etc for portrait presentation
self.logoImageView.frame = CGRectMake(229.0, 21.0, 309.0, 55.0);
}
- (void)moveSubviewsToLandscape
{
// Set the frames/etc for landscape presentation
self.logoImageView.frame = CGRectMake(88.0, 21.0, 309.0, 55.0);
}
I also put moveSubviewsToOrientation in viewWillAppear to have it rotate
I struggled with this a bit and here are some facts I found:
1- When a device is face up or down, the device reverts to the last orientation prior to it being face up or down since those 2 orientations do not tell you necessarily on their own whether the device is portrait or landscape. So for example, if you were in landscape and then put the device flat face up, then launch an app, it will launch in the landscape orientation.
2- When viewDidLoad is called, the bounds have not been set, so you need to put any calls that pertain to the orientation in viewWillAppear or viewDidLayoutSubviews.
3- If for some odd reason, you need to use the bounds before viewDidLoad, or maybe to do something in a model, I have found that the best way to put settings that pertain to the orientation is to trust the statusbar, which you can call as follows for example.
if(UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]))
PS: Regarding your added question, refer to:
https://developer.apple.com/library/ios/#qa/qa2010/qa1688.html
You are most likely one of the last 2 bullets. I ran into this issue before and quite frankly found it easiest to just implement:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
for all VCs just to be on the safe side especially that the behaviour has changed from iOS5 to iOS6.
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDeviceOrientationChanges/RespondingtoDeviceOrientationChanges.html#//apple_ref/doc/uid/TP40007457-CH7-SW1
Hope this helps

Auto Rotate while switching between View Controllers

I have a problem in programming. I have two view controllers. The first runs only in landscape left / right, the other runs only in portrait. If I switching between the views the orientation do not change automaticly. Only when I turn the device the orientation is changing. After that the orientation is fixed. Is it possible to change the orientation automaticly when I switching between the controllers?
As Example when I press the BackButton in the Landscape View Controller and it switch to the Portrait View Controller that the orientation automaticly switch from Landscape to Portrait?
Sorry for my bad english, it is not my native language.
Thanks,
Tim
Try the following Code for Portrait in Second View.
- (void)viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationPortrait];
}
Try the following Code for Landscape in First View.
- (void)viewWillAppear:(BOOL)animated {
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscape];
}
if(UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
{
self.view.frame = CGRectMake(0,0,1024,768);
}
else
{
self.view.frame = CGRectMake(0,0,768,1024);
}
----use this code where you want to change---

Rotating an image 90 degrees on same position when orientation changes

Ended up with doing this:
Easiest way to support multiple orientations? How do I load a custom NIB when the application is in Landscape? Works superb!
I have made an image in Photoshop which I want to use as background for my info-screen in my iPad application. The image contains text and some icons also. Around the image I have a border which is green.
The effect I am trying to achieve is:
When the user goes from the portrait orientation to landscape orientation I want the image (just the frame and the icons) to rotate 90 degrees so the image appear in landscape mode, instead of having a portrait view of the frame in landscape. The text and icons are decoupled (different layers which I have organized in different UIImageView's)they shall rotate 90 degrees.
What I have done already is the following:
Experimented a bit with this method:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:
and tried to do this:
self.btnFacebook.transform = CGAffineTransformMakeRotation(1.5707964);
which I think will rotate the btnFacebook property 90 degrees to the right (correct me if I am wrong) since it specifies positive radians. I can't seem to get it to work correctly though. Should'nt this rotate the button 90 degrees around its center coordinate in the frame? That wouldn't cause a change in the position of the button (the button is square)?
EDIT
Made an image:
As the image shows the image background (some custom graphics on it which looks good in both orientations) goes from portrait to landscape and rotates so it does not appear as portrait in landscape, the icons are decoupled from the background so they need to rotate as well because they need to be in the right orientation (it is social icons). The text however are on the same position, it only rotates 90 degrees without repositioning.
I understand your question as that you are trying to not to rotate the interface as whole but to rotate the purple and red squares individually.
I created a UIViewController that resembles your layout.
The black square is a UIView and the white square is there only so that I can tell when the black view rotates. This view is wired to view1 property on the controller.
There are 4 buttons that are wired to btnx (x runs 1 through 4) properties.
Since I no longer want to auto rotate the interface I only support the portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
To do the rotation manually, I added a method to the ViewController. It determines the angle it needs to rotate the components from portrait to current orientation, creates a rotation transform and applies it to all outlets.
- (void)deviceDidRotate:(NSNotification *)notification
{
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
double rotation = 0;
UIInterfaceOrientation statusBarOrientation;
switch (currentOrientation) {
case UIDeviceOrientationFaceDown:
case UIDeviceOrientationFaceUp:
case UIDeviceOrientationUnknown:
return;
case UIDeviceOrientationPortrait:
rotation = 0;
statusBarOrientation = UIInterfaceOrientationPortrait;
break;
case UIDeviceOrientationPortraitUpsideDown:
rotation = -M_PI;
statusBarOrientation = UIInterfaceOrientationPortraitUpsideDown;
break;
case UIDeviceOrientationLandscapeLeft:
rotation = M_PI_2;
statusBarOrientation = UIInterfaceOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
rotation = -M_PI_2;
statusBarOrientation = UIInterfaceOrientationLandscapeLeft;
break;
}
CGAffineTransform transform = CGAffineTransformMakeRotation(rotation);
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.btn1 setTransform:transform];
[self.btn2 setTransform:transform];
[self.btn3 setTransform:transform];
[self.btn4 setTransform:transform];
[self.view1 setTransform:transform];
[[UIApplication sharedApplication] setStatusBarOrientation:statusBarOrientation];
} completion:nil];
}
The last thing to do is to get the OS to call my method. To achieve that I added the following code to application:didFinishLaunchingWithOptions: in the AppDelegate.
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self.viewController selector:#selector(deviceDidRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
I am not sure whether this is exactly what you wanted but I believe it is at least similar so you can get some ideas from it how to solve your problem. I can provide source code for a working iPad application that I created to illustrate this.

Change or disable the iPhone rotating animation when orientation changes

How do I change or disable the rotating animation when screen orientation changes from landscape to portrait, or vice versa?
Yes, it is possible to disable the animation, without breaking everything apart.
The following codes will disable the "black box" rotation animation, without messing with other animations or orientation code:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[UIView setAnimationsEnabled:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[UIView setAnimationsEnabled:NO];
/* Your original orientation booleans*/
}
Place it in your UIViewController and all should be well. Same method can be applied to any undesired animation in iOS.
Best of luck with your project.
If you dont want your view controllers to rotate just override the shouldAutoRotateToInterface view controller method to return false for whichever orientation you dont want to support...Here is a reference.
In the case that u just want to handle rotation some other way, you can return false in the above methods and register for UIDeviceOrientationDidChangeNotification like so
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(handleOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
Now when u get the notifications u can do whatever you want with it...
The answer by #Nils Munch above is find for < iOS7. For iOS 7 or later you can use:
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[UIView setAnimationsEnabled:NO];
[coordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[UIView setAnimationsEnabled:YES];
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}