I know how to create singleton class in swift. The best and easy way to create singleton class is the following:
class Singleton {
static let sharedInstance = Singleton()
}
But I don't need singleton for any normal class. I need to create singleton for a viewcontroller class. So I'm using this code create singleton
class AViewController:UIViewController {
static let sharedInstance = AViewController()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
it gives me error near AViewController()
Missing argument for parameter 'coder' in call
Looks like it want me to initialize with init(coder: NSCoder). But what parameter or value should I pass through the coder?
If you really wanted to have singleton for a view controller corresponding to some scene, you'd probably do something like:
class SecondViewController: UIViewController {
static let shared = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Foo")
}
In this example, the storyboard was Main.storyboard and the storyboard identifier for the scene in question was Foo. Obviously, replace those values for whatever was appropriate in your case.
Then your other view controller that was invoking this could do something like:
#IBAction func didTapButton(_ sender: Any) {
let controller = SecondViewController.shared
show(controller, sender: self)
}
I wouldn't recommend singletons for view controllers. View controllers (and their views) should be created when needed and be allowed to be deallocated when they're dismissed. And you're losing many storyboard benefits (by which you see the logical flow between scenes with segues between them). And, if you use this view controller in different contexts, you're inviting problems stemming from the view controller hierarchy falling out of sync with the view hierarchy. I really would discourage you from using singletons for view controllers.
But if you were going to do it, you could do something like that...
Try to do:
AppDelegate:
Add a reference to the ViewController, so you can access it globally, like so:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var viewController: ViewController?
func getViewController() -> ViewController {
if viewController == nil {
// make sure that the name of the storyboard is "Main"
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// make sure that you named the viewcontroller in storyboard (Storyboard ID), it is the identifier
viewController = storyboard.instantiateViewController(withIdentifier: "ViewControllerStoryboardID") as! ViewController
}
return viewController!
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// ....
}
AnotherViewController (Usage):
Now you can access it via "AppDelegate", like so:
class AnotherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = appDelegate.getViewController()
}
// ...
}
Hope this helped.
I would recommend something like:
Related
I realize that this might be difficult to achieve, but I would like to be able to initialize ViewControllers from storyboard using init(coder: NSCoder) function directly. I mean not using storyboard.instantiateViewController(identifier: coder: ) - I know I could use this, but this is what I would like to skip. Instead I would prefer to have a init in ViewController like this:
init(viewModel: ViewModel) and use this init to initialize this ViewController from the storyboard. Inside this init I imagine having some mechanism that would open my storyboard, and extracted it's coder somehow, so that I could write:
static let storyboard = UIStoryboard(named: "Game")
private let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
let identifier = String(describing: Self.self)
let coder: NSCoder = Self.storyboard.somehowGetTheCoderForViewController(withId: identifier)
super.init(coder: coder)
}
Problem is how to read storyboard in such a way that I was able to get the coder of particular ViewController from it.
Again - I know this can be solved by using something like this
storyboard.instantiateViewController(identifier: String(describing: Self.self)) { coder in
Self.init(coder: coder, viewModel: viewModel)
}
But Im looking for a way to not use instantiateViewController, and just be able to get the coder, so that later I could just initiate VC like this:
let viewController = ViewContorller(viewModel: viewModel)
So the question is how to unpack storyboard, and retrieve coder object for some ViewController.
You are not supposed to do this.
As the documentation of init(nibName:bundle:) says:
This is the designated initializer for this class. When using a storyboard to define your view controller and its associated views, you never initialize your view controller class directly. Instead, view controllers are instantiated by the storyboard either automatically when a segue is triggered or programmatically when your app calls the instantiateViewController(withIdentifier:) method of a storyboard object.
When initialising a VC using an initialiser, that initialiser is what you should use, not init(coder:). If you use storyboards, then you should use instantiateViewController, or use segues to get new VCs.
So to achieve this syntax:
let viewController = ViewContorller(viewModel: viewModel)
You can put your VCs in xib files, and do:
init(viewModel: ViewModel) {
self.viewModel = viewModel
let identifier = String(describing: Self.self)
let coder: NSCoder = Self.storyboard.somehowGetTheCoderForViewController(withId: identifier)
super.init(nibName: String(describing: Self.self), bundle: nil)
}
If you must use storyboards, then you can't use an initialiser syntax for initialising VCs. You can instead make a factory method, such as:
let viewController = ViewContorller.from(viewModel: viewModel)
And implement it using UIStoryboard.instantiateViewController.
This is why the initializer instantiateViewController(identifier:creator:) exists.
https://developer.apple.com/documentation/uikit/uistoryboard/3213989-instantiateviewcontroller
You receive the coder and use it to call a custom initializer that itself calls the coder initializer but also does other custom initialization.
To illustrate, suppose we have a ViewController class with a message String property to be presented to the user in a UILabel when the view appears. So we've given ViewController an init(coder:message:) initializer:
class ViewController: UIViewController {
#IBOutlet var lab : UILabel!
var message: String = ""
convenience init(coder:NSCoder, message:String) {
self.init(coder:coder)!
self.message = message
}
override func viewDidLoad() {
super.viewDidLoad()
self.lab.text = self.message
}
}
Left to its own devices, however, the runtime will never call our init(coder:message:) initializer; it knows nothing of it! The only way the runtime knows to instantiate a view controller from a storyboard is by calling init(coder:). But we can call init(coder:message:) when we instantiate the view controller from the storyboard.
Suppose this is the storyboard's initial view controller, and we're calling it in the scene delegate at launch time:
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
let message = "Howdy, world!" // or whatever
self.window = UIWindow(windowScene: scene)
let sb = UIStoryboard(name: "Main", bundle: nil)
self.window?.rootViewController = sb.instantiateInitialViewController {
return ViewController(coder: $0, message: message)
}
self.window?.makeKeyAndVisible()
}
We call instantiateViewController(identifier:creator:) and the creator: function calls init(coder:message:) — which, in turn, calls init(coder:). Thus our use of the creator: function is legal, and the view appears correctly.
I want to produce some function when user comes out of app
I found applicationDidEnterBackground function in appDelegate file but I can't reach view controller which is open right now therefore I can't reach needed function
I can't use instantiateViewController function because it creates a new one, but I need the info stored in the view which is already open
Is there any way to call function of already open instance of view controller???
I sometimes have a similar requirement, so I made a UIViewController extension with a static method to return the "top" viewController. It looks like this:
extension UIViewController {
static func topViewController(_ viewController: UIViewController? = nil) -> UIViewController? {
let viewController = viewController ?? UIApplication.shared.keyWindow?.rootViewController
if let navigationController = viewController as? UINavigationController, !navigationController.viewControllers.isEmpty {
return self.topViewController(navigationController.viewControllers.last)
} else if let tabBarController = viewController as? UITabBarController,
let selectedController = tabBarController.selectedViewController {
return self.topViewController(selectedController)
} else if let presentedController = viewController?.presentedViewController {
return self.topViewController(presentedController)
}
return viewController
}
}
This allows you to get a reference to the viewController being displayed by calling let top: UIViewController? = UIViewController.topViewController().
I found some way
I am pretty sure it is real stupid way, but it works in my situation because I exactly know which View Controller I need
1st step: create a variable in appDelegate class
class AppDelegate: UIResponder, UIApplicationDelegate {
var myViewController:ExampleViewController?
2nd step: in myViewController class
override func viewDidLoad() {
super.viewDidLoad()
let delegate = UIApplication.shared.delegate as! AppDelegate
delegate. myViewController = self
}
And now I can call any function of myViewController class from AppDelegate class
variable of view controller is optional so I unwrap it with "?" sign and don't have problems when app didEnterBackground from other view controllers
I know how to create singleton class in swift. The best and easy way to create singleton class is the following:
class Singleton {
static let sharedInstance = Singleton()
}
But I don't need singleton for any normal class. I need to create singleton for a viewcontroller class. So I'm using this code create singleton
class AViewController:UIViewController {
static let sharedInstance = AViewController()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
it gives me error near AViewController()
Missing argument for parameter 'coder' in call
Looks like it want me to initialize with init(coder: NSCoder). But what parameter or value should I pass through the coder?
If you really wanted to have singleton for a view controller corresponding to some scene, you'd probably do something like:
class SecondViewController: UIViewController {
static let shared = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Foo")
}
In this example, the storyboard was Main.storyboard and the storyboard identifier for the scene in question was Foo. Obviously, replace those values for whatever was appropriate in your case.
Then your other view controller that was invoking this could do something like:
#IBAction func didTapButton(_ sender: Any) {
let controller = SecondViewController.shared
show(controller, sender: self)
}
I wouldn't recommend singletons for view controllers. View controllers (and their views) should be created when needed and be allowed to be deallocated when they're dismissed. And you're losing many storyboard benefits (by which you see the logical flow between scenes with segues between them). And, if you use this view controller in different contexts, you're inviting problems stemming from the view controller hierarchy falling out of sync with the view hierarchy. I really would discourage you from using singletons for view controllers.
But if you were going to do it, you could do something like that...
Try to do:
AppDelegate:
Add a reference to the ViewController, so you can access it globally, like so:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
private var viewController: ViewController?
func getViewController() -> ViewController {
if viewController == nil {
// make sure that the name of the storyboard is "Main"
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// make sure that you named the viewcontroller in storyboard (Storyboard ID), it is the identifier
viewController = storyboard.instantiateViewController(withIdentifier: "ViewControllerStoryboardID") as! ViewController
}
return viewController!
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// ....
}
AnotherViewController (Usage):
Now you can access it via "AppDelegate", like so:
class AnotherViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = appDelegate.getViewController()
}
// ...
}
Hope this helped.
I would recommend something like:
From my appdelegate I need to execute a method that is from a viewcontroller.
Please, can anybody tell me how to easily have the possibility to call whatever method I need from my appdelegate?
A lot of questions regarding this argument but none of these are useful/complete/right, so please avoid to post URL to other topics: I already checked all questions but I really can't find any precise answer regarding doing this in Swift. :)
It depends on how you've arranged your view controllers but here's an example from a simple iPhone master/detail project.
let root : UINavigationController = self.window!.rootViewController! as UINavigationController
let master : MasterViewController = root.topViewController as MasterViewController
println("\(master.description)")
The way I did it was to search for the view controller I want, starting on AppDelegate's var window: UIWindow? and then going deeper until I find it. I originally tried to implement NSNotification but this is not recommended on swift (I think computed property is a good replace for that, but it don't work in this case).
This is how I did for my tab based application with a NavigationController on top of my ViewController:
if let rootViewController = self.window?.rootViewController as? UITabBarController
if let viewControllers = rootViewController.viewControllers {
for navigationController in viewControllers {
if let yourViewController = navigationController.topViewController as? YourCustomViewController {
if yourViewController.hasSomeFlag {
yourViewController.theMethod()
}
break
}
}
}
}
You can do it with notifications, just add observer to your viewcontroller, post a notification from your app delegate, this observer will catch it and run a function you specify.
this tutorial should help get you started: https://www.codefellows.org/blog/how-to-implement-an-nsnotification-observer-in-swift
Try this
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
// Override point for customization after application launch.
return true
}
func customMethod()
{
}
}
Call custom method from ViewController
class ViewController: UIViewController {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
appDelegate.customMethod()
}
With the aim to implement a splash screen that only shows once i've modified didFinishLaunchingWithOptions in order to dynamically select the appropriate view controller. The logic seems to work fine, and the view I intended to load is the one launched
However, the UI seems to be missing elements that would otherwise be displayed should I have not altered the didFinishLaunchingWithOptions function.
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool
{
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
var entryViewController: UIViewController?
if NSUserDefaults.standardUserDefaults().boolForKey("hasSeenWelcomeScreen") == true
{
entryViewController = storyBoard.instantiateViewControllerWithIdentifier("NavigationController") as? UIViewController
}
else
{
entryViewController = storyBoard.instantiateViewControllerWithIdentifier("WelcomeViewController") as? UIViewController
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "hasSeenWelcomeScreen")
NSUserDefaults.standardUserDefaults().synchronize()
}
self.window?.rootViewController = entryViewController
self.window?.makeKeyAndVisible()
return true
}
My WelcomeViewController is a simple view with 1 label, 1 button and a movie which plays in the background (resembling Spotify/Vine's welcome screen). Debugging the code I can see the initialization methods do get executed, but is just the frame that does not seem to be displayed when I dynamically override the initial view
import UIKit
import MediaPlayer
import QuartzCore
class WelcomeViewController: UIViewController {
var moviePlayerController: MPMoviePlayerController = MPMoviePlayerController()
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var appNameLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
buildMoviePreview()
buildButtonDesign()
}
override func viewWillAppear(animated: Bool)
{
self.view.addSubview(self.moviePlayerController.view)
self.view.addSubview(self.loginButton)
self.view.addSubview(self.appNameLabel)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
private func buildButtonDesign()
{
loginButton.layer.borderColor = UIColor.whiteColor().CGColor
loginButton.layer.borderWidth = 2.0
loginButton.layer.cornerRadius = 7.0
}
private func buildMoviePreview()
{
let filePath = NSBundle.mainBundle().pathForResource("intro", ofType: "mov")
self.moviePlayerController.contentURL = NSURL.fileURLWithPath(filePath)
self.moviePlayerController.movieSourceType = .File
self.moviePlayerController.repeatMode = .One
self.moviePlayerController.view.frame = self.view.bounds
self.moviePlayerController.scalingMode = .AspectFill
self.moviePlayerController.controlStyle = .None
self.moviePlayerController.allowsAirPlay = false
self.moviePlayerController.shouldAutoplay = true
self.moviePlayerController.play()
}
}
For completeness, these are the discrepancies in the layout when using the XCode UI debugger. Please note that they differ even though they implement the same viewController. The only difference is that one has been programmatically set as the initial view, while the other has been set as the initial view through storyboard.
Screenshots of the rendering issue side-by-side
Your approach is... unusual. A Storyboard has a root view controller for a reason and typically at startup you would just let the application handle loading the storyboard and installing that root view controller as the window's main view controller. (The Storyboard loaded is specified in the application target's general settings as the "Main Interface")
In this case, what I would recommend is making your root view controller the "Normal" view of the application... the one you want users to see when they launch the app on a day-to-day basis.
Define your "on first launch" view controller as a separate view controller in the storyboard and add a modal segue from the root view controller to the on first launch view controller.
Then in your applicationDidFinishLaunching, if the user has never seen the first launch controller... simply ask the Storyboard to take that segue. If the user has already seen the first launch presentation that segue will be skipped.
Another issue I see with your code is in your viewWillAppear method. You should not have to add your views as subviews in viewWillAppear... those subviews should already have been set up at the time the view was loaded from the nib file.
The one exception is the view of your movie player, but your movie player is owned by a separate view controller. That separate view controller is detached from the view controller hierarchy and does not have it's own view controller methods called at the right times. (so it never receives calls like "viewWillAppear" that might tell it to get it's movie ready to play).
What you probably want to do is implement "awakeFromNib" and make sure that the movie player's view controller is a sub-controller of this view controller. (so in awakeFromNib for the WelcomeViewController use addChildViewController to make sure the movie controller is in the hierarchy).
It will be better to user 2 storyboards :
one with your welcome screen
the other one with the rest of your app
The application launching wil look like this :
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
var storyBoard : UIStoryboard!
if NSUserDefaults.standardUserDefaults().boolForKey("hasSeenWelcomeScreen") == true {
changeStoryBoard("Main")
}
else {
changeStoryBoard("Welcome")
}
return true
}
func changeStoryBoard(name :String) {
var storyBoard = UIStoryboard(name:name, bundle: nil)
var viewController: AnyObject! = storyBoard.instantiateInitialViewController() ;
self.window!.rootViewController = viewController as UIViewController
}