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.
Related
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.
The piece of code below prints the content of whichever cell is clicked on in my TableView.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(self.cell[indexPath.row])
}
I want to use the result that is printed in a label on another ViewController.
How do I get the string value from the function and then use it on on the other view? My thought is to use a global variable but I need to get the string value out first.
For example, You can use simple organization of a singleton of another ViewController (SecondScreen) with var main (in case, as usual, when SecondScreen inited via a Storyboard):
class SecondScreen : UIViewController {
// 1. add this var
static var main : SecondScreen? = nil
// 2. Your some UI element
#IBOutlet weak var textButton: UIButton!
// 3. add this method
func updateUI(string : String) {
textButton.setTitle(string, for: .normal)
}
// 4. setting a var
override func viewDidLoad() {
if SecondScreen.main == nil {
SecondScreen.main = self
}
}
// ... another your and standard methods
}
And you can update your SecondScreen like this:
let v = SecondScreen.main
v?.updateUI(string: "yourString")
Also I recommend you to call method async:
DispatchQueue.main.async {
SecondScreen.main?.updateUI(withString : string)
}
I suggest you to learn more about singletons...
At first, when you create a tableView, you have to collect data (string here) of cells in an array or another data collection. And you can get a needed data (strings) with indexPath variable in the method didSelectRowAt. And you can pass the string to another ViewController (let use SecondViewController) with several ways.
Here is an example:
// declaration an array of your strings
var array : [String] = ["First", "Second", "Third", ...]
...
// getting a string from method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let string = array[indexPath.row]
print(string)
// next, for example, you need to pass the string to a singleton SecondViewController with static var **main**:
SecondViewController.main?.neededString = string
}
Don't forget update in async DispatchQueue:
DispatchQueue.main.async {
SecondViewController.main?.updateUI(withString : string)
}
I'm creating a sort of Contact list app w/ Xcode and everything was running smoothly until it came time for the app the actually add the new contact to the Contact list. What happens is that Once the user goes to the ViewController that helps the user create a new contact; the previously added Contact is removed from the list. I've tried to investigate and what I've discovered is that once I create a new Element (or Contact)for the Array; my code for some reason deletes the old Contact and replaces it with the newly created one. Whats also interesting is that by simply going to another page on my app (going to a new ViewController) my app for some reason removes the newly created Contact from the Contact list and leaves my TableView empty.
Here is a breakdown of my Coding and what I've tried:
this is the initial ViewController (I've implemented a TableView and created arrays that are organized in a cell within the TableView)
class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate {
#IBOutlet var TableView: UITableView!
var names = [String]()
var numbers = [String]()
override func viewDidAppear(animated: Bool) {
TableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
cell.Name.text = names[indexPath.row]
cell.PhoneNumber.text = numbers[indexPath.row]
cell.reloadInputViews()
return cell
}
}
This is Where my user will create a new Contact and how I try to append the new Contact to the contact list
classPickViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if Save.touchInside{
var VC :ViewController = segue.destinationViewController as! ViewController
if VC.names.count >= 1 {
VC.names.insert(NameText, atIndex: (VC.names.count)+1)
VC.numbers.insert(PhoneText,atIndex: (VC.numbers.count)+1)
}
else{
VC.names.insert(NameText, atIndex: (VC.names.count)+0)
VC.numbers.insert(PhoneText,atIndex: (VC.numbers.count)+0)
}
}
}
#IBAction func SaveContact(sender: UIButton) {
performSegueWithIdentifier("SaveEm", sender: self)//this takes the usr to the Contact list
}
Here is the code Ive used to initialize my Cells in the TableView (UITableViewCell file)
class CustomCell: UITableViewCell {
#IBOutlet var Name: UILabel!
#IBOutlet var PhoneNumber: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Your app is navigating from ViewController->classPickViewController->ViewController, so you have a new instance of ViewController which will start with a new empty array.
You should look into unwind segues so that you can move back to the presenting ViewController.
You also need to use something like Core Data to persist your data between executions of your app.
I would store an array of a "Contact" struct rather than having two arrays
reloadInputViews() is an overkill when you are already in the cellForRowAtIndexPath. remove that and put reloaddata appropriately. If needed put again when you know that the control flow is over.
So remove reloadInputViews(), and revisit each view of tableviewcell in cellForRowAtIndexPath instead.
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.
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)