flutter 控制iOS设备屏幕可旋转支持方向

场景:flutter开发一个app,非module形式,即:app内部大部分页面是横屏,有部分页面是需要视屏显示(不参与喷子:写一个空控件旋转90度不就好了?但是这样的话状态栏之前的状态,如果你不需要状态栏的话那也没关系。我们不扯远,这里只是单纯的做技术的屏幕可旋转实现,来实现flutter控制iOS设备屏幕可旋转的可设定方位的限制)

下面我来讲一下关于iOS屏幕旋转的有效实现的三种方式,都是可以实现的,只是不同场景,由易到复杂递增,可根据不同的需求来选择参考使用,也希望能对你的成长有所帮助。

屏幕旋转控制方案我总结起来有三种(在不同场景下读是可以满足的):

一,开发工具直接限制可转动方向(不推荐),虽然我们开发的过程中经常是这样用的,方便快捷。但是这个前提下不满足)

二、用代码控制单个屏幕旋转与否(你可以统一继承,这里是从原理上是单个页面的处理方式来讲的)

override var shouldAutorotate: Bool

override var supportedInterfaceOrientations: UIInterfaceOrientationMask

这两个方法就可以控制屏幕的旋转和可支持的旋转方向了,具体实现应该网上很多了,就不再赘述了。

三、flutter app开发,通过桥接开控制,仿照现有的SystemChrome通过桥接控制,

那就会有小朋友讲:为什么不用SystemChrome来控制,答案是现在还没有兼容iOS,你可以试试就会发现这个问题。不然也不会有这篇文章的出现了。😄!

SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeRight,
    DeviceOrientation.landscapeRight,
  ]);

也会有人说,不是还有一个orientation。答案是一样的效果。目前都是对安卓可以,但是iOS不支持,看也有给flutter官方提出这个问题,但是还没有得到解决。

那么接下来,我们就仿照官方推荐的SystemChrome来建立一个iOS的桥接来实现:

第一步我们先实现iOS这边的代码和桥接:

创建一个继承与NSObject的一个工具类FlutterIOSDevicePlugin来同一处理桥接事件接收

//
//  FlutterIOSDevicePlugin.h
//  Runner
//
//  Created by 曹世鑫 on 2020/7/9.
//

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>

NS_ASSUME_NONNULL_BEGIN

@interface FlutterIOSDevicePlugin : NSObject

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(nullable FlutterViewController*) controller;

- (instancetype)newInstance:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(nullable FlutterViewController*) controller;


@end

NS_ASSUME_NONNULL_END
//
//  FlutterIOSDevicePlugin.m
//  Runner
//
//  Created by 曹世鑫 on 2020/7/9.
//

#import "FlutterIOSDevicePlugin.h"
#import <UIKit/UIKit.h>

@interface FlutterIOSDevicePlugin () {
    NSObject<FlutterPluginRegistrar> *_registrar;
    FlutterViewController *_controller;
    UIDeviceOrientation iOSOrientation;
    UIDeviceOrientation lastLandOrientation;
}
@end

static NSString *const CHANNEL_NAME = @"flutter_ios_device_rotation";
static NSString *const METHOD_CHANGE_ORIENTATION = @"change_screen_orientation";

@implementation FlutterIOSDevicePlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    FlutterMethodChannel *channel = [FlutterMethodChannel
                                     methodChannelWithName:CHANNEL_NAME
                                     binaryMessenger:[registrar messenger]];
    FlutterIOSDevicePlugin *instance = [[FlutterIOSDevicePlugin alloc] newInstance:registrar flutterViewController:nil];
    [registrar addMethodCallDelegate:instance channel:channel];
}

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller {
    FlutterMethodChannel *channel = [FlutterMethodChannel
                                     methodChannelWithName:CHANNEL_NAME
                                     binaryMessenger:[registrar messenger]];
    FlutterIOSDevicePlugin *instance = [[FlutterIOSDevicePlugin alloc] newInstance:registrar flutterViewController:controller];
    [registrar addMethodCallDelegate:instance channel:channel];
}

- (instancetype)newInstance:(NSObject<FlutterPluginRegistrar>*)registrar flutterViewController:(FlutterViewController*) controller{
    _registrar = registrar;
    _controller = controller;
    iOSOrientation = [UIDevice currentDevice].orientation;
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotateDevice:) name:UIDeviceOrientationDidChangeNotification object:nil];
    return self;
}

- (void)rotateDevice:(NSObject *)sender {
    UIDevice *device = [sender valueForKey:@"object"];
    iOSOrientation = device.orientation;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([METHOD_CHANGE_ORIENTATION isEqualToString:call.method]) {
        NSNumber *index = [NSNumber numberWithInt: [call.arguments[0] intValue]];
        if ([index isEqualToNumber:@0]) {
            if (iOSOrientation == UIDeviceOrientationLandscapeLeft || iOSOrientation == UIDeviceOrientationLandscapeRight){
                lastLandOrientation = iOSOrientation;
            }
            iOSOrientation = UIDeviceOrientationPortrait;
        } else if([index isEqualToNumber:@1]){
            iOSOrientation = UIDeviceOrientationLandscapeLeft;
        } else if([index isEqualToNumber:@2]){
            iOSOrientation = UIDeviceOrientationLandscapeRight;
        } else if([index isEqualToNumber:@3]){
            if (iOSOrientation != UIDeviceOrientationPortrait && iOSOrientation != UIDeviceOrientationPortraitUpsideDown){
                iOSOrientation = UIDeviceOrientationPortrait;
            }
        } else if([index isEqualToNumber:@4]){
            if (iOSOrientation != UIDeviceOrientationLandscapeLeft && iOSOrientation != UIDeviceOrientationLandscapeRight){
                if (lastLandOrientation) {
                    iOSOrientation = lastLandOrientation;
                } else {
                    iOSOrientation = UIDeviceOrientationLandscapeLeft;
                }
            }
            lastLandOrientation = iOSOrientation;
        } else if([index isEqualToNumber:@6]){
            if (iOSOrientation == UIDeviceOrientationPortraitUpsideDown) {
                iOSOrientation = UIDeviceOrientationPortrait;
            }
        }
        [[UIDevice currentDevice] setValue:@(iOSOrientation) forKey:@"orientation"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"FlutterIOSDevicePlugin" object:nil userInfo:@{@"orientationMask": index}];
        [UIViewController attemptRotationToDeviceOrientation];
        result(nil);
    } else {
        result(FlutterMethodNotImplemented);
    }
}


@end

这里我们在handleMethodCall里面设置了设备的方向,并通过[UIViewController attemptRotationToDeviceOrientation];来刷新设备的展示,事件里面看到我们发出了一个通知:[[NSNotificationCenter defaultCenter] postNotificationName:@"FlutterIOSDevicePlugin" object:nil userInfo:@{@"orientationMask": index}];

是通过通知将收到的参数传递到AppDelegate里面,进而控制设备旋转刷新的时候的可支持方向(下面我会贴上AppDelegate.swift的代码)

这里介绍设置设备方向代码,下面我列出了两种都可以,一个是kvc形式,一个是runtime形式(也可以说是kvc的内部实现方式)

 //1.设置屏幕的转向为竖屏
    [[UIDevice currentDevice] setValue:@(UIDeviceOrientationPortrait) forKey:@"orientation"];


//2.设置屏幕的转向为竖屏
- (void)interfaceOrientation:(UIInterfaceOrientation)orientation{
  if([[UIDevicecurrentDevice] respondsToSelector:@selector(setOrientation:)]) {        
      SEL selector  =NSSelectorFromString(@"setOrientation:");
      NSInvocation*invocation = [NSInvocationinvocationWithMethodSignature:[UIDeviceinstanceMethodSignatureForSelector:selector]];     
      [invocation setSelector:selector];        
      [invocation setTarget:[UIDevicecurrentDevice]];
      intval = orientation;
      // 从2开始是因为0 1 两个参数已经被selector和target占用
      [invocation setArgument:&val atIndex:2];                                                     
      [invocation invoke];   
  }}

下面我们来看AppDelegate.swift里面的代码(这里有一部分是flutter app已经加入的,比如didFinishLaunchingWithOptions里面的GeneratedPluginRegistrant注册)

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    
    var orientationMask: UIInterfaceOrientationMask = .all;
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    NotificationCenter.default.addObserver(self, selector: #selector(changeLandscape(center:)), name:NSNotification.Name(rawValue: "FlutterIOSDevicePlugin"), object: nil)
    GeneratedPluginRegistrant.register(with: self)
    FlutterIOSDevicePlugin.register(with: self.registrar(forPlugin: "FlutterIOSDevicePlugin"), flutterViewController: nil)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
    
    override func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return orientationMask;
    }
    
    @objc func changeLandscape(center: Notification){
        let index: NSNumber = (center.userInfo?["orientationMask"] ?? 5) as! NSNumber
        orientationMask = OrientationUtil.getOrientationMaskWithIndex(number: index)
        _ = application(UIApplication.shared, supportedInterfaceOrientationsFor: UIApplication.shared.keyWindow)
    }
    
}

这里我们在设备支持方向的代码中添加了自定义的方向设定,在接收到通知的方法中处理好支持的方向类型,然后我们可以手动调用一下supportedInterfaceOrientationsFor来实现一下触发。

 

另外这里用到swift调用oc的实现。所以我们需要在已存在的桥接文件Runner-Bridging-Header.h里面加入我们的oc头文件(如果你想了解怎么创建桥接文件可以自行百度,这里不再做赘述),结果如下:

#import "GeneratedPluginRegistrant.h"
#import "FlutterIOSDevicePlugin.h"

可以看到AppDelgate.swift里面有一个changeLandscape来处理通知接收到的信息的OrientationUtil用来处理(这里的对应关系要和flutter端的定义一一对应,这里两端都是我写的,哈哈,就我自己和自己协商了,就都按照iOS官方的命名来近似定义了):

//
//  OrientationUtil.swift
//  Runner
//
//  Created by 曹世鑫 on 2020/7/10.
//

import UIKit

class OrientationUtil: NSObject {
    
    //转化工具
   static func getOrientationMaskWithIndex(number: NSNumber) -> UIInterfaceOrientationMask {
        var mask : UIInterfaceOrientationMask = .all
        switch number {
        case 0:
            mask = .portrait
            break
        case 1:
            mask = .landscapeLeft
            break
        case 2:
            mask = .landscapeRight
            break
        case 3:
            mask = .portraitUpsideDown
            break
        case 4:
            mask = .landscape
            break
        case 5:
            mask = .all
            break
        case 6:
            mask = .allButUpsideDown
            break
        
        default:
            mask = .all
            break
        }
        return mask
    }
}

接下来。我们就转向flutter代码:

首先我们创建一个屏幕旋转的桥接类,由于安卓得不需要,这里我就单设置iOS的了:ScreenRotationiOS

class ScreenRotationiOS {
  static const MethodChannel _channel = MethodChannel('flutter_ios_device_rotation');

  static Future changeScreenOrientation(DeviceOrientationMask orientationMask) {
    return _channel.invokeMethod('change_screen_orientation', [orientationMask.index]);
  }
}

enum DeviceOrientationMask {
  //竖屏
  Portrait,
  //左旋转
  LandscapeLeft,
  //右旋转
  LandscapeRight,
  //竖直方向向上向下可旋转
  PortraitUpsideDown,
  //横屏
  Landscape,
  //全部四个方位
  All,
  //左右旋转
  AllButUpsideDown,
}

可以看到这里的注释枚举是和iOS方面的switch的接收是一一对应的。

 

接下来就是使用:

在flutter代码中需要设置的地方:

if (Platform.isIOS) {
      await ScreenRotationiOS.changeScreenOrientation(
                DeviceOrientationMask.Landscape);
    } 

内容枚举自定义选择。

这里我们也可以不用加上判断是iOS平台才执行,但是本着之后代码的清晰,防止新来小伙伴再以为安卓里面也有定义的桥接造成不必要的误会,建议还是使用的地方加上iOS平台的判断。

然后就写一个demo来看一下效果吧:

至此,全部结束!

 

偶然遇到的小问题:

1.运行在iphone上面有效果,在ipad上面触发没反应。还是会上下左右任性的旋转。

解决办法:在iOS项目中打开info.plist打开全屏显示:

 

喜欢的小朋友们可以关注我一下吧。我会及时更新有意义的技术文章和攻坚点的。

 

已发布到pub.dev,项目中使用到的同学,可以直接yaml里面添加依赖:limiting_direction_csx  喜欢的可以点一个like,鼓励一下。谢谢了。

 

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值