I have the following class:
import UIKit
import CloudKit
class FirstViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var listTableView: UITableView!
var list: TLListModel!
var specificList: CKRecord!
override func viewDidLoad()
{
super.viewDidLoad()
let myContainer = CKContainer.default()
list = TLListModel(container: myContainer, viewController: self)
if(listTableView != nil){
listTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("number of items: %i", list.lists.count)
return list.lists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = listTableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
let list: CKRecord = self.list.lists[(indexPath as NSIndexPath).row]
cell.textLabel?.text = list.value(forKey: "ListName") as? String
cell.textLabel?.font = UIFont (name: "Slim Joe", size: 20)
cell.accessoryType = .disclosureIndicator
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("This object has been selected")
print(self.list.lists[(indexPath as NSIndexPath).row])
specificList = self.list.lists[(indexPath as NSIndexPath).row]
performSegue(withIdentifier: "TLSpecificListSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TLSpecificListSegue"{
if let destinationVC = segue.destination as? TLSpecificListViewController{
destinationVC.listObject = specificList
}
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
let cloudkit = TLCloudKitHelper()
cloudkit.deleteListItem(self.list.lists[(indexPath as NSIndexPath).row], callback: { (listName) in
TLAlertHelper.notifyUser("List Deleted", message: NSString(format: "List for %# successfully deleted", listName) as String, sender: self)
let myContainer = CKContainer.default()
self.list = TLListModel(container: myContainer, viewController: self)
DispatchQueue.main.async {
self.listTableView.reloadData()
}
})
}
}
}
When I call it from another view controller using the following method:
#IBAction func createListAction(_ sender: AnyObject) {
let cloudkit = TLCloudKitHelper()
let listArray = createListFromTextField(textInputArea.text)
if(!(listNameTextField.text?.isEmpty)!){
cloudkit.createList(listNameTextField.text!) { (response) in
let listId = response
if (!listArray.isEmpty){
for item in listArray{
cloudkit.saveItemRecord(item, listId: listId, recordName: response)
}
}
let fvc: FirstViewController = FirstViewController()
DispatchQueue.main.async {
self.present(fvc, animated: true, completion: nil)
}
}
}else{
TLAlertHelper.notifyUser("Give the list a name", message: "You need to give you list a name...", sender:self)
}
}
I get an error saying fatal error: unexpectedly found nil while unwrapping an Optional value
I don't understand why I am getting this error. I've tried looking at the answers here: Simple UITableView in Swift - unexpectedly found nil but I none of those answers helped. Can someone tell me why this this crashing and how I can fix it?
The problem is this line:
let fvc: FirstViewController = FirstViewController()
This creates a blank FirstViewController instance — one completely unconnected with the interface you designed in the storyboard. Its view is empty. It has no table view in it. Therefore, since there is no table view, there is no outlet connection from any table view, and listTableView remains nil.
What you want to do is get the FirstViewController instance from the storyboard, the one whose interface you have already designed in the storyboard. You can do that by talking to the storyboard and using the FirstViewController's identifier, i.e., call instantiateViewController(withIdentifier:). (You might have to give the FirstViewController in the storyboard an identifier for this purpose.)
EDIT This is such a common mistake that I've written a blog post about it: http://www.programmingios.net/dont-make-a-new-instance-by-mistake/
Related
Another issue with learning.
I found this in Apple Dev documentation: THIS
My target is to handle one tap on my list of items. When I click I need to open edit window and handle which row I selected. I trying to put that solution into my code but I have no idea what is TrailViewController (I getting Chinese links at first Google search page). So I decided to put my code there. I getting error:
Use of undeclared type 'TrailViewController'.
They appear after I adding this into my code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedTrail = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "TrailViewController") as? TrailViewController {
viewController.trail = selectedTrail
navigationController?.pushViewController(viewController, animated: true)
}
}
Full code from file below:
import UIKit
import Firebase
import FirebaseFirestore
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listOfItemsTableView: UITableView!
var elements: [Element] = []
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).addSnapshotListener({ (snapshot, error) in
if let snapshot = snapshot {
var elementsTemp = [Element]()
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
elementsTemp.append(Element(name: itemName))
}
}
self.elements = elementsTemp
self.listOfItemsTableView.reloadData()
} else {
if let error = error {
print(error)
}
}
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.listOfItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "elementCell", for: indexPath) as! ElementCell
cell.elementNameLabel.text = elements[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedTrail = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "TrailViewController") as? TrailViewController {
viewController.trail = selectedTrail
navigationController?.pushViewController(viewController, animated: true)
}
}
#IBAction func addItemButtonClicked(_ sender: Any) {
self.performSegue(withIdentifier: "toAddItemView", sender: self)
}
}
class Element {
var name = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
Update 1
Ok, I made some progress, but at this moment I can't navigate to EditItemViewController. This is how my code looks now:
import UIKit
import Firebase
import FirebaseFirestore
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listOfItemsTableView: UITableView!
var elements: [Element] = []
var element: Element?
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
db.collection("recipes").whereField("uid", isEqualTo: Auth.auth().currentUser!.uid).addSnapshotListener({ (snapshot, error) in
if let snapshot = snapshot {
var elementsTemp = [Element]()
for doc in snapshot.documents {
if let itemName = doc.get("name") as? String {
elementsTemp.append(Element(name: itemName))
}
}
self.elements = elementsTemp
self.listOfItemsTableView.reloadData()
} else {
if let error = error {
print(error)
}
}
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.listOfItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "elementCell", for: indexPath) as! ElementCell
cell.elementNameLabel.text = elements[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedElement = elements[indexPath.row]
if let viewController = storyboard?.instantiateViewController(identifier: "EditItemViewControllerID") as? MainViewController {
viewController.element = selectedElement
self.navigationController?.pushViewController(viewController, animated: true)
}
}
#IBAction func addItemButtonClicked(_ sender: Any) {
self.performSegue(withIdentifier: "toAddItemView", sender: self)
}
}
class Element {
var name = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
And how my storyboard looks at this moment:
Maybe I missed something?
Make a UIViewController named TrailViewController and add it as the class for a new UIViewController in your storyboard and you'll no longer get this error:
class TrailViewController: UIViewController {
var trail: Element?
// ...
}
Note: Also, don't forget to add the identifier for the new TrailViewController added in the storyboard as "TrailViewController" to get the UIViewController when calling instantiateViewController(identifier: in didSelectRow and perform navigation successfully.
I'm a little newbie and I have a doubt, I have a TableViewController and another ViewController that I have as a detailViewController, what I try to do is that when a cell is selected in the tableview, it presents the corresponding data stored in core data for that cell in the detailViewcontroller.
This is the file that controls the tableViewcontroller :
import UIKit
class CostumerTableViewController: UITableViewController {
var costumerArray:[Costumer] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.reloadData()
self.fetchData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return costumerArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let name = costumerArray[indexPath.row]
cell.textLabel?.text = name.costumerName!
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
let costumerDelete = costumerArray[indexPath.row]
context.delete(costumerDelete)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
do {
costumerArray = try context.fetch(Costumer.fetchRequest())
} catch {
print(error)
}
}
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "costumerDetailViewController") as! costumerDetailViewController
let n = costumerArray[indexPath.row]
let Cn = n.costumerName!
DvC.getCostumerName = Cn
self.navigationController?.pushViewController(DvC, animated: true)
}
func fetchData() {
// se crea el context
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do { // se hace el request del array
costumerArray = try context.fetch(Costumer.fetchRequest())
} catch {
print(error)
}
}
}
In the compilation does not give me any problem, some everything goes well the problem is that it does not present anything in the detail viewController label that in this case I try to send the data from this code.
This is the detailViewController code :
import UIKit
class costumerDetailViewController: UIViewController {
var getCostumerName = String()
#IBOutlet weak var labelName: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
labelName.text! = getCostumerName
}
}
First Check Cn has value or "" on this line.
let Cn = n.costumerName
Change your code in class costumerDetailViewController for declare getCostumerName
var getCostumerName = "" //for avoid crash. if value goes nil.
Use Print() in viewDidLoad and debug it.
Hope this will help you.
I have a tableView in a SubMenuViewController, when a user taps (using didSelectRowAt) on a cell and segues, I need to pass that cell to the next UserInputViewController,
Here is my code:
class SubMenuViewController: UIViewController {
//MARK: - Properties and outlets
var node: Node?
#IBOutlet weak var tableView: UITableView!
//MARK: - View controller methods
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = false
self.navigationItem.title = node?.value.rawValue
let nib = UINib(nibName: "SubMenuTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "SubMenuCell")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "userInput" {
let vc = segue.destination as! UserInputViewController
let indexPath = sender as! IndexPath
vc.node = node?.childenNode[indexPath.row]
}
}
}
//MARK: UITableViewDataSource methods
extension SubMenuViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return node!.childCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubMenuCell", for: indexPath) as! SubMenuTableViewCell
let desciptionModule = node?.childenNode[indexPath.row].value
let description = Modules.description(module: desciptionModule!)
cell.title.text = description.main
cell.subtitle.text = description.sub
return cell
}
}
//MARK: - UITableViewDelegate methods
extension SubMenuViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 68
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let selectedNode = node?.childenNode[indexPath.row] else {
return
}
if selectedNode.isLeaveNode() {
performSegue(withIdentifier: "userInput", sender: indexPath)
} else {
let subMenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "subMenu") as! SubMenuViewController
subMenuViewController.node = selectedNode
//let subMenuViewController = SubMenuViewController(node: selectedNode)
self.navigationController?.pushViewController(subMenuViewController, animated: true)
}
}
}
Right now, in my performSegue, I passed in my indexPath into the sender, and I should expect to get it back in prepareForSegue, but I can't. Any suggestions guys?
Thanks
In my opinion it isn't very good practice to pass the index path (or any other value that counts a s "data") as the sender argument; as its name suggests, it is intended for passing the object that sent the message (i.e., called the action method), in this case self (you could "relay" the original sender if your action method calls another action method instead, but that's off-topic here).
As #sCha kindly pointed out in the comments, the Apple documentation on this method in particular, though, seems to leave room for doubt nevertheless. The parameter name sender clearly comes from the homonimous argument in all control actions that follow Cocoa's target/action pattern.
My suggestion:
The best you can do I think is to store the index path in a property of your view controller:
var selectedIndexPath: IndexPath?
...set it on tableView(_:didSelectRowAt:):
if selectedNode.isLeaveNode() {
self.selectedIndexPath = indexPath
performSegue(withIdentifier: "userInput", sender: indexPath)
} else {
self.selectedIndexPath = nil
// ...
...and retrieve it (while resetting the property) in the prepareForSegue(_:sender:) implementation of the target view controller:
if let vc = segue.source as? SubmenuViewController {
if let indexPath = vc.selectedIndexPath {
vc.selectedIndexPath = nil // (reset it, just to be safe)
// Use indexPath...
}
}
I am trying to edit the table row cell. I have different tableViewController set up to display/edit the current events when they touch on the displayed events from initial tableViewController. Whenever I edit an event and hit save, it goes back to the main viewController where i am initially displaying my events, but it doesn't display the edited events. it keeps showing the original events.
Here is the portion of my main tableView file where i am displaying my events:
class EventsTable: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableData = ViewController()
#IBOutlet weak var table: UITableView!
#IBAction func saveToMainViewController (change:UIStoryboardSegue) {
let editViewController = change.source as! EditEventsTableViewController
let index = editViewController.index
let titleString = editViewController.editedTitle
tableData.eventsArray[index!].title = titleString
table.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return tableData.eventsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomeCell
cell.eventTitle.text = tableData.eventsArray[indexPath.row].title
cell.eventLocation.text = tableData.eventsArray[indexPath.row].location
cell.eventDateAndTime.text = tableData.eventsArray[indexPath.row].date
return cell
}
//function to delete cell and saves it
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableData.eventsArray.remove(at: indexPath.row)
table.reloadData()
let savedEvents = NSKeyedArchiver.archivedData(withRootObject: tableData.eventsArray)
UserDefaults.standard.set(savedEvents, forKey: "savedEvents")
UserDefaults.standard.synchronize()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "edit" {
var path = table.indexPathForSelectedRow
let detailViewController = segue.destination as! EditEventsTableViewController
detailViewController.index = path?.row
detailViewController.eventsArray = tableData.eventsArray
}
}
Here is the other tableViewController where i edit my data
class EditEventsTableViewController: UITableViewController {
#IBOutlet weak var txtEditTitle: UITextField!
var index:Int!
var eventsArray = [Event]()
var editedTitle: String!
override func viewDidLoad() {
super.viewDidLoad()
txtEditTitle.text = eventsArray[index!].title
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0 && indexPath.row == 0 {
txtEditTitle.becomeFirstResponder()
}
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "save" {
editedTitle = txtEditTitle.text
}
}
}
incase you guys need a better look at the project,
download link for the project
Probably in EditEventsTableViewController you missed table view reloading?
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
txtEditTitle.text = eventsArray[index!].title
}
It's unclear, where do you provide data for EditEventsTableViewController. Is it static table view, designed in storyboard?
I am trying to figure out from long time. Can someone tell me why my delegate method is never called. Its a tvOS project but i believe it should work as simple iOS app. On click of button i have a popup table view and on select i am trying to update button label with selected option.
protocol PopupSelectionHandlerProtocol{
func UpdateSelected(data:String)
}
class PopupViewController: UIViewController, UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var myTable: UITableView!
let months = [1,2,3,4,5,6,7,8,9,10,11,12]
let days = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31]
let yearsRange = [2015,2016,2017,2018,2019,2020]
var popupType:String!
var delegate:PopupSelectionHandlerProtocol?
override func viewDidLoad() {
super.viewDidLoad()
popupType = "months"
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (popupType == "months"){
return 12
}else if (popupType == "days"){
return 31
}else if (popupType == "years")
{
return 6
}
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = String(months[indexPath.row])
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text)
delegate?.UpdateSelected((tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text)!)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
And then This -
class VacationPlannerController: UIViewController,PopupSelectionHandlerProtocol {
#IBOutlet weak var fromMonth: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let popupDelegate = PopupViewController()
popupDelegate.delegate = self
}
func UpdateSelected(data:String){
print("Inside UpdateSelected VacationPlannerController \(data)")
fromMonth.titleLabel?.text = data
}
}
The problem is that, you are getting your delegate as nil, since there can be only one ViewController at a time presented. Since your popupViewController's view is not loaded. The viewDidLoad() method is not getting called, resulting in non-setting of popupDelegate.
If you want to check its nullity. Try this in your didSelect... Method
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.navigationController?.pushViewController(VacationPlannerController(), animated: true)
if(delegate==nil){
print("delegate is nil")
}
delegate?.UpdateSelected((tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text)!)
}
If you want the fromMonth button to be updated. First you will need to present/push VacationPlannerController in order to call its viewDidLoad(). Then only you will be able to update its property, that is, fromMonth label.
Two things to resolve this issue -
First in PopupViewController-
In didSelectRowAtIndexPath, replaced
delegate?.UpdateSelected((tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text)!)
with
self.delegate?.UpdateSelected((tableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text)!)
And Second in VacationPlannerController-
Removed below code from viewDidLoad -
let popupDelegate = PopupViewController()
popupDelegate.delegate = self
And added prepareForSegue -
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! PopupViewController
destinationVC.delegate = self
}
And issue resolved yeeee :)