本文主要介绍 SJVideoPlayer 中旋转模块针对 iOS 16.0 的处理完成进程;
先看效果图:
旋转功能主要是在两个window之间来回切换, 一个是 App 自身的 window; 另一个则是由播放器创立的, 并在横屏显现的 window;
为方便区分, 这儿咱们把 App 的 window 起名为sourceWindow
, 而横屏显现的 window 起名为fullscreenWindow
;
竖屏旋转到横屏
从竖屏旋转到横屏的处理进程:
1. 将播放器视图从父视图中移除, 直接增加到 sourceWindow 上, 这个进程需求进行坐标转化, 保持播放器视图在屏幕中的方位不变;
2. 动画旋转至横屏, 修正播放器视图的 transform, bounds, center等;
动画结束后:
3. 设置 fullscreenWindow 显现, 它需求直接显现成横屏指定的方向;
4. 将播放器视图从 sourceWindow 中移除, 增加到 fullscreenWindow 中;
到此就旋转完了, 示例代码如下:
// 竖屏转横屏示例代码
UIInterfaceOrientation fromOrientation = UIInterfaceOrientationPortrait;
UIInterfaceOrientation toOrientation = UIInterfaceOrientationLandscapeLeft;
CGRect screenBounds = UIScreen.mainScreen.bounds;
CGFloat maxSize = MAX(screenBounds.size.width, screenBounds.size.height);
CGFloat minSize = MIN(screenBounds.size.width, screenBounds.size.height);
// 播放器父视图
UIView *playerSuperview;
// 播放器视图
UIView *playerView;
// App 自身的 window
UIWindow *sourceWindow = playerSuperview.window;
CGRect sourceFrame = [playerSuperview convertRect:playerSuperview.bounds toView:sourceWindow];
// 播放器横屏 window
UIWindow *fullscreenWindow;
// 0. 记载当时方向
_currentOrientation = toOrientation;
// 1. 将播放器视图从父视图中移除, 直接增加到 sourceWindow 上, 这个进程需求进行坐标转化, 保持播放器视图在屏幕中的方位不变;
[playerView removeFromSuperview];
[playerView setFrame:sourceFrame];
[sourceWindow addSubview:playerView];
// 2. 动画旋转至横屏, 修正播放器视图的 transform, bounds, center等;
CGRect rotationBounds = (CGRect){ CGPointZero, (CGSize){maxSize, minSize} };
CGPoint rotationCenter = (CGPoint){ maxSize * 0.5, minSize * 0.5 };
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
switch ( toOrientation ) {
case UIInterfaceOrientationLandscapeLeft:
rotationTransform = CGAffineTransformMakeRotation(M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
rotationTransform = CGAffineTransformMakeRotation(-M_PI_2);
break;
}
[UIView animateWithDuration:0.3 animations:^{
playerView.bounds = rotationBounds;
playerView.center = rootationCenter;
playerView.transform = rotationTransform;
} completion:^(BOOL finished) {
// 3. 设置 fullscreenWindow 显现, 它需求直接显现成横屏指定的方向;
[fullscreenWindow makeKeyAndVisible];
// 直接显现成横屏指定的方向(接下来解说)
[self setNeedsUpdateOfSupportedInterfaceOrientations];
// 4. 将播放器视图从 sourceWindow 中移除, 增加到 fullscreenWindow 中;
[playerView removeFromSuperview];
playerView.transform = CGAffineTransformIdentity;
playerView.bounds = fullscreenWindow.bounds;
playerView.center = (CGPoint){
playerView.bounds.size.width * 0.5,
playerView.bounds.size.height * 0.5
};
[fullscreenWindow.rootViewController.view addSubview:playerView];
}];
_currentOrientation
用于记载当时旋转方向, 方便后续运用;
transform, bounds, center 这些转化就不介绍了, 咱们重点关注一下如何指定 window 显现方向; 在 fullscreenWindow
显现时, 调用了setNeedsUpdateOfSupportedInterfaceOrientations
, 该方法的具体完成如下:
- (void)setNeedsUpdateOfSupportedInterfaceOrientations {
UIWindow *sourceWindow;
UIWindow *fullscreenWindow;
[sourceWindow.rootViewController setNeedsUpdateOfSupportedInterfaceOrientations];
[fullscreenWindow.rootViewController setNeedsUpdateOfSupportedInterfaceOrientations];
}
这个方法是在 iOS 16.0 之后新增的方法, 用于更新界面当时支撑的方向; 在调用更新后, 相应的 AppDelegate.application:supportedInterfaceOrientationsForWindow:
将会被执行;
来看 AppDelegate 中增加的处理:
@implementation AppDelegate
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
UIInterfaceOrientation currentOrientation;
if ( window == fullscreenWindow )
return 1 << currentOrientation;
return UIInterfaceOrientationMaskPortrait;
}
@end
如上, currentOrientation
便是咱们之前指定过的方向, 诀窍便是这儿, 只回来单个指定的方向, window 终究就会以该方向呈现界面;
至此, 竖屏至横屏的处理算是完成了, 咱们再略微做一下优化, 将步骤2和步骤3互换次序, 使得动画之前先显现 fullscreenWindow, 这会让整体的旋转体验流畅许多.
横屏转竖屏
处理进程如下:
1. 将播放器视图从 fullscreenWindow 中移除, 增加到 sourceWindow 中, 修正 transform, bounds, center, 要保持播放器视图在屏幕中的方位不变;
2. 设置 sourceWindow 为 keyWindow, 躲藏 fullscreenWindow;
3. 旋转回竖屏, 动画修正播放器视图的 transform, bounds, center等;
动画结束后:
4. 将播放器视图从 sourceWindow 中移除, 增加回父视图中;
示例代码如下:
// 横屏转竖屏示例代码如下:
UIInterfaceOrientation fromOrientation = UIInterfaceOrientationPortrait;
UIInterfaceOrientation toOrientation = UIInterfaceOrientationLandscapeLeft;
CGRect screenBounds = UIScreen.mainScreen.bounds;
CGFloat maxSize = MAX(screenBounds.size.width, screenBounds.size.height);
CGFloat minSize = MIN(screenBounds.size.width, screenBounds.size.height);
// 播放器父视图
UIView *playerSuperview;
// 播放器视图
UIView *playerView;
// App 自身的 window
UIWindow *sourceWindow = playerSuperview.window;
CGRect sourceFrame = [playerSuperview convertRect:playerSuperview.bounds toView:sourceWindow];
// 播放器横屏 window
UIWindow *fullscreenWindow;
// 0. 记载当时方向
_currentOrientation = toOrientation;
// 1. 将播放器视图从 fullscreenWindow 中移除, 增加到 sourceWindow 中, 修正 transform, bounds, center, 要保持播放器视图在屏幕中的方位不变;
[playerView removeFromSuperview];
[playerView setBounds:(CGRect){ CGPointZero, (CGSize){maxSize, minSize} }];
[playerView setCenter:(CGPoint){ minSize * 0.5, maxSize * 0.5 }];
switch ( fromOrientation ) {
case UIInterfaceOrientationLandscapeLeft:
playerView.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
case UIInterfaceOrientationLandscapeRight:
playerView.transform = CGAffineTransformMakeRotation(-M_PI_2);
break;
}
[sourceWindow addSubview:playerView];
// 2. 设置 sourceWindow 为 keyWindow, 躲藏 fullscreenWindow;
[UIView performWithoutAnimation:^{
[sourceWindow becomeKeyWindow];
[fullscreenWindow setHidden:YES];
[self setNeedsUpdateOfSupportedInterfaceOrientations];
}];
// 3. 旋转回竖屏, 动画修正播放器视图的 transform, bounds, center等;
CGRect rotationBounds = (CGRect){ CGPointZero, sourceFrame.size };
CGPoint rotationCenter = (CGPoint){
sourceFrame.origin.x + rotationBounds.size.width * 0.5,
sourceFrame.origin.y + rotationBounds.size.height * 0.5,
};
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
[UIView animateWithDuration:0.3 animations:^{
playerView.bounds = rotationBounds;
playerView.center = rootationCenter;
playerView.transform = rotationTransform;
} completion:^(BOOL finished) {
// 4. 将播放器视图从 sourceWindow 中移除, 增加回父视图中;
[playerView removeFromSuperview];
[playerSuperview addSubview:playerView];
playerView.transform = CGAffineTransformIdentity;
playerView.bounds = playerSuperview.bounds;
playerView.center = (CGPoint){
playerView.bounds.size.width * 0.5,
playerView.bounds.size.height * 0.5
};
}];
完善
运用中发现横屏转回竖屏的时分, sourceWindow 会先变成横屏再变回竖屏, 可能导致某些页面布局发生偏移;
定位问题发现是 sourceWindow 在 makeKeyWindow 后, 因为当时 App 还处于横屏状况, window 会被系统设置为横屏, 再变回竖屏状况, 这个进程只要再设置 keyWindow 时触发;
问题很明显了, App 需求先康复为竖屏, 再设置 sourceWindow 为 keyWindow;
于是咱们新增了 portraitOrientationFixingWindow, 使其仅支撑竖屏, 先设置他为 keyWindow, 用于修正 App 方向, 最终设置 sourceWindow 为真实的 keyWindow;
END
处理探索的进程很苦逼, 也很享受, 期望苹果每次更新都整点幺蛾子, 好让我继续写安卓;
项目地址: github.com/changsanjia…