programmatically changing views with Swift - not working - ios

I have the following code which is intended to change views. This code is executed when my 'UITableViewRowAction' is selected and is intended to navigate to a different UITableViewController. However, I am getting the error below and I do not understand what I am doing incorrectly.
let storyBoard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let selectFolderViewController = storyBoard.instantiateViewController(withIdentifier: "SaveArticleTableViewController") as! SaveArticleTableViewController
self.navigationController?.pushViewController(selectFolderViewController, animated: true)
The error that i am getting in the console is:
Could not cast value of type 'UINavigationController' (0x106f5c898) to 'lifesci_PubMed.SaveArticleTableViewController' (0x104dbb658).
All my ViewControllers are embedded within a Navigation Controller

It's very likely that you have not properly set the custom class of the view controller in Storyboard. Here's an image ...
You would type "SaveArticleTableViewController" in that slot.
It's quite possible you have other problem as well, but that will get you going.

The error is actually quite descriptive here:
Could not cast value of type 'UINavigationController' to 'SaveArticleTableViewController'
At first I thought that this was because your view controller SaveArticleTableViewController is not actually set with a custom class. If you're using Interface Builder, make sure you've set the class type in the view controller's property inspector:
If you're doing it in code, make sure your custom class is inheriting properly.
But taking a leap of a guess, your target is "UINavigationController", but your custom class name that it's trying to cast to suggests that your target is a table view "SaveArticleTableViewController". Moreover, I'd bet that this is the tableview that is the source not the destination. I'd double check that you're actually instantiating the right view controller - typically you'd name this something like "SaveArticleDetailViewController", or guessing by your code, maybe it's "SelectFolderViewController", but that's up to you.
For example:
let storyBoard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let selectFolderViewController = storyBoard.instantiateViewController(withIdentifier: "SelectFolderViewController") as! SelectFolderViewController
self.navigationController?.pushViewController(selectFolderViewController, animated: true)
This also requires that you set self.navigationController to the containing nav controller.

Related

Can't downcast after `storyboard.instantiateViewController`

I'm using storyboard.instantiateViewController to start a TableViewController, but when I try to downcast it, I get an error:
Could not cast value of type 'UITableViewController' (0x109b30de8) to 'PresenceTableViewController' (0x1060329f0).
My class PresenceTableViewController is a subclass of UITableViewController, and the storyboard has assigned the VC class correctly.
So why would this fail?
presenceViewController = storyboard.instantiateViewController(withIdentifier: "userPresenceTable") as! PresenceTableViewController
I tried the possible cases for "Could not cast value..." and it was woking fine for all cases, and then I generated this error in one case when module was not inherited from target. This solved the problem for me.
Just re-enter the class type on storyboard, it will automatically select "Inherit module from target". This would solve your problem.
Also make sure that your controller from you want to use must be embed with navigation bar
let tableviewController = self.storyboard?.instantiateViewController(withIdentifier: "tableviewController") as! tableviewController
self.navigationController?.pushViewController(tableviewController, animated: true)

Progress bar crashes when updated from swift file that is not it's viewController

I have a file in Swift that holds all my queries. And when saving a record with saveOperation.perRecordProgressBlock this file call ChatView view controller and updates the progressBarUpdate function.
So far I can get the print within progressBarUpdate to print the progress just fine. But when I get to update progressBarMessage.setProgress(value!, animated: true) the application just crash with the following error: fatal error: unexpectedly found nil while unwrapping an Optional value
If I try to run progressBarMessage.setProgress(value!, animated: true) through viewDidLoad it updates the progress bar fine, no error. Which means the outlet is working just fine.
Other thing to consider, is that my print(".... perRecordProgressBlock - CHAT VIEW\(value)") works just fine. If gets the updates from Queris.swift. It is just the progressBarUpdate that is causing issues.
# my Queries.swift file option 1
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
var chatView = ChatView()
chatView.progressBarUpdate(Float(progress))
}
# my Queries.swift file option 2
saveOperation.perRecordProgressBlock = { (recordID, progress) -> Void in
print("... perRecordProgressBlock \(Float(progress))")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("ChatViewVC") as! ChatView
chatViewController.progressBarUpdate(Float(progress))
}
# ChatView view controller
func progressBarUpdate(value: Float)
{
print(".... perRecordProgressBlock - CHAT VIEW\(value)")
if (value as? Float) != nil
{
progressBarMessage.setProgress(value, animated: true)
}
}
The way you are instantiating the viewController is not the right way and hence the crash/nil val. viewController loads its view hierarchy only when something sends it a view message. The system will do this by its own when to put the view hierarchy on the screen. And it happens after calls like prepareForSegue:sender: and viewWillAppear: , loadView(), self.view.
So here your outlets are still nil since it is not loaded yet.
Just try to force your viewController to call self.view and then access the functions from that viewController.
var chatView = ChatView()
I'm going to go out on a limb here and say you are using storyboards/xibs. If so, the above would not be the correct way to instantiate a new view controller. Here's some information on the difference (the question refers to Objective-C but the concept is the same in Swift)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let chatViewController = storyboard.instantiateViewControllerWithIdentifier("identifier-you-set-in-storyboard") as! ChatView
Where identifier-you-set-in-storyboard is set in the interface builder (the linked question is old but illustrates the concept, the field label might have changed in newer versions)
If by some off chance you are creating you are setting up your views in code (as opposed to storyboards), you'd need to call chatView.loadView() before chatView.progressBarUpdate.... (Or just try to access the view property and it should call loadView for you.)

UIViewController initWithNibNamed:bundle: initialized two objects?

I have a UIViewController that I have had in a storyboard for a while with no problems. As my application grew, and I was using that view controller in more and more places, I realized that I should probably make it more portable, rather than have so many segues to it from hither and yon across the board. I've done splits like this before, so I did what I figured was logical here. I selected that view controller, cut it, and pasted into an empty .xib file. After changing each call to performSegueWithIdentifier to an init(nibName:bundle:) and presentViewController, I get a crash, with an object found unexpectedly nil in viewDidLoad()...
I set the value of this object after each init(...) call, just before presenting the view controller. The nil object is called from viewDidLoad(). This is a problem. I just set this, and now it's gone?!
I overrode the init(...) method, and found that self in init(nibName:bundle:) doesn't have the same memory address as self in viewDidLoad(). Also strange.
I overrode the other init() methods, and found that, after I call to present my view, my object is being instantiated again via init(coder:)! The self in here happens to be the exact self where my property is found nil!
The only reason I see for init(coder:) to be called at all is that I am loading my view from a .xib, but I thought this was handled in init(nibNamed:bundle:)? According to the docs, I do indeed get a call to init(coder:) if I'm loading from a storyboard, and doesn't touch the former... It also says that the nib isn't loaded until the controller's view is queried. If I understand it correctly, my view shouldn't get queried until I present the view. As the crash happens only when I present it, the issue likely stems from that.
I'm stuck here. I still need to get this contextual information to the view controller before it's presented. I've even tried making a proxy class to do the instantiating and property setting before presentation, but I still can't shake this second instance! I get one from init(nibName:bundle:), and another from init(coder:). Neither gets presented, and the latter gives me a nil object error. Any help at all in understanding why this is, and how I might work around this bug (feature?) would be much appreciated. Thank you!
Update:
On a whim, I decided to paste the view controller back into the storyboard, separate from the main hierarchy, and try instantiating it by its identifier. It worked! Not entirely sure how, but by George it worked! Now my question is this: Why?? What is so terribly evil and taboo about .xibs that Xcode and iOS won't tell me? I'm not a little flummoxed by this behavior. I'll keep trying with the .xib, if only to keep Xcode from yelling at me about entrance points...
I don't know what dark magic Xcode is doing, but here's two helper methods I wrote to easily instantiate any Storyboard VC - you just need the Storyboard name and VC identifier (optionally, otherwise will initial VC). By splitting up my VCs into many different Storyboards, I avoid dealing with xibs while still keeping things simple. One loads it into a nav controller of your choice, the other just returns it by itself:
struct StoryboardHelper {
///instantiates a VC with (optional) identifier viewController from storyboardName, pushes it to hierarcy of navigationController, and runs setup block on it, animated specifies whether the push is animated
internal static func showStoryboard(storyboardName: String, viewController: String?, navigationController: UINavigationController, animated: Bool = true, setup: (UIViewController) -> () ){
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let destinationVC = viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
setup(destinationVC)
navigationController.pushViewController(destinationVC, animated: animated)
}
///instantiates and returns a VC with (optional) identifier viewController from storyboardName
internal static func instantiateViewControllerFromStoryboard(storyboardName: String, viewController: String?) -> UIViewController{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
}
}

Cast to a subclass from Storyboard

Imagine i have a BaseViewController. Then i have 2 scenarios, New and Edit, where both shares the same UI and the most of logic. So i created class NewViewController and EditViewController, subclassing BaseViewController. The problem comes when i try to instantiate "BaseViewController" from the storyboard cause i want to specify which implementation is.
if isEdit {
storyboard.instantiateViewControllerWithIdentifier("baseVCIdentifier") as! EditViewController
} else {
storyboard.instantiateViewControllerWithIdentifier("baseVCIdentifier") as! NewViewController
}
Then i get an error:
Could not cast value of type 'Test.BaseViewController' (0x10ee5e0f0) to 'Test.EditViewController' (0x10ee5f000).
I dont wanto to have both ViewController on the storyboard since i dont want to redo the same UI 2 times.
You can do this using instantiateViewController(identifier:creator:).
I assume you have the view controller in a storyboard, with identifier template. The class assigned to the view controller in the storyboard should be the superclass:
let storyboard = UIStoryboard(name: "main", bundle: nil)
let viewController = storyboard.instantiateViewController(identifier: "template") { coder in
// this passes us with a coder for the storyboard, we can now init the preferred subclass.
if useSubclass {
return SpecialViewController(coder: coder)
} else {
return BaseViewController(coder: coder)
}
}
Here is the documentation
You can't do that. Instead of sub classing create 'interaction manager' classes, or state manager classes. The base view controller would then be provided with a manager instance as part of the segue and it would forward all UI interaction to the manager for processing. You then have a single VC in the storyboard as is required and you can supply a new or edit manager. The managers can also have specific instance variables which the view controller doesn't care about.

Navigation Segue not Working properly iOS Swift

My segue is returning with an error (unwrapping an optional value) from this line > self.navigationController?....
In my code...
let nextScreen = self.storyboard!.instantiateViewControllerWithIdentifier("onboarding-what") as? ViewController
self.navigationController?.pushViewController(nextScreen!, animated: true)
This above code is implemented in my onboarding-start view controller
This is my storyboard set up...
the last view controller has the id of "onboarding-what".
I've read the documentation for the navigation controller but can't seem to find my problem. Does anyone have a solution?
You can use performSegueWithIdentifier rather than pushViewController if you connected them in storyboard
However did you fill "onboarding-what" in the storyboardId field?
pls check this image
Also "onboarding-what" view controller should be instance of ViewController
(There's not default class called "ViewController" only "UIViewController" or "NSViewController" for osX.)
Obviously nextScreen is nil. It is most likely that "onboarding-what" identifier is incorrect

Resources