How can I restart an application programmatically in Swift 4 on iOS? - 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)

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?

Present alert after dismissing View Controller

I'm using the newest Xcode and Swift version.
I'm presenting a specific View Controller like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let contactViewController = storyboard.instantiateViewController(identifier: "contactViewController")
show(contactViewController, sender: self)
I'm dismissing this View Controller like this:
self.presentingViewController?.dismiss(animated: true, completion: nil)
I want to present an UIAlertController right after dismissing the View Controller.
This:
self.presentingViewController?.dismiss(animated: true, completion: nil)
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
self.present(alertMessage, animated: true, completion: nil)
… of course doesn't work because I cannot present an UIAlertController on a dismissed View Controller.
What's the best way to present this UIAlertController after the View Controller is dismissed?
You can do it in completion handler by getting top controller like this
self.presentingViewController?.dismiss(animated: true, completion: {
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
UIApplication.getTopMostViewController()?.present(alertMessage, animated: true, completion: nil)
})
Using this extension
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}
Use Jawad Ali's extension, we could anchor the current presented ViewController.
And if you want to dismiss that alert later, you could do it in another completion handler as below code showed. In my case, I save a song to one playlist and dismiss this playlist and show a short time alert to let user know that saving ok.
DispatchQueue.main.async {
self?.removeSpinner()
self?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "Save to playlist", message: nil, preferredStyle: .alert)
UIApplication.getTopMostViewController()?.present(alert, animated: true, completion: {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
alert.dismiss(animated: true)
}
})
})
}

Warning: Attempt to present <UIAlertController:> on <App name:> whose view is not in the window hierarchy

I'm getting this error because of I'm presenting alert in another VC, but how to solve this.
My code is :
{
let message = res[0]["message"] as! String
//If session expaired move to login page
if message == "Session Expired" {
//Session Expired
DispatchQueue.main.async {
//Remove all navigations
self.navigationController?.viewControllers.removeAll()
//Check whether the user logined or not
UserDefaults.standard.set(false, forKey: "isUserLoggedIn")
//Clear user defaults
SharedClass.sharedInstance.clearDataFromUserDefaults()
let lvc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LVC") as! LoginViewController
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = lvc
}
}
//Call alert function
self.showAlert(title: "", msg: message)
}
Alert function :
//Alert function
extension UIViewController {
func showAlert(title: String, msg: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
How to present alert properly in my case....
Try presenting alertController on rootViewController as,
extension UIViewController {
func showAlert(title: String, msg: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
}
The viewController you are calling showAlert method on is not currently presented viewController in the view hierarchy so either you should get the latest presented viewController in the view hierarchy or try presenting on the rootViewController.
You can check this to get the currently presented viewController.
Just use
self.navigationController?.popToRootViewController(animated: false)
or
self.navigationController?.setViewControllers([LoginViewController], animated: true)
instead of
self.navigationController?.viewControllers.removeAll()

How to present an alert after view controller has been dismissed

I have an app where a user uploads a file in the background that usually takes a couple of seconds. The upload is kicked off when they tap a "Done" button and that also dismisses the view controller. What I would I would like to happen is an alert comes up when the download is done. I thought I would just add the code below to the upload function but it isn't working. How can I have an alert box appear to confirm that that the upload was successful?
#IBAction func tapDone(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
if let image = newImage {
submit2Parse(image: image)
}
}
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? "" submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
self.dismiss(animated: true, completion: nil)
}))
self.present(alert,animated: true, completion: nil)
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
The code gives me this build error:
Implicit use of 'self' in closure; use 'self.' to make capture semantics explicit
In your tapDone method, you need to utilize the completion of the dismissal of your controller, like so:
self.dismiss(animated: true) {
if let image = newImage {
self.submit2Parse(image: image)
}
}
This only means that the self.submit2Parse(image: image) will ONLY be executed after you dismiss the controller. Additionally, the error
Implicit use of 'self' in closure; use 'self.' to make capture
semantics explicit
only means that you need to use self. to explicitly call a variable or method when you're inside a closure. So your submit2Parse... would now be like this:
self.submit2Parse(image: image).
If I understood your question correctly you want to dismiss your SecondViewController when user tap on tapDone button. And after that once your image upload complete then you want to present alert for success.
But if you dismiss it once button tapped you won't get any alert because your SecondViewController is no longer in window hierarchy and if you will try to present the alert you will get an error in Console like:
Attempt to present on
whose view is not in the
window hierarchy!
To solve this problem you need to present a alert from your first view controller which will appear after you dismiss your second view controller and you can achieve it with delegate.
Consider the below example:
First of all declare a protocol outside your FirstViewController:
protocol UplaodSuccessDelegate:class {
func uploadSuccess(message: String)
}
then confirm your delegate to same class:
class ViewController: FirstViewController, UplaodSuccessDelegate {
then you need to pass the delegate when you present SecondViewController:
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
vc.delegate = self. //Pass delegate
self.present(vc, animated: true, completion: nil)
and add delegate method to same class:
func uploadSuccess(message: String) {
let message = "Save in bg worked"
print(message)
let alert = UIAlertController(title: "title", message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: {
(action) in
}))
self.present(alert,animated: true, completion: nil)
}
now in your SecondViewController you need to add
weak var delegate: UplaodSuccessDelegate?
and in your tapDone method replace a code with:
self.dismiss(animated: true) {
if let image = newImage {
submit2Parse(image: image)
}
}
and once your upload complete you need to call delegate method (once your upload completes) like:
self.delegate?.uploadSuccess(message: "your message")
This will call your delegate method from FirstViewController.
When you drop the function below into the viewController that initiates the data upload in the background, and then call this function in the closure that fires when the submit completes it is able to successfully create an alert even thought the initial view controller was dismissed (or long dismissed if it was a long upload).
// This is the function that performs my background upload
func submit2Parse (image: UIImage) {
if let pfImage = image2PFFile(image: image) {
// Insert PFFile into parse server
let submittedImage = PFObject(className: "Images")
submittedImage["imageFile"] = pfImage
submittedImage["imageCreateDt"] = newImageCreateDate
submittedImage["type"] = "submittedFromUserHome"
submittedImage["bride"] = brideSwitch.isOn
submittedImage["groom"] = groomSwitch.isOn
submittedImage["user"] = userSwitch.isOn
submittedImage["picturePeriod"] = pickerSelected
submittedImage["uploadedByUserId"] = PFUser.current()?.objectId ?? ""
submittedImage["uploadedByUserName"] = PFUser.current()?.username ?? ""
if !txtIsPlaceHolder { submittedImage["description"] = imageDescription.text }
// Get the image timestamp, every photo has one
// How do you get the thumbnail image too?
submittedImage.saveInBackground { (success, error) in
if success {
let message = "Save in bg worked"
print(message)
self.showAlertFromAppDelegates()
} else {
print(error?.localizedDescription ?? "")
}
}
}
}
// This is the function that creates the alert
func showAlertFromAppDelegates(){
var topWindow: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
topWindow?.rootViewController = UIViewController()
topWindow?.windowLevel = UIWindow.Level.alert + 1
let alert: UIAlertController = UIAlertController(title: "Upload Complete", message: "Your image was successfully submitted!", preferredStyle: .alert)
alert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: { (alertAction) in
topWindow?.isHidden = true
topWindow = nil
}))
topWindow?.makeKeyAndVisible()
topWindow?.rootViewController?.present(alert, animated: true, completion:nil)
}

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.

Resources