头图

Summary: How to implement custom screen rotation scenes more flexibly and conveniently, this article will show you the secret!

 
Article|Iconsist iOS Application Development Team

Screen rotation is a common scene in live video streaming apps. In the Roomkit SDK released by Instant Technology, there is also a scene where the screen automatically rotates with the phone.

In the process of Roomkit SDK development and customer access, we will also find that the need for screen rotation is often not so smooth, and problems such as inability to rotate and layout adaptation after rotation are often encountered.

This article sorts out the relevant practical methods of screen rotation implementation based on our previous development experience, and analyzes the common problems encountered in the implementation process.

1. Quickly realize rotation

The implementation of iOS screen rotation involves a bunch of enumeration values and callback methods. For developers who have not done rotation related requirements, they may be dizzy when they come up, so let's get started first and let the screen rotate.

There are two main ways to achieve rotation, follow the mobile phone induction rotation and manual rotation, and then introduce these two methods one by one.

Method 1: Follow the mobile phone sensor to rotate

To automatically follow the mobile phone rotation, first let the current view controller implement the following three methods:

/// 是否自动旋转
- (BOOL)shouldAutorotate {
    return YES;
}

/// 当前 VC支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft;
}

/// 优先的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

This method needs to pay attention to the following points:

ShouldAutorotate returns YES to follow the system rotation, but affected by the return value of the supportedInterfaceOrientations method, it only supports following the mobile phone sensor to rotate to a supported direction.

preferredInterfaceOrientationForPresentation needs to return the supported directions in supportedInterfaceOrientations, otherwise the crash of'UIApplicationInvalidInterfaceOrientation' will occur.

Method two: manual rotation

This method is very common in many video software, click the button and then rotate to the horizontal screen.

At this time, you need to return yes in shouldAutorotate, and then in this method, UIInterfaceOrientation is passed in the direction you need to rotate to. Note that this is a private method, please consider whether to use it or not.

- (void)changeVCToOrientation:(UIInterfaceOrientation)orientation {
    if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
        SEL selector = NSSelectorFromString(@"setOrientation:");
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
        [invocation setSelector:selector];
        [invocation setTarget:[UIDevice currentDevice]];
        int val = orientation;
        [invocation setArgument:&val atIndex:2];
        [invocation invoke];
    }
}

Scenario application

  • Automatic rotation

If your iPhone does not turn off the system screen rotation, you can find that the page of the system album APP can be rotated following the direction of the phone's rotation.

If you want to achieve the same effect as it, you only need to configure your view controller according to the previous method (rotating with the phone sensor), and then the controller can realize free rotation in the direction returned by supportedInterfaceOrientations.

  • Can only be rotated manually

This kind of scene is relatively rare. The common scene in live video apps is a combination of automatic and manual rotation.

If you want to implement a method that can only be rotated by clicking a button, you first need to return the direction you need to support in the supportedInterfaceOrientations method. The focus here is the return value of the shouldAutorotate method.

The second method (manual rotation) above shows that manual rotation requires shouldAutorotate to return YES, but this will also allow the controller to support automatic rotation, which does not meet this requirement, so we handle it as follows:

- (BOOL)shouldAutorotate {
    if (self.isRotationNeeded) {
        return YES;
    } else {
        return NO;
    }
} 

The attribute isRotationNeeded is used as the mark of whether to rotate or not. The default isRotationNeeded is NO. At this time, even if you rotate the device, YES will not be returned when the shouldAutorotate method is called, so the screen will not rotate automatically.

The rest only requires you to set isRotationNeeded to YES after clicking the rotation button and call the manual rotation method, so that the effect of manual rotation can only be achieved after processing.

2. UI layout update after rotation

Normally, after the application is rotated to the horizontal and vertical screen, because different aspect ratios will have different UI, we need to solve the problem of UI adaptation after the screen is rotated in the scene of the screen rotation.

When the phone is rotating, if shouldAutorotate returns YES under normal circumstances, the viewWillTransitionToSize method will be triggered when the view controller needs to be rotated, so that we have found the time to update the horizontal and vertical screen UI, which is to complete the rotation in the completion block. With logic.

/*
This method is called when the view controller's view's size is
changed by its parent (i.e. for the root view controller when its window rotates or is resized).

If you override this method, you should either call super to
propagate the change to children or manually forward the 
change to children.
 */
- (void)viewWillTransitionToSize:(CGSize)size
       withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    
    [coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        //横屏:size.width > size.height
        //竖屏: size.width < size.height
        NSLog(@"旋转完成,更新布局");
    
    }];
}

3. Related issues

When developing the requirements for rotating scenes, due to the complex multi-level configuration and the large number of enumerated types, it is inevitable that you will encounter some crashes and unable to rotate. Let's summarize these problems.

Question 1: Cannot rotate automatically

First check whether the system screen rotary switch is locked. After the system screen lock switch is turned on, the app cannot be rotated automatically, but you can call the method mentioned above to manually rotate.

Problem two: wrong setting of multi-level screen rotation control

The following methods can set the global permissions for screen rotation:

  • Device Orientation property configuration: "TARGETS> General> Deployment Info> Device Orientation". The figure shows the default configuration of xcode. It is worth noting that the iPhone does not support rotation to the Upside Down direction.

  • Appdelegate's supportedInterfaceOrientationsForWindow method:
// 返回需要支持的方向
// 如果我们实现了Appdelegate的这一方法,那么我们的App的全局旋转设置将以这里的为准
- (UIInterfaceOrientationMask)application:(UIApplication *)applicatio supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window {
    return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskPortrait;
}

Priority of the above two methods: Appdelegate method> Target configuration, the configuration of these two methods and the supportedInterfaceOrientations method of the controller will affect the direction that the final view controller ultimately supports.

Taking the way of opening the controller with present in iOS 14 as an example, the screen orientation finally supported by the current controller depends on the value of the highest priority mode of the above two methods, and the intersection of the controller supportedInterfaceOrientations.

In summary, there are the following situations:

  • If the intersection is empty and returns YES in the shouldAutorotate method of the controller, UIApplicationInvalidInterfaceOrientation will crash.
  • If the intersection is empty and the controller’s shouldAutorotate method returns NO, the controller’s supportedInterfaceOrientations method and the preferredInterfaceOrientationForPresentation method return values do not conflict (the return value of the former includes the return value of the latter), then it is displayed as the direction configured by the controller.
  • If the intersection is empty and the controller's shouldAutorotate method returns NO, the controller's supportedInterfaceOrientations method conflicts with the preferredInterfaceOrientationForPresentation method return value (the return value of the former does not include the return value of the latter), UIApplicationInvalidInterfaceOrientation will crash.
  • If the intersection is not empty, the controller's supportedInterfaceOrientations method and the preferredInterfaceOrientationForPresentation method return values conflict, and UIApplicationInvalidInterfaceOrientation will crash.
  • If the intersection is not empty, the supportedInterfaceOrientations method of the controller does not conflict with the return value of the preferredInterfaceOrientationForPresentation method, and the current controller determines whether to automatically rotate in the direction of the intersection according to the return value of shouldAutorotate.

It is recommended that if there is no need for global configuration, do not change the Target property configuration or implement the Appdelegate method, just implement the code in the previously mentioned way in the ViewController to achieve the rotation effect.

Problem 3: Turn on the system lock screen switch when the screen is landscaped, the view is forced to return to the portrait screen

Due to the closed source of iOS, of course we have no way of knowing why Apple operates in this way, but we can circumvent this problem by some means. Fortunately, when such a rotation is generated, the system will also trigger the same method call as in normal rotation.

Take iPhone X as an example. When the control page is pulled down to open, we will receive the system notification of UIApplicationWillResignActiveNotification. After the control page is closed, we will receive the UIApplicationDidBecomeActiveNotification notification. Use these two notifications to record the status, and judge whether it is Active in shouldAutorotate The status returns YES/NO.

- (void)setupNotification {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification object:nil];
}

- (BOOL)shouldAutorotate {
    if (!self.isApplicationActive) {
            return NO;
        } else {
            return YES;
        }
    }
}

Question 4: Adaptation of screen rotation and ZegoExpressEngine

Many friends have already connected to our ZegoExpressEngine real-time audio and video engine, so you have to consider the impact of rotation on push and pull streams in the rotating scene. Taking the use of the RoomKit SDK as an example, there are roughly the following situations:

  • The current page is displayed in a fixed direction. You only need to set the video resolution that matches the current direction (the default value of the engine is "360 × 640", which is determined according to your needs), and then call the engine's setAppOrientation interface to set the current direction. The following code is left horizontal Take the screen orientation as an example:
ZegoVideoConfig *videoConfig = [[ZegoVideoConfig alloc] init];
// 左横屏分辨率设置如下:
videoConfig.encodeResolution = CGSizeMake(1280, 720);
[[ZegoExpressEngine sharedEngine] setVideoConfig:videoConfig];
// 调用 setAppOrientation 接口设置视频的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:UIInterfaceOrientationLandscapeLeft];
  • There is a rotating scene on the current page. At this time, you need to update the direction and video resolution of the ZegoExpressEngine engine after the rotation is completed. Note that the current direction here is the direction of the current status bar.
// 根据当前方向设置分辨率
ZegoVideoConfig *videoConfig = [ZegoVideoConfig defaultConfig];
if (isCurPortrait) {
    videoConfig.captureResolution = CGSizeMake(720, 1280);
} else {
    videoConfig.captureResolution = CGSizeMake(1280, 720);
}
// 调用 setAppOrientation 接口设置视频的朝向
[[ZegoExpressEngine sharedEngine] setAppOrientation:[UIApplication sharedApplication].statusBarOrientation];

The adaptation logic of the ZegoExpressEngine audio and video engine above after the screen is rotated, the processing timing is after the view controller is rotated, that is, in the completion block of the viewWillTransitionToSize method, the [UIApplication sharedApplication].statusBarOrientation direction obtained at this time is related to the current controller The direction is consistent.

(For more ZegoExpressEngine audio and video engine screen rotation issues, please refer to: https://doc-zh.zego.im/article/4823?w= )

Four, related enumeration values

In the previous description, we also recognized some enumerated values related to screen rotation. At first glance, this piece of content does feel dazzling, but we can see the keywords in their names such as: Device, Interface, and understand its meaning where each enumeration type is used, which is also reasonable. Clear the logic inside.

1. Device orientation: UIDeviceOrientation

UIDeviceOrientation is based on the position of the home button. It is affected by the sensor and has nothing to do with the direction of the current screen display, so it can only be set but not set.

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);

This enumeration is not used directly in the screen rotation method described above, but it becomes useful if you need to monitor the current orientation of the device. You can get the direction of the current device through [UIDevice currentDevice].orientation. If you want to monitor the direction change of the device, you can use the following code to achieve:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
 [[NSNotificationCenter defaultCenter] addObserver:observer
                                          selector:@selector(onDeviceOrientationChange:)
                                              name:UIDeviceOrientationDidChangeNotification
                                            object:nil];

2. Page orientation: UIInterfaceOrientation

UIInterfaceOrientation is the orientation of the current view controller, which is different from the device orientation. It is the orientation of the screen being displayed. The return value of the preferredInterfaceOrientationForPresentation method is this enumeration type.

/// 优先的屏幕方向
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}

Note that UIInterfaceOrientationLandscapeLeft and UIDeviceOrientationLandscapeRight are corresponding, and the two enumeration types are left and right opposite.
 

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
} API_UNAVAILABLE(tvos);

3. Page orientation: UIInterfaceOrientationMask

Observing the value of UIInterfaceOrientationMask enumeration, we will find that this is a type defined to support multiple UIInterfaceOrientations, which is used as the return value of the supportedInterfaceOrientations method. For example, we return UIInterfaceOrientationMaskAll in this method to support all directions.

/// 当前 VC支持的屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskAll;
}
typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
} API_UNAVAILABLE(tvos);

V. Conclusion

ZEGO RoomKit SDK currently supports screen rotation scenes, and in the form of JSON configuration in version 2.0.0, it supports more flexible and convenient implementation of custom screen rotation scenes.

In live video apps, screen rotation is often an inevitable link. Sort out the meaning of the above three enumerations and the timing of calling the rotation method, and refresh the rotated layout at the right time. iOS rotation is suitable. Matching is no longer difficult.

The above is about the technical interpretation of screen rotation on iOS, and everyone is welcome to use the RoomKit SDK to experience the demo. Click the link to have the experience: https://doc-zh.zego.im/scene-plan/23


ZEGO即构
30 声望15 粉丝

音视频云服务商