In my code, I have an area on which when users taps and holds, UILongPressGestureRecognizer causes an animation to run and at the end of animation a method gets called.
(It's basically a long tap on a small area square which contains one CGPoint so that it starts a long press animation and when it finishes, the point is removed.)
The problem: On iPhone 5s,SE and 6, UILongPressGestureRecognizer works fine but on any device released after that, it gets dismissed very quickly and I have tried it on at least 11 different devices.
Why is that?!
I appreciate any help.
-(void)prepareLongPressGesture{
_longGesture = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongGesture:)];
_longGesture.delegate = self;
_longGesture.allowableMovement = self.frame.size.width*0.15;
_longGesture.minimumPressDuration = 1.0;
[self addGestureRecognizer:_longGesture];
}
- (void)handleLongGesture:(UILongPressGestureRecognizer*)gesture {
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
[self removeModifiablePoint];
break;
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
if (self.animatedView.superview) {
[self.animatedView removeFromSuperview];
}
} break;
default:
break;
}
}
- (void)removeModifiablePoint {
CGFloat sizeOFLongPressAnimation = 0.0;
if (IS_IPAD) {
sizeOFLongPressAnimation = 0.15*self.frame.size.width;
}else{
sizeOFLongPressAnimation = 0.27*self.frame.size.width;
}
if (!_animatedView) {
self.animatedView =
[[UIView alloc] initWithFrame:CGRectMake(0, 0, sizeOFLongPressAnimation, sizeOFLongPressAnimation)];
self.animatedView.alpha = 0.7f;
self.animatedView.layer.borderColor = [UIColor redColor].CGColor;
self.animatedView.layer.borderWidth = 3.f;
self.animatedView.layer.cornerRadius = sizeOFLongPressAnimation / 2.0f;
self.progressView = [[UIView alloc] initWithFrame:self.animatedView.bounds];
self.progressView.backgroundColor = [UIColor redColor];
self.progressView.layer.cornerRadius = sizeOFLongPressAnimation / 2.0f;
[self.animatedView addSubview:self.progressView];
}
CGPoint center = [self.modifiablePointsArray[_modifiablePointIndex] CGPointValue];
self.animatedView.center = center;
[self addSubview:self.animatedView];
self.progressView.transform =
CGAffineTransformScale(CGAffineTransformIdentity, 0.1, 0.1f);
[UIView animateWithDuration:2.f animations:^{
self.progressView.transform = CGAffineTransformIdentity;
}completion:^(BOOL finished) {
if (finished) {
[self.animatedView removeFromSuperview];
[self.modifiablePointsArray removeObjectAtIndex:_modifiablePointIndex];
[self setNeedsDisplay];
}
}];
}
I have a custom UIView which I want to stick to the top of the UIWindow even when interface orientation changes. Here is the my view in portrait orientation
The problem is that prior iOS 8 UIWindow coordinate system is not being changed with the orientation changes. So I need to make all the calculations by hand.
The first thing is to change the transform of the UIView, which I do using this method
-(CGFloat)angleForOrientation:(UIInterfaceOrientation)orientation
{
CGFloat angle;
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
angle = -M_PI /2.0;
NSLog(#"UIInterfaceOrientationLandscapeLeft");
break;
case UIInterfaceOrientationLandscapeRight:
angle = M_PI /2.0;
NSLog(#"UIInterfaceOrientationLandscapeRight");
break;
case UIInterfaceOrientationPortraitUpsideDown:
angle = M_PI;
NSLog(#"UIInterfaceOrientationPortraitUpsideDown");
break;
default:
angle = 0;
NSLog(#"UIInterfaceOrientationPortrait");
break;
}
return angle;
}
The second thing is to somehow map actual coordinate systems to UIWindow coordinate system, which stays the same.
So how should I calculate the frame of the custom UIView that even when the user rotates the view to other orientations I will have the same sized UIView sticked to the top centre of the screen?
For instance this is how should the view look like in landscape
By the way this image is generated from iOS 8 version. For which I do the following
self.frame = CGRectMake(window.bounds.size/2-50, 0, 100, 100);
CGFloat angle = [self angleForOrientation:orientation];
self.transform = CGAffineTransformMakeRotation(angle);
I need to do something similar to iOS 7. How can I achieve this?
Thanks for help!
So Finally I figured out how to implement this.
I've created the following method to calculate the rect which will stick the view to top based on given bounds.
-(CGRect)getTopRectForBounds:(CGRect)bounds orientation:(UIInterfaceOrientation)orientation window:(UIWindow *)window
{
CGRect newRect;
CGFloat statusBarHeight = [self getStatusBarHeight];
CGSize screenSize = window.screen.bounds.size;
CGFloat sW = screenSize.width;
CGFloat sH = screenSize.height;
CGFloat W = rect.size.width;
CGFloat H = rect.size.height;
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
newRect = CGRectMake(statusBarHeight, (sH-W)/2, H,W);
break;
case UIInterfaceOrientationLandscapeRight:
newRect = CGRectMake(sW-H-statusBarHeight, (sH-W)/2, H,W);
break;
case UIInterfaceOrientationPortraitUpsideDown:
newRect = CGRectMake((sW-W)/2, sH-H-statusBarHeight, W,H);
break;
default:
newRect = CGRectMake((sW-W)/2, statusBarHeight, W,H);
break;
}
return newRect;
}
Then I just change the frame whenever orientation changes.
So first I listen to Orientation changes
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
Then in the orientation change event handler and change the transform and the frame. (see my question to see the method which handles the transforms)
CGRect bounds = CGRectMake(0, 0, 100, 100);
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
self.frame = [self getTopRectForBounds:bounds orientation:orientation window:self.window]
CGFloat angle = [self angleForOrientation:orientation];
self.transform = CGAffineTransformMakeRotation(angle);
I created a simple video player that when portrait it shows the video in a small window on top of the view but when switched to landscape it automatically switches to full screen.
A lot like the youTube app does.
My issue is that it doesn't transition well to the full screen mode. The navigation/tabbar controllers transition but my video just basically pops from one to the other.
Here's a little video of what's happening:
http://screencast.com/t/jaOoPOHLh
Here's the code I'm using to play the video and switch the orientation.
self.controller = [[LBYouTubePlayerController alloc] initWithYouTubeURL:[NSURL URLWithString:#"http://www.youtube.com/watch?v=1fTIhC1WSew&list=FLEYfH4kbq85W_CiOTuSjf8w&feature=mh_lolz"] quality:LBYouTubeVideoQualityLarge];
self.controller.delegate = self;
self.controller.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 180.0f);
- (void) orientationChanged:(NSNotification *)note
{
//self.controller.view.hidden = YES;
UIDevice * device = note.object;
switch(device.orientation)
{
case UIDeviceOrientationPortrait:
/* start special animation */
self.controller.fullscreen = NO;
//self.controller.view.hidden = NO;
break;
case UIDeviceOrientationPortraitUpsideDown:
/* start special animation */
break;
case UIDeviceOrientationLandscapeLeft:
// do something
//self.controller.fullscreen = YES;
//self.controller.scalingMode = MPMovieScalingModeAspectFit;
//self.controller.view.hidden = NO;
break;
case UIDeviceOrientationLandscapeRight:
self.controller.fullscreen = YES;
self.controller.scalingMode = MPMovieScalingModeAspectFit;
self.controller.view.hidden = NO;
break;
default:
break;
};
}
Is there something I need to add or custom transition I'm suppose to do to make the video transition and match the navigation/tabbar?
Summary:
I can't force the CALayer to respond correctly to orientation changes.
Whenever I try to use cgaffinetransform I am getting weird results (layer is not centered).
Any help will be appreciated!
thanks,
Process
I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.
I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.
I am adding a preview layer using the following code:
CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer]setFrame:CGRectMake(0, height, width, height)];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
And it is displayed properly in portrait mode.
When I try to rotate the device, preview layer behaves weird. It seems like it doesn't resize itself, and it doesn't rotate correctly.
I tried to fix it by adding the following method
-(void)rotateLayer{
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];
switch (orientation) {
case UIDeviceOrientationLandscapeLeft:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress
NSLog(#"Landscape Left");
[stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];
break;
case UIDeviceOrientationLandscapeRight:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
NSLog(#"Landscape Right");
[stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];
break;
case UIDeviceOrientationPortraitUpsideDown:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
NSLog(#"Landscape Upside down");
break;
default:
stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
break;
}
float h1 = stuckview.frame.size.height;
float w1 = stuckview.frame.size.width;
if(UIDeviceOrientationIsPortrait(orientation))
{
stuckview.position =CGPointMake(h1/2.0, w1/2.0);
NSLog(#"Portrait");
}
else{
stuckview.position =CGPointMake(w1/2.0, h1/2.0);
NSLog(#"Portrait");
}
}
After adding the method above I can see a progress. Now layer rotates correctly reflecting current device orientation and it's displayed correctly in landscape mode, but NOT in portrait mode.
The layer is not positioned correctly, it isn't centered on the screen (look at the screenshot). To see what's happening I added following debug statements:
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
float h = stuckview.bounds.size.height;
float w = stuckview.bounds.size.width;
float x = stuckview.bounds.origin.x;
float y = stuckview.bounds.origin.y;
float h1 = stuckview.frame.size.height;
float w1 = stuckview.frame.size.width;
float x1 = stuckview.frame.origin.x;
float y1 = stuckview.frame.origin.y;
NSLog(#"%f %f %f %f ", h,w,x,y );
NSLog(#"%f %f %f %f ", h1,w1,x1,y1 );
NSLog(#"Anchor Point: %f %f",stuckview.anchorPoint.x, stuckview.anchorPoint.y);
NSLog(#"Position: %f %f",stuckview.position.x, stuckview.position.y);
CGAffineTransform at = stuckview.affineTransform;
NSLog(#"Affine Transform After : %f %f %f %f %f %f %f", at.a,at.b, at.c, at.d, at.tx,at.tx, at.ty);
And get the following output:
2012-09-30 13:25:12.067 RotatePreviewLayer[2776:907] 1024.000000 768.000000 0.000000 0.000000
2012-09-30 RotatePreviewLayer[2776:907] 1024.000000 768.000000 128.000000 -128.000000
2012-09-30 13:25:12.070 RotatePreviewLayer[2776:907] Portrait
2012-09-30 13:25:12.072 RotatePreviewLayer[2776:907] Anchor Point: 0.500000 0.500000
2012-09-30 13:25:12.074 RotatePreviewLayer[2776:907] Position: 512.000000 384.000000
2012-09-30 13:25:12.076 RotatePreviewLayer[2776:907] Affine Transform after: -1.000000 0.000000 -0.000000 -1.000000 0.000000 0.000000 0.000000
Notice the second line of the debug output. The frame of the preview layer is moved by 128,-128.
Can anyone explain me why is this happening and how to fix the orientation issues with the preview layer?
thank you,
Janusz
What about this?
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
_videoPreviewLayer.connection.videoOrientation = toInterfaceOrientation;
}
This the method I use in the view controller to maintain the orientation of the capture layer so that it is always right-side-up:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
if (_previewLayer.connection.supportsVideoOrientation)
{
switch (toInterfaceOrientation)
{
case UIInterfaceOrientationPortrait:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;
break;
}
case UIInterfaceOrientationPortraitUpsideDown:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
}
case UIInterfaceOrientationLandscapeLeft:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
break;
}
case UIInterfaceOrientationLandscapeRight:
{
_previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
break;
}
}
}
A little wordy, but safe even if either enumeration ever changes in the future.
I am still not sure what's causing the problem but I managed to fix it. Here is how I did it:
in viewDidLoad I am adding a layer:
CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
[[[self view] layer] addSublayer:[[self captureManager] previewLayer]];
Then I am adding call to the rotateLayer method to didRotate
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[self rotateLayer];
}
and finally the rotateLayer method looks like:
-(void)rotateLayer{
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];
switch (orientation) {
case UIDeviceOrientationLandscapeLeft:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress
break;
case UIDeviceOrientationLandscapeRight:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
break;
case UIDeviceOrientationPortraitUpsideDown:
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
break;
default:
stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
[stuckview setBounds:layerRect];
break;
}
[stuckview setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
}
I still don't understand why it works in this way. If anyone can explain it will be great.
#Janusz Chudzynski here is the detailed explanation what rotateLayer method does
This method is created after examine how different orientation affect the previewLayer so creater has checked when orientation is in LandscapeLeft then it should be 270 degrees rotated to make it in correct position for that he has used
stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2);
M_PI 3.14159265358979323846264338327950288 // pi = 180 degree
+
M_PI_2 1.57079632679489661923132169163975144 // pi/2 = 90 degree
// Total = 270 degree
so creater has noticed that if I will rotate previewLayer to 270 degrees when its in LandscapeLeft then it will be in correct position just like that he has rotate previewLayer for every rotation possible
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
previewLayer.orientation = toInterfaceOrientation;
}
I'm with #Siegfault, although I also found that when my view loads in landscape orientation on the iPad initially, the orientation is still in correct. To fix, I call that same delegate method in viewDidAppear: with the current interfaceOrientation:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0.0];
}
The answer using willRotateToUserInterfaceOrientation works fine, except that that method has been deprecated. So if you're able to use iOS 9, then here's the way to do it, in Swift:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let newOrientation = UIDevice.currentDevice().orientation
switch newOrientation {
case .LandscapeLeft:
self.capturePreviewLayer?.connection.videoOrientation = .LandscapeLeft
case .LandscapeRight:
self.capturePreviewLayer?.connection.videoOrientation = .LandscapeRight
case .Portrait, .Unknown, .FaceUp, .FaceDown:
self.capturePreviewLayer?.connection.videoOrientation = .Portrait
case .PortraitUpsideDown:
self.capturePreviewLayer?.connection.videoOrientation = .PortraitUpsideDown
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
// layout iOS 8+ animated
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
NSString *timingFunc = nil;
switch ( [context completionCurve] )
{
case UIViewAnimationCurveEaseIn: timingFunc = kCAMediaTimingFunctionEaseIn; break;
case UIViewAnimationCurveEaseInOut: timingFunc = kCAMediaTimingFunctionEaseInEaseOut; break;
case UIViewAnimationCurveEaseOut: timingFunc = kCAMediaTimingFunctionEaseOut; break;
case UIViewAnimationCurveLinear: timingFunc = kCAMediaTimingFunctionLinear; break;
}
[CATransaction begin];
[CATransaction setAnimationDuration:[context transitionDuration]];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:timingFunc]];
[self updatePreviewLayer];
[CATransaction commit];
UIInterfaceOrientation toOrientation = [[UIApplication sharedApplication] statusBarOrientation];
// layout ui if needed
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}
- (void)updatePreviewLayer
{
CGAffineTransform transform = CGAffineTransformIdentity;
switch ( UIDevice.currentDevice.orientation )
{
case UIDeviceOrientationLandscapeLeft:
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
case UIDeviceOrientationLandscapeRight:
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIDeviceOrientationPortraitUpsideDown:
transform = CGAffineTransformRotate(transform, M_PI);
break;
default:
break;
}
preview.affineTransform = transform;
preview.frame = self.view.bounds;
}
I am working on a universal application that starts with a really short intro video. So I simply add a MPMoviePlayerViewController and modally present it. On the iPhone everything works fine even if I play the video in landscape mode. But on the iPad the video always plays in portrait mode no matter what interface orientation the device currently has. I searched for hours and tried a lot of different things like CGAffineTransformation or adding a new view, but nothing seems to work. Here is the code that I work with:
NSString *filePath = [[NSBundle mainBundle] pathForResource:videoName ofType:#"mp4"];
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
self.startupController = [[MPMoviePlayerViewController alloc] initWithContentURL:fileURL];
[self.startupController.moviePlayer setControlStyle:MPMovieControlStyleNone];
[self.startupController.moviePlayer setScalingMode:MPMovieScalingModeAspectFit];
[self.startupController.moviePlayer setFullscreen:YES];
[self presentModalViewController:self.startupController animated:NO];
Do you have any idea how I can fix this issue? In my opinion this should be a simple feature offered by Apple, but sometimes the easiest things are the ones I have to worry about most...
Here is how I did it.
First, listen to this notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(detectOrientation) name:#"UIDeviceOrientationDidChangeNotification" object:nil];
Then implement this :
- (void)detectOrientation {
NSLog(#"handling orientation");
[self setMoviePlayerViewOrientation:[[UIDevice currentDevice] orientation]];
}
- (void)setMoviePlayerViewOrientation:(UIDeviceOrientation)orientation {
if (lwvc != nil)
return;
if (moviePlayerController.playbackState == MPMoviePlaybackStateStopped) return;
//Rotate the view!
CGFloat degree = 0;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1];
switch (orientation) {
case UIDeviceOrientationPortrait:
case UIDeviceOrientationPortraitUpsideDown:
degree = 0;
moviePlayerController.view.bounds = CGRectMake(0, 0, 320, height);
break;
case UIDeviceOrientationLandscapeLeft:
degree = 90;
moviePlayerController.view.bounds = CGRectMake(0, 0, height, 320);
break;
case UIDeviceOrientationLandscapeRight:
degree = -90;
moviePlayerController.view.bounds = CGRectMake(0, 0, height, 320);
break;
default:
break;
}
lastOrientation = orientation;
CGAffineTransform cgCTM = CGAffineTransformMakeRotation((degree) * M_PI / 180);
moviePlayerController.view.transform = cgCTM;
[UIView commitAnimations];
[[UIApplication sharedApplication] setStatusBarOrientation:orientation];
}
So the trick is just using transformation