Exc_Bad_Instruction when using navigationController PushViewController - ios

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)

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)

UITableView with controller separate from ViewController

I'm a newbie to Swift and XCode, taking a class in iOS development this summer. A lot of projects we're doing and examples I'm seeing for UI elements like PickerViews, TableViews, etc. are defining everything in the ViewController.swift file that acts as the controller for the main view. This works fine, but I'm starting to get to the point of project complexity where I'd really like all of my code to not be crammed into the same Swift file. I've talked to a friend who does iOS development on the side, he said this is sane and reasonable and well in-line with proper object-oriented programming... but I just can't seem to get it to work. Through trial and error I've gotten to this situation: the app runs in the simulator, the UITableView appears, but I'm not getting it populated with entries. I can get it working just fine when all the code is in the ViewController, but once I start trying to create a new controller class and make an instance of that class the dataSource/delegate of the UITableView I start getting nothing. I feel like I'm either missing some core understanding of Swift here, or doing something wrong with the Interface Builder in XCode.
My end result should be a UITableView with three entries in it; currently I'm getting a UITableView with no entries. I'm following along with a few different examples I've Googled, but primarily this other SO question: UITableView example for Swift
ViewController.swift:
import UIKit
class ViewController: UIViewController{
#IBOutlet var stateTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
StateViewController.swift:
import UIKit
class StateViewController: UITableViewController{
var states = ["Indiana", "Illinois", "Nebraska"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return states.count;
}
func tableView(cellForRowAttableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"cell")
cell.textLabel?.text = states[indexPath.row]
return cell
}
}
In XCode I have the UITableView hooked up to the View Controller; the outlets are set to dataSource and delegate and the referencing outlet is stateTableView.
I'm not getting any errors; I do get a warning on my `var viewController = StateViewController()' statement in ViewController.swift where it wants me to use a constant, but switching it to a constant doesn't change the behavior (this is as it should be, I assume).
Originally I assumed that the error was in my StateViewController.swift file, where I'm not creating an object that adheres to the UITableViewDataSource or UITableViewDelegate protocol, but if I even add them into the class statement I immediately get errors like "Redundant conformance of 'StateViewController' to protocol 'UITableViewDataSource'" - I'm reading that this is because inheriting from UITableViewController automatically inherits the other protocols as well.
The last thing I tried was instead referring to self.states in the StateViewController's tableView functions, but I'm pretty sure self in Swift works the same as it does in Python and it feels like I'm just trying to add magic words at this point.
I've investigated as far as my currently-limited Swift knowledge can take me, so any answer that explains what I'm doing wrong rather than just telling me what to fix would be very appreciated.
Your issue is being caused by a memory management problem. You have the following code:
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
Think about the lifetime of the viewController variable. It ends when the end of viewDidLoad is reached. And since a table view's dataSource and delegate properties are weak, there is no strong reference to keep your StateViewController alive once viewDidLoad ends. The result, due to the weak references, is that the dataSource and delegate properties of the table view revert back to nil after the end of viewDidLoad is reached.
The solution is to create a strong reference to your StateViewController. Do this by adding a property to your view controller class:
class ViewController: UIViewController{
#IBOutlet var stateTableView: UITableView!
let viewController = StateViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
Now your code will work.
Once you get that working, review the answer by Ahmed F. There is absolutely no reason why your StateViewController class should be a view controller. It's not a view controller in any sense. It's simply a class that implements the table view data source and delegate methods.
Although I find it more readable and understandable to implement dataSource/delegate methods in the same viewcontroller, what are you trying to achive is also valid. However, StateViewController class does not have to be a subclass of UITableViewController (I think that is the part that you are misunderstanding it), for instance (adapted from another answer for me):
import UIKit
// ViewController File
class ViewController: UIViewController {
var handler: Handler!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
handler = Handler()
tableView.dataSource = handler
}
}
Handler Class:
import UIKit
class Handler:NSObject, UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell")
cell?.textLabel?.text = "row #\(indexPath.row + 1)"
return cell!
}
}
You can also use adapter to resolve this with super clean code and easy to understand, Like
protocol MyTableViewAdapterDelegate: class {
func myTableAdapter(_ adapter:MyTableViewAdapter, didSelect item: Any)
}
class MyTableViewAdapter: NSObject {
private let tableView:UITableView
private weak var delegate:MyTableViewAdapterDelegate!
var items:[Any] = []
init(_ tableView:UITableView, _ delegate:MyTableViewAdapterDelegate) {
self.tableView = tableView
self.delegate = delegate
super.init()
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setData(data:[Any]) {
self.items = data
reloadData()
}
func reloadData() {
tableView.reloadData()
}
}
extension MyTableViewAdapter: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Hi im \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
delegate?.myTableAdapter(self, didSelect: items[indexPath.row])
}
}
Use Plug and Play
class ViewController: UIViewController, MyTableViewAdapterDelegate {
#IBOutlet var stateTableView: UITableView!
var myTableViewAdapter:MyTableViewAdapter!
override func viewDidLoad() {
super.viewDidLoad()
myTableViewAdapter = MyTableViewAdapter(stateTableView, self)
}
func myTableAdapter(_ adapter: MyTableViewAdapter, didSelect item: Any) {
print(item)
}
}
You are trying to set datasource and delegate of UITableView as UITableViewController. As #Ahmad mentioned its more understandable in same class i.e. ViewController, you can take clear approach separating datasource and delegate of UITableView from UIViewController. You can make subclass of NSObject preferably and use it as datasource and delgate class of your UITableView.
You can also also use a container view and embed a UITableViewController. All your table view code will move to your UITableViewController subclass.Hence seprating your table view logic from your View Controller
Hope it helps. Happy Coding!!
The way I separate those concerns in my projects, is by creating a class to keep track of the state of the app and do the required operations on data. This class is responsible for getting the actual data (either creating it hard-coded or getting it from the persistent store). This is a real example:
import Foundation
class CountriesStateController {
private var countries: [Country] = [
Country(name: "United States", visited: true),
Country(name: "United Kingdom", visited: false),
Country(name: "France", visited: false),
Country(name: "Italy", visited: false),
Country(name: "Spain", visited: false),
Country(name: "Russia", visited: false),
Country(name: "Moldova", visited: false),
Country(name: "Romania", visited: false)
]
func toggleVisitedCountry(at index: Int) {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
let country = countries[index]
country.visited = !country.visited
}
func numberOfCountries() -> Int {
return countries.count
}
func countryAt(index: Int) -> Country {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
return countries[index]
}
}
Then, I create separate classes that implement the UITableViewDataSource and UITableViewDelegate protocols:
import UIKit
class CountriesTableViewDataSource: NSObject {
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
super.init()
self.tableView.dataSource = self
}
}
extension CountriesTableViewDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return the number of items in the section(s)
return countriesStateController.numberOfCountries()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// return a cell of type UITableViewCell or another subclass
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let country = countriesStateController.countryAt(index: indexPath.row)
let countryName = country.name
let visited = country.visited
cell.textLabel?.text = countryName
cell.accessoryType = visited ? .checkmark : .none
return cell
}
}
import UIKit
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
class CountriesTableViewDelegate: NSObject {
weak var interactionDelegate: CountryCellInteractionDelegate?
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
super.init()
self.tableView.delegate = self
}
}
extension CountriesTableViewDelegate: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected row at index: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: false)
countriesStateController.toggleVisitedCountry(at: indexPath.row)
tableView.reloadRows(at: [indexPath], with: .none)
interactionDelegate?.didSelectCountry(at: indexPath.row)
}
}
And this is how easy is to use them from the ViewController class now:
import UIKit
class ViewController: UIViewController, CountryCellInteractionDelegate {
public var countriesStateController: CountriesStateController!
private var countriesTableViewDataSource: CountriesTableViewDataSource!
private var countriesTableViewDelegate: CountriesTableViewDelegate!
private lazy var countriesTableView: UITableView = createCountriesTableView()
func createCountriesTableView() -> UITableView {
let tableViewOrigin = CGPoint(x: 0, y: 0)
let tableViewSize = view.bounds.size
let tableViewFrame = CGRect(origin: tableViewOrigin, size: tableViewSize)
let tableView = UITableView(frame: tableViewFrame, style: .plain)
return tableView
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard countriesStateController != nil else {
fatalError("viewDidLoad() - Error: countriesStateController was not injected")
}
view.addSubview(countriesTableView)
configureCountriesTableViewDelegates()
}
func configureCountriesTableViewDelegates() {
countriesTableViewDataSource = CountriesTableViewDataSource(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate = CountriesTableViewDelegate(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate.interactionDelegate = self
}
func didSelectCountry(at index: Int) {
let country = countriesStateController.countryAt(index: index)
print("Selected country: \(country.name)")
}
}
Note that ViewController didn't create the countriesStateController object, so it must be injected. We can do that from the Flow Controller, from the Coordinator or Presenter, etc. I did it from AppDelegate like so:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let countriesStateController = CountriesStateController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if let viewController = window?.rootViewController as? ViewController {
viewController.countriesStateController = countriesStateController
}
return true
}
/* ... */
}
If it's never injected - we get a runt-time crash, so we know we must fix it straight away.
This is the Country class:
import Foundation
class Country {
var name: String
var visited: Bool
init(name: String, visited: Bool) {
self.name = name
self.visited = visited
}
}
Note how clean and slim the ViewController class is. It's less than 50 lines, and if create the table view from Interface Builder - it becomes 8-9 lines smaller.
ViewController above does what it's supposed to do, and that's to be a mediator between View and Model objects. It doesn't really care if the table displays one type or many types of cells, so the code to register the cell(s) belongs to CountriesTableViewDataSource class, which is responsible to create each cell as needed.
Some people combine CountriesTableViewDataSource and CountriesTableViewDelegate in one class, but I think it breaks the Single Responsibility Principle. Those two classes both need access to the same DataProvider / State Controller object, and ViewController needs access to that as well.
Note that View Controller had now way to know when didSelectRowAt was called, so we needed to create an additional protocol inside UITableViewDelegate:
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
And we also need a delegate property to make the communication possible:
weak var interactionDelegate: CountryCellInteractionDelegate?
Note that neither CountriesTableViewDataSource not CountriesTableViewDelegate class knows about the existence of the ViewController class. Using Protocol-Oriented-Programming - we could even remove the tight-coupling between those two classes and the CountriesStateController class.

unexpectedly found nil while unwrapping an Optional value in Base ViewController

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.

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