Swapping centreViewControllers with FloatingDrawers - ios

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.

Related

Callback is not working when button tapped

I want to trigger an action on button tap with my callback. Also I have presenter and coordinator. But nothing happenes. My code is not working in this closure:
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
In my ViewController I have enum:
enum StartViewControllerButton {
case registrationButtonTapped
case loginButtonTapped
}
callback:
var output: ((StartViewControllerButton) -> Void)?
and selectors:
#objc func registrationButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.registrationButtonTapped)
}
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
My Presenter
class StartModulPresenter: StartModulPresenterProtocol {
var navigationController: UINavigationController
var coordinator: CoordinatorProtocol?
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
coordinator = AuthorizationCoordinator(navigationController: navigationController)
}
//Functions
func openNextScreen() {
coordinator?.start()
}
}
My Coordinator:
class AuthorizationCoordinator: RegistrationCoordinatorProtocol {
var presenter: PresenterProtocol?
var navigationController: UINavigationController
var childCoordinators: [CoordinatorProtocol] = []
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
private func showRegistrationViewController() {
let registrationViewController = RegistrationViewController()
registrationViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(registrationViewController, animated: true)
}
private func showLoginViewController() {
let loginViewController = LoginViewController()
loginViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(loginViewController, animated: true)
}
}
Could you check if startViewController is pushed/presented or not?
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
And, is self.output is nil or not? If it is nil please check your assignment call, it needed to be called before you use this variable.
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
Honestly, I don't recommend you to use this design pattern, just a simple thing but the real result is too complicated.
Just use protocol-based MVC. View communicate with Controller via protocol/closure or Reactive-based with Combine (PassthroughSubject/CurrentValueSubject)

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
}
}

Flutter adding new ViewController for BlinkId plugin

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

Open ViewController (which is a navigationController within a tabBarController) and run function

I want to allow the user to open an image up in my app from their email. I have set up functions in the app delegate as follows to navigate to my settingsTableViewController. The settingsTableViewController is a navigationController and that is a tabBarViewController.
func application(app: UIApplication, openURL url: NSURL,
options: [String : AnyObject]) -> Bool {
self.window!.visibleViewController
goToSecond()
return true
}
func goToSecond() {
let tabBarController: UITabBarController = (self.window?.rootViewController as? UITabBarController)!
tabBarController.selectedIndex = 3
}
The extension I used to make the above work was:
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
The above works until I add my code in to pass the url to the settingsTableViewController as follows:
let vc = self.window?.rootViewController as! SettingsTableViewController
vc.displayImage(url)
I get the following error:
Could not cast value of type '.TabBarController' (0x100161c00) to '.SettingsTableViewController'
Any suggestions?
Your error tells you exactly what the problem is. The RVC is actually a TabBarController, not your SettingsTableViewController. This means you need to access the TabBarController's viewController array until you arrive at your SettingsTableViewController

Resources