Pop view controller from Swift class in storyboard based iOS project - ios

I'm working on an iOS app using Xcode 6 and Swift. I'm working with storyboard, so I don't have to instantiate a NavigationController in AppDelegate.
But in my applicationDidFinishLaunching, I instantiate a controller to control Philips Hue
var window: UIWindow?
var phController:PHController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.phController = PHController()
return true
}
In this PHController I have a heartbeat function, which is checking all 10 seconds if the connection to the Philips Hue Bridge is still alive.
If not (and this could happen all the time, INDEPENDENT from where the user is currently in the app), I would like to pop a view controller (SearchForNewBridgeViewController).
The question is:
How can I pop/present modally a ViewController from PHController class instantiated when the app did finish launching?
My idea is to instantiate the PHController with the navigationController:
self.phController = PHController(self.navigationController)
But my project is as I mentioned Storyboard based, so I don't have a navigationConntroller in my AppDelegate

Ok I found a solution :)
Solution:
Instantiate my controller and give AppDelegate as argument:
var window: UIWindow?
var phController:PHController?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.phController = PHController(delegate:self)
return true
}
Initializer of PHController:
var delegate:AppDelegate?
init(delegate:AppDelegate) {
self.delegate = delegate
...
}
Present SearchForNewBridgeViewController modally from PHController:
func showSearchForNewBridge() {
let storyboard = UIStoryboard(name: "Main", bundle: nil);
var searchForNewBridgeViewController = storyboard.instantiateViewControllerWithIdentifier("searchForNewBridge") as SearchForNewBridgeViewController
var navigationController = self.delegate!.window!.rootViewController as UINavigationController
navigationController.presentViewController(searchForNewBridgeViewController, animated: true, completion: nil)
}
It's working perfectly for me

Related

navigationController.pushViewController doesn't seem to work in Xcode 11?

I have created a basic single view application. Using Xcode 11 now. I always build apps programatically because the tutorials I have started with never use the interface builder. For some reason not able to get the pushViewController to work. Seems to work just fine in my other projects built using Xcode 10.
In App Delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow()
window?.makeKeyAndVisible()
let nc = UINavigationController(rootViewController: ViewController())
window?.rootViewController = nc
return true
}
In my viewController
#objc func handleChat(){
print("Chat pressed")
navigationController?.pushViewController(InboxViewController(), animated: true)
}
AppDelegate.swift
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let VC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController") as? ViewController
var Nav: UINavigationController? = nil
if let VC = VC {
Nav = UINavigationController(rootViewController: VC)
}
window?.rootViewController = Nav
return true
}
Remove SceneDelegate.swift
Goto Info.plist
Remove Application Scene Manifest
It's work for me.

iOS state restoration issue with DrawerController

I have an app written in Swift 3.1, using Xcode 8.3.3.
I am currently trying to implement state preservation/restoration.
To do this I have implemented shouldSaveApplicationState and willFinishLaunchingWithOptions methods in AppDelegate.swift and set to return true:
// AppDelegate.swift
// STATE RESTORATION CALLBACKS
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
debug_print(this: "shouldSaveApplicationState")
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
debug_print(this: "shouldRestoreApplicationState")
restoringState = true
return true
}
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
debug_print(this: "willFinishLaunchingWithOptions")
return true
}
I’ve also provided restoration IDs for all involved viewcontrollers and navigationcontrollers.
I'm using a 3rd party library to handle side drawer navigation container (https://github.com/sascha/DrawerController). The initial viewcontroller is set programmatically inside the didFinishLaunchingWithOptions method, see below.
// AppDelegate.swift
var centerContainer: DrawerController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let centerViewController = mainStoryboard.instantiateViewController(withIdentifier: "RootViewControllerNav") as! UINavigationController
let leftViewController = mainStoryboard.instantiateViewController(withIdentifier: "SideDrawerViewController") as! UITableViewController
centerContainer = DrawerController(centerViewController: centerViewController, leftDrawerViewController: leftViewController)
centerContainer?.restorationIdentifier = "DrawerControllerView"
window = UIWindow(frame: UIScreen.main.bounds)
window?.restorationIdentifier = "MainWindow"
window?.rootViewController = centerContainer
window?.makeKeyAndVisible()
return true
}
When app opens and attempts to restore state, it displays the correct viewcontroller (last controller before app closed) temporarily, then once app becomes active it reverts back to the initial viewcontroller.
For example, the following happens:
Open app Navigate to the “settings” view via the side menu
Navigate to the home screen
Stop running xcode and start it again
App will open showing settings view, then revert back to home view
Can anyone tell me what is causing this, or where I am going wrong? Let me know if you need any more code examples.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let leftSideDrawerViewController = mainStoryboard.instantiateViewController(withIdentifier: "SideDrawerViewController") as! UITableViewController
let centerViewController = mainStoryboard.instantiateViewController(withIdentifier: "RootViewControllerNav") as! UINavigationController
let navigationController = UINavigationController(rootViewController: centerViewController)
navigationController.restorationIdentifier = "navigationControllerKey"
let leftSideNavController = UINavigationController(rootViewController: leftSideDrawerViewController)
leftSideNavController.restorationIdentifier = "LeftNavigationController"
self.drawerController = DrawerController(centerViewController: navigationController, leftDrawerViewController: leftSideNavController, rightDrawerViewController: nil)
self.drawerController.openDrawerGestureModeMask = .all
self.drawerController.closeDrawerGestureModeMask = .all
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = self.drawerController
return true
}

How can I delay splash launch screen programmatically in Swift Xcode iOS

I have put an image in imageView in LaunchStoreyboard. How can I delay the time of image programmatically?
Here is the Launch Screen Guideline from Apple.
Here is code for Launch Screen View controller:
import UIKit
class LaunshViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.delay(0.4)
}
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
}
As of today there is no predefine method from Apple to hold launch screen. Here are some Approaches which are not optimum but works
Approach #1
Create a Separate ViewController which has Launch logo & create a timer or perform some operation (Like Database/Loads some essential network call) depends on your app type this way you can ready with data before hand & hold the launch screen as well :)
Approach #2 Not Optimum
Use Sleep code which holds up the app for a while.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Thread.sleep(forTimeInterval: 3.0)
// Override point for customization after application launch.
return true
}
Would not recommending setting the entire application in a waiting state.
If the application needs to do more work before finishing the watchdog could kill the application for taking too long time to start up.
Instead you could do something like this to delay the launch screen.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
return true
}
Swift 4.x
It is Not a good practice to put your application to sleep!
Booting your App should be as fast as possible, so the Launch screen delay is something you do not want to use.
But, instead of sleeping you can run a loop during which the receiver processes data from all attached input sources:
This will prolong the launch-screen's visibility time.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
RunLoop.current.run(until: NSDate(timeIntervalSinceNow:1) as Date)
return true
}
Swift 5.x, iOS 13.x.x
Modifying the following function in the AppDelegate class does not work in Swift 5.x/iOS 13.x.x.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
Instead, you will have to modify the scene function in SceneDelegate class as following. It will delay the LaunchSceen for 3 seconds.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
guard let _ = (scene as? UIWindowScene) else { return }
}
The window variable should already be there in SceneDelegate class like the following.
var window: UIWindow?
Definitely your app should not be put to sleep as it may be killed by the OS for being unresponsive for so long.
If you're using a static image for your launch screen, what works for me is to use the image in the LaunchScreen.storyboard, and then when your main controller launches, modally present a VC with the same image as the background in the ViewDidAppear of your main controller (with animated set to false).
You can then use your logic to know when to dismiss the launch screen (dismiss method in the VC with animated set to false).
The transition from the actual LaunchScreen to my VC presenting the same screen looks to me imperceptible.
PS: the ViewDidAppear method might be called more than once, in which case you need to use logic to not present the VC with the launch screen a second time.
Create a ViewController and use NSTimer to detect the delay time. and when the timer ends push the first UIViewcontroller.
In ViewDidLoad method..
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(fireMethod) userInfo:nil repeats:NO];
-(void)fireMethod
{
// push view controller here..
}
Put one line of code in AppDelegate Class;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Thread.sleep(forTimeInterval: 3.0)
return true
}
SwiftUI
For SwiftUI, you can put a very similar code to the accepted answer into ContentView.onAppearing:
struct ContentView: View {
var body: some View {
Text("Hello")
.onAppear {
Thread.sleep(forTimeInterval: 3.0)
}
}
}
Putting a thread to sleep is not a good idea.
I would suggest you go to SceneDelegate's "willConnectTo" function and paste this piece of code and you are good to go.
window?.rootViewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()
window?.makeKeyAndVisible()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()
}
guard let _ = (scene as? UIWindowScene) else { return }

Class AppDelegate has no initializers

I downloaded sample code from here for a location tracking application https://www.pubnub.com/blog/2015-05-05-getting-started-ios-location-tracking-and-streaming-w-swift-programming-language/. I am trying to run the application but in the AppDelegate class I am getting an error saying "Class AppDelegate has no initializers". What is causing this error and how can I fix it?
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Properties
//var window: UIWindow?
var window = UIWindow(frame: UIScreen.mainScreen().bounds)
// MARK: - App Life Cycle
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Adding a Navigation Controller and tool bar
self.window.rootViewController = UINavigationController(rootViewController: MainViewController(nibName: nil, bundle: nil))
// Make window visible
self.window.makeKeyAndVisible()
return true
}
}
I would set the window to be an optional value with no default. just as you originally had then commented out
var window: UIWindow?
then give window a value and programmatically add the root view controller when your app launches
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let viewController = ViewController(nibName: nil, bundle: nil) //ViewController = Name of your controller
let navigationController = UINavigationController(rootViewController: viewController)
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}

Manually Created UIWindow is Wrong Size

I am learning how to create an iOS app without Interface Builder (i.e. storyboards, xibs, etc.). Below is the basic app delegate class I am working with. The problem is that when displayed the UIWindow does not use up the full screen of the device (see attached screenshot). This occurs on all the devices and simulators I test with. Why isn't the fullscreen being used?
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
lazy var window: UIWindow? = {
debugPrint("AppDelegate: Creating Window")
let screen: UIScreen = UIScreen.mainScreen()
let window: UIWindow = UIWindow.init(frame: screen.bounds)
window.backgroundColor = UIColor.whiteColor()
return window
}()
lazy var rootViewController: ShapesViewController = {
debugPrint("AppDelegate: Creating Root View Controller")
let rootViewController = ShapesViewController.init(nibName: nil, bundle: nil)
return rootViewController
}()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
debugPrint("AppDelegate: applicationWillEnterForeground")
window?.rootViewController = rootViewController
window?.makeKeyAndVisible()
return true
}
}
The problem turned out to be that I did not have a launch image or storyboard. For some reason without this the app defaults to the 3.5" size. Credit to this comment: A launch storyboard or xib must be provided unless the app requires full screen
This always works for me (I don't see any clear difference though). Do you mind sharing your ShapesViewController code as well?
Updated to swift 4.2
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
I know sounds silly but...
For a fresh project what macinjosh is saying works.
For a project of mine in Xcode 8 and swift 3, it didn't (probably because I deleted the launch screen and had to create again).
Just created new project fresh, and follow what macinjosh said. Then migrate all your files back in.

Resources