Flutter项目结合iOS OC原生页面禁止截屏

Flutter项目结合iOS OC原生代码

需求:

设备截图,隐藏原有页面,截取到的内容需要展示'敏感数据,禁止截屏';

如果返回桌面进入网格桌面展示'敏感数据,禁止截屏'需要在

ScreenShieldView.m文件中 的 appWillLeave self.protectedView.hidden = NO;注释掉

iOS原生代码

ScreenShieldView.h

objectivec 复制代码
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

API_AVAILABLE(ios(13.0))
@interface ScreenShieldView : UIView

+ (instancetype)createWithFrame:(CGRect)frame;
+ (instancetype)createWithFrame:(CGRect)frame protectedView:(UIView * _Nullable)protectedView;

@end

NS_ASSUME_NONNULL_END

ScreenShieldView.m

objectivec 复制代码
#import "ScreenShieldView.h"

@interface ScreenShieldView ()
@property (nonatomic, strong, nullable) UIView *protectedView;
@property (nonatomic, strong, nullable) UIView *portiereView;
@property (nonatomic, strong, nullable) UIView *safeZone;
@end

@implementation ScreenShieldView

+ (instancetype)createWithFrame:(CGRect)frame {
    return [[self alloc] initWithFrame:frame protectedView:nil];
}

+ (instancetype)createWithFrame:(CGRect)frame protectedView:(UIView * _Nullable)protectedView {
    return [[self alloc] initWithFrame:frame protectedView:protectedView];
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    [super setBackgroundColor:backgroundColor];
    self.portiereView.backgroundColor = backgroundColor;
}

- (instancetype)initWithFrame:(CGRect)frame protectedView:(UIView * _Nullable)protectedView {
    self = [super initWithFrame:frame];
    if (self) {
        _safeZone = [self makeSecureView] ?: [[UIView alloc] init];
        _protectedView = protectedView;
        
        if (_safeZone) {
            if (self.protectedView) {
                [self.protectedView removeFromSuperview];
                [super addSubview:self.protectedView];
                
                self.protectedView.translatesAutoresizingMaskIntoConstraints = NO;
                [NSLayoutConstraint activateConstraints:@[
                    [self.protectedView.topAnchor constraintEqualToAnchor:self.topAnchor],
                    [self.protectedView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
                    [self.protectedView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
                    [self.protectedView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
                ]];
            }
            
            [self addSubview:_safeZone];
            
            UILayoutPriority layoutDefaultLowPriority = UILayoutPriorityDefaultLow - 1;
            UILayoutPriority layoutDefaultHighPriority = UILayoutPriorityDefaultHigh - 1;
            
            _safeZone.translatesAutoresizingMaskIntoConstraints = NO;
            [_safeZone setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisVertical];
            [_safeZone setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisHorizontal];
            [_safeZone setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisVertical];
            [_safeZone setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisHorizontal];
            
            [NSLayoutConstraint activateConstraints:@[
                [_safeZone.topAnchor constraintEqualToAnchor:self.topAnchor],
                [_safeZone.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
                [_safeZone.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
                [_safeZone.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
            ]];
            
            if (self.protectedView) {
                self.portiereView = [[UIView alloc] init];
                self.portiereView.backgroundColor = UIColor.whiteColor;
                [self addSubview:self.portiereView];
                
                self.portiereView.translatesAutoresizingMaskIntoConstraints = NO;
                [NSLayoutConstraint activateConstraints:@[
                    [self.portiereView.topAnchor constraintEqualToAnchor:self.topAnchor],
                    [self.portiereView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
                    [self.portiereView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
                    [self.portiereView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]
                ]];
            }
            
            [NSNotificationCenter.defaultCenter addObserver:self
                                                   selector:@selector(appWillLeave:)
                                                       name:UIApplicationWillResignActiveNotification
                                                     object:nil];
            [NSNotificationCenter.defaultCenter addObserver:self
                                                   selector:@selector(appDidBack:)
                                                       name:UIApplicationDidBecomeActiveNotification
                                                     object:nil];
        }
    }
    return self;
}

- (void)appWillLeave:(NSNotification *)notify {
//    self.protectedView.hidden = NO;
//    NSLog(@"appWillLeave");
}

- (void)appDidBack:(NSNotification *)notify {
    self.protectedView.hidden = NO;
}

- (void)addSubview:(UIView *)view {
    if (self.safeZone && view != self.safeZone) {
        [self.safeZone addSubview:view];
    } else {
        [super addSubview:view];
    }
}

- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview {
    if (self.safeZone && view != self.safeZone) {
        [self.safeZone insertSubview:view belowSubview:siblingSubview];
    } else {
        [super insertSubview:view belowSubview:siblingSubview];
    }
}

- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview {
    if (self.safeZone && view != self.safeZone) {
        [self.safeZone insertSubview:view aboveSubview:siblingSubview];
    } else {
        [super insertSubview:view aboveSubview:siblingSubview];
    }
}

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index {
    if (self.safeZone && view != self.safeZone) {
        if (self.protectedView && index == 0) {
            [self.safeZone insertSubview:view atIndex:1];
        } else {
            [self.safeZone insertSubview:view atIndex:index];
        }
    } else {
        [super insertSubview:view atIndex:index];
    }
}

- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2 {
    if (self.safeZone) {
        [self.safeZone exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
    } else {
        [super exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
    }
}

- (void)bringSubviewToFront:(UIView *)view {
    if (self.safeZone && view != self.safeZone) {
        [self.safeZone bringSubviewToFront:view];
    } else {
        [super bringSubviewToFront:view];
    }
}

- (void)sendSubviewToBack:(UIView *)view {
    if (self.safeZone && view != self.safeZone) {
        [self.safeZone sendSubviewToBack:view];
    } else {
        [super sendSubviewToBack:view];
    }
}

- (UIView *)makeSecureView {
    if (![self isOSVersionSafe]) {
        return nil;
    }
    
    UITextField *field = [[UITextField alloc] init];
    field.secureTextEntry = YES;
    UIView *fv = field.subviews.firstObject;
    
    for (UIView *subview in fv.subviews) {
        [subview removeFromSuperview];
    }
    fv.userInteractionEnabled = YES;
    
    NSString *errorMsg = @"[ScreenShieldView log] Create safeZone failed!";
#if DEBUG
    NSAssert(fv != nil, errorMsg);
#else
    if (!fv) {
        NSLog(@"%@", errorMsg);
    }
#endif
    
    return fv;
}

- (NSArray<NSString *> *)unsafeOSVersion {
    return @[@"15.1"];
}

- (NSString *)osVersion {
    return UIDevice.currentDevice.systemVersion;
}

- (BOOL)isOSVersionSafe {
    for (NSString *version in [self unsafeOSVersion]) {
        if ([self.osVersion containsString:version]) {
            return NO;
        }
    }
    return YES;
}

- (void)dealloc {
    [NSNotificationCenter.defaultCenter removeObserver:self];
}

@end

ScreenShieldPlaceholder.h

objectivec 复制代码
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface ScreenShieldPlaceholder : UIView

@end

NS_ASSUME_NONNULL_END
objectivec 复制代码
#import "ScreenShieldPlaceholder.h"
#define SCREEN_HEIGHT   [[UIScreen mainScreen] bounds].size.height
#define SCREEN_WIDTH    [[UIScreen mainScreen] bounds].size.width
@implementation ScreenShieldPlaceholder

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        [self createUI];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self createUI];
    }
    return self;
}
-(void)createUI{
    UIView * navView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, [self getNavHeight]+[self getStatusHeight])];
    navView.backgroundColor = [UIColor whiteColor];
    [self addSubview:navView];
    
    UIView * stV = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, [self getStatusHeight])];
    stV.backgroundColor = [UIColor clearColor];
    [navView addSubview:stV];
    
    UILabel * titleLab = [[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(stV.frame), SCREEN_WIDTH, [self getNavHeight])];
    titleLab.backgroundColor = [UIColor clearColor];
    titleLab.text = @"APP名字";
    titleLab.textColor = [UIColor colorWithRed:51/255.f green:51/255.f blue:51/255.f alpha:1];
    titleLab.font = [UIFont boldSystemFontOfSize:17];
    titleLab.textAlignment = NSTextAlignmentCenter;
    [navView addSubview:titleLab];

    
    UIView * botView = [[UIView alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(navView.frame), SCREEN_WIDTH, SCREEN_HEIGHT - ([self getNavHeight]+[self getStatusHeight]))];
    botView.backgroundColor = [UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];
    [self addSubview:botView];
    
    UIImageView *imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 60, 60)];
    imgView.center = CGPointMake(botView.center.x, CGRectGetHeight(botView.frame)/2-100);
    imgView.image = [UIImage imageNamed:@"min_gan_ios"];
    [botView addSubview:imgView];
    
    UILabel * textLab = [[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(imgView.frame), CGRectGetWidth(botView.frame), 50)];
    textLab.text = @"敏感数据,禁止截屏";
    textLab.textColor = [UIColor blackColor];
    textLab.font = [UIFont systemFontOfSize:20.f];
    textLab.textAlignment = NSTextAlignmentCenter;
    [botView addSubview:textLab];
}

-(CGFloat)getNavHeight{
    return  44.0;
}
-(CGFloat)getStatusHeight{
    CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;

    return  statusBarHeight;
}
@end

ScreenshotChannel.h

objectivec 复制代码
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface ScreenshotChannel : NSObject
+(void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar;
@end

NS_ASSUME_NONNULL_END

ScreenshotChannel.m

objectivec 复制代码
#import "ScreenshotChannel.h"
#import "ScreenShieldView.h"
#import "ScreenShieldPlaceholder.h"
@interface ScreenshotChannel ()<FlutterPlugin,FlutterStreamHandler>
@property(nonatomic,strong)FlutterResult result;
@property (nonatomic, strong) FlutterEventSink eventSink;
@property (nonatomic, strong) UIView *containerView;
@property(nonatomic,strong)ScreenShieldView *currentShieldView;
// 记录 Flutter 核心视图的原父视图和原索引(用于还原)
@property (nonatomic, weak) UIView *flutterOriginalSuperview; // 弱引用,避免内存泄漏
@property (nonatomic, assign) NSInteger flutterOriginalIndex; // 原父视图中的层级索引
@end
@implementation ScreenshotChannel
+ (instancetype)sharedInstance {
    static ScreenshotChannel *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
+ (void)registerWithRegistrar:(nonnull NSObject<FlutterPluginRegistrar> *)registrar {
    FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"ios_screenshot" binaryMessenger:[registrar messenger]];
    ScreenshotChannel * instance = [ScreenshotChannel sharedInstance];
    [registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{
    if ([call.method isEqualToString:@"enableSecure"]) {
        [self addSecureOverlay];
        result(nil);
    }
    if ([call.method isEqualToString:@"disableSecure"]) {
        // 关闭禁止截屏
        [self removeSecureOverlay];
        result(nil);
    }
}
// 添加安全覆盖层(核心:阻止截屏)
- (void)addSecureOverlay {
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    if (!keyWindow || !keyWindow.rootViewController) {
        return;
    }
    
    UIViewController *rootVC = keyWindow.rootViewController;
    // 找到 Flutter 核心视图(真正承载 UI 的视图,必须转移它本身)
    UIView *flutterCoreView = [self findFlutterCoreViewInView:rootVC.view];
    if (!flutterCoreView) {
        return;
    }
    self.flutterOriginalSuperview = flutterCoreView.superview; // 原父视图
    self.flutterOriginalIndex = [self.flutterOriginalSuperview.subviews indexOfObject:flutterCoreView]; // 原索引位置
        
    self.currentShieldView = [ScreenShieldView createWithFrame:flutterCoreView.frame protectedView:[[ScreenShieldPlaceholder alloc]init]]; // 占满屏幕
    self.currentShieldView.backgroundColor = [UIColor whiteColor];
    self.containerView = [[UIView alloc] initWithFrame:self.currentShieldView.frame];
    self.containerView.backgroundColor = [UIColor whiteColor];
    
    [flutterCoreView removeFromSuperview];
    [self.containerView addSubview:flutterCoreView];
    
    //修正约束:让 FlutterCoreView 占满 containerView(适配屏幕旋转/尺寸变化)
    flutterCoreView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [flutterCoreView.leadingAnchor constraintEqualToAnchor:self.containerView.leadingAnchor],
        [flutterCoreView.trailingAnchor constraintEqualToAnchor:self.containerView.trailingAnchor],
        [flutterCoreView.topAnchor constraintEqualToAnchor:self.containerView.topAnchor],
        [flutterCoreView.bottomAnchor constraintEqualToAnchor:self.containerView.bottomAnchor]
    ]];

    self.currentShieldView.backgroundColor = [UIColor whiteColor];
    self.currentShieldView.autoresizingMask = flutterCoreView.autoresizingMask;
    self.currentShieldView.autoresizesSubviews = flutterCoreView.autoresizesSubviews;
    [self.currentShieldView addSubview:self.containerView];
    [keyWindow addSubview:self.currentShieldView];
    NSLog(@"添加安全覆盖层完成");
}

- (UIView *)findFlutterCoreViewInView:(UIView *)view {
    if ([view isKindOfClass:NSClassFromString(@"FlutterView")] ||
        [view isKindOfClass:NSClassFromString(@"FlutterSurfaceView")]) {
        return view;
    }
    for (UIView *subview in view.subviews) {
        UIView *flutterView = [self findFlutterCoreViewInView:subview];
        if (flutterView) return flutterView;
    }
    return nil;
}
// 移除安全覆盖层
- (void)removeSecureOverlay {
    if (!self.currentShieldView || !self.containerView || !self.flutterOriginalSuperview) {
        NSLog(@"无需要移除的遮罩视图或原父视图已释放");
        return;
    }
    
    UIView *flutterCoreView = nil;
    for (UIView *subview in self.containerView.subviews) {
        if ([subview isKindOfClass:NSClassFromString(@"FlutterView")] ||
            [subview isKindOfClass:NSClassFromString(@"FlutterSurfaceView")]) {
            flutterCoreView = subview;
            break;
        }
    }
    
    if (flutterCoreView) {
        //还原到原父视图的原索引位置
        [flutterCoreView removeConstraints:flutterCoreView.constraints]; // 移除添加时的约束
        flutterCoreView.translatesAutoresizingMaskIntoConstraints = YES; // 恢复默认适配
        
        [flutterCoreView removeFromSuperview];
        
        // 还原到原父视图:如果原父视图还存在,添加到原索引位置;否则添加到 rootVC.view
        if (self.flutterOriginalSuperview.superview) { // 原父视图未被释放
            [self.flutterOriginalSuperview insertSubview:flutterCoreView atIndex:self.flutterOriginalIndex];
        } else { // 原父视图已释放(极端情况),降级添加到 rootVC.view
            UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
            if (rootVC && rootVC.view) {
                [rootVC.view addSubview:flutterCoreView];
                flutterCoreView.frame = rootVC.view.bounds;
                flutterCoreView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
            }
        }
        
        // 还原原有的 autoresizingMask(确保和添加前一致)
        flutterCoreView.autoresizingMask = self.currentShieldView.autoresizingMask;
    }
    
    [self.containerView removeFromSuperview];
    [self.currentShieldView removeFromSuperview];
    
    self.containerView = nil;
    self.currentShieldView = nil;
    self.flutterOriginalSuperview = nil; // 弱引用无需手动释放,但置空避免重复使用
    self.flutterOriginalIndex = 0;
    
    NSLog(@"遮罩已成功移除,Flutter 视图还原完成");
}

// 获取当前活跃窗口
- (UIWindow *)getCurrentKeyWindow {
    if (@available(iOS 13.0, *)) {
        for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
            if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
                UIWindowScene *windowScene = (UIWindowScene *)scene;
                for (UIWindow *win in windowScene.windows) {
                    if (win.isKeyWindow) {
                        return win;
                    }
                }
            }
        }
    } else {
        return [UIApplication sharedApplication].keyWindow;
    }
    return nil;
}

- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink {
    self.eventSink = eventSink;
    return nil;
}

- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    self.eventSink = nil;
    return nil;
}

@end

AppDelegate.m

objectivec 复制代码
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import "ScreenshotChannel.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
    [ScreenshotChannel registerWithRegistrar:[self registrarForPlugin:@"ios_screenshot"]];
    
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

Flutterchannel

Dart 复制代码
class ScreenshotChannel{
  ///iOS 截屏
  static const MethodChannel screenshotIos = MethodChannel('ios_screenshot');
  // iOS开启禁止截屏
  static Future<void> enableSecureModeIOS() async {
    await screenshotIos.invokeMethod('enableSecure');
  }

  // iOS关闭禁止截屏
  static Future<void> disableSecureModeIOS() async {
    await screenshotIos.invokeMethod('disableSecure');
  }
}

Flutter页面调用

Dart 复制代码
@override
  void initState() {
    // TODO: implement initState
    super.initState();
    Future.delayed(const Duration(milliseconds: 1000),(){
      ScreenshotChannel.enableSecureModeIOS();
    });
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    ScreenshotChannel.disableSecureModeIOS();
  }
相关推荐
2501_916007471 小时前
深入理解 iOS 文件管理体系,从沙盒结构到多工具协同的工程化文件管理实践
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张1 小时前
iOS 性能检测工具深度解析 多工具协同下的全维度性能检测体系建设
android·ios·小程序·https·uni-app·iphone·webview
私人珍藏库1 小时前
[吾爱大神原创工具] 【2025-12-03更新】【免越狱】iOS任意版本号APP下载v8.1
macos·ios·cocoa
0xAaron1 小时前
使用 atos 符号化具体崩溃行
ios·调试·崩溃·符号化·atos
00后程序员张1 小时前
Fiddler调试工具全面解析 HTTPHTTPS抓包、代理设置与接口测试实战教程
前端·测试工具·ios·小程序·fiddler·uni-app·webview
2501_915918411 小时前
uniapp iOS 打包和上架流程,一次跨端项目的工程化交付记录
android·ios·小程序·https·uni-app·iphone·webview
sunly_1 小时前
Flutter:布局:NestedScrollView+SliverAppBar+SmartRefresher(分页),实现顶部背景图+导航栏渐变+分页列表
flutter
西西学代码1 小时前
flutter---进度条
前端·javascript·flutter
阿桂有点桂2 小时前
Flutter使用VS Code打包app
vscode·flutter·安卓