fatal error while unwrapping a view controller value - ios

I have a few viewcontrollers in storyboard with certain identifiers and when I try to access them like this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let mainViewController = sideMenuController!
let vcName = identities[indexPath.row]
let viewController: UIViewController! = storyboard?.instantiateViewController(withIdentifier: vcName)
let navigationController = mainViewController.rootViewController as! NavigationController
navigationController.pushViewController(viewController, animated: true)
mainViewController.hideLeftView(animated: true, completionHandler: nil)
}
The app crashes. This line is detecting that the value is nil. Can anyone explain how this is nil while unwrapping?
navigationController.pushViewController(viewController, animated: true)
After several attempts I managed to get rid off the fatal error. However the view controller still does not appear upon tapping on the table view item.
Original Project -
link
Edited Project -
link

Well, the project is a little "messed up" :-)
First: You need to somehow get "the" UINavigationController (or derived class).
Since your LeftViewController is not on the navigation stack, it does not have a navigationController set. So, you need to get it from the mainViewController:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
print(storyboard)
let mainViewController = sideMenuController!
let vcName = identities[indexPath.row]
let viewController: UIViewController! = storyboard.instantiateViewController(withIdentifier: vcName)
print(viewController)
if let theNavigationController = mainViewController.rootViewController as? UINavigationController {
print("Gotcha")
theNavigationController.pushViewController(viewController!, animated: true)
mainViewController.hideLeftView(animated: true, completionHandler: nil)
}
}
Second, there is still a problem:
The view controller with identifier 'A' is of type EngineTunningParamteres, derived from UINavigationController. You cannot push a navigation controller onto an existing navigation controller. Therefore, we have to change the class, too:
class EngineTunningParamteres: UIViewController {
override func viewDidLoad() {
print("hi") }
}
Now it works. You're welcome.

let viewController: UIViewController!
is an Optional, with this ! you are just telling the compiler "I know this may be nil but don't worry, at run time it will never be nil".
If you want to get rid of this Optional you have to unwrap the storyboard :
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let mainViewController = sideMenuController!
let vcName = identities[indexPath.row]
let viewController = storyboard!.instantiateViewController(withIdentifier: vcName)
let navigationController = mainViewController.rootViewController as! NavigationController
navigationController.pushViewController(viewController, animated: true)
mainViewController.hideLeftView(animated: true, completionHandler: nil)
}

Related

Pass String Variable Data from one Class/ViewController to Class/ViewController

I have two viewControllers:
1. CheckoutVC
2. DeliveryTimeVC
In DeliveryTimeVC I have the following variable:
class DeliveryTimeVC: UIViewController {
var tableViewDay:String = ""
}
I use the following push to go from CheckoutVC to DeliveryTimeVC:
let storyboard = UIStoryboard(name: Storyboard.DeliveryTimeStoryboard, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: StoryboardId.DeliveryTimeVC)
navigationController?.pushViewController(controller, animated: true)
In DeliveryTimeVC I have a tableView where on didSelect I go back to CheckoutVC and in didSelect func I have the following code to append my variable before leaving the controller:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableViewDay.removeAll()
tableViewDay.append(cell.weekDayLbl.text!)
navigationController?.popViewController(animated: true)
}
How can I transfer the String variable data from DeliveryTimeVC to another variable in CheckoutVC? Lets say I create a variable in CheckoutVC as:
class CheckoutVC: UIViewController, CartProductCellDelegate {
var tableViewDayTransferedData:String = ""
}
How can I transfer the data from tableViewDay:String to tableViewDayTransferedData:String
well you can use this lines
let storyboard = UIStoryboard(name: "NAMEOFYOURSTORYBOARD", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "IDOFYOURVIEW") as! CheckoutVC
vc.tableViewDayTransferedData = self. tableViewDay
self.navigationController!.pushViewController(vc, animated: true)
Based on #Hassan Shahbazi comment above I was able to change his code slightly and get the final answer that works for me:
protocol DeliveryTimeDelegate: class {
func didGetData(tableViewDayTransferedData: String)
}
class DeliveryTimeVC: UIViewController {
weak var delegate: DeliveryTimeDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableViewDay.removeAll()
tableViewDay.append(cell.weekDayLbl.text!)
delegate?.didGetData(tableViewDayTransferedData: tableViewDay)
navigationController?.popViewController(animated: true)
}
}
class CheckoutVC: UIViewController, DeliveryTimeDelegate {
func ...() {
let storyboard = UIStoryboard(name: Storyboard.DeliveryTimeStoryboard, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: StoryboardId.DeliveryTimeVC) as! DeliveryTimeVC
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
}
func didGetData(tableViewDayTransferedData: String) {
tableViewDayTransferedData = tableViewDayTransferedData
}
}
in the last func you see two same variables; they just have the same name: tableViewDayTransferedData = tableViewDayTransferedData first one is from CheckoutVC. the second one is from the protocol.
There are generally 2 main patterns for data/variable passing in iOS.
Delegate Pattern
The popular pattern for Objective-c days, but still useful and easy to do. You'll need to define a delegate protocol and use it for passing variables.
protocol DeliveryTimeDelegate: class {
func didGetData(tableViewDay: String)
}
class DeliveryTimeVC: UIViewController {
weak var delegate: DeliveryTimeDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableViewDay.removeAll()
tableViewDay.append(cell.weekDayLbl.text!)
delegate?.didGetData(tableViewDay: "YOUR_VALUE")
navigationController?.popViewController(animated: true)
}
}
class CheckoutVC: UIViewController, CartProductCellDelegate {
func ...() {
let storyboard = UIStoryboard(name: Storyboard.DeliveryTimeStoryboard, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: StoryboardId.DeliveryTimeVC)
controller.delegate = self
navigationController?.pushViewController(controller, animated: true)
}
}
extension CheckoutVC: DeliveryTimeDelegate {
func didGetData(tableViewDay: String) {
tableViewDayTransferedData = tableViewDay
}
}
Closure Pattern
The pattern could be seen as the swift friendly implementation of delegate. It's important to pay enough attention to memory management and retain cycle issues when you're using closure.
class DeliveryTimeVC: UIViewController {
var onDataTransfered: ((String) -> Void)?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableViewDay.removeAll()
tableViewDay.append(cell.weekDayLbl.text!)
self.onDataTransfered?("YOUR_VALUE")
navigationController?.popViewController(animated: true)
}
}
class CheckoutVC: UIViewController, DeliveryTimeDelegate {
func ...() {
let storyboard = UIStoryboard(name: Storyboard.DeliveryTimeStoryboard, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: StoryboardId.DeliveryTimeVC)
controller.onDataTransfered = { [weak self] tableViewDayTransferedData in
self?.tableViewDay = tableViewDayTransferedData
}
navigationController?.pushViewController(controller, animated: true)
}
}

Push new view controller programmatically from within didSelectRow method

I'm building a meme generator that employs a tableView and a collectionView for viewing saved memes. When the user taps a cell within the tableView the image should be presented by sliding the view from the previous controller not by presenting modally as a new view, which is the case. Here is my current code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let meme = memes[indexPath.row]
let detailController = UIStoryboard(name: "DetailsStoryboard", bundle: nil).instantiateViewController(withIdentifier: "DetailsViewController") as! DetailsViewController
present(detailController, animated: true, completion: nil)
detailController.detailsImageView.image = meme.memedImage
}
Everything I am finding is depending on using a segue, which I am not. Can I use a segue within the didSelectRow method? Here is a link to the repo.
You have to use UINavigationController to push the controller.
And second thing you can't set image to DetailsViewController from TableView didSelect method.
Instead you can pass this image to DetailsViewController and set this image in particular controller.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let meme = memes[indexPath.row]
let detailController = UIStoryboard(name: "DetailsStoryboard", bundle: nil).instantiateViewController(withIdentifier: "DetailsViewController") as! DetailsViewController
detailController.memeImage = meme.memedImage //Pass image like this
self.navigationController?.pushViewController(detailController, animated: true)
}
In DetailsViewController do like this
class DetailsViewController: UIViewController {
//Add one memeImage variable
var memeImage: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
detailsImageView.image = memeImage
}
}

Need to double tap on cell to present modally VC

I am generating table cell.
cell2 = settingsTableView.dequeueReusableCell(withIdentifier: "ModuleCell", for: indexPath) as! ModuleCell
It looks fine, the cell has tag = 2000.
In override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath), I am checking the tag end if tag == 2000 I want to present modal view. I am doing that in this way
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalView = storyboard.instantiateViewController(withIdentifier: "ModalView")
present(modalView, animated: true, completion: nil)
Then in modalView I have a button which should dismiss modalView, what's happening as expected.
#IBAction func saveAndClosePopup(_ sender: UIButton) {
UserDefaults.standard.removeObject(forKey: "ActiveCantachoOptions")
UserDefaults.standard.set(Modules.activeCantachoOptions, forKey: "ActiveCantachoOptions")
self.dismiss(animated: true, completion: nil)
}
However, when I want to immediately present modalView again, sometimes it's okay, but sometimes I need to hit two times on the cell, which should show modalView. After the first hit, there is no difference if I hit the cell again in 1 second or in 30 seconds. The modalView will appear after the second one. What is the cause?
Try this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let modalView = storyboard.instantiateViewController(withIdentifier: "ModalView")
self.present(modalView, animated: true) {
tableView.deselectRow(at: indexPath, animated: false)
}
}

Data not appearing in view controller

I have been trying everything I could but nothing seems to work. I am trying to pass firebase user information from one view controller to another, but it never appears.
It starts from a table view
The table view is populated with users that are being pulled from my firebase database. When the user selects a cell the following code gets executed:
I reference function "showProfileControllerForUser" as a way to transport the data of the cell that was selected
var mainProfile: userProfileNavigator?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user1 = self.users[indexPath.row]
self.mainProfile?.showProfileControllerForUser(user3: user1)
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UINavigationController = storyboard.instantiateViewController(withIdentifier: "mainNavProfile") as! UINavigationController
self.present(vc, animated: true, completion: nil)
}
In "userProfileNavigator" is where the function that was used before is. The function passes the information that was collected and passes that to the final destination.
func showProfileControllerForUser(user3: User){
mainProfile?.user2 = user3
}
Finally, "mainProfile" is where I want the data to be passed to.
var user2: User?{
didSet{
navigationItem.title = user2?.name
}
I could imagine this being very confusing, I can provide anymore information or clarity if needed
attempted to setup table as such:
var mainProfile: mainProfile?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user1 = self.users[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UIViewController = storyboard.instantiateViewController(withIdentifier: "mainProfile")
self.mainProfile?.user2 = user1
self.present(vc, animated: true, completion: nil)
}
And the ViewController in which the information is being passed as such:
import UIKit
import Firebase
class mainProfile: UIViewController{
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print("lets see if this is the new user", user2.name as Any)
}
var user2 = User()
}
but it still comes up as "nil"
If I understood your question right, then you have two ViewControllers. One which contain UITableView, and another is ShowProfileControllerForUser.
In ViewController which contain UITableView, code this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user1 = self.users[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UINavigationController = storyboard.instantiateViewController(withIdentifier: "mainNavProfile") as! UINavigationController
vc.user3 = user1
self.present(vc, animated: true, completion: nil)
}
Also, I am not sure if you're presenting the right ViewController.
In your ShowProfileControllerForUser.swift,
var user3 = User // declare a variable with proper type.
Now in your viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
showProfileControllerForUser(user3)
}
Edit:
var mainProfile: mainProfile?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let user1 = self.users[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UIViewController = storyboard.instantiateViewController(withIdentifier: "mainProfile")
//self.mainProfile?.user2 = user1 // Instead of this line
vc.user2 = user1 // use this line
self.present(vc, animated: true, completion: nil)
}
If you want to open controller with that passed value, you can set that value after instantiating it
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UINavigationController = storyboard.instantiateViewController(withIdentifier: "mainNavProfile") as! UINavigationController
// this is what your showProfileControllerForUser doing
vc.user2 = user1
self.present(vc, animated: true, completion: nil)

Push different UIView for each row of UITableView

I'm trying to push a new view when I select a row.
let identifiers:[String] = ["vc1", "vc2", "vc3", "vc4", "vc5"]
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let identifier = identifiers[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UINavigationController = storyboard.instantiateViewControllerWithIdentifier("\(identifier)") as! UINavigationController
self.presentViewController(vc, animated: true, completion: nil)
}
All the different views called with this tableView have their own class and Storyboard ID.
At the moment I get the error:
Could not cast value of type 'testApp.CalculatorViewController' to 'UINavigationController'
I think you are trying to cast UIViewController to UINavigationController.
Please try this. This may help you.
let identifiers:[String] = ["vc1", "vc2", "vc3", "vc4", "vc5"]
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let identifier = identifiers[indexPath.row]
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = storyboard.instantiateViewControllerWithIdentifier("\(identifier)") as! UIViewController
self.navigationController?.pushViewController(vc, animated: true, completion: nil)
}

Resources