flutter 自定义插件 手写识别字母 MyScript S
2023-10-06 本文已影响0人
卢融霜

dart端关键代码:
part of myscript;
class MyScriptWidget extends StatefulWidget {
final int width;
final int height;
final String bgColor;
final String penColor;
const MyScriptWidget(
{required this.width,
required this.height,
super.key,
this.bgColor = "#000000",
this.penColor = "#FFFFFF"});
@override
State<MyScriptWidget> createState() => _MyScriptWidgetState();
}
const String viewType = "com.example.myscriptwidget.view";
class _MyScriptWidgetState extends State<MyScriptWidget> {
@override
Widget build(BuildContext context) {
return getScriptWidget();
}
Widget getScriptWidget() {
Widget myScriptWidget = const Placeholder();
ViewParams viewParams = ViewParams(
width: widget.width,
height: widget.height,
bgColor: widget.bgColor,
penColor: widget.penColor);
if (Platform.isAndroid) {
myScriptWidget = AndroidView(
viewType: viewType,
creationParams: viewParams.toJson(),
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
creationParamsCodec: const StandardMessageCodec(),
);
} else if (Platform.isIOS) {
myScriptWidget = UiKitView(
viewType: viewType,
creationParams: viewParams.toJson(),
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
creationParamsCodec: const StandardMessageCodec());
}
return Container(
width: widget.width.toDouble(),
height: widget.height.toDouble(),
child: myScriptWidget);
}
}
class ViewParams {
int width;
int height;
String bgColor;
String penColor;
ViewParams(
{required this.width,
required this.height,
required this.bgColor,
required this.penColor});
Map<String, dynamic> toJson() => <String, dynamic>{
"width": width,
"height": height,
"bgColor": bgColor,
"penColor": penColor
};
}
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'my_script_widget_platform_interface.dart';
/// An implementation of [MyscriptwidgetPlatform] that uses method channels.
class MethodChannelMyScriptWidget extends MyScriptWidgetPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel(
'com.example.myScriptWidget_example.myScriptWidgetMethodChannel');
@override
Future<String?> getPlatformVersion() async {
final version =
await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
@override
Future<String?> clear() {
return methodChannel.invokeMethod("clear");
}
@override
Future<String?> initMyScriptWidget(
List<int> myCertificate, String configPath) async {
final res = await methodChannel.invokeMethod(
"initMyScriptWidget", <String, dynamic>{
'myCertificate': myCertificate,
"configPath": configPath
});
return res;
}
@override
Future<String?> getChannel() async {
final res = await methodChannel.invokeMethod("getChannel");
return res;
}
}
part of myscript;
class MyScriptWidgetPlugin {
static const EventChannel eventChannel = EventChannel(
"com.example.myScriptWidget_example.myScriptWidgetEventChannel");
static Future<String?> getPlatformVersion() {
return MyScriptWidgetPlatform.instance.getPlatformVersion();
}
static Stream<dynamic> getBroadcastStream() {
return eventChannel
.receiveBroadcastStream()
.map((event) => event.toString())
.asBroadcastStream();
}
static void clear() {
MyScriptWidgetPlatform.instance.clear();
}
static Future<String?> initMyScriptWidget(
{required List<int> myCertificate, required String configPath}) {
return MyScriptWidgetPlatform.instance
.initMyScriptWidget(myCertificate, configPath);
}
static Future<String?> getChannel() {
return MyScriptWidgetPlatform.instance.getChannel();
}
}
Android端关键代码:
package com.example.myscriptwidget;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.example.myscriptwidget.engine.MyScriptEngine;
import com.example.myscriptwidget.view.MyScriptViewFactory;
import com.example.myscriptwidget.view.TextUpdate;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
/**
* MyscriptwidgetPlugin
*/
public class MyscriptwidgetPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, TextUpdate {
private MethodChannel channel;
private EventChannel eventChannel;
private EventChannel.EventSink eventSink;
private FlutterPluginBinding flutterPluginBinding;
private MyScriptViewFactory scriptViewFactory;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "com.example.myScriptWidget_example.myScriptWidgetMethodChannel");
channel.setMethodCallHandler(this);
eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "com.example.myScriptWidget_example.myScriptWidgetEventChannel");
eventChannel.setStreamHandler(this);
scriptViewFactory = new MyScriptViewFactory(flutterPluginBinding.getBinaryMessenger(), this);
//注册组件
flutterPluginBinding
.getPlatformViewRegistry()
.registerViewFactory(
MyScriptViewFactory.viewTypeId,
scriptViewFactory);
this.flutterPluginBinding = flutterPluginBinding;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + Build.VERSION.RELEASE);
} else if (call.method.equals("clear")) {
scriptViewFactory.clear();
result.success("success");
} else if (call.method.equals("initMyScriptWidget")) {
try {
MyScriptEngine.configPath = call.argument("configPath");
List<Integer> myArray = call.argument("myCertificate");
List<Byte> bytes = new ArrayList<>();
for (int i = 0; i < myArray.size(); i++) {
byte b = Byte.valueOf(myArray.get(i).toString());
bytes.add(b);
}
MyScriptEngine.myCertificate = listTobyte(bytes);
MyScriptEngine.initEngine();
result.success("success");
} catch (Exception e) {
Log.e("MyscriptwidgetPlugin", e.toString());
result.error("-1", e.getMessage(), e.fillInStackTrace());
}
} else if (call.method.equals("getChannel")) {
try {
ApplicationInfo appInfo = flutterPluginBinding.getApplicationContext().getPackageManager().getApplicationInfo(
flutterPluginBinding.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
String channel = appInfo.metaData.getString("CHANNEL_NAME");
if (channel == null) {
channel = "default";
}
result.success(channel);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
result.error(String.valueOf(-1), e.getMessage(), e.fillInStackTrace());
}
} else {
result.notImplemented();
}
}
private byte[] listTobyte(List<Byte> list) {
if (list == null || list.size() < 0)
return null;
byte[] bytes = new byte[list.size()];
int i = 0;
Iterator<Byte> iterator = list.iterator();
while (iterator.hasNext()) {
bytes[i] = iterator.next();
i++;
}
return bytes;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
eventSink = events;
}
@Override
public void onCancel(Object arguments) {
eventSink = null;
}
@Override
public void onEvent(String text) {
if (eventSink != null) {
new Handler(Looper.getMainLooper()).post(() -> {
eventSink.success(text);
});
}
}
}
package com.example.myscriptwidget.engine;
import static io.flutter.util.PathUtils.getCacheDirectory;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.example.myscriptwidget.R;
import com.example.myscriptwidget.bean.EditorViewBean;
import com.myscript.iink.Configuration;
import com.myscript.iink.ContentPackage;
import com.myscript.iink.ContentPart;
import com.myscript.iink.Editor;
import com.myscript.iink.Engine;
import com.myscript.iink.ParameterSet;
import com.myscript.iink.PointerTool;
import com.myscript.iink.Renderer;
import com.myscript.iink.uireferenceimplementation.EditorBinding;
import com.myscript.iink.uireferenceimplementation.EditorData;
import com.myscript.iink.uireferenceimplementation.EditorView;
import com.myscript.iink.uireferenceimplementation.FontUtils;
import com.myscript.iink.uireferenceimplementation.InputController;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MyScriptEngine {
private static final String TAG = "MyScriptEngine";
private static Engine engine;
private static final Map<Integer, EditorViewBean> viewMap = new HashMap<>();
public static byte[] myCertificate = {};
public static String configPath = "";
public static EditorViewBean getEditorView(Context applicationContext, int viewId, Map<String, Object> args) {
initEditorView(applicationContext, viewId, args);
return viewMap.get(viewId);
}
public static Engine getEngine() {
return engine;
}
/**
* 初始化Engine
*/
public static void initEngine() {
if (engine == null || engine.isClosed()) {
initCreateEngine();
}
}
private static synchronized void initCreateEngine() {
// engine = Engine.create(MyCertificate.getBytes());
try {
engine = Engine.create(myCertificate);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
public static EditorViewBean getEditorViewBean(int id) {
return viewMap.get(id);
}
private static View initEditorView(Context applicationContext, int viewId, Map<String, Object> args) {
Editor editor;
View editorViewInflate;
EditorView editorView;
ContentPackage contentPackage = null;
EditorData editorData;
ContentPart contentPart = null;
ParameterSet exportParams;
FrameLayout flView;
// configure recognition
Configuration conf = engine.getConfiguration();
// String confDir = "zip://" + applicationContext.getPackageCodePath() + "!/assets/conf";
String confDir = configPath;
conf.setStringArray("configuration-manager.search-path", new String[]{confDir});
String tempDir = getCacheDirectory(applicationContext) + File.separator + "tmp";
conf.setString("content-package.temp-folder", tempDir);
LayoutInflater inflater = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
editorViewInflate = inflater.inflate(R.layout.activity_main, null);
editorView = editorViewInflate.findViewById(R.id.editor_view);
flView = editorViewInflate.findViewById(R.id.flView);
// load fonts
AssetManager assetManager = applicationContext.getAssets();
Map<String, Typeface> typefaceMap = FontUtils.loadFontsFromAssets(assetManager);
editorView.setTypefaces(typefaceMap);
EditorBinding editorBinding = new EditorBinding(engine, typefaceMap);
editorData = editorBinding.openEditor(editorView);
editor = editorData.getEditor();
assert editor != null;
setMargins(editor, applicationContext);
assert editorData.getInputController() != null;
editorData.getInputController().setInputMode(InputController.INPUT_MODE_FORCE_PEN);
String packageName = "File1_" + viewId + ".iink";
File file = new File(applicationContext.getFilesDir(), packageName);
try {
contentPackage = engine.createPackage(file);
// Choose type of content (possible values are: "Text Document", "Text", "Diagram", "Math", "Drawing" and "Raw Content")
contentPart = contentPackage.createPart("Text");
} catch (IOException | IllegalArgumentException e) {
Log.e(TAG, "Failed to open package \"" + packageName + "\"", e);
}
// wait for view size initialization before setting part
editorView.post(() -> {
Renderer renderer = editorView.getRenderer();
if (renderer != null) {
renderer.setViewOffset(0, 0);
editorView.getRenderer().setViewScale(1);
editorView.setVisibility(View.VISIBLE);
editor.setPart(getEditorViewBean(viewId).contentPart);
}
});
exportParams = engine.createParameterSet();
exportParams.setBoolean("export.jiix.text.words", true);
viewMap.put(viewId, new EditorViewBean(editor, editorViewInflate, editorView, contentPackage, editorData, contentPart, exportParams));
try {
String bgColor = (String) args.get("bgColor");
editorView.setBackgroundColor(Color.parseColor(bgColor));
flView.setBackgroundColor(Color.parseColor(bgColor));
} catch (Exception e) {
Log.e(TAG, Objects.requireNonNull(e.getMessage()));
}
try {
String penColor = (String) args.get("penColor");
editor.getToolController().setToolStyle(PointerTool.PEN, "color: " + penColor + ";");
} catch (Exception e) {
Log.e(TAG, Objects.requireNonNull(e.getMessage()));
}
return editorViewInflate;
}
public static void disposeView() {
for (Map.Entry<Integer, EditorViewBean> editorViewBeanEntry : viewMap.entrySet()) {
EditorViewBean editorViewBean = editorViewBeanEntry.getValue();
EditorView editorView = editorViewBean.editorView;
if (editorView != null) {
editorView.setEditor(null);
}
ContentPart contentPart = editorViewBean.contentPart;
if (contentPart != null && !contentPart.isClosed()) {
contentPart.getPackage().close();
contentPart.close();
}
Editor editor = editorViewBean.editor;
if (editor != null && !editor.isClosed()) {
editor.getRenderer().close();
editor.close();
}
ContentPackage contentPackage = editorViewBean.contentPackage;
if (contentPackage != null && !contentPackage.isClosed()) {
contentPackage.close();
}
}
viewMap.clear();
// engine.close();
}
private static void setMargins(Editor editor, Context applicationContext) {
DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics();
Configuration conf = editor.getConfiguration();
float verticalMarginPX = 2;
float verticalMarginMM = 25.4f * verticalMarginPX / displayMetrics.ydpi;
float horizontalMarginPX = 10;
float horizontalMarginMM = 25.4f * horizontalMarginPX / displayMetrics.xdpi;
conf.setNumber("text.margin.top", verticalMarginMM);
conf.setNumber("text.margin.left", horizontalMarginMM);
conf.setNumber("text.margin.right", horizontalMarginMM);
conf.setNumber("math.margin.top", verticalMarginMM);
conf.setNumber("math.margin.bottom", verticalMarginMM);
conf.setNumber("math.margin.left", horizontalMarginMM);
conf.setNumber("math.margin.right", horizontalMarginMM);
conf.setBoolean("text.guides.enable", false);
}
}
package com.example.myscriptwidget.view;
import android.content.Context;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.myscriptwidget.R;
import com.example.myscriptwidget.bean.EditorViewBean;
import com.example.myscriptwidget.bean.JSONData;
import com.example.myscriptwidget.engine.MyScriptEngine;
import com.google.gson.Gson;
import com.myscript.iink.ContentBlock;
import com.myscript.iink.ContentPart;
import com.myscript.iink.Editor;
import com.myscript.iink.EditorError;
import com.myscript.iink.IEditorListener;
import com.myscript.iink.MimeType;
import java.io.IOException;
import java.util.Map;
import io.flutter.Log;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformView;
public class MyScriptView implements PlatformView {
private final Context context;
private BinaryMessenger messenger;
private final int viewId;
private Object args;
private View myView;
private final String TAG = "MyScriptView";
private TextUpdate textUpdate;
public MyScriptView(Context context, BinaryMessenger messenger, int viewId, Object args, TextUpdate textUpdate) {
this.context = context;
this.messenger = messenger;
this.viewId = viewId;
this.args = args;
this.textUpdate = textUpdate;
}
@Nullable
@Override
public View getView() {
///返回原生视图对象。当Flutter需要显示PlatformView时,会调用此方法来获取原生视图,然后将其嵌入到Flutter界面中。
if (myView != null) {
return myView;
}
if (MyScriptEngine.getEngine() == null) {
TextView textView = new TextView(context);
textView.setText("初始化失败 engine is null");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
textView.setTextColor(context.getColor(R.color.control_gray));
}
textView.setGravity(Gravity.CENTER);
return textView;
}
EditorViewBean editorViewBean = MyScriptEngine.getEditorView(context, viewId, (Map<String, Object>) args);
myView = editorViewBean.editorViewInflate;
Editor editor = editorViewBean.editor;
editor.addListener(new IEditorListener() {
@Override
public void partChanging(@NonNull Editor editor, ContentPart oldPart, ContentPart newPart) {
// no-op
android.util.Log.e("MyScriptView", "partChanging");
}
@Override
public void partChanged(@NonNull Editor editor) {
android.util.Log.e("MyScriptView", "partChanged");
}
@Override
public void contentChanged(@NonNull Editor editor, @NonNull String[] blockIds) {
for (String blockId : blockIds) {
String label = "";
ContentBlock contentBlock = editor.getBlockById(blockId);
try {
String jiixString = editor.export_(contentBlock, MimeType.JIIX, MyScriptEngine.getEditorViewBean(viewId).exportParams);
Gson gson = new Gson();
JSONData jsonElement = gson.fromJson(jiixString, JSONData.class);
label = jsonElement.getLabel();
android.util.Log.w(TAG, label);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (textUpdate != null) {
textUpdate.onEvent(label);
}
Log.w("editor", label);
}
}
@Override
public void onError(@NonNull Editor editor, @NonNull String blockId, @NonNull EditorError error, @NonNull String message) {
android.util.Log.e(TAG, "Failed to edit block \"" + blockId + "\"" + message);
}
@Override
public void selectionChanged(@NonNull Editor editor) {
// no-op
android.util.Log.e("MyScriptView", "selectionChanged");
}
@Override
public void activeBlockChanged(@NonNull Editor editor, @NonNull String blockId) {
// no-op
android.util.Log.e("MyScriptView", "activeBlockChanged");
}
});
return myView;
}
@Override
public void onFlutterViewAttached(@NonNull View flutterView) {
///当PlatformView被附加到Flutter视图层级时,即将显示在Flutter界面上时,
// 会调用此方法。您可以在此方法中执行一些初始化操作,或者监听与Flutter视图的交互事件。
PlatformView.super.onFlutterViewAttached(flutterView);
}
@Override
public void onFlutterViewDetached() {
//当PlatformView从Flutter视图层级中分离(不再显示)时,
//会调用此方法。您可以在此方法中执行一些清理操作,停止监听与Flutter视图的交互事件。
PlatformView.super.onFlutterViewDetached();
}
@Override
public void dispose() {
///当PlatformView需要被销毁时,会调用此方法。
// 您应该在这里释放与PlatformView相关的资源,确保不会出现内存泄漏
Log.e("MyScriptView", "dispose");
MyScriptEngine.disposeView();
}
@Override
public void onInputConnectionLocked() {
//当Flutter视图需要锁定输入连接时,会调用此方法。
// 这可以发生在需要处理输入事件时,例如文本输入。您可以在此方法中实现处理输入事件所需的逻辑。
PlatformView.super.onInputConnectionLocked();
}
@Override
public void onInputConnectionUnlocked() {
///当Flutter视图不再需要锁定输入连接时,会调用此方法。您可以在此方法中实现停止处理输入事件所需的逻辑。
//这些方法允许您与Flutter视图进行交互并管理PlatformView的生命周期。您可以根据需要在这些方法中添加逻辑,
// 以确保PlatformView在Flutter中正确地显示和交互。
PlatformView.super.onInputConnectionUnlocked();
}
}
ios端关键代码:
import Flutter
import UIKit
@available(iOS 13.0, *)
public class MyScriptWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler ,TextUpdate{
static var myScriptViewFac : MyScriptViewFactory?;
@available(iOS 13.0, *)
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "com.example.myScriptWidget_example.myScriptWidgetMethodChannel", binaryMessenger: registrar.messenger())
let instance = MyScriptWidgetPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
let eventChannel = FlutterEventChannel(name: "com.example.myScriptWidget_example.myScriptWidgetEventChannel", binaryMessenger: registrar.messenger())
eventChannel.setStreamHandler(instance)
myScriptViewFac = MyScriptViewFactory(messenger: registrar.messenger(),textUpdate: instance);
registrar.register(myScriptViewFac!, withId: "com.example.myscriptwidget.view")
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
break;
case "clear":
MyScriptWidgetPlugin.myScriptViewFac?.clear();
result("success");
break;
case "initMyScriptWidget":
if let dictValue = call.arguments as? [String: Any] {
let arr = (dictValue["myCertificate"] as? [CChar]) ?? [];
print(arr);
EngineProvider.myCertificate_BYTES = arr;
EngineProvider.configPath = (dictValue["configPath"] as? String) ?? "";
}
result("success");
break;
case "getChannel":
result("appStore");
break;
default:
result(FlutterMethodNotImplemented)
}
}
var eventSinkIt : FlutterEventSink? = nil;
public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSinkIt = events;
return nil;
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSinkIt = nil;
return nil;
}
public func sendEvent(event:Any) {
eventSinkIt?(event)
}
func onEvent(text: String) {
sendEvent(event: text);
}
}
//
// MyscriptView.swift
// Pods
//
// Created by qqss on 2023/9/15.
//
import Foundation
import UIKit
import Flutter
@available(iOS 13.0, *)
public class MyscriptView : NSObject,FlutterPlatformView, IINKEditorDelegate{
var editor: IINKEditor?;
var framSize :CGRect;
var args :Any?;
var textUpdate:TextUpdate?;
var viewID: Int64;
init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger,textUpdate:TextUpdate) {
self.framSize = frame;
self.viewID = viewID;
super.init();
self.args = args;
self.textUpdate = textUpdate;
var _ = self.initEditorView();
createDefaultPackage(packageName: "\(viewID)_new", packageType:"Text", engineProvider: EngineProvider.sharedInstance)
}
public func view() -> UIView {
if(EngineProvider.sharedInstance.engine == nil){
let lable : UILabel = UILabel();
lable.text = "初始化失败 engine is null";
lable.textAlignment = NSTextAlignment.center;
return lable;
}
return initEditorView();
}
var editorContainerView : UIView?;
var viewModel :EditorViewModel?;
var controller:EditorViewController?;
var iinkParamterSet : IINKParameterSet = IINKParameterSet.init();
public func initEditorView()-> UIView {
var width : CGFloat = 350.0;
var height : CGFloat = 350.0;
var bgColor : String = "#FFFFFF";
if let dictValue = args as? [String: Any] {
width = dictValue["width"] as! CGFloat;
height = dictValue["height"] as! CGFloat;
bgColor = dictValue["bgColor"] as! String;
}
let size : CGSize = CGSize(width: width, height: height);
let frame: CGRect = CGRect(origin: CGPoint.init(x: 0, y: 0), size:size);
if(editorContainerView == nil ){
editorContainerView = UIView.init(frame: frame);
}else{
if(viewModel != nil){
viewModel!.setEditorViewSize(size: size)
viewModel!.configureEditorUI(with: size)
controller!.view.layoutIfNeeded();
}
return editorContainerView!;
}
if(EngineProvider.sharedInstance.engine == nil){
let label = UILabel();
label.text = EngineProvider.sharedInstance.engineErrorMessage;
return label;
}
editorContainerView!.frame = frame;
viewModel = EditorViewModel(engine: EngineProvider.sharedInstance.engine!, inputMode: InputMode.forcePen, editorDelegate: self, smartGuideDelegate: nil);
controller = EditorViewController(viewModel: viewModel!);
controller!.view.frame = frame;
controller!.view.backgroundColor = bgColor.uicolor();
editorContainerView!.addSubview(controller!.view);
controller!.view.layoutIfNeeded();
iinkParamterSet = EngineProvider.sharedInstance.engine!.createParameterSet() ?? IINKParameterSet.init();
try? iinkParamterSet.set(boolean: true, forKey: "export.jiix.text.words")
return editorContainerView!;
}
private func createDefaultPackage(packageName: String, packageType: String, engineProvider: EngineProvider) {
guard let engine = engineProvider.engine else {
createAlert(title: "Certificate error", message: engineProvider.engineErrorMessage)
return
}
do {
var resultPackage: IINKContentPackage?
let fullPath = FileManager.default.pathForFileInDocumentDirectory(fileName: packageName) + ".iink"
resultPackage = try engine.createPackage(fullPath.decomposedStringWithCanonicalMapping)
// Add a blank page type Text Document
try resultPackage?.createPart(with: packageType)
if let package = resultPackage {
try self.editor?.part = package.part(at: 0)
}
} catch {
createAlert(title: "Error", message: "An error occured during the page creation")
print("Error while creating package : " + error.localizedDescription)
}
}
private func createAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
exit(1)
}))
}
public func editUpdate(){
let ed = EditorViewBean(editor: editor, iinkParamterSet: iinkParamterSet, editView: editorContainerView!);
MyScriptEngine.addEditView(viewID: viewID, eb: ed);
var penColor : String = "#000000";
if let dictValue = args as? [String: Any] {
penColor = dictValue["penColor"] as! String;
try? self.editor?.toolController.set(style: "color: " + penColor + ";", forTool: IINKPointerTool.toolPen);
}
}
}
@available(iOS 13.0, *)
extension MyscriptView: EditorDelegate {
public func partChanged(_ editor: IINKEditor) {
print("");
}
public func contentChanged(_ editor: IINKEditor, blockIds: [String]) {
for blockId in blockIds {
let blockEn : IINKContentBlock? = editor.getBlockById(blockId);
var jiixString :String = "";
let jsonStr :String? = try? editor.export(selection: blockEn, mimeType: IINKMimeType.JIIX, overrideConfiguration: iinkParamterSet);
if(jsonStr != nil){
jiixString = jsonStr!;
let jsonData : Data? = jiixString.data(using: .utf8)
let json = try? JSONSerialization.jsonObject(with: jsonData!, options: [])
if let dict = json as? [String: Any] {
if let text = dict["label"] as? String {
self.textUpdate?.onEvent(text: text);
}
}
// print(jiixString);
}
}
}
public func onError(_ editor: IINKEditor, blockId: String, message: String) {
print("");
}
func didCreateEditor(editor: IINKEditor?) {
self.editor = editor;
editor?.addDelegate(self)
self.editUpdate();
}
}
protocol TextUpdate {
func onEvent(text : String)-> Void
}
extension String {
/// 十六进制字符串颜色转为UIColor
/// - Parameter alpha: 透明度
func uicolor(alpha: CGFloat = 1.0) -> UIColor {
// 存储转换后的数值
var red: UInt64 = 0, green: UInt64 = 0, blue: UInt64 = 0
var hex = self
// 如果传入的十六进制颜色有前缀,去掉前缀
if hex.hasPrefix("0x") || hex.hasPrefix("0X") {
hex = String(hex[hex.index(hex.startIndex, offsetBy: 2)...])
} else if hex.hasPrefix("#") {
hex = String(hex[hex.index(hex.startIndex, offsetBy: 1)...])
}
// 如果传入的字符数量不足6位按照后边都为0处理,当然你也可以进行其它操作
if hex.count < 6 {
for _ in 0..<6 - hex.count {
hex += "0"
}
}
// 分别进行转换
// 红
Scanner(string: String(hex[..<hex.index(hex.startIndex, offsetBy: 2)])).scanHexInt64(&red)
// 绿
Scanner(string: String(hex[hex.index(hex.startIndex, offsetBy: 2)..<hex.index(hex.startIndex, offsetBy: 4)])).scanHexInt64(&green)
// 蓝
Scanner(string: String(hex[hex.index(startIndex, offsetBy: 4)...])).scanHexInt64(&blue)
return UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: alpha)
}
}
//
// MyScriptEngine.swift
// Pods
//
// Created by qqss on 2023/9/19.
//
import Foundation
@available(iOS 13.0, *)
public class MyScriptEngine{
// public init() {
// }
static var editMap = [Int64:EditorViewBean]();
static func getEditView(viewId : Int64) ->EditorViewBean?{
return editMap[viewId];
}
static func addEditView(viewID: Int64,eb :EditorViewBean){
editMap[viewID] = eb;
}
static func removeAll(){
for (_, em) in editMap {
em.editor?.clear();
}
editMap.removeAll();
}
static func initEditorView(viewID: Int64,args :Any?,editorDelegate :EditorDelegate,editor : IINKEditor?)-> UIView {
let editView = getEditView(viewId: viewID);
if(editView != nil){
return editView!.editView;
}
var editorContainerView : UIView?;
var viewModel :EditorViewModel?;
var controller:EditorViewController?;
var iinkParamterSet : IINKParameterSet = IINKParameterSet.init();
var width : CGFloat = 350.0;
var height : CGFloat = 350.0;
if let dictValue = args as? [String: Any] {
width = dictValue["width"] as! CGFloat;
height = dictValue["height"] as! CGFloat;
}
let size : CGSize = CGSize(width: width, height: height);
let frame: CGRect = CGRect(origin: CGPoint.init(x: 0, y: 0), size:size);
if(editorContainerView == nil ){
editorContainerView = UIView.init(frame: frame);
}else{
viewModel!.setEditorViewSize(size: size)
viewModel!.configureEditorUI(with: size)
controller!.view.layoutIfNeeded();
return editorContainerView!;
}
editorContainerView!.frame = frame;
viewModel = EditorViewModel(engine: EngineProvider.sharedInstance.engine!, inputMode: InputMode.forcePen, editorDelegate: editorDelegate, smartGuideDelegate: nil);
controller = EditorViewController(viewModel: viewModel!);
controller!.view.frame = frame;
controller!.view.backgroundColor = UIColor.red;
editorContainerView!.addSubview(controller!.view);
controller!.view.layoutIfNeeded();
iinkParamterSet = EngineProvider.sharedInstance.engine!.createParameterSet() ?? IINKParameterSet.init();
addEditView(viewID: viewID, eb: EditorViewBean(editor: editor, iinkParamterSet: iinkParamterSet, editView: editorContainerView!))
return editorContainerView!;
}
}