I have three uiViewController with seperate nativePlatformViewFactor for each. But only one is being presented on the window view hierarchy even though I have properly initialised each one of them due to this I have to resort using single uiViewController for these different features based on flags.
AppDelegate.swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
// This is required to make any communication available in the action isolate.
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
GeneratedPluginRegistrant.register(with: registry)
}
GeneratedPluginRegistrant.register(with: self)
//runner or flutter
if #available(iOS 14.0, *) {
let plugin : FlutterPluginRegistrar = registrar(forPlugin: "flutter")!
let nativeMlView = FLNativeViewFactory(messenger: plugin.messenger())
let reportViewFactory = ReportNativeViewFactory(messenger: plugin.messenger())
let inhalerDetectionViewFactory = InhalerDetectionViewFactory(messenger: plugin.messenger())
plugin.register(nativeMlView, withId: "native_ml_view")
plugin.register(reportViewFactory, withId: "edit_report_native_ml_view")
plugin.register(inhalerDetectionViewFactory, withId: "verify_inhaler_view")
} else {
// Fallback on earlier versions
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
In the below viewFactory I am compelled to use MainViewController inspite of OcrViewController because it won't attach its view to superview.
InhalerDetectionViewFactory.swift
@available(iOS 14.0, *)
class InhalerDetectionViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return InhalerDetectionNativeView(
frame: frame,
viewIdentifier: viewId,
arguments: args
)
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
class InhalerDetectionNativeView: NSObject, FlutterPlatformView {
let width: String
let height: String
private var _view: UIView
let medicineList : [String]
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) {
_view = UIView()
_view.backgroundColor = UIColor.black
if let arguments = args as? [String: Any],
let wdth = arguments["width"] as? String,
let ht = arguments["height"] as? String,
let meds = arguments["medicineList"] as? [String] {
width = wdth;
height = ht;
medicineList = meds;
} else {
width = "0"
height = "0"
medicineList = ["test1", "test2", "test3", "test4", "test5","test6","test7","test8"]
}
let controller : FlutterViewController = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.keyWindow?.rootViewController as! FlutterViewController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// let vc = mainStoryboard.instantiateViewController(identifier: "OcrVC")
// if let ocrVC : OcrViewController = vc as? OcrViewController {
// ocrVC.channel = FlutterMethodChannel(name: "inhaler_verification", binaryMessenger: controller.binaryMessenger)
//
// print("OcrViewController initialised")
// print(String.init(describing: "Vc hash: \(vc.hash), isLoaded: \(vc.isViewLoaded)"))
// }
let vc = mainStoryboard.instantiateViewController(identifier: "MainVC")
if let mainVC : MainViewController = vc as? MainViewController {
mainVC.channel = FlutterMethodChannel(name: "inhaler_verification", binaryMessenger: controller.binaryMessenger)
mainVC.stepId = ""
mainVC.withSpacer = false
mainVC.isEditReport = false
mainVC.medicineList = medicineList
print("MainViewController initialised")
print(String.init(describing: "Vc hash: \(vc.hash), isLoaded: \(vc.isViewLoaded)"))
}
_view.frame = CGRect(x: 0, y: 0, width: Int(width) ?? 0, height: Int(height) ?? 0)
_view.addSubview(vc.view)
super.init()
}
func view() -> UIView {
return _view
}
}
Same for the rest of platformViewFactories I am compelled to use MainViewController as the rendering UiView.
MainViewController.swift
import UIKit
import Vision
import Flutter
import AVFoundation
import VisionKit
@available(iOS 14.0, *)
class MainViewController: UIViewController {
var previewLayer: AVCaptureVideoPreviewLayer?
@IBOutlet var imageView: UIImageView!
@IBOutlet weak var labelStack: UIStackView!
@IBOutlet weak var actionLabel: UILabel!
@IBOutlet weak var cameraButton: UIButton!
var stepNumber:Int = 1
var videoCapture: VideoCapture!
var videoProcessingChain: VideoProcessingChain!
var actionFrameCounts = [String: Int]()
@IBOutlet weak var shakeStack: UIStackView!
@IBOutlet weak var shakeCountLabel: UILabel!
@IBOutlet weak var timerView: UIView!
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var medVerifyErrorView: UIView!
var channel: FlutterMethodChannel
let channelName = "take_test"
var stepId: String
var stepIndex: Int = 0
var withSpacer: Bool
var isEditReport: Bool
var medicineList : [String] = []
var medicineVerified = false
@IBOutlet weak var instructionLableView: UIView!
private let dataScannerViewController = DataScannerViewController(recognizedDataTypes: [.text()],
qualityLevel: .balanced,
recognizesMultipleItems: false,
isHighFrameRateTrackingEnabled: true,
isPinchToZoomEnabled: true,
isGuidanceEnabled: true,
isHighlightingEnabled: true)
private var scannerAvailable: Bool { DataScannerViewController.isSupported && DataScannerViewController.isAvailable }
init(stepId: String, viewId: Int64, isEditReport: Bool) {
let controller : FlutterViewController = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.keyWindow?.rootViewController as! FlutterViewController
channel = FlutterMethodChannel(name: isEditReport ? "report_ocr_scan" : "take_test", binaryMessenger: controller.binaryMessenger)
self.stepId = stepId
self.withSpacer = false
self.isEditReport = isEditReport
self.medicineList = []
super.init(nibName: "MainViewController", bundle: Bundle.main)
}
required init?(coder: NSCoder) {
channel = FlutterMethodChannel()
stepId = ""
withSpacer = false;
isEditReport = false;
medicineList = [];
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Disable the idle timer to prevent the screen from locking.
UIApplication.shared.isIdleTimerDisabled = true
hideInstructionView()
medVerifyErrorView.isHidden = true
if (isEditReport) {
labelStack.isHidden = true
shakeStack.isHidden = true
dataScannerViewController.delegate = self
if scannerAvailable {
present(dataScannerViewController, animated: true)
view.layer.addSublayer(dataScannerViewController.view.layer)
dataScannerViewController.view.frame = view.frame
try? dataScannerViewController.startScanning()
}
} else {
if (medicineList.isEmpty) {
setupImage()
} else {
dataScannerViewController.delegate = self
if scannerAvailable {
present(dataScannerViewController, animated: true)
view.layer.addSublayer(dataScannerViewController.view.layer)
dataScannerViewController.view.frame = view.frame
try? dataScannerViewController.startScanning()
}
}
}
channel.setMethodCallHandler(handle)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Update the device's orientation.
//videoCapture.updateDeviceOrientation()
}
private func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "next_step":
let arguments = call.arguments as! [Dictionary<String, Any?>]
let stepId = arguments.first?["stepId"] as! String
self.stepId = stepId
result("next step initialized")
case "dealloc_resources":
print("Method call from flutter to dealloc resources")
self.videoCapture = nil
self.videoCapture?.delegate = nil
self.videoProcessingChain = nil
self.videoProcessingChain?.delegate = nil
self.dismiss(animated: false)
result("receiveFromFlutter success")
default:
result(FlutterMethodNotImplemented)
}
}
override func viewWillDisappear(_ animated: Bool) {
print("View will disappear")
if (!isEditReport) {
videoCapture = nil
videoCapture?.delegate = nil
videoProcessingChain = nil
videoProcessingChain?.delegate = nil
}
super.viewWillDisappear(animated)
}
func setupImage() {
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else{ return }
guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return }
captureSession.addInput(input)
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.setSampleBufferDelegate(self, queue:DispatchQueue(label: "videoQueue"))
captureSession.addOutput(dataOutput)
DispatchQueue.global(qos: .background).async {
captureSession.startRunning()
}
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.frame
self.imageView.layer.addSublayer(previewLayer)
previewLayer.videoGravity = .resizeAspectFill
self.previewLayer = previewLayer
self.shakeStack.isHidden = true
}
func stopCaptureSession() {
guard let captureSession = previewLayer?.session else { return }
DispatchQueue.main.async {
self.previewLayer?.removeFromSuperlayer()
self.previewLayer = nil
}
DispatchQueue.global(qos: .background).async {
if captureSession.isRunning {
captureSession.stopRunning()
}
}
}
}
I tried various solutions for multiple platformViews but not able to figure out so I am currently using single UIViewController for all the platformViewFactories.