After logging out the user, I use the code below after logging in again. The problem is that the application starts duplicated, all messages are duplicated. How to prevent it?
let appDelegate = UIApplication.shared.delegate! as! AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "view_controller_phone")
appDelegate.window?.rootViewController = initialViewController
appDelegate.window?.makeKeyAndVisible()
UIView.transition(with: appDelegate.window!, duration: 1.0, options: .transitionCrossDissolve, animations: nil, completion: nil)
It's a bit difficult to answer you without seeing what are you doing. But I'll give you 2 tips for look around ;)
First, check that your UIViewController are dismissed (it's your responsibility on doing that as Apple says) BEFORE you set the rootController again. A little trick is this:
https://stackoverflow.com/a/50333715/12277616
Second, take care on the observers if you have used them along with Notification Center. If this is the case I suggest you to remove the observers every time you dismiss a UIViewController so you are sure that they don't exist anymore in your runtime foreground.
Remember that dismiss(animated:true) can be closurable without parameters like this:
self.dismiss(animated:true) {
//Code to be executed when dismissing the controller
//Remove the observer
NotificationCenter.default.removeObserver(self, name: [observername],object:nil)
}
Related
I have been searching all day on how to present a view controller from within the appdelegate. It appears that in xcode 11 the window property was moved to the scenedelegate which has been confusing me. I want to present a view controller from within the appdelegate from the didReceiveRemoteNotification function so when the user receives a notification it takes them to a separate view controller with information. I have tried to do:
self.window?.rootViewController?.present(LoginViewController(), animated: false, completion: nil)
within the appdelegate which used to work in a previous application of mine but it does not seem to work anymore. Any help would be much appreciated.
I was able to solve this issue by using shared windows to get the window from scenedelegate to present the view controller on.
UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: false, completion: nil)
Best approach to present view controller through app delegate is without falling for hierarchy like below:
if let vc = UIStoryboard(name: "YOURSTORYBOARD", bundle: nil).instantiateViewController(withIdentifier: "YOURVIEWCONTROLLER") as? YOURVIEWCONTROLLER {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentController = currentController.presentedViewController {
currentController = presentController
}
currentController.present(vc, animated: true, completion: nil)
}
}
I have a timer running in my UIApplication subclass, that is should send the user to a certain ViewController when it runs out.
I am able to instantiate the ViewController I want to go to...
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "StartVC")
...but I do not know how to actually present it. Inside AppDelegate I would be able to do window.rootViewController etc. But this is not available in my UIApplication subclass.
I have also tried to self.windows[0].rootViewController but that is always just the first ViewController, that was present when the app was started. Same with self.keyWindow.rootViewController. And I honestly do not know what both of there properties are.
Full code for context:
import Foundation
import UIKit
class MyApplication: UIApplication {
var inactivityTimer: Timer!
override init() {
super.init()
restartInactivityTimer()
}
#objc func timerExceeded() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "StartVC")
//...here I would need to present "vc"
}
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
restartInactivityTimer()
}
func restartInactivityTimer() {
if inactivityTimer != nil { inactivityTimer.invalidate() }
inactivityTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(timerExceeded), userInfo: nil, repeats: false)
}
}
Implementing an inactivity timer does not require subclassing UIApplication. According to the subclassing notes from the UIApplication documentation, subclassing should only be needed in very rare cases:
Most apps do not need to subclass UIApplication. Instead, use an app delegate to manage interactions between the system and the app.
If your app must handle incoming events before the system does—a very rare situation—you can implement a custom event or action dispatching mechanism. To do this, subclass UIApplication and override the sendEvent(:) and/or the sendAction(:to:from:for:) methods. For every event you intercept, dispatch it back to the system by calling [super sendEvent:event] after you handle the event. Intercepting events is only rarely required and you should avoid it if possible.
As you already mentioned in your question, you can access everything you need from / via the AppDelegate. So, why not handle the inactivity timeout in / via the AppDelegate?
I ended up solving it myself like this:
//inside my UIApplication subclass
#objc func timerExceeded() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "StartVC") as! StartVC
self.windows.first?.rootViewController = vc
self.windows.first?.makeKeyAndVisible()
}
I have followed the following tutorial to implement CallKit within my app:
https://www.raywenderlich.com/150015/callkit-tutorial-ios
But I would like to go further, and display my own ViewController while the call is active. I am doing a videocall service so I would like to have my own interface.
Is that possible at all? I have been trying to launch the ViewController from the method provider(CXProvider:CXAnswerCallAction) which is the one called when the user answers the call, but it seems to crash every time. I am trying to instantiate it with this (Swift 3):
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "VideoCallViewController") as! VideoCallViewController
UIApplication.shared.keyWindow?.rootViewController?.present(vc, animated: true, completion: nil)
It crashes on the second line without explanation. It shows lldb, I have tried to get the backtrace by entering bt but it doesn't return anything.
I figured it out:
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = mainStoryboard.instantiateViewController(withIdentifier: "VideoCallViewController") as! VideoCallViewController
then, either:
vc.view.frame = UIScreen.main.bounds
UIView.transition(with: self.window!, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.window!.rootViewController = vc
}, completion: nil)
or:
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
According to https://stackoverflow.com/a/35226874/5798668, the first option is preferable because if you use the second one you will have multiple UIWindows active in your app at the same time.
NOTE: In my case the ProviderDelegate did not have a self.window attribute, this was passed to it by the AppDelegate.swift, in which a Push notification was executing the reportIncomingCall() of the delegate.
Actually I am having navigationcontroller as root controller it is embed in main.storyboard,i am having two screens one screen login and another one home as per login credentials i need to skip login screen and i need to show home screen.From appdelegate i am doing this skipping it is not working properly
Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x7fadf384c600>.
let storyboard=UIStoryboard.init(name: "Main", bundle: nil)
let navigationController=storyboard.instantiateInitialViewController()
let username=UserDefaultUtil.getString(key: AppConstants.PREF_USERID)
print(username!)
if username != ""
{
window?.rootViewController=navigationController
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
navigationController?.present(sectionController, animated: true, completion: nil)
}
I guess you are trying to present your sectionController in navigationController, its not really how it works, try this code:
let navigationController = self.storyboard?.instantiateInitialViewController() as! UINavigationController
and replace the present with this:
navigationController.setViewControllers([sectionController], animated: false)
or just drop the navigationController instantiate and create it with code and set it as window?.rootViewController:
let sectionController=SectionController(nibName: "SectionController" , bundle: nil)
let nav = UINavigationController(rootViewController: sectionController)
window?.rootViewController = nav
First, check the user credentials in the login page. Then use:
if hasCredentials {
let vc:AnyObject! = self.storyboard?.instantiateViewController(withIdentifier: "someViewController")
self.show(vc as! UIViewController, sender: vc)
}
Sidenote: Personally, I do this from the login page because it simplifies the process and I do not like having weight sitting in my AppDelegate. If you were thinking you did not want people seeing your login screen who are already members, you can do it from an AppDelegate, but take into account the user experience might be diminished during the loading process if this is the route you decide to take.
I have a button in my SettingsViewController that when pressed, I want to present the same instance of my TimerViewController each time the button is pressed.
I think I have gotten fairly close with this post here, iOS Swift, returning to the same instance of a view controller. So if you could point me to saving the instance of TimerViewController, that would work the best.
This is the code I have right now -
var yourVariable : UIViewController!
if yourVariable == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
yourVariable = storyboard.instantiateViewControllerWithIdentifier("timer") as! TimerInterface
}
presentViewController(yourVariable, animated: true, completion: nil)
the code you provided should work. if your SettingsViewController gets deallocated though the timerViewController also gets deallocated and recreated the next time you present it. so you have to make sure to save its instance at an appropriate location.
var timerViewController: TimerViewController!
if timerViewController == nil {
let timerViewController = UIStoryboard(name: "Main", bundle: nil)
yourVariable = storyboard.instantiateViewControllerWithIdentifier("timer") as! TimerInterface
}
presentViewController(timerViewController, animated: true, completion: nil)
The best would be to save the ViewController somewhere , and get back to it .
A way to "get back to it" :
add
var tvc: TimerViewController? = nil
inside AppDelegate
when you get to your Timer (the best would be when you left it , in viewDidDisappear)
you add :
(UIApplication.sharedAplication().delegate as! AppDelegate).tvc = self
then when you get to the setting , if you want to segue back to the timer
let tvc = (UIApplication.sharedAplication().delegate as! AppDelegate).tvc
(UIApplication.sharedApplication().delegate? as! AppDelegate).window?.rootViewController?.presentViewController(tvc, animated: true , completion: nil)
if you ask yourself why should you present it with the rootViewController (last line ) it is because you can present an already active viewController , this will not present an already active vc .