【Flutter】スクリーンショットを制限する方法(Android & iOS)
先日お仕事で使いました。著作権に関わる情報やセキュリティに関わる情報の表示に絡み、スクリーンショットおよび画面のキャプチャ(以下、「スクショ」と呼ぶ)を制限したい、そんな方法を今ご紹介します。
なお、本記事では同時にiOSに限ってはパッケージのみで対応できないため、ネイティブのメソッドを呼び出す形で実装しておりますので、そういった点でも参考になるかもしれません。
イメージ
スクショ制限する動作をスクショしているので、見づらいです。すみません😂
スクショ制限と解除のボタンを用意し、交互に押しながら、スクショを撮って比べています。
概要
パッケージ一つで簡単!! とまでは行きませんが、流れは以下の通り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にしか利用できないでのですが、スクショ制限を可能にするパッケージを使います。
下記のメソッドを使用するのみです。パッケージの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、再起動等々、不具合があれば試してみましょう
まとめ
やってみると結構簡単です。機会があれば試してみてください🙋♂️
Discussion