unexpectedly found nil while unwrapping an Optional value in Base ViewController - ios

I'm Using pagemenu project https://github.com/uacaps/PageMenu to include in my app the segmented control to switch between view controllers,this control consists in one base view controller(PageMenuViewController1) and two child view controllers (ViewController1, ViewController2) but when I launch this PageMenuViewController1 the app fails in the child view controllers(below I show exactly where the app fails) and throws this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
below you can see the code for the viewcontrollers.
The base View Controller is:
class PageMenuViewController1: UIViewController {
var pageMenu : CAPSPageMenu?
override func viewDidLoad() {
// Array to keep track of controllers in page menu
var controllerArray : [UIViewController] = []
// Create variables for all view controllers you want to put in the
// page menu, initialize them, and add each to the controller array.
// (Can be any UIViewController subclass)
// Make sure the title property of all view controllers is set
// Example:
var controller1 : ViewController1 = ViewController1()
controller1.title = "ViewController1"
controllerArray.append(controller1)
var controller2 : ViewController2 = ViewController2()
controller2.title = "ViewController2"
controllerArray.append(controller2)
// Customize page menu to your liking (optional) or use default settings by sending nil for 'options' in the init
// Example:
var parameters: [CAPSPageMenuOption] = [
.MenuItemSeparatorWidth(4.3),
.UseMenuLikeSegmentedControl(true),
.MenuItemSeparatorPercentageHeight(0.1)
]
// Initialize page menu with controller array, frame, and optional parameters
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 0.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
// Lastly add page menu as subview of base view controller view
// or use pageMenu controller in you view hierachy as desired
self.view.addSubview(pageMenu!.view)
}
}
the Child View Controllers:
-- ViewController1
class ViewController1: UIViewController, UITableViewDataSource{
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var clubs: [Club]!
override func viewDidLoad() {
super.viewDidLoad()
apiClient.Service.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview.reloadData()// Here is where the lldb throws the exception
}
else {
println("error: \(error)")
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clubs?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("clubObjectCell") as! ClubTableViewCell
cell.clubObject = self.clubs?[indexPath.row]
return cell
}
}
-- ViewController2
class ViewController2: UIViewController, UITableViewDataSource {
#IBOutlet var tableview:UITableView!
let apiClient = ApiClient()
var parties: [Party]? = []
override func viewDidLoad() {
super.viewDidLoad()
apiClient.partiesService.getList() { parties, error in
if parties != nil {
self.parties = parties
self.tableview.reloadData()// Here is where the lldb throws the exception
}
else {
println("error: \(error)")
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.parties?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("partyObjectCell") as! PartyTableViewCell
cell.partyObject = self.parties?[indexPath.row]
return cell
}
}
I hope my issue it's clear :)

Most likely, you forgot to connect the tableView IBOutlet property to a view in your storyboard. In the gutter, to the left of the line of code that says #IBOutlet var tableview: UITableView!, you should see a circle. If the circle is filled, then the outlet is already connected. But if the circle is empty, you need to connect it.
This connection can be made in a few different ways. A common method is to use the Assistant Editor. Open ViewController1.swift on one pane, and open Main.storyboard on the other pane. Select the appropriate UITableView in the Storyboard. Then, control-drag to the empty circle near the IBoutlet in ViewController1.
If your outlet is already connected (you can see a full circle), then you should try moving the reloadData() call to viewWillAppear(:) like so:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
apiClient.Service.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableview.reloadData()// Here is where the lldb throws the exception
}
else {
println("error: \(error)")
}
}
}

I think the way you are instantiating ViewController1 and ViewController2 is not correct.
If you are using storyboard then you must instantiate as follows:
var controller1 : ViewController1 = storyboard.instantiateViewControllerWithIdentifier("<view controller 1 identifier in storyboard>")
If you are not using storyboard then you must instantiate using NSBundle.mainModule().loadNibNamed(...).
Just calling the class initializer will not attach them to the storyboard or xib.

Related

How to Pass Data Between Two Side-by-Side Instances of a UITableViewController

In interface builder, I embedded two instances of a UITableViewController in container views in a UIStackView. Both TableViewControllers are linked to the same custom class document (see code below). The only difference between them is in the data they display. Both have UITableViews that allow multiple selection – but I also want so that selecting anything in one table causes the deselection of everything in the other table, and vice versa. I tried setting this up with delegation, but I don't know how to reference one instance from the other within UITableViewController, to assign each as the delegate of the other.
I couldn't find anything relevant about delegation or about referencing a view controller by anything other than its subclass name. So in my latest attempt, I tried referring to the other child of the parent object. Here's the relevant code:
protocol TableViewSelectionDelegate: AnyObject {
func didSelectInTableView(_ tableView: UITableView)
}
class TableViewController: UITableViewController, TableViewSelectionDelegate {
weak var delegate: TableViewSelectionDelegate?
#IBOutlet var numbersTableView: UITableView!
#IBOutlet var lettersTableView: UITableView!
// Received by segue
var displayables: [Character] = []
override func viewDidLoad() {
super.viewDidLoad()
}
// (It's too soon to determine parents/children in viewDidLoad())
override func viewWillAppear(_ animated: Bool) {
guard let tableViewControllers = parent?.children else {
print("No tableViewControllers found!")
return
}
switch restorationIdentifier {
case "NumbersTableViewController":
for tableViewController in tableViewControllers {
if tableViewController.restorationIdentifier == "LettersTableViewController" {
delegate = tableViewController as? TableViewSelectionDelegate
}
}
case "LettersTableViewController":
for tableViewController in tableViewControllers {
if tableViewController.restorationIdentifier == "NumbersTableViewController" {
delegate = tableViewController as? TableViewSelectionDelegate
}
}
default: print("Unidentified Table View Controller")
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectInTableView(tableView)
}
func didSelectInTableView(_ tableView: UITableView) {
switch tableView {
case numbersTableView:
numbersTableView.indexPathsForSelectedRows?.forEach { indexPath in
numbersTableView.deselectRow(at: indexPath, animated: false)
}
case lettersTableView:
lettersTableView.indexPathsForSelectedRows?.forEach { indexPath in
lettersTableView.deselectRow(at: indexPath, animated: false)
}
default: print("Unidentified Table View")
}
}
}
Running the above and tapping in either table results in "Unidentified Table View" printed to the console, and neither table's selections are cleared by making a selection in the other.
Any insights into how I could get the results that I want would be appreciated. If something here isn't clear, let me know, and I'll make updates.
Passing information between two instances of a UITableViewController through delegation is apparently not as complicated as it at first seemed. The only noteworthy part is the setting of the delegate. Within the custom TableViewController class, when one instance is initialized, it needs to set itself as the delegate of the other instance. That's it!
In this case, to reference one instance from within another, one can use the tableViewController's parent to get to the other child tableViewController. Although there might be a better way to do this, see the code for my particular solution. Notably, since the parent property is not yet set just after viewDidLoad(), I needed to set things up in viewWillAppear(). Also note that this approach doesn't require using restorationIdentifiers or tags. Rather, it indirectly determines the tableViewController instance through its tableView property.
The delegated didSelectInTableView() function passes the selectedInTableView that was selected in the other tableViewController instance. Since the delegate needs to clear its own selected rows, the selectedInTableView is not needed for this purpose. That is, for just clearing rows, the function doesn't need to pass anything.
protocol TableViewSelectionDelegate: AnyObject {
func didSelectInTableView(_ selectedInTableView: UITableView)
}
class TableViewController: UITableViewController, TableViewSelectionDelegate {
weak var delegate: TableViewSelectionDelegate?
// Received by segue
var displayables: [Character] = []
override func viewDidLoad() {
super.viewDidLoad()
}
// (It's too soon to determine parents/children in viewDidLoad())
override func viewWillAppear(_ animated: Bool) {
guard let siblingTableViewControllers = parent?.children as? [TableViewController] else { return }
switch tableView {
case siblingTableViewControllers[0].tableView: siblingTableViewControllers[1].delegate = self
case siblingTableViewControllers[1].tableView: siblingTableViewControllers[0].delegate = self
default: print("Unidentified Table View Controller")
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.didSelectInTableView(tableView)
}
func didSelectInTableView(_ selectedInTableView: UITableView) {
// selectedTableView is NOT the one that needs to be cleared
// The function only makes it available for other purposes
tableView.indexPathsForSelectedRows?.forEach { indexPath in
tableView.deselectRow(at: indexPath, animated: false)
}
}
}
Please feel free to correct my conceptualization and terminology.

swift, tableView selectedTypes buttons

i need an help, see this class
import UIKit
protocol TypesTableViewControllerDelegate: class {
func typesController(controller: TypesTableViewController, didSelectTypes types: [String])
}
class TypesTableViewController: UITableViewController {
let possibleTypesDictionary = ["bakery":"Bakery", "bar":"Bar", "cafe":"Cafe", "grocery_or_supermarket":"Supermarket", "restaurant":"Restaurant"]
var selectedTypes: [String]!
weak var delegate: TypesTableViewControllerDelegate!
var sortedKeys: [String] {
return possibleTypesDictionary.keys.sort()
}
// MARK: - Actions
#IBAction func donePressed(sender: AnyObject) {
delegate?.typesController(self, didSelectTypes: selectedTypes)
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return possibleTypesDictionary.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TypeCell", forIndexPath: indexPath)
let key = sortedKeys[indexPath.row]
let type = possibleTypesDictionary[key]!
cell.textLabel?.text = type
cell.imageView?.image = UIImage(named: key)
cell.accessoryType = (selectedTypes!).contains(key) ? .Checkmark : .None
return cell
}
// MARK: - Table view delegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let key = sortedKeys[indexPath.row]
if (selectedTypes!).contains(key) {
selectedTypes = selectedTypes.filter({$0 != key})
} else {
selectedTypes.append(key)
}
tableView.reloadData()
}
}
here the user can tap a cell of the tableView so that his prefer types are used on the next viewController for a search, now i need to build a class that do the same thing but there is no a tableview rather only 6 buttons in a view that the user can tap (so a viewController with only 6 different buttons to tap). The problem is that i don't know how to pass to the next viewController what buttons have been pressed and what are not, how can i build this class?
here is the function in the other class that need to know what buttons have been pressed
func fetchNearbyPlaces(coordinate: CLLocationCoordinate2D) {
mapView.clear()
dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
let marker = PlaceMarker(place: place)
marker.map = self.mapView
where is "types: serchedTypes"
What you wanna do is called delegation here is how you do it:
Make a protocol like this one:
protocol TransferProtocol : class
{
func transferData(types:[String])
}
Make the view controller with the buttons conform to that protocol, I like to do it by adding extensions to my classes like so:
extension ButtonsViewController:TransferProtocol{
func transferData(types:[String]){
//Do whatever you want here
}
}
Declare a variable in your Table View Controller class with the protocol you created as its type, this is called a delegate
weak var transferDelegate:TransferProtocol?
Before you segue to the Buttons View Controller you want to set that view controller as the delegate you just created like so:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as? ButtonsViewController
transferDelegate = vc
vc?.transferData(types: selected)
}
If done correctly you should be able to work with the array you built in the Table View Controller(TypesTableViewController)

How to push a new view controller when a table cell is tapped?

I have created this table with 3 sections and 7 rows. The code is shown below
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var subjectTabelView: UITableView!
var slSubject = ["English Lang&Lit", "Chinese Lang&Lit", "Economics"]
var hlSubject = ["Mathematics", "Chemistry", "Biology"]
var tokSubject = ["Theory of Knowledge"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
subjectTabelView.dataSource = self
subjectTabelView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return hlSubject.count
}else if section == 1{
return slSubject.count
}else {
return tokSubject.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let subjectCell = tableView.dequeueReusableCellWithIdentifier("idSubjectCell", forIndexPath: indexPath) as! UITableViewCell
if indexPath.section == 0 {
subjectCell.textLabel?.text = hlSubject[indexPath.row]
} else if indexPath.section == 1{
subjectCell.textLabel?.text = slSubject[indexPath.row]
} else {
subjectCell.textLabel?.text = tokSubject[indexPath.row]
}
return subjectCell
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "HL"
} else if section == 1{
return "SL"
} else {
return "ToK"
}
}
}
What do I have to do to make every cell in this table pushes a new view controller when it is tapped? The picture of my storyboard is shown below. In my storyboard, my view controller, I have already created a navigation controller, and made the view controller that has the table the rootViewController. And for now, my tableView has only one prototype cell and one cell identifier.
Thank you!
Suppose your "locationVC" is:
class LocationVC: UIViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This can be changed when creating this UIViewController
var textToShow : String?
override func viewWillAppear(animated: Bool) {
if let textToShow = textToShow {
fromWhereLabel.text = textToShow
}
}
}
then, just adding function below to your code in ViewController named UIViewController (that should have a better name ;-)) you can achieve your goal.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//if such cell exists and destination controller (the one to show) exists too..
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier("locationVC") as? LocationVC{
//This is a bonus, I will be showing at destionation controller the same text of the cell from where it comes...
if let text = subjectCell.textLabel?.text {
destinationViewController.textToShow = text
} else {
destinationViewController.textToShow = "Tapped Cell's textLabel is empty"
}
//Then just push the controller into the view hierarchy
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
You will be able to have a LocationVC UIViewController launched every time you tap a cell, and it will have some value to prove it right. :)
Hope it Helps!
UPDATE: Code and Instructions below are for allowing to launch
different UIViewControllers after tap on cells
1.- Let's create a class that will be the parent for every one of our new UIViewControllers (the ones we are willing to go from our tableview cell's tap):
public class CommonDataViewController: UIViewController {
//Here we are going to be putting any data we want to share with this view
var data: AnyObject?
}
2.- Let's create some sort of Navigation rules, just to be organised ;-)
enum Navigation: Int {
case vc1 = 0, vc2 = 1, vc3 = 2, vc4 = 3
//How many rules we have (for not to exceed this number)
static let definedNavigations = 4
//This must return the identifier for this view on the Storyboard
func storyboardIdentifier() -> String {
//for this example's sake, we have a common prefix for every new view controller, if it's not the case, you can use a switch(self) here
return "locationVC_\(self.rawValue + 1)"
}
}
Now, let's build upon previous code:
3.- For clarity, let's change a little our previous LocationVC (that for this example, will have an Storyboard Identifier with the text "locationVC_1")
class LocationVC: CommonDataViewController {
#IBOutlet weak var fromWhereLabel: UILabel!
//This is optional, but improves clarity..here we take our AnyObject? variable data and transforms it into the type of data this view is excepting
var thisVCReceivedData: String? {
return data as? String
}
override func viewWillAppear(animated: Bool) {
if let textToShow = thisVCReceivedData {
fromWhereLabel.text = textToShow
}
}
}
4.- Now, we trigger all of this in our didSelectRowAtIndexPath function.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Just to avoid tapping on a cell that doesn't have an UIViewController asociated
if Navigation.definedNavigations > indexPath.row {
//The view's instance on our Navigation enum to which we most go after tapping this cell
let nextView = Navigation(rawValue: indexPath.row)!
//The identifier of the destination CommonDataViewController's son in our Storyboard
let identifier = nextView.storyboardIdentifier()
//If everything exists...
if let subjectCell = tableView.cellForRowAtIndexPath(indexPath), let destinationViewController = navigationController?.storyboard?.instantiateViewControllerWithIdentifier(identifier) as? CommonDataViewController {
//here you can use a switch around "nextView" for passing different data to every View Controller..for this example, we just pass same String to everyone
if let text = subjectCell.textLabel?.text {
destinationViewController.data = text
} else {
destinationViewController.data = "Tapped Cell's textLabel is empty"
}
navigationController?.pushViewController(destinationViewController, animated: true)
}
}
}
Notice that you can achieve same results using protocols and delegate approach, this is just simpler to explain
Well to push a view controller in a UINavigationController you just use this code:
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
The method you are looking for is this one:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ViewController *viewController = [self.navigationController.storyboard instantiateViewControllerWithIdentifier:#"locationVC"];
[self.navigationController pushViewController:viewController animated:YES];
}
You could use prepareForSegue method. You just need to set up the destination view. or the didselectrowatindexpath
prepareForSegue code looks like :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "nameofTheSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController{
// do whatever you want with the data you want to pass.
}
}
}

Exc_Bad_Instruction when using navigationController PushViewController

I am trying to push to a different view when a user selects a table cell and when I get down to where it would make the push, it throws an error.
The special situation here is that I am working two UIViews with tableViews under the same UIViewController. As a result, the UIView and TableDelegates are in their own class.
Any suggestions are greatly appreciated.
import Foundation
import UIKit
import CoreData
class FavoritesViewController: UIViewController {
//MARK: - UIViewController Delegates
override func viewWillAppear(animated: Bool) {}
}
//MARK: - Lenders Table View Delegates
class FavoritesLenderViewController: UIView, UITableViewDelegate, UITableViewDataSource {
let favoritesViewController = FavoritesViewController()
let lenderDetailsViewController = LenderDetailsViewController()
var lenders = Array<Lenders>()
var lenderUserComments = Array<LenderUserComments>()
let lendersModel = LendersModel()
//MARK: View Parameters
#IBOutlet weak var favoriteLendersTableView: UITableView!
// Equivalent to viewWillAppear for a subclass.
override func willMoveToSuperview(newSuperview: UIView?) {
self.lenders = lendersModel.readLenderData() as Array
// Reload data (for when the user comes back to this page from the details page.)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.favoriteLendersTableView.reloadData()
})
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var favoritesViewController = FavoritesViewController()
return self.lenders.count
}
// On selecting, send variables to target class.
func tableView(tableView: UITableView!, didSelectRowAtIndexPath path: NSIndexPath!) {
// Setup what segue path is going to be used on selecting the row as set the type as the class for the view your going to.
let lenderDetailsViewController = self.favoritesViewController.storyboard?.instantiateViewControllerWithIdentifier("LendersDetailsView") as LenderDetailsViewController
// Get the lenderId from the selected path.row and put the values to the variables in the target class.
let lendersNSArray = lenders as NSArray
lenderDetailsViewController.lenderIdPassed = lendersNSArray[path.row].valueForKey("lenderId") as String!
// tell the new controller to present itself
// BREAKS HERE : EXC_BAD_INSTRUCTION code=EXC_I386_INVOP
lenderDetailsViewController.navigationController?.pushViewController(lenderDetailsViewController, animated: true)
}
....//additional table delegates
}
You don't push the new view controller on itself. Try this:
self.navigationController?.pushViewController(lenderDetailsViewController, animated: true)

Passing values from one view controller to another in Swift

I am working on Swift, I've got an error in tableview's didSelectRowAtIndexPath method.
I want to pass a value to another view controller i.e 'secondViewController'. Here EmployeesId is an Array. The relevant code is as follows:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var view: Dashboard = self.storyboard?.instantiateViewControllerWithIdentifier("Dashboard") as Dashboard
self.navigationController?.pushViewController(view, animated: true)
secondViewController.UserId = employeesId[indexPath.item] //getting an error here.
}
But I am getting this Error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
Any help will be appreciated.
Here's a general solution with two assumptions. First, UserId is not a UILabel. Second, you meant to use view which was instantiated in the second line, instead of using secondViewController
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var view: Dashboard = self.storyboard?.instantiateViewControllerWithIdentifier("Dashboard") as Dashboard
view.userId = employeesId[indexPath.row]
self.navigationController?.pushViewController(view, animated: true)
}
Here's what Dashboard looks like:
class Dashboard: UIViewController {
var userId: String!
#IBOutlet var userIDLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
userIDLabel.text = UserId
}
...
}
We can pass value using "struct" in swift 3
Create a swift file in your project
Create a class in the swift file created like bellow
class StructOperation {
struct glovalVariable {
static var userName = String();
}
}
Assing value to "static var userName" variable of " struct glovalVariable" in "StructOperation" class from First View Controller like
#IBAction func btnSend(_ sender: Any) {
StructOperation.glovalVariable.userName = "Enamul Haque";
//Code Move to Sencon View Controller
}
In destination view controller get value like bellow
var username = StructOperation.glovalVariable.userName;
is UserId(strong, nonatomic) in the secondViewController?
If it is weak, it sent trash on the fly. You have to make it strong.

Resources