Swift - Present UIAlertController in extension function - swift

I've wrote a label extension which detect links in the label.
It's attributed text with NSLinkAttributeName for links and I'm using
func handleTapOnLabel(_ tapGesture:UITapGestureRecognizer){} to detect where is the tap and which link to choose to open. The problem is that I use the function in extension for UILabel and I want to present an UIAlertController to ask the person - "You are about to open this link in Safari. Would you like to proceed?"... So I can't access viewcontroller to use the function present(UIAlertController... to display the alert. Any suggestions how this can be happened in extension? How to access label's viewcontroller directly from extension ?

Method 1) Present UIAlertController on top most ViewController in ViewController's hierarchy
extension UILabel { //your extension
func openLinkAction() { //your extension custom method
//code
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
}
// topController should now be your topmost view controller
// present alert on this controller
}
//and present here with vcInstance
}
}
Method 2) Pass ViewController's object as parameter
extension UILabel { //your extension
func openLink(vc: UIViewController) { //your extension custom method function
// code
// present alert on this vc
}
}
// For getting top most controller you ca use..
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
// topController
if let topController = UIApplication.topViewController() {
}

Related

viper module without link on view

I downloaded template for viper module and it has no link on view, how do I use navigation here?
Scene Delegate
let startModule = CardsCollectionRouter.createModule()
let navigationController = UINavigationController(rootViewController: startModule)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
Router
class CardsCollectionRouter: PresenterToRouterCardsCollectionProtocol {
// MARK: Static methods
static func createModule() -> UIViewController {
let viewController = CardsCollectionViewController()
let presenter: ViewToPresenterCardsCollectionProtocol & InteractorToPresenterCardsCollectionProtocol = CardsCollectionPresenter()
let networkService = NetworkService()
viewController.presenter = presenter
viewController.presenter?.router = CardsCollectionRouter()
viewController.presenter?.view = viewController
viewController.presenter?.interactor = CardsCollectionInteractor(networkService: networkService)
viewController.presenter?.interactor?.presenter = presenter
return viewController
}
func showDescription(description: CharacterModel?) {
let descriptionVC = DescripModuleRouter.createModule(description: description)
//How to go on next screen??
let viewController = CardsCollectionViewController()
viewController.navigationController?.pushViewController(descriptionVC, animated: true)
}
}
may be I have an easy way to get a link to the view?
like view.goToAnotherScreen from router and I will have this method on my ViewController
You shouldn't have to create navigation methods outside your router when using this pattern, as navigation is the router's responsibility. You can create router.goToAnotherScreen and call it from your view controller. When creating the router, you should pass the view controller to the router and inside your router you should call viewController.navigationController?.push...
class CardsCollectionRouter: PresenterToRouterCardsCollectionProtocol {
// MARK: Dependencies
private weak var viewController: UIViewController!
// MARK: Initialization
init(viewController: UIViewController) {
self.viewController = viewController
}
// MARK: Static methods
static func createModule() -> UIViewController {
let viewController = CardsCollectionViewController()
let presenter = CardsCollectionPresenter()
let networkService = NetworkService()
viewController.presenter = presenter
viewController.presenter?.router = CardsCollectionRouter(viewController: viewController)
viewController.presenter?.view = viewController
viewController.presenter?.interactor = CardsCollectionInteractor(networkService: networkService)
viewController.presenter?.interactor?.presenter = presenter
return viewController
}
func showDescription(description: CharacterModel?) {
let descriptionVC = DescripModuleRouter.createModule(description: description)
//How to go on next screen: Use your VC instance, which is the one your VIPER scene is controlling
self.viewController.navigationController?.pushViewController(descriptionVC, animated: true)
}
}

swift get topmostViewController avoid UIAlertController

i want to get topMostVC so i did this function:
func topMostViewController() -> UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController()
}
if let navigation = self as? UINavigationController {
return navigation.visibleViewController?.topMostViewController() ?? navigation
}
if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController() ?? tab
}
return self
}
Then called using:
AppDelegate.shared.window?.rootViewController?.topMostViewController()
But i have problem is when UIAlertController is showing in screen, it's return UIAlertController, not my expect viewcontroller
Can anyone tech me how to avoid UIAlertController, thanks
Just add one more condition
if let presented = self.presentedViewController,
!(presented is UIAlertController) { // <== Here or use !presented.isKind(of: UIAlertController.self)
return presented.topMostViewController()
}

Get the instance of another view controller in the same tab bar

My tab bar has 3 tabs. If I'm on the view controller of tab 1 first and then sometime later in the app I end up in tab 2, how do I get the instance of tab 1 (in code) that is currently loaded? The view controller's viewDidLoad function only gets called once so as long as I visited a tab, it's view controller is still somewhere.
Is it possible to get a reference to an instance of another view controller in a tab bar controller? I don't want to instantiate a new view controller, I want the current instance that has been loaded.
Yes, it is possible. You can get it's instance by providing the index of the UIViewController you want to the viewCotnrollers property in the UITabBarController.
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
let firstViewController = viewControllers?[0]
print(firstViewController?.title)
}
}
If you're trying to get the firstViewController from the secondViewController which is embedded inside the UINavigationController use this:
class SecondController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
let firstViewController = navigationController?.tabBarController?.viewControllers?[0]
print(firstViewController?.title)
}
}
And here's the extended version in case you're not sure:
class SecondController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// after loading is done
if let navigationController = navigationController {
if let tabBarController = navigationController.tabBarController {
let firstViewController = tabBarController.viewControllers?[0]
print(firstViewController?.title)
} else {
print("I'm not embedded in a tabBar controller")
}
} else {
print("I'm not embedded in a navigation controller")
if let tabBarController = tabBarController {
let firstViewController = tabBarController.viewControllers?[0]
print(firstViewController?.title)
} else {
print("I'm not embedded in a tabBar controller")
}
}
}
}
If you are inside a vc in tab 2 for example, you can get viewController from tab 1 like this:
if let firstTabVc = tabBarController?.viewControllers.first {
//you have vc here
}

IOS swift fatal error nil exception when trying to access TabBar

I have the tabBar below and each of those TabBars belongs to it's own specific UIViewController . My issue is with my Home Tab Bar . That TabBar has a TableView if I am in another TabBar item like 'Search' or 'Notification' and go back to my Home tab Bar item then I want my TableView to remain in the same place . I have been able to do that by calling this
controller.dismiss(animated: false, completion: nil)
however as you can see from the image below my Home Tab Bar item does not get highlighted and I created a method that attempts to get my Home Tab highlighted but I get a nil exception . First let me show you my custom function all Tab Bar items on click go through here
class HomeProfile: NSObject {
var mycontroller = ControllerEnum()
func TabBarLogic(_ tabBar: UITabBar, didSelect item: UITabBarItem, streamsModel: Int, controller: UIViewController) {
if tabBar.items?.index(of: item) == 0 {
tabBar.tintColor = UIColor(hexString: "#004d99")
if TabBarCounter == 0 {
// To Refresh
let nextViewController = controller.storyboard?.instantiateViewController(withIdentifier: "HomeC") as! HomeC
controller.present(nextViewController, animated:false, completion: nil)
} else {
// keeps TableView position but does not highlight Tab
controller.dismiss(animated: false, completion: nil)
let Home = HomeC()
Home.HighlightTabBar() // Nil exception here
TabBarCounter = 0
}
}
if tabBar.items?.index(of: item)! == 1 {
// Search Tab Item
tabBar.tintColor = UIColor(hexString: "#004d99")
let nextViewController = controller.storyboard?.instantiateViewController(withIdentifier: "LocalSearchC") as! LocalSearchC
controller.present(nextViewController, animated:false, completion:nil)
TabBarCounter = 1
}
if tabBar.items?.index(of: item)! == 2 {
// Post
}
if tabBar.items?.index(of: item)! == 3 {
// Notification
}
if tabBar.items?.index(of: item)! == 4 {
// Menu
}
}
}
This functionality is meant to be similar to facebook, if you are on the HomePage and click the Home Tab again then it will refresh the TableView however if you are on another Tab and go back to the Home tab it'll maintain your position; The TabBarCounter checks on that .
Ok this is my Home Controller code for the Tab
class HomeC: UIViewController,UITableViewDataSource,UITableViewDelegate, UITabBarDelegate{
#IBOutlet weak var TabBar: UITabBar!
override func viewDidLoad() {
super.viewDidLoad()
TabBar.delegate = self
TabBar.selectedItem = TabBar.items![0]
TabBar.tintColor = UIColor(hexString: "#004d99")
}
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
homeProfile.TabBarLogic(tabBar, didSelect: item, streamsModel: TabCounter,controller: self)
}
func HighlightTabBar() {
TabBar.delegate = self
TabBar.selectedItem = TabBar.items![0] // this fails
TabBar.tintColor = UIColor(hexString: "#004d99")
}
}
I know that the reason why it fails is because the TabBar in HighlightTabBar() when calling it does not have the reference to the TabBar . How can I make it so that this works . I have been looking at this one How to programmatically change UITabBar selected index after Dismiss? but so far nothing works .
Put the code in App delegate or button action
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let tabBarController = appDelegate.window?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 2

Get the top ViewController in iOS Swift

I want to implement a separate ErrorHandler class, which displays error messages on certain events. The behavior of this class should be called from different other classes.
When an error occurs, it will have an UIAlertView as output.
The display of this AlertView should ALWAYS be on top. So no matter from where the Error gets thrown, the topmost viewController should display the AlertMessage (e.g. when an asynchronous background process fails, I want an error message, no matter what View is displayed in the foreground).
I have found several gists which seem to solve my problem (see the code below).
But calling UIApplication.sharedApplication().keyWindow?.visibleViewController() does return a nil-value.
Extension from gist
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {
let navigationController = vc as! UINavigationController
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)
} else if vc.isKindOfClass(UITabBarController.self) {
let tabBarController = vc as! UITabBarController
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!)
} else {
if let presentedViewController = vc.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
} else {
return vc;
}
}
}
}
Amit89 brought a way to a solution up. You have to call the .windowproperty of the AppDelegate.
So I changed the Swift code from the link below to work as intended to find the topmost ViewController. Make sure, that the view is already in the view hierarchy. So this method cannot be called from a .viewDidLoad
Extension to find the topmost ViewController*
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
This code originated from GitHub user Yonat in a comment to an objectiveC equivalent. I only changed the bits of code to get it to work without the .keyWindow property
Did you try this from the same link?
let appDelegate = UIApplication.sharedApplication().delegate as! MYAppDelegate//Your app delegate class name.
extension UIApplication {
class func topViewController(base: UIViewController? = appDelegate.window!.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}