AppDelegate PUSH viewcontroller - ios

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 {

Related

pushing ViewController over current ViewController from deeplink swift

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.

How to perform action on tap of Push Notification rather than performing action by itself when app is in foreground?

currently I am using below-mentioned code
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void){
completionHandler(.alert)
let aps = notification.request.content.userInfo[AnyHashable("aps")] as! NSDictionary
let alert = aps["alert"] as! String
if alert == "Welcome to XYZ. To place order please click here"{
let stryBrd = UIStoryboard(name: "Main", bundle: nil)
let vc = stryBrd.instantiateViewController(withIdentifier: "viewController") as! viewController
self.window?.rootViewController = vc
}else{
print("false")
}
}
But in this code app is running automatically to desired view controller whereas I want it to happen only if a user taps on the notification.
Whereas it is working fine when an app is in the background. It's only happening if we are clicking on the notification
It should be the same as WhatsApp. When you are chatting with Mr ABC and miss PQR sends you a text then it will show push but won't do anything unless you tap on the push to open Miss PQR chat
Use UIAlertController to show push notification and on tap of button write the code to perform action
let stryBrd = UIStoryboard(name: "Main", bundle: nil)
let vc = stryBrd.instantiateViewController(withIdentifier: "viewController") as! viewController
self.window?.rootViewController = vc
More explanation:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void){
completionHandler(.alert)
let aps = notification.request.content.userInfo[AnyHashable("aps")] as! NSDictionary
let alert = aps["alert"] as! String
if alert == "Welcome to XYZ. To place order please click here"{
//show alert to the user after getting push notification
let myalert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
myalert.addAction(UIAlertAction(title: "OK", style: .default) { (action:UIAlertAction!) in
//perform action of push notification
let stryBrd = UIStoryboard(name: "Main", bundle: nil)
let vc = stryBrd.instantiateViewController(withIdentifier: "viewController") as! viewController
self.window?.rootViewController = vc
})
myalert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { (action:UIAlertAction!) in
//handle cancel event
})
currentViewController.present(myalert, animated: true)
}else{
print("false")
}
}

How can I restart an application programmatically in Swift 4 on iOS?

I have issues. After language changing, I want to restart my application.
So I want to get an alert message with the text "Do you want to restart app to change language?" "Yes" "No"
And if the user presses YES, how can I restart the app?
My solution:
let alertController = UIAlertController(title: "Language".localized(), message: "To changing language you need to restart application, do you want to restart?".localized(), preferredStyle: .alert)
let okAction = UIAlertAction(title: "Yes".localized(), style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("OK Pressed")
exit(0)
}
let cancelAction = UIAlertAction(title: "Restart later".localized(), style: UIAlertActionStyle.cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
After the app will close, the user will manually start the app.
You cannot restart an iOS app. One thing you could do is to pop to your rootViewController.
func restartApplication () {
let viewController = LaunchScreenViewController()
let navCtrl = UINavigationController(rootViewController: viewController)
guard
let window = UIApplication.shared.keyWindow,
let rootViewController = window.rootViewController
else {
return
}
navCtrl.view.frame = rootViewController.view.frame
navCtrl.view.layoutIfNeeded()
UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
window.rootViewController = navCtrl
})
}
In one of my apps, I needed to restart. I wrapped all of the loading logic into a LaunchScreenViewController. Above is the piece of code for "restarting the app".
It's not perfect, but I solved this by sending a timed local notification just before exiting the application. Then the user only needs to click on the notification to restart the app so they don't need to look for the app to relaunch it. I also suggest displaying an alert informing the user of the restart:
import UserNotifications
import Darwin // needed for exit(0)
struct RestartAppView: View {
#State private var showConfirm = false
var body: some View {
VStack {
Button(action: {
self.showConfirm = true
}) {
Text("Update Configuration")
}
}.alert(isPresented: $showConfirm, content: { confirmChange })
}
var confirmChange: Alert {
Alert(title: Text("Change Configuration?"), message: Text("This application needs to restart to update the configuration.\n\nDo you want to restart the application?"),
primaryButton: .default (Text("Yes")) {
restartApplication()
},
secondaryButton: .cancel(Text("No"))
)
}
func restartApplication(){
var localUserInfo: [AnyHashable : Any] = [:]
localUserInfo["pushType"] = "restart"
let content = UNMutableNotificationContent()
content.title = "Configuration Update Complete"
content.body = "Tap to reopen the application"
content.sound = UNNotificationSound.default
content.userInfo = localUserInfo
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.5, repeats: false)
let identifier = "com.domain.restart"
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.current()
center.add(request)
exit(0)
}
}
You can change your root controller and don't need to restart it. Just change the root view controller or update or refresh or recall the root view controller:
let alertController = UIAlertController(title: "Language".localized(), message: "To change language you need to restart the application. Do you want to restart?".localized(), preferredStyle: .alert)
let okAction = UIAlertAction(title: "Yes".localized(), style: UIAlertActionStyle.default) {
UIAlertAction in
// Change update / refresh rootview controller here...
}
You can add to AppDelegate
func resetApp() {
UIApplication.shared.windows[0].rootViewController = UIStoryboard(
name: "Main",
bundle: nil
).instantiateInitialViewController()
}
Call this function where you want
let appDelegate = AppDelegate()
appDelegate.startWith()
Add this to your viewDidLoad for starting the viewcontroller (VC):
override func viewDidLoad() {
super.viewDidLoad()
// Make dismiss for all VC that was presented from this start VC
self.children.forEach({vc in
print("Dismiss \(vc.description)")
vc.dismiss(animated: false, completion: nil)
})
// ....
}
And in the restart initiator:
// ...
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "startVC")
self.present(vc, animated: false, completion: nil)

push root viewcontroller from appdelegate

My app rootviewcontroller is with tab bar, but when I use firebase push notification it only allows me "present" method. I want to view the controller with push (like child) to have the tab bar and back option when i view this view controller.
In my code I want to transfer from present to PUSH but i cant find solution. currentController.present(controller, animated: true, completion: nil)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
if Defaults.hasKey(.logged), let logged = Defaults[.logged], logged == true {
//TODO
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)
}
}
}
print(userInfo)
}
completionHandler()
}
The easiest way to do it would be to wrap your root view controller in a UINavigationController in your storyboard. This would make the actual root controller a UINavigationController rather than a UITabBarController.
once that is done you can simply do
if let window = self.window, let rootNavController = window.rootViewController as? UINavigationController {
navigationController?.pushViewController(controller, animated: true)
}
Alternately, if all you're really looking for is a "Slide from the right" animation and don't need a full navigation stack, you could write a custom view controller transition that gave a similar effect using modal controllers.
I have not tested yet, but I believe it's just works. Try this:
func pushViewController() {
let storyboard = UIStoryboard.init(name: "YourStoryboardName", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourTabBarController") as! UITabBarController
let navigationController = storyboard.instantiateViewController(withIdentifier: "YourNavigationController") as! UINavigationController
let productDetailsViewController = storyboard.instantiateViewController(withIdentifier: "ProductDetailsViewController") as! ProductDetailsViewController
tabBarController.viewControllers = [navigationController]
if tabBarController.selectedViewController == navigationController {
navigationController.pushViewController(productDetailsViewController, animated: true)
}
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
}

presentViewController() pops the view behind the last view

I'm trying to perform a transition to another UIViewController in another storyboard but the problem is that the view that I want to display pops behind the view where I'm from.
I know that because my app includes a Snapchat-like navigation between views, so I can drag the view to the left or the right and I can see the view behind.
Here is how I attempt to do that :
#IBAction func account(sender: UIButton) {
if self._isOnline {
self.pageController?.reverseViewControllerAnimated(true)
}
else {
let alertView = UIAlertController(title: "blablablabla", message: "blablablabla", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "No", style: .Cancel, handler: nil))
alertView.addAction(UIAlertAction(title: "Yes", style: .Default, handler: { (alertAction) -> Void in
let storyboard = UIStoryboard(name: "SignUpLogin", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateInitialViewController() as! UIViewController
self.presentViewController(vc, animated: false, completion: nil)
}))
presentViewController(alertView, animated: true, completion: nil)
}
}
With SignUpLogin that is the name of my second .storyboard file, here I'm in Heart.storyboard.
The action takes place in a UIAlertController, if the user press no, nothing append, if he press yes he's supposed to be redirected to the initial view of my SignUpLogin.storyboard file.
My problem come from the view at the bottom left corner. And if I use the code above in the view at the top right corner, that works ! Why ?
I have no idea of how I can do differently.
I've found the solution to my problem !
#IBAction func account(sender: UIButton) {
if self._isOnline {
self.pageController?.reverseViewControllerAnimated(true)
}
else {
let alertView = UIAlertController(title: Constants.LocalizedJoinOurBand, message: Constants.LocalizedNeedToCreatAnAccount, preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: Constants.LocalizedNo, style: .Cancel, handler: nil))
alertView.addAction(UIAlertAction(title: Constants.LocalizedYes, style: .Default, handler: { (alertAction) -> Void in
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
let storyboard = UIStoryboard(name: "SignUpLogin", bundle: NSBundle.mainBundle())
let vc = storyboard.instantiateInitialViewController() as! UIViewController
UIApplication.sharedApplication().keyWindow?.rootViewController = vc
}
}))
presentViewController(alertView, animated: true, completion: nil)
}
}
I don't know why in this case particularly but I have to directly set the rootViewController of my AppDelegate with this line :
UIApplication.sharedApplication().keyWindow?.rootViewController = vc

Resources