pushing ViewController over current ViewController from deeplink swift - ios

I am using deeplink in my application and from the central area, I am trying push a screen in another ViewController based on my deeplink.
The issues I have is that when I cancel the pushed ViewController, the entire application stack is dismissed and I just want to pop back to the presenting viewcontroler.
func getCurrentNanvigationController() -> UINavigationController? {
//targeted UINavigationController
var navigationController: UINavigationController? = nil
if let nav = window?.rootViewController as? UINavigationController {
if let topNav = window?.topViewController()?.navigationController {
navigationController = topNav
}
else{
navigationController = nav
}
}
// Wallet Module is the stand alone module, which means its not embeded in the Navigator app
else if let tabBar = window?.rootViewController as? UITabBarController,
let nav = tabBar.selectedViewController as? UINavigationController {
navigationController = nav
}
else {
//should not happen, window root controller shouldbe be either UINavigationController or UITabBarController
}
return navigationController
}
public extension UIWindow {
func topViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
This is the currentViewController that I am trying to push another controller over from the deeplink
#objc func addNavBarItemTapped() {
let storyBoard = UIStoryboard(storyboard: .addVC, bundle: .main)
let controller = storyBoard.instantiateViewController(withIdentifier: "AddViewController")
self.navigationController?.pushViewController(controller, animated: true)
}
How can I effectively push over the AddViewController because that is the presenting ViewController and when I dismiss the presented ViewController from the navigation stack, I am not removing the entire app navigation but instead returning back to AddViewController
How the dismissal of this view controller is achieved is like this.
public extension UIViewController {
func alert(title: String,
message: String? = nil ,
attributedMessage: NSMutableAttributedString? = nil,
okAction: AlertActionButton = ("ok_button".fpxLocalizedText, .default, nil),
cancelAction: AlertActionButton = (nil, .cancel, nil),
complete: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
if let attributedMessage = attributedMessage {
alertController.setValue(attributedMessage, forKey: "attributedMessage")
}
let oKAction = UIAlertAction(title: okAction.0, style: okAction.1, handler: okAction.2)
if let cancelButtonTitle = cancelAction.0 {
let cancelAction = UIAlertAction(title: cancelButtonTitle, style: cancelAction.1, handler: cancelAction.2)
alertController.addAction(cancelAction)
}
alertController.addAction(oKAction)
self.present(alertController, animated: true, completion: complete)
}
}
In the presented Viewcontroller
public func declineButtonClicked() {
alert(title: "decline_warning_title".fpxLocalizedText,
message: "decline_warning_message".fpxLocalizedText,
okAction: ("yes_button".fpxLocalizedText, .destructive, { _ in self.sendDeclineRequest() }),
cancelAction: ("decline_cancel_button".fpxLocalizedText, .cancel, { _ in self.dismiss(animated: true, completion: nil) }))
}
Also sometimes if I have a presented Viewcontroller and need to show this ViewController, it is often presented in the background of the presented viewcontroller

In iOs push duty perform with UINavigationController
wherever you need to push so you need to UINavigationController.
You should Create another UInavigationController in Deep Link,
let storyBoard = UIStoryboard(storyboard: .addVC, bundle: .main)
let controller = storyBoard.instantiateViewController(withIdentifier: "AddViewController")
let navigationController = UINavigationController(rootViewController:controller)
self.navigationController?.present(navigationController, animated: true, completion: nil)
or
window?.getCurrentNanvigationController().present(navigationController, animated: true, completion: nil)
so now you have a new navigation controller that work stand alone.

Related

How to present UIAlertController in front of modal sheet in Swift

I'm trying to show an alert with a textfield from a modal in swift, but getting an error when I try to show it.
I have to use a UIAlertController because the default alert in Swift doesn't support Textfields currently.
I am calling my modal from my base view with
.sheet(isPresented: $showingLocationSheet) {
LocationSelectionView(selectedLocations: $locations)
}
From my LocationSelectionView, I then call the alert as follows:
let alert = UIAlertController(title: "Add Location", message: "Enter Location Name", preferredStyle: .alert)
alert.addTextField { (name) in
name.placeholder = "Bathroom"
}
let add = UIAlertAction(title: "Add", style: .default) { _ in
print("add Location")
}
let cancel = UIAlertAction(title: "Cancel", style: .destructive)
alert.addAction(add)
alert.addAction(cancel)
DispatchQueue.main.async {
UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true)
}
but then I receive this error:
Attempt to present <UIAlertController: 0x7fc8a78c7c00> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7fc8a800b8d0> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x7fc8a800b8d0>) which is already presenting <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x7fc8a6e51980>.
I'm not quite sure how to get it to show here. I've also tried using an extension on the UIAlertController like this:
public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindow.Level.alert + 1 // Swift 3-4: UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
as well as getting the top most controller like so:
func topmostController() -> UIViewController? {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
return nil
}
but no luck. Any ideas?

Prevent presenting the UIAlertViewController after navigating to the other view

I have one scenario when the user did not use the application for more than 5 min app will show a popup with session expiration message.
The code for session expiration is added in the appDelegate and from there the popup will be presented on the current view controller.
code is
#objc func applicationDidTimeout(notification: NSNotification) {
if (window?.rootViewController?.isKind(of: UITabBarController.self))! {
for view in window?.rootViewController?.view.subviews ?? [(window?.rootViewController?.view)!] {
if view.isKind(of: MBProgressHUD.self) {
return
}
}
if window?.rootViewController?.presentedViewController != nil {
window?.rootViewController?.dismiss(animated: true, completion: {
self.showMessage(message: Message.sessionTimeout)
})
} else {
self.showMessage(message: Message.sessionTimeout)
}
}
}
fileprivate func showMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
DispatchQueue.main.async {
UIView.transition(with: self.window!, duration: 0.3, options: UIView.AnimationOptions.transitionCrossDissolve, animations: {
CommonFunctions.setLoginAsRootVC()
}, completion: nil)
}
}
alert.addAction(actionOkay)
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Now if the user is doing some data entry and at that time, if the user leaves application ideal for 5 min or more the keyboard will dismiss and the session expiration message shown there.
But as the text field's delegate method textFieldShouldEndEditing has some validation and if that validation fails it shows a popup with the message and ok button.
So when the user taps on the ok button in the session expiration message popup, it will redirect the user to the login screen but due to the text field's delegate method validation, it shows one pop up in the login screen.
Code for the validation fail message popup is
fileprivate func showErrorMessage(message: String) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
self.txtField.becomeFirstResponder()
}
alert.addAction(actionOkay)
self.present(alert, animated: true, completion: nil)
}
How to prevent the popup from being present in the login screen?
I try to get the proper way to prevent the popup from appearing on the login screen.
But Finally, I found one heck to solve this issue.
I have declared one boolean in AppDelegate and set it's value to false when I want to prevent the popup from appearing and then revert it back to true when I want to show the popup.
I know this is not the elegant or efficient solution for the issue, but it works for now.
If anyone knows the better answer can post here, I'm still open to any better solution.
#objc func applicationDidTimeout(notification: NSNotification)
{
let visibleView : UIViewController = self.getVisibleViewControllerFrom(self.window?.rootViewController)!
self.showMessage(message: Message.sessionTimeout,Controller: visibleView)
}
fileprivate func showMessage(message: String , Controller : UIViewController) {
let alert = UIAlertController(title: appName, message: message, preferredStyle: .alert)
let actionOkay = UIAlertAction(title: "OK", style: .default) { (action) in
//Now apply your code here to set login view controller as rootview
// This controller is for demo
window!.rootViewController = UIStoryboard(name: "Main", bundle:
nil).instantiateViewController(withIdentifier: "loginview")
window!.makeKeyAndVisible()
}
alert.addAction(actionOkay)
Controller.present(alert, animated: true, completion: nil)
}
//MARK:- Supporting method to get visible viewcontroller from window
func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return self.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return self.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return self.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
Try this code, I've use this code many times may be it's work for you.

How present a UIAlert in a Skscene

When presenting a UIAlert in SKScene nothing shows up
Here is the code
var alertController = UIAlertController(title: "Nothing Selected",
message: "You have selected a picture.",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "HI!", style: UIAlertActionStyle.cancel, handler: nil))
self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
From within the scene you need to present the Alert Controller at the root View Controller level.
if let vc = self.scene?.view?.window?.rootViewController {
vc.present(alertController, animated: true, completion: nil)
}
Try to present from the top view controller returned by this extension (taken from this post):
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
}
}

Dispatch asyncAfter for multiple UIAlertControllers

I have a dispatch async where I expect 4 alerts pop up on the screen..and then each get dismissed before a new alert is to be shown. (I've set a 3 second delay in between my Alert)
class ViewController: UIViewController {
var counter = 1
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...4{
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0 * Double(counter) , execute: {
print("called")
self.showAlert()
})
}
}
func showAlert(){
let alert = UIAlertController(title: "sampleTitle \(counter)", message: "sampleMessage \(counter)", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
counter += 1
if self.presentedViewController != nil {
dismiss(animated: true, completion: nil)
UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
}else{
UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
}
}
}
Problem1: But for some reason the entire for loop is executed without any delay in between. I'm guessing I'm not understanding something about main queue being a serial queue.
Problem2: And I also get the following logs in my console, even though I'm dismissing the presentedViewController.
called
called
called
called
2017-06-26 11:10:57.000 topViewAndAlertTest[3360:210226] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fe630c03350> while a presentation or dismiss is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to present <UIAlertController: 0x7fe630c04180> on <UIAlertController: 0x7fe630f06fe0> while a presentation is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fe630c03350> while a presentation or dismiss is in progress!
2017-06-26 11:10:57.001 topViewAndAlertTest[3360:210226] Warning: Attempt to present <UIAlertController: 0x7fe630c06b40> on <UIAlertController: 0x7fe630f06fe0> while a presentation is in progress!
FYI My topviewcontroller is using the code from this answer
Problem3: Only 2 alerts pop...I never see the 3rd, 4th alerts!
EDIT:
After rmaddy's suggestion, my errors are slightly changed:
called
called
called
called
2017-06-26 11:59:33.417 topViewAndAlertTest[4834:441163] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fb596d05a30> while a presentation or dismiss is in progress!
2017-06-26 11:59:33.417 topViewAndAlertTest[4834:441163] Warning: Attempt to dismiss from view controller <topViewAndAlertTest.ViewController: 0x7fb596d05a30> while a presentation or dismiss is in progress!
I get 2 less warnings. But still: As soon as alert 1 is on screen, alert 2 dismisses it and that's it! No delay no 3rd,4th alert!
Details
xCode 8.3.2, Swift 3.1
Full Code
import UIKit
class ViewController: UIViewController {
var counter = 1
private var alertViewController: UIAlertController?
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .utility).async {
for _ in 1...4 {
sleep(2)
DispatchQueue.main.async {
print("called")
self.showAlert()
}
}
}
}
private func createAlertView() -> UIAlertController {
let alertViewController = UIAlertController(title: "sampleTitle \(counter)", message: "sampleMessage \(counter)", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alertViewController.addAction(action)
return alertViewController
}
func showAlert(){
let presentAlert = {
DispatchQueue.main.async { [weak self] in
if let _self = self {
_self.alertViewController = _self.createAlertView()
UIApplication.topViewController()?.present(_self.alertViewController!, animated: true, completion: nil)
}
}
}
DispatchQueue.main.async { [weak self] in
if let alertViewController = self?.alertViewController {
alertViewController.dismiss(animated: true) {
presentAlert()
}
} else {
presentAlert()
}
self?.counter += 1
}
}
}
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.shared.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
}
}

AppDelegate PUSH viewcontroller

AppDelegate - How to push my viewcontroller instead of present, the reason is that I want to have the tab bar on my next viewcontroller with navigation bar too. I tryed this: "controller.navigationController?.pushViewController(controller, animated: true)" but It gives me fatal error, found nil while unwraping optional value. Any idea?
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
if Defaults.hasKey(.logged), let logged = Defaults[.logged], logged == true {
let userInfo = notification.request.content.userInfo
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
let topWindow = UIWindow(frame: UIScreen.main.bounds)
topWindow.rootViewController = UIViewController()
topWindow.windowLevel = UIWindowLevelAlert + 1
let alert = UIAlertController(title: userInfo["title"] as? String, message: userInfo["body"] as? String, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Show Product", comment: "confirm"), style: .default, handler: {(_ action: UIAlertAction) -> Void in
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ProductDetailsViewController") as? ProductDetailsViewController {
controller.productFromNotificationByCode(code: userInfo["product_code"] as! String, productID: userInfo["product_id"] as! String)
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "cancel"), style: .destructive, handler: {(_ action: UIAlertAction) -> Void in
topWindow.isHidden = true
}))
topWindow.makeKeyAndVisible()
topWindow.rootViewController?.present(alert, animated: true, completion: { _ in })
}
}
completionHandler([])
}
First off, you should keep your AppDelegate as clean as possible. The app delegate should really only handle things that are happening right when the app launches. More often than not, that's just going to be setting your initial window (i.e. what will be the entrance into your app). So you should come up with whatever your first view controller and set that to your rootViewController and window in FinishedLaunching in the AppDelegate. If you want to use a UINavigationController (sounds like you do) you can embed the ViewController you want to start with in the UINavigationController (UINavigationController(FirstViewController())) and set the navigation controller to the rootViewController.
It sounds like you haven't done this step of embedding the view controller you're using in the UINavigationController. Once you've done this step, you should be able to just use show(nextViewController) which is a function on the UIViewController that you want to "push" from. If you're in a UINavigationController, this will do the Push animation and the next view will be part of the navigation stack, like you're wanting.
The solution for me was to duplicate the ProductViewController and set storyboard id. In the code below write the storyboard of the new viewcontroller in withIdentifier, in my case "NotificationProductDetailsViewController". Dont forget to add Go Back Button in this viewcontroller.
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NotificationProductDetailsViewController") as? ProductDetailsViewController {

Resources