My application works as many, typically banking, apps do: when the user moves the app into background (e.g. by tapping on the Home button), the current visible ViewController is replaced by a launch screen. When the app is brought back to foreground, it displays the previous ViewController or some other ViewController (which depends on a number of conditions, such as, for example, the duration the app was in the background, etc. - but these are not important here).
On iOS 12 and earlier, I use a very simple mechanism - on applicationDidEnterBackground, I set the current ViewController to a local variable on AppDelegate and display the launch screen.
savedViewController = window?.rootViewController
On applicationWillEnterForeground, I check whether all required conditions are met and set the Window's ViewController to the stored ViewController.
window?.rootViewController = savedViewController
This no longer works on iOS 13. When the app enters foreground, the initial story boars is displayed. Setting the rootViewController on the current Window has no affect at all.
I tried to solve this issue by enabling scenes in my application, but that didn't work either.
In SceneDelegate, I'm able to set a default ViewControllerin the scene:willConnecTo method like this:
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let launchStoryboard = UIStoryboard(name: "LaunchScreenVersion", bundle: Bundle.main)
let launchViewController = launchStoryboard.instantiateInitialViewController()
window?.rootViewController = launchViewController
window?.becomeKey()
The above code works, but the method is only called when the scene first loads. I then tried to move my code from from applicationWillEnterForeground to sceneWillEnterForeground, but this didn't work either. When I set the Window's ViewController in this method, that doesn't have any effect and only the ViewController set initially loads.
I also tried moving the code to sceneDidBecomeActive, but it had exactly the same effect.
I know that iOS has built-in state restoration, but, due to security policies, we can't use them in the app as no user state can be saved if the app is no longer running.
Any ideas how I can swap the ViewController with a splash screen when the app goes into background and set it back when the app goes into foreground?
Related
I want my app to check at start conditionaly if a variable is correct or not. Based on that it should either go to an intro screen (where he can select a variable in my case select a team) or it should start the main view. After searching i found this code and edited it. But there still seems to be problems. First of all I dont have two identifier. The Intro has one but not the main view. My main View is called WeatherViewController and the Intro screen is called FirstScreenViewController. I also added a picture of my Main.storyboard.
I also googled a lot about conditional UINavigationController but I can only understand with a video and did not found a video about it.
I tried to use the code from here.
var id = hello ? "goToIntro" : "???"
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let WeatherViewController: UIViewController = mainStoryboard.FirstScreenViewController(withIdentifier: WVC has no identifier??) as UIViewController
self.window?.rootViewController = WeatherViewController
self.window?.makeKeyAndVisible()
if hello {
self.performSegue(withIdentifier: "goToIntro", sender: self)
} else {
/here nothing should happen. It should open the Main View
self.performSegue(withIdentifier: "???", sender: self)
}
Note: This answer was referring to the original question (before any edits) that was attempting a segue inside loadView().
You're supposed to manually create your controller's view in loadView. You're currently doing nothing, hence the black screen. Furthermore, if you are using a Storyboard or a xib for managing this controller you shouldn't be overriding loadView at all.
Having said that it might be a better idea to move this branching logic a step back, to "something" (a container controller like a UINavigationController or a custom one, or even directly setting the root controller of your window if it makes sense) that will present (or set) A or B based on some condition and thus avoid to load this controller altogether (have in mind that in your code, the controller is initialized, it will be part of the hierarchy and all the lifecycle methods will be called even if you segued directly to another one)
Finally, if you decide for some reason to override loadView you don't have to call viewDidLoad yourself, the system will call this method.
I have an app that has a begin journey activity and a choose category activity. I want to make it that after the user presses the start journey button he won't be able to return there or be able with a certain button.
Set the next viewController as the root viewController as described here: How to set the rootViewController with Swift, iOS 7
It seems like you’re looking for the root view of the app to be changed to the beginning of the journey, rather than the initial home screen. This can be done by setting the rootViewController property of the app’s main window, UIApplication.shared.keyWindow.
This can be done using the following code block:
let viewController = JourneyViewController()
guard
let window = UIApplication.shared.keyWindow
else {
return
}
window.rootViewController = viewController
Please note, I work in Xcode 11 beta with iOS 13. My apologies if this doesn’t work with older versions of iOS/OS X.
Whenever the user gets a notification and they click it, they should be taken to that specific tab. For instance, when a user gets a message and they open the notification, the app should open up but to the messages tab. Think of twitter & dm's.
With my project, Im trying to implement something very similar. In the appdelegate and notificationReceived (OneSignal for Push notifications), I've attempted to do so by this:
let sb: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let messageNavigationController: UINavigationController = sb.instantiateViewController(withIdentifier: "MessagesNav") as! UINavigationController
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = messageNavigationController
self.window?.makeKeyAndVisible()
But nothing happens. Whenever a user receives a notification, the app just opens up one the main tab (tab 0). What am i doing incorrectly?
I don't know what you mean by notificationReceived, that's not a predefined function of UIApplicationDelegate. If you want to open different pages when a push notification happens with OneSignal you should probably pass your handling function to the handleNotificationAction of OneSignal.initWithLaunchOptions.
Also you mention changing tabs in your tab controller but the code you pasted is setting your root view to a UINavigationController. If you actually want to change tabs you need to get a reference to the tab controller and set the correct selectedIndex on it.
I'm using Xcode 8 and storyboards for mac development.
I have 2 storyboards with NSWindowController in each of them.
1). How can I display both windows when the app is launched?
2). How can I display window on pressing button on other window?
Edit:
Code:
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let st = NSStoryboard(name: "Logs", bundle: nil) // Logs is my storyboard name
var logWindow: NSWindowController? = nil
logWindow = st.instantiateInitialController() as! NSWindowController?
if logWindow?.window?.isVisible == false
{
logWindow?.window?.setIsVisible(true)
}
}
Everything is correct but the problem is with logWindow.
I assume, you know that the local variable is dead after the execution of method.
As you can see logWindow is a local variable. When applicationDidFinishLaunching(_ aNotification: Notification) is executing, the variable is alive, a new window is created and also displayed.
When applicationDidFinishLaunching(_ aNotification: Notification) is finished executing, your variable logWindow is not alive. So, the allocated memory is de-allocated automatically (by ARC) and the window is destroyed/de-allocated.
All this is happening so fast and it is the reason you are not seeing the window inspite of isVisible() returning true(it returns true because the method is still in execution and logWindow is still alive).
So just make logWindow a variable of class(in what ever class you are trying to display the window) and you are good to go.
Both questions boil down to pretty much the same thing: How can I display a window from a storyboard? Whether you want to do it when the app is launched or when the user clicks a button shouldn't really affect what you need to do to display the window.
Take a look at the NSStoryboard class and you'll find methods for instantiating a window controller from a storyboard. So, create an instance of NSStoryboard if you don't already have one, and then use that to instantiate the window controller in question. For example, if your window controller is called "My Window Controller" and is located in the main storyboard...
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "My Window Controller") as! NSWindowController
Then do whatever you like with windowController. If you want to display two windows when the app is launched, then maybe your app delegate has two properties for keeping track of the corresponding window controllers. In your app delegate's applicationDidFinishLaunching(notification:) method, you could create two windows using code like I've shown and assign the window controllers to those windows. If you want to open a window when the user clicks a button, put code like the code above in the button's action, and store a reference to the resulting window controller in some appropriate object, possibly the window controller where the action appears.
Here's what I'd like to do: use a SplitViewController but not have it be the first thing that shows up. I want an initialization view (which does some network stuff and then completes) which then punches over to the split view.
I've got this working. BUT not sure if what I'm doing makes sense and it has a weird behavior.
Here's what I think I know: to use a SplitViewController, it must be the rootViewController. Also, working with separate storyboards seemed the only way to get it to work. (I've tried having them all in one storyboard but couldn't get it to work).
My first storyboard's controller does it's init and then, to switch to the "SplitViewBoard" storyboard and launch the split view, does the following:
let mainStrbd = UIStoryboard(name: "SplitViewBoard", bundle: nil)
let splitController = mainStrbd.instantiateInitialViewController() as UIViewController
if splitController is UISplitViewController {
var mySplitController: UISplitViewController = splitController as UISplitViewController
mySplitController.delegate = appDel
appDel.window?.rootViewController = splitController
} else {
debugPrint("badness")
}
Now, this all works fine. With one exception. After setting the rootViewController to the splitController, there's an 8-10 second delay before the split view shows up on the screen! I'm imagining there's some way of telling the delegate (or whatever) to "refresh" or of just being more explicit but haven't found it yet. Is this way reasonable? Or it there a much better way?