Show a View Controller Programmatically in Swift error - ios

I tried this solution but I get an error AnyObject is not convertible to 'UIViewController' did you mean to use 'as!' to force downcast?
What am I doing wrong?
I call the following code in LoginViewController viewDidLoad, I check if the user has logged in yet if yes then I want to skip the login view and go directly to Latest Photos which is FirstViewController...
override func viewDidLoad() {
super.viewDidLoad()
if(defaults.objectForKey("loggedIn") != nil){
let vc : UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LatestPhotosView") as! UIViewController;
self.presentViewController(vc, animated: true, completion: nil)
}
}

Since Swift 1.2, you have to cast explicitly, change your as to as!.
This indicates to the programmer that the cast can fail. This decreases the probability of errors in your code.
Edit:
You have to call this in your viewDidAppear: method, as this will fail on your viewDidLoad() (Your view doesn't exist yet)

Related

Iboutlets have nil value on present call

#objc func handleGoToSearch() {
// present(UINavigationController(rootViewController: searchDisplayController()), animated: true, completion: nil)
performSegue(withIdentifier: "searchSegue", sender: nil)
}
I was using the present call to SearchViewController but the ib outlets on where nil, I have a feeling it has something to do with passing a new instance, and the outlets are not loading in time. (What I read somewhere else). when I use perform segue not errors.
I would like to know exactly what's happening. I also thought that maybe present() doesn't work with storyboard, or I'm passing in the wrong value type.
Your outlets are nil because you are not creating the viewcontroller from the Swift class only, not from the storyboard. To create the viewcontroller from the storyboard, do this:
let viewController = UIStoryboard(name: "Name of the storyboard").instantiateViewController(withIdentifier: "Identifier of the viewcontroller")
You should fill in the name of the storyboard (usually Main) and give the viewcontroller an identifier in the storyboard, and use that identifier in the code.

How to programmatically change a view controller while not in a ViewController Class

I know this question has been asked countless times already, and I've seen many variations including
func performSegue(withIdentifier identifier: String,
sender: Any?)
and all these other variations mentioned here: How to call a View Controller programmatically
but how would you change a view controller outside of a ViewController class? For example, a user is currently on ViewController_A, when a bluetooth device has been disconnected (out of range, weak signal, etc) the didDisconnectPeripheral method of CBCentral gets triggered. In that same method, I want to change current view to ViewController_B, however this method doesn't occur in a ViewController class, so methods like performSegue won't work.
One suggestion I've implemented in my AppDelegate that seems to work (used to grab the appropriate storyboard file for the iphone screen size / I hate AutoLayout with so much passion)
var storyboard: UIStoryboard = self.grabStoryboard()
display storyboard
self.window!.rootViewController = storyboard.instantiateInitialViewController()
self.window!.makeKeyAndVisible()
And then I tried to do the same in my non-ViewController class
var window: UIWindow?
var storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) //assume this is the same storyboard pulled in `AppDelegate`
self.window!.rootViewController = storyboard.instantiateViewController(withIdentifier: "ViewController_B")
self.window!.makeKeyAndVisible()
However I get an exception thrown saying fatal error: unexpectedly found nil while unwrapping an Optional value presumably from the window!
Any suggestions on what I can do, and what the correct design pattern is?
Try this:
protocol BTDeviceDelegate {
func deviceDidDisconnect()
func deviceDidConnect()
}
class YourClassWhichIsNotAViewController {
weak var deviceDelegate: BTDeviceDelegate?
func yourMethod() {
deviceDelegate?.deviceDidDisconnect()
}
}
class ViewController_A {
var deviceManager: YourClassWhichIsNotAViewController?
override func viewDidLoad() {
deviceManager = YourClassWhichIsNotAViewController()
deviceManager.delegate = self
}
}
extension ViewController_A: BTDeviceDelegate {
func deviceDidDisconnect() {
DispatchQueue.main.async {
// change the VC however you want here :)
// updated answer with 2 examples.
// The DispatchQueue.main.async is used here because you always want to do UI related stuff on the main queue
// and I am fairly certain that yourMethod is going to get called from a background queue because it is handling
// the status of your BT device which is usually done in the background...
// There are numerous ways to change your current VC so the decision is up to your liking / use-case.
// 1. If you are using a storyboard - create a segue from VC_A to VC_B with an identifier and use it in your code like this
performSegue(withIdentifier: "YourSegueIdentifierWhichYouveSpecifiedInYourSeguesAttibutesInspector", sender: nil)
// 2. Instantiate your VC_B from a XIB file which you've created in your project. You could think of a XIB file as a
// mini-storyboard made for one controller only. The nibName argument is the file's name.
let viewControllerB = ViewControllerB(nibName: "VC_B", bundle: nil)
// This presents the VC_B modally
present(viewControllerB, animated: true, completion: nil)
}
}
func deviceDidConnect() {}
}
YourClassWhichIsNotAViewController is the class which handles the bluetooth device status. Initiate it inside the VC_A and respond to the delegate methods appropriately. This should be the design pattern you are looking for.
I prefer dvdblk's solution, but I wasn't sure how to implement DispatchQueue.main.async (I'm still pretty new at Swift). So this is my roundabout, inefficient solution:
In my didDisconnectPeripheral I have a singleton with a boolean attribute that would signify whenever there would be a disconnect.
In my viewdidload of my ViewController I would run a scheduledTimer function that would periodically check the state of the boolean attribute. Subsequently, in my viewWillDisappear I invalidated the timer.

How to perform a successful segue programmatically from SKScene to UIViewController

I have tried several different answers and have yet to find an answer that works.
I keep getting
fatal error: unexpectedly found nil while unwrapping an Optional value
I have a SKScene called selectUpgrade that is in my GameViewController and I am trying to segue to a UIViewController called MissileUpgrade.
var vc2 = MissileUpgrade() //trying to get to this UIViewController
var gameVC = GameViewController() // currently in a scene in this UIViewController
I am calling this in my scene to segue
func goToMissileUpgrade() {
gameVC.presentViewController(vc2, animated: true, completion: nil)
}
These are in the same storyboard. If I set MissileUpgrade as the initial VC it will load fine so I know it has nothing to do on that end. I am lost on why this is not working. Thanks for your help!
If you are using storyboards, don't use presentViewController use performSegueWithIdentifier
Give your Segue an Identifier in the Storyboard, then refer to it in the code like so:
self.performSegueWithIdentifier("yourSegueIdentifierHere", sender: self)

How to access a button from another view controller?

Let's say I have a firstViewController and a secondViewController. The first one contains a firstButton and the second one - a secondButton. Here's what I want to do: when user clicks the secondButton, some firstButton's property changes.
Unfortunately, when I create an instance of a firstViewController in a secondViewController and then trying to access a firstButton, I get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
So, technically, I'm trying to do this as follows:
var ins = firstViewController()
#IBAction func secondButtonisPressed(){
ins.firstButton.alpha = 0
}
What is the proper way to implement that?
Thanks in advance.
Your problem here is that the IBOutlets of your firstViewController are only available (!= nil) after the viewDidLoad() firstViewController's method has being called.
In other words, you have to present the view, before you can make any changes to a UIViewController IBOutlet.
How you can solve this?
Add a variable into FirstViewController that works as a flag for you.
for example: var hideFirstButton = false
in the viewDidLoad or viewWillAppear method of FirstViewController check for hideFirstButton's value and hide or show your firstButton.
Then, before you present your FirstViewController change the value of hideFirstButton to the needed for your application to run fine.
UPDATE:
Other workaround, using Storyboard is (This approach has the inconvenient that the completion handler is called after viewWillAppear() so the button is visible for a second):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as! FirstViewController
self.presentViewController(firstViewController, animated: true, completion: {
//This lines will be called after the view is loaded so the code will run
firstViewController.firstButton.alpha = 0
})
EXAMPLE: an example at GitHub
You could try to do this using delegation, similar to the way Apple does it in their existing frameworks. For an example, look at the way that you use UITableViewDelegate when working with a UITableView object.
If you wanted to use delegation to tell secondViewController that firstButton was pressed using delegation, you could do it as follows:
Step 1:
Create a protocol containing a method for the button press event.
protocol buttonPressDelegate {
func buttonPressed() -> Void
}
Step 2:
In firstViewController, declare that you have an instance of an object of type buttonPressProtocol.
var buttonPressDelegateObj: buttonPressDelegate?
Step 3:
In firstViewController, initialize your buttonPressDelegateObj to contain a reference to your instance of secondViewController. If you want you can create a method to set the reference contained in buttonPressDelegateObj, or do it viewDidLoad in firstViewController, etc.
buttonPressDelegateObj = secondViewControllerObj
Step 4:
In secondViewController, declare that you adopt the buttonPressDelegate protocol.
class secondViewController: UIViewController, buttonPressDelegate {
Step 5:
In secondViewController, implement the protocol method buttonPressed() by adding the function with your desired implementation. Here's an example:
func buttonPressed() {
secondButton.alpha = 0
}
Step 6:
Create an #IBAction on the button in firstViewController, so that when the button is pressed it calls buttonPressDelegateObj.buttonPressed() and you can respond to the event
#IBAction func firstButtonPressed() {
if (buttonPressDelegateObj != nil) {
buttonPressDelegateObj.buttonPressed()
}
else {
print("You forgot to set your reference in buttonPressDelegateObj to contain an instance of secondViewController!")
}
}
Note: This is just one way that you could do this. To tell firstViewController that secondButton was pressed (go the other way), have firstViewController implement the protocol buttonPressDelegate, have secondViewController contain a reference to firstViewController as an instance of type buttonPressDelegate?, and create an #IBAction in secondViewController that fires when secondButton is pressed that calls your the buttonPressDelegate method.
Note: There is a similar pattern employed in the Android world to get a Fragment to communicate to an Activity, that you can read more about here

How to dismiss current view and reload another?

#IBAction func DoneButton(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController") as UIViewController
viewController.viewWillAppear()
}
Its suppose to dismiss my current view and reload ViewController
but it crashes with fatal error: unexpectedly
found nil while unwrapping an Optional value
is it because I'm dismissing before?
The call to instantiateViewControllerWithIdentifier() returns an optional. So if the view controller with the specified identifier cannot be found in your storyboard, it will be set to nil and the next line where you call viewWillAppear will crash.
It is also possible that you crash inside viewWillAppear actually. That is not a method that you should call. Instead, UIKit will call that method for you when your view controller is presented.
I guess what you are trying to do is something like this:
if let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController") as UIViewController {
self.presentViewController(viewController,
animated: true, completion: nil)
}
If not, provide more context to your question.
I was thinking to do either
1.
let viewController = ViewController()
viewController.viewWillAppear(false)
2.
In secondary class:
let viewController = ViewController()
viewController.shouldRefresh = true
In primary class:
var shouldRefresh: Bool!
override func viewWillAppear(animated: Bool) {
if shouldRefresh == true {
}
}
3.
In secondary class:
let viewController = ViewController()
viewController.reloadRoutineData()
In primary class:
func reloadRoutineData() {
// Do my stuff here
}
The third option is my variable because I only need to do certain code, not really reload the view completely. But all of them crash with nil while unwrapping.

Resources