🙄

【Flutter】スクリーンショットを制限する方法(Android & iOS)

2023/05/16に公開

先日お仕事で使いました。著作権に関わる情報やセキュリティに関わる情報の表示に絡み、スクリーンショットおよび画面のキャプチャ(以下、「スクショ」と呼ぶ)を制限したい、そんな方法を今ご紹介します。

なお、本記事では同時にiOSに限ってはパッケージのみで対応できないため、ネイティブのメソッドを呼び出す形で実装しておりますので、そういった点でも参考になるかもしれません。

イメージ

スクショ制限する動作をスクショしているので、見づらいです。すみません😂 
スクショ制限と解除のボタンを用意し、交互に押しながら、スクショを撮って比べています。

https://twitter.com/osanaiks/status/1658362210635350017?s=20

概要

パッケージ一つで簡単!! とまでは行きませんが、流れは以下の通り3ステップです。ポイントはiOSで、swiftを使う分なれないかもしれませんが、コピペしてみましょう。

1. iOSの設定用の設定 => iOSフォルダ内のファイルを編集する
2. Android用の設定 => flutter_windowmanagerパッケージを使う
3. メソッドをコーディングする

ステップ1 AppDelegate.swiftを編集(iOS)

ios/Runner/AppDelegate.swiftを次のように編集します。
本当はソースも引用したいのですが、実装からだいぶ時間が経ち、どれをもとにしたか思い出せません🥲

コード

Swiftは書いたことないのですが、"secureiOS"とか"unSecureiOS"と唱えると、動くぐらいに思えば良いかなと思います。実際は真似てミスして修正してと、当初はだいぶ勉強させてもらいました


import UIKit
import Flutter


 class AppDelegate: FlutterAppDelegate {

  ////////////////////////////////////
  /// スクショ制限機能として追加
  private var textField = UITextField()
  ////////////////////////////////////

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    ///////////////////////////////// /
    /// スクショ制限機能として追加
    makeSecureYourScreen()
    let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController
    let securityChannel = FlutterMethodChannel(name: "secureScreenshotChannel", binaryMessenger: controller.binaryMessenger)
    securityChannel.setMethodCallHandler({
        (call: FlutterMethodCall, result:  FlutterResult) -> Void in
        if call.method == "secureiOS" {
            self.textField.isSecureTextEntry = true
        } else if call.method == "unSecureiOS" {
            self.textField.isSecureTextEntry = false
        }
    })
    //////////////////////////////////

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  //////////////////////////////////
  /// スクショ制限機能として追加
  private func makeSecureYourScreen() {
    if(!self.window.subviews.contains(textField)){
      self.window.addSubview(textField)
      textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
      textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
      self.window.layer.superlayer?.addSublayer(textField.layer)
      textField.layer.sublayers?.first?.addSublayer(self.window.layer)
    }
  }
  //////////////////////////////////
}

ステップ2 flutter_windowmanagerを追加(Android)

Androidにしか利用できないでのですが、スクショ制限を可能にするパッケージを使います。

https://pub.dev/packages/flutter_windowmanager

下記のメソッドを使用するのみです。パッケージのexampleを見ると良いでしょう🙋

コード
// スクリーンショット制限
FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);

// スクリーンショット制限解除
FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);

ステップ3 メソッドを加える

今回はスクショ制限、スクショ制限解除の二つのメソッドを用意します。
メソッドの中ではOS判定を行ってから、iOSなら設定したメソッドチャンネル、AndroidならFlutterWindowManagerを呼び出します。

コード
  MethodChannel iosSecureScreenShotChannel =
      const MethodChannel('secureScreenshotChannel');

  Future<void> allowScreenshot() async {
    if (Platform.isAndroid) {
      await FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
    }
    if (Platform.isIOS) {
      await iosSecureScreenShotChannel.invokeMethod('unSecureiOS');
    }
  }

  Future<void> preventScreenshot() async {
    if (Platform.isAndroid) {
      await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
    }
    if (Platform.isIOS) {
      await iosSecureScreenShotChannel.invokeMethod('secureiOS');
    }
  }
  

全体のコード

コード
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_windowmanager/flutter_windowmanager.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  MethodChannel iosSecureScreenShotChannel =
      const MethodChannel('secureScreenshotChannel');

  void allowScreenshot() {
    if (Platform.isAndroid) {
      FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
    }
    if (Platform.isIOS) {
      iosSecureScreenShotChannel.invokeMethod('unSecureiOS');
    }
  }

  void preventScreenshot() {
    if (Platform.isAndroid) {
      FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
    }
    if (Platform.isIOS) {
      iosSecureScreenShotChannel.invokeMethod('secureiOS');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                allowScreenshot();
              },
              child: const Text('allow screenshot'),
            ),
            const SizedBox(height: 25),
            ElevatedButton(
              onPressed: () {
                preventScreenshot();
              },
              child: const Text('prevent screenshot'),
            ),
          ],
        ),
      ),
    );
  }
}

注意

念の為実機で試しましょう。また。iOSに関してはXcodeのキャッシュか分かりませんが、うまくいかない時もありましたので、Flutter CleanやPod install、再起動等々、不具合があれば試してみましょう

まとめ

やってみると結構簡単です。機会があれば試してみてください🙋‍♂️

Flutter大学

Discussion