Flutter adding new ViewController for BlinkId plugin - ios

I am working on a flutter app and I have tp integrate BlinkId plugin to scan documents
which doesn't have flutter plugin so I used MethodChannel to invoke a method on the native code then I tried to add the native code of the plugin.
everything working and the view of the plugin is opening and scanning is done successfully but the scanning view doesn't close at all even if I click close which suppose to cancel the scanning.
non of the methods seems to be working except viewdidload but didfinishscanning or didtabclose are not working.
here is my code:
import UIKit
import Flutter
import GoogleMaps
import Microblink
var _result: FlutterResult?
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let scanIdChannel = FlutterMethodChannel(name: "native.wasfago.scanId",
binaryMessenger: controller.binaryMessenger)
scanIdChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
// Note: this method is invoked on the UI thread.
guard call.method == "scanId" else {
result(FlutterMethodNotImplemented)
return
}
_result = result
self.scanId(result: result) // handle click event from flutter button
print("clicked!")
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func scanId(result: FlutterResult) {
let viewCtrl = ViewController()
topMostController().view.addSubview(viewCtrl.view)
}
func topMostController() -> UIViewController {
var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
while ((topController?.presentedViewController) != nil) {
topController = topController?.presentedViewController
}
return topController!
}
}
class ViewController: UIViewController ,MBBlinkIdOverlayViewControllerDelegate{
func blinkIdOverlayViewControllerDidTapClose(_ blinkIdOverlayViewController: MBBlinkIdOverlayViewController) {
print("Closed!!!!!!!!!")
}
var blinkIdRecognizer : MBBlinkIdCombinedRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
// Valid until: 2020-06-26
self.didTapScan()
print("View Loaded!")
}
#IBAction func didTapScan() {
MBMicroblinkSDK.sharedInstance().setLicenseKey("sRwAAAEPY29tLnczNC53YXNmYWdvfIRuYWSSC81qt+lUDRzpTwtWuUsIPrIHmH2dNCTjx5qYKCfr3nKw9UVE7TIRv2nq/jDlTtqhcVZA+4dyVG8moP4DeOygPcRAkdy6L+WpNhacuZMjrTAUmGwooe3CSzaj8D8Y6Znf98SHVIE9bxdSv23SOfCQnNsoCSksIYvpjVjpT5DUExr6qSY+QqeH3EUxDR9GqIPgeiEGIXZUeOdqnIyNiGH8PYpfF9Uv79HEacBncbHDMwfzZTSXc2VYttRgae1QAA9h5hAtUc8VhH1g")
/** Create BlinkID recognizer */
self.blinkIdRecognizer = MBBlinkIdCombinedRecognizer()
self.blinkIdRecognizer?.returnFullDocumentImage = true;
/** Create settings */
let settings : MBBlinkIdOverlaySettings = MBBlinkIdOverlaySettings()
/** Crate recognizer collection */
let recognizerList = [self.blinkIdRecognizer!]
let recognizerCollection : MBRecognizerCollection = MBRecognizerCollection(recognizers: recognizerList)
/** Create your overlay view controller */
let blinkIdOverlayViewController : MBBlinkIdOverlayViewController = MBBlinkIdOverlayViewController(settings: settings, recognizerCollection: recognizerCollection, delegate: self)
/** Create recognizer view controller with wanted overlay view controller */
let recognizerRunneViewController : UIViewController = MBViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: blinkIdOverlayViewController)
recognizerRunneViewController.modalPresentationStyle = .fullScreen
/** Present the recognizer runner view controller. You can use other presentation methods as well (instead of presentViewController) */
self.topMostController().present(recognizerRunneViewController, animated: true, completion: nil)
// let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController
// navigationController?.pushViewController(recognizerRunneViewController, animated: true)
}
func topMostController() -> UIViewController {
var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
while ((topController?.presentedViewController) != nil) {
topController = topController?.presentedViewController
}
return topController!
}
func blinkIdOverlayViewControllerDidFinishScanning(_ blinkIdOverlayViewController: MBBlinkIdOverlayViewController, state: MBRecognizerResultState) {
/** This is done on background thread */
print("success scaning");
}
}

You should remove the intermediate ViewController instance, and present the recognizerRunneViewController (which is responsible for ID Scanning) on top of either UINavigationViewController, or modally directly over the topMostViewController.
Here's a link to github issue which helped with the solution: https://github.com/BlinkID/blinkid-ios/issues/294
EDIT: BlinkID from June 2020. supports out of the box integration in Flutter apps with an official plugin. The plugin is available here: https://github.com/blinkid/blinkid-flutter

Related

How to fix problem with opening ViewController by action from Coordinator in Swift?

I'm trying to open another controller by tapping on the cell of my tableView. I'm coding with MVVM and Coordinator pattern.
In the beginning we see this screen - it is declarated in the method start()
let service = Service()
private(set) weak var navigationController: UINavigationController?
func start() -> UINavigationController {
let vm = ContinentsViewModel(service: service)
let vc = ContinentsViewController(viewModel: vm)
let navigationController = UINavigationController()
self.navigationController = navigationController
navigationController.setViewControllers([vc], animated: false)
bindContinentsViewModel(viewModel: vm)
return navigationController
}
Later, my goal is to open all list of countries of the continent, but now l just need to open empty ViewController by tap on the cell (ex. Africa or Antarctica). Here is my methods for it, but they don't work.
private func showCountries() {
let vc = ViewController()
navigationController?.pushViewController(vc, animated: true)
}
private func bindContinentsViewModel(viewModel: ContinentsViewModel) {
viewModel
.flow
.bind { [weak self] flow in
switch flow {
case .onContinentTap:
self?.showCountries() // don't work
// print("show \(continent)") // work - continent is a param of .onContinentTap, which prints an geo-id of the continent, just if you need to know.
}
}
.disposed(by: viewModel.bag)
}
Thank you so much!
The following works as expected. What are you doing differently?
#main
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var viewModel: ViewModel?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
viewModel = ViewModel()
let controller = viewModel?.start()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = controller
window?.makeKeyAndVisible()
return true
}
}
final class ViewModel {
private(set) weak var navigationController: UINavigationController?
func start() -> UINavigationController {
let vm = ContinentsViewModel()
let vc = ContinentsViewController(viewModel: vm)
let navigationController = UINavigationController()
self.navigationController = navigationController
navigationController.setViewControllers([vc], animated: false)
bindContinentsViewModel(viewModel: vm)
return navigationController
}
private func showCountries() {
let vc = UIViewController()
vc.view.backgroundColor = .blue
navigationController?.pushViewController(vc, animated: true)
}
private func bindContinentsViewModel(viewModel: ContinentsViewModel) {
viewModel.flow
.bind { [weak self] flow in
switch flow {
case .onContinentTap:
self?.showCountries()
}
}
.disposed(by: viewModel.bag)
}
}
final class ContinentsViewModel {
enum Flow {
case onContinentTap
}
let flow: Observable<Flow>
let bag = DisposeBag()
init() {
flow = .just(.onContinentTap)
.delay(.seconds(3), scheduler: MainScheduler.instance)
}
}
final class ContinentsViewController: UIViewController {
var viewModel: ContinentsViewModel
init(viewModel: ContinentsViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
}

react-native & ios: How to launch View controller from native module manager

I want to launch UIViewController from native module manager.
From react-native: MyNativeModuleManager.viewStreet()
Is this the right way? using DispatchQueue.main.async { to launch UI ViewController.
Most of the times code works but sometimes react-native fails to launch UI View Controller.
I want back button too and hence I am using UINavigationController
//MyNativeModuleManager.m
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#interface RCT_EXTERN_MODULE(MyNativeModuleManager, RCTEventEmitter)
RCT_EXTERN_METHOD(viewStreet)
#end
//MyNativeModuleManager.swift
import Foundation
import UIKit
#objc(MyNativeModuleManager)
class MyNativeModuleManager: RCTEventEmitter {
#objc
override static func requiresMainQueueSetup() -> Bool {
return false
}
override func supportedEvents() -> [String]! {
return []
}
#objc
func viewStreet() -> Void {
DispatchQueue.main.async {
let topController = UIApplication.shared.topMostViewController()
let vc: UIViewController;
vc = ViewStreetUIViewController()
let navController = UINavigationController(rootViewController: vc)
navController.modalPresentationStyle = .fullScreen
_ = vc.view
UIView.transition(with: UIApplication.shared.keyWindow!, duration: 0.65,options: [], animations: {
topController?.present(navController, animated: false, completion: nil)
})
}
}
}
&
//UIApplicationExt.swift
import Foundation
extension UIApplication {
func topMostViewController() -> UIViewController? {
var topViewController: UIViewController? = nil
if #available(iOS 13, *) {
for scene in connectedScenes {
if let windowScene = scene as? UIWindowScene {
for window in windowScene.windows {
if window.isKeyWindow {
topViewController = window.rootViewController
}
}
}
}
} else {
topViewController = keyWindow?.rootViewController
}
while true {
if let presented = topViewController?.presentedViewController {
topViewController = presented
} else if let navController = topViewController as? UINavigationController {
topViewController = navController.topViewController
} else if let tabBarController = topViewController as? UITabBarController {
topViewController = tabBarController.selectedViewController
} else {
// Handle any other third party container in `else if` if required
break
}
}
return topViewController
}
}

Prevent iOS app from loading default Storyboard entry point

I am following this tutorial on Coordinator pattern, and so far I have it going through creating the coordinator and running the start() from app delegate. But then calling a function on that coordinator doesn't work, because suddenly the coordinator var is nil. It seems that the initial view controller that is shown is not the one coming from the coordinator.start() but rather the one from the storyboard entry point. I did disable the Main in the main interface of the projects target.
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let registry = DependencyResolver.shared as? DependencyRegistry {
DependencyGraph.setup(for: registry)
}
let navController = UINavigationController()
coordinator = MainCoordinator(navigationController: navController)
coordinator?.start()
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
Main coordinator:
class MainCoordinator: Coordinator {
var navigationController: UINavigationController
var childCoordinators = [Coordinator]()
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = InitViewController.instantiate()
vc.coordinator = self. //!!I do hit this breakpoint
navigationController.pushViewController(vc, animated: false)
}
}
Init view controller (the one that's initial in the storyboard, but I'm showing it with coordinator):
class InitViewController: UIViewController, Storyboarded {
private var cameraViewModel: CameraViewModel!
weak var coordinator: MainCoordinator?
#IBAction func openCameraView(_ sender: Any) {
coordinator?.openCameraView() //!!here coordinator is nil
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Storyboarded protocol - used to get view controller out of the storyboard by id:
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
let fullName = NSStringFromClass(self)
let className = fullName.components(separatedBy: ".")[1]
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let leController = storyboard.instantiateViewController(withIdentifier: className) as! Self
return leController
}
}
The problem is merely that the tutorial you're looking at is too old for current conditions. There is no Single View App template any more, and the App Delegate no longer contains the window. If you create a project in Xcode 11 or Xcode 12, the window is owned by the scene delegate. Implement the scene delegate's willConnect method to do the work that the app delegate was doing in the tutorial, and everything will spring to life.
Also the mechanism for preventing Main.storyboard from trying to load automatically has changed; you have to remove it from Application Scene Manifest in the Info.plist (editing by hand — there is no simple interface).

How to add an Avatar image in GetStream iOS Activity Feed component?

My config: XCode 10.3, Swift 5, MacOS Catalina v10.15
I followed the native iOS Activity Feed demo (https://getstream.io/ios-activity-feed/tutorial/?language=python) to successfully add an activity feed to my XCode project.
How do I add an avatar image for each user? Here is what I have tried so far:
I uploaded an avatar image to my backend storage, obtained the corresponding URL, and used a json object to create a new user using my backend server like so:
{
"id" : "cqtGMiITVSOLE589PJaRt",
"data" : {
"name" : "User4",
"avatarURL" : "https:\/\/firebasestorage.googleapis.com\/v0\/b\/champXXXXX.appspot.com\/o\/profileImage%2FcqtGMiITVSOLXXXXXXXX"
}
}
Verified that user was created successfully, but the FlatFeedPresenter view controller shows up with a blank avatar image even though activities in the feed show up correctly. How can I use the user's data.avatarURL property to populate the avatar image correctly?
Here is the StreamActivity ViewController class behind the Main storyboard.
import UIKit
import GetStream
import GetStreamActivityFeed
class StreamActivityViewController: FlatFeedViewController<GetStreamActivityFeed.Activity> {
let textToolBar = TextToolBar.make()
override func viewDidLoad() {
if let feedId = FeedId(feedSlug: "timeline") {
let timelineFlatFeed = Client.shared.flatFeed(feedId)
presenter = FlatFeedPresenter<GetStreamActivityFeed.Activity>(flatFeed: timelineFlatFeed, reactionTypes: [.likes, .comments])
}
super.viewDidLoad()
setupTextToolBar()
subscribeForUpdates()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let detailViewController = DetailViewController<GetStreamActivityFeed.Activity>()
detailViewController.activityPresenter = activityPresenter(in: indexPath.section)
detailViewController.sections = [.activity, .comments]
present(UINavigationController(rootViewController: detailViewController), animated: true)
}
func setupTextToolBar() {
textToolBar.addToSuperview(view, placeholderText: "Share something...")
// Enable image picker
textToolBar.enableImagePicking(with: self)
// Enable URL unfurling
textToolBar.linksDetectorEnabled = true
textToolBar.sendButton.addTarget(self,
action: #selector(save(_:)),
for: .touchUpInside)
textToolBar.updatePlaceholder()
}
#objc func save(_ sender: UIButton) {
// Hide the keyboard.
view.endEditing(true)
if textToolBar.isValidContent, let presenter = presenter {
// print("Message validated!")
textToolBar.addActivity(to: presenter.flatFeed) { result in
// print("From textToolBar: \(result)")
}
}
}
}
UPDATE:
I updated the AppDelegate as suggested in the answer below, but avatar image still does not update even though rest of the feed does load properly. Set a breakpoint at the following line and found that avatarURL property of createdUser is nil even though streamUser.avatarURL is set correctly.
print("createdUser: \(createdUser)")
Updated AppDelegate code (had to comment out
initialViewController?.reloadData() to address a "Value of type 'UIViewController' has no member 'reloadData'" error -- not sure whether is contributing to the avatar issue.)
import UIKit
import Firebase
import GetStream
import GetStreamActivityFeed
import GoogleSignIn
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
GIDSignIn.sharedInstance()?.clientID = FirebaseApp.app()?.options.clientID
Database.database().isPersistenceEnabled = true
configureInitialRootViewController(for: window)
return true
}
}
extension AppDelegate {
func configureInitialRootViewController(for window: UIWindow?) {
let defaults = UserDefaults.standard
let initialViewController: UIViewController
if let _ = Auth.auth().currentUser, let userData = defaults.object(forKey: Constants.UserDefaults.currentUser) as? Data, let user = try? JSONDecoder().decode(AppUser.self, from: userData) {
initialViewController = UIStoryboard.initialViewController(for: .main)
AppUser.setCurrent(user)
Client.config = .init(apiKey: Constants.Stream.apiKey, appId: Constants.Stream.appId, token: AppUser.current.userToken)
let streamUser = GetStreamActivityFeed.User(name: user.name, id: user.id)
let avatarURL = URL(string: user.profileImageURL)
streamUser.avatarURL = avatarURL
Client.shared.create(user: streamUser) { [weak initialViewController] result in
if let createdUser = try? result.get() {
print("createdUser: \(createdUser)")
// Refresh from here your view controller.
// Reload data in your timeline feed:
// initialViewController?.reloadData()
}
}
} else {
initialViewController = UIStoryboard.initialViewController(for: .login)
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
}
The recommended approach is to ensure the user exists on Stream's side in AppDelegate.
extension AppDelegate {
func configureInitialRootViewController(for window: UIWindow?) {
let defaults = UserDefaults.standard
let initialViewController: UIViewController
if let _ = Auth.auth().currentUser, let userData = defaults.object(forKey: Constants.UserDefaults.currentUser) as? Data, let user = try? JSONDecoder().decode(AppUser.self, from: userData) {
initialViewController = UIStoryboard.initialViewController(for: .main)
AppUser.setCurrent(user)
Client.config = .init(apiKey: Constants.Stream.apiKey,
appId: Constants.Stream.appId,
token: AppUser.current.userToken,
logsEnabled: true)
let streamUser = GetStreamActivityFeed.User(name: user.name, id: user.id)
streamUser.avatarURL = user.avatarURL
// ensures that the user exists on Stream (if not it will create it)
Client.shared.create(user: streamUser) { [weak initialViewController] result in
if let createdUser = try? result.get() {
Client.shared.currentUser = createdUser
// Refresh from here your view controller.
// Reload data in your timeline feed:
// flatFeedViewController?.reloadData()
}
}
} else {
initialViewController = UIStoryboard.initialViewController(for: .login)
}
window?.rootViewController = initialViewController
window?.makeKeyAndVisible()
}
}

Swapping centreViewControllers with FloatingDrawers

I am using a third party pod KGFloatingDrawer which is great because it achieves this:
and is a reimplementation of JVFloatingDrawer. I used their sample code and the sliding drawers are working great!
BUT
When I first run my app I call one centreViewController with no drawers (Login). Then after login I call a new centreViewController with
appDelegate.centerViewController = appDelegate.navigationBarController()
which only works if I restart the app. Am I missing something?
The logout seems fine though
appDelegate.centerViewController = appDelegate.drawerSettingsViewController()
which puzzles me a bit because then I think I'm on the right track?
Am I supposed to only use normal segues and such first and then only call the drawerViewController?
Here is the other code when setting up the floating drawers :
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = drawerViewController
window?.makeKeyAndVisible()
return true
}
private var _drawerViewController: KGDrawerViewController?
var drawerViewController: KGDrawerViewController {
get {
if let viewController = _drawerViewController {
return viewController
}
return prepareDrawerViewController()
}
}
func prepareDrawerViewController() -> KGDrawerViewController {
let drawerViewController = KGDrawerViewController()
drawerViewController.centerViewController = drawerSettingsViewController()
drawerViewController.leftViewController = leftViewController()
drawerViewController.rightViewController = rightViewController()
drawerViewController.backgroundImage = UIImage(named: "sky3")
_drawerViewController = drawerViewController
return drawerViewController
}
private func drawerStoryboard() -> UIStoryboard {
let storyboard = UIStoryboard(name: StoryboardIDs.MainStoryBoardID , bundle: nil)
return storyboard
}
private func viewControllerForStoryboardId(storyboardId: String) -> UIViewController {
let viewController: UIViewController = drawerStoryboard().instantiateViewControllerWithIdentifier(storyboardId)
return viewController
}
func drawerSettingsViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.LoginViewConSid)
return viewController
}
func sourcePageViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.SettingsViewConID)
return viewController
}
func navigationBarController() -> UIViewController{
let viewController = viewControllerForStoryboardId(StoryboardIDs.NavConSid)
return viewController
}
private func leftViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.LeftViewConID)
return viewController
}
private func rightViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.RightViewConID)
return viewController
}
func toggleLeftDrawer(sender:AnyObject, animated:Bool) {
_drawerViewController?.toggleDrawer(.Left, animated: animated, complete: { (finished) -> Void in
// do nothing
})
}
func toggleRightDrawer(sender:AnyObject, animated:Bool) {
_drawerViewController?.toggleDrawer(.Right, animated: animated, complete: { (finished) -> Void in
// do nothing
})
}
func closeDrawer(sender:AnyObject, animated:Bool){
_drawerViewController?.closeDrawer(.Left, animated: animated, complete: { (finished) -> Void in
})
}
private var _centerViewController: UIViewController?
var centerViewController: UIViewController {
get {
if let viewController = _centerViewController {
return viewController
}
return drawerSettingsViewController()
}
set {
if let drawerViewController = _drawerViewController {
drawerViewController.closeDrawer(drawerViewController.currentlyOpenedSide, animated: true) { finished in }
if drawerViewController.centerViewController != newValue {
drawerViewController.centerViewController = newValue
}
}
_centerViewController = newValue
}
}
Any help/suggestions would be appreciated :D
Just gonna put this here in case anyone has similar problems.
After a week long struggle to find the problem. I eventually found that whenever I changed the centreViewController with
appDelegate.centerViewController = appDelegate.navigationBarController()
OR
appDelegate.centerViewController = appDelegate.logoutController()
that the methods
deinit {
print("deinit called")
notifCentre.removeObserver(self)
}
were not being called in any of the viewControllers.
So I added the line
self.dismissViewControllerAnimated(false, completion: {})
every time that I change the centreViewController.
Apparently Swift normally deinits automagically but when the using the third party methods there is some confusion with the memory handler and we need to step in. Good to know though as it could be a general swift issue as well.

Resources