Select a Row in a tableView after fetching CoreData to pass the result to sender View Controller - didselectrowatindexpath

I have been struggling with this issue for a long time being a novice in Swift iOs coding. Hope that someone can point me to the right direction.
In the following code I do a Fetchrequest to a CoreData Entity with names of persons. Once I get the results I am trying to pick (tapping on the corresponding row) one name and pass it back to the ViewController that invoked this view with a prepare for segue.
But each time I click on the row of the name I want to select, I end up with a: "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.'"
It seems I am invoking my indexPath in the wrong way.
Here below my code:
.....
.....
let fetchedResults =
managedObjectContext.executeFetchRequest(fetchRequest,
error: &error) as [NSManagedObject]?
if let results = fetchedResults {
names = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
// MARK: - UITableViewDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell") as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey("kidName") as String?
if kidName != selectedKid {
cell.accessoryType = .None
} else {
cell.accessoryType = .Checkmark
selectedIndexPath = indexPath
}
return cell
}
// MARK: - UITableViewDelegate
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row != selectedIndexPath.row {
if let newCell = tableView.cellForRowAtIndexPath(indexPath) {
newCell.accessoryType = .Checkmark
}
if let oldCell = tableView.cellForRowAtIndexPath(selectedIndexPath) {
oldCell.accessoryType = .None
}
selectedIndexPath = indexPath
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SelectedKid" {
let cell = sender as UITableViewCell
if let indexPath = tableView.indexPathForCell(cell) {
let kidName = names[indexPath.row]
selectedKid = kidName.valueForKey("kid") as String!
}
}
}
}
The idea is that when I tap on the name I go back with an unwind segue to the sender controller and I put the selectedName in the correct place.
Thank you fro any help!
Cristiano

your problem are with the lines below...
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell") as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey("kidName") as String?
your code should look like...
let cell = tableView.dequeueReusableCellWithIdentifier("KidCell", forIndexPath: indexPath ) as UITableViewCell
let kidName = names[indexPath.row]
cell.textLabel!.text = kidName.valueForKey(kidName)

Related

Wrong cell data if fast scroll CollectionView swift 5

Can anybody help me with CollectionView? I parce JSON from some API, and get String values - title and image adress, then assign that Data to custom collectionViewCell in method cellForItem(). BUT than i fast scroll the collection view some cell can be with wrong images, or duplicate images. I also override method prepareForReuse in my custom cell class and set cell.image = UIImage() , but it didnt work. Please help me to understand what wrong)
Sorry for my poor English...
collectionViewController
'''
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Result",
for: indexPath) as! ResultCell
let film = results[indexPath.item]
cell.name.text = film.title
cell.imageView.layer.borderColor = UIColor(white: 0, alpha: 0.3).cgColor
cell.imageView.layer.borderWidth = 2
cell.imageView.layer.cornerRadius = 10
cell.layer.cornerRadius = 10
// setUp image for cell
cell.imageView.image = nil
contentManager.getImage(film.poster.image, cell.imageView)
return cell
}
'''
method get image
'''
func getImage(_ url_str:String, _ imageView:UIImageView) {
let url:URL = URL(string: url_str)!
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {(data, response, error) in
if data != nil {
let image = UIImage(data: data!)
if(image != nil) {
DispatchQueue.main.async(execute: {
imageView.image = image
imageView.alpha = 0
UIView.animate(withDuration: 1, animations: {
imageView.alpha = 1.0
})
})
}
}
})
task.resume()
}
'''
Cell custom class
'''
class ResultCell: UICollectionViewCell {
#IBOutlet var imageView: UIImageView!
#IBOutlet var name : UILabel!
override func prepareForReuse() {
self.imageView.image = UIImage()
self.name.text = nil
super.prepareForReuse()
}
}
'''

Reorder and Move NSTableView Row with NSPasteboard

I have an NSTableView where I can drag and drop table rows to reorder them. This works by setting a drag type in my view controller:
#IBOutlet weak var tableView: NSTableView!
let dragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerForDraggedTypes([dragType])
}
...and then implementing the reordering with these table delegate methods:
//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
let item = NSPasteboardItem()
item.setString(String(row), forType: dragType)
return item
}
//Verify proposed drop
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
if dropOperation == .above {
return .move
}else{
return []
}
}
//Accept drop of one or multiple rows
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
var oldIndexes = [Int]()
info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) { dragItem, _, _ in
if let str = (dragItem.item as! NSPasteboardItem).string(forType: self.dragType), let index = Int(str) {
oldIndexes.append(index)
}
}
//Do a bunch of logic to reorder the table rows...
}
Now, in addition to reordering my table rows, I want to be able to drag a row and drop it somewhere else in my app--sort of like moving the row to a different place.
I have a custom NSView set up as the drag destination for this, and I can drag a table row and the custom view reacts appropriately with a table row dragged over it:
class MyCustomView: NSView{
required init?(coder: NSCoder) {
super.init(coder: coder)
let taskDragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")
registerForDraggedTypes([taskDragType])
}
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
//...
}
override func draggingExited(_ sender: NSDraggingInfo?) {
//...
}
}
But the part I'm unclear on is how to get the table row, and its associated object set as a property on the NSTableCellView, when the drop occurs:
//This is another method in MyCustomView
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
guard let items = draggingInfo.draggingPasteboard.pasteboardItems else{ return false }
for item in items{
print("---")
print(item) //<-- NSPasteboardItem
let index = item.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "myapp.task"))
print(index) //<-- Index of the table row
//How can I also get the task object associated with the row?
}
}
I can get the index of the row, but what I need is the entire object from the row's data source so I can take action on the object it represents. My suspicion is that I need to change how I'm using pasteboardWriterForRow to put my object on the pasteboard, but I'm unsure how to do that.
How can I pass both the row index and the object to the pasteboard?
Soon after posting this, I decided to try something crazy, and it turns out I found a way to make this a lot simpler. It seems that NSPasteboard is really only necessary if you need to get stuff into and out of your app. Since I am just moving something from one part of my app to another, I can use the drag and drop delegate methods as events and handle the data myself.
First, I set up a global array for adding dragged task objects:
var draggedTasks = [Task]()
Whenever a task is dragged from my NSTableView, I add them to the array in the aforementioned delegate method where dragging starts:
//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
//Queue tasks for moving to phases or projects
draggedTasks.append(tasks[row])
//Queue row for reordering
let item = NSPasteboardItem()
item.setString(String(row), forType: dragType)
return item
}
Then where I accept the drop in MyCustomView, I take action on the draggedTasks array:
//Save dropped tasks
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
//Do stuff to draggedTasks based on the context of where they are dropped
return true
}
This is much simpler than going down the NSPasteboard route. 🙂

UICollectionView-refreshing: getting each cell more than one time

Hi I'm having the issue that after refreshing my collectionView I am getting each cell twice on
my view. And after refreshing a second time I am getting each cell 3 times. I am also getting this issue when presenting the view with a segue a second time.
How can I fix that?
Can someone help me?
Thank you in advance!!!
import UIKit
import Firebase
class FeedViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let refreshControl = UIRefreshControl()
#IBOutlet weak var collectionview: UICollectionView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
collectionview.refreshControl = refreshControl
refreshControl.addTarget(self, action: #selector(refreshWeatherData(_:)), for: .valueChanged)
refreshControl.tintColor = UIColor(hex: "#F17D32")
}
#objc private func refreshData(_ sender: Any) {
fetchData()
posts.removeAll()
collectionview.reloadData()
}
private func fetchData() {
getPost()
self.refreshControl.endRefreshing()
}
func getPosts(){
let ref = Database.database().reference()
ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
let postsSnap = snap.value as! [String : AnyObject]
for (_,post) in postsSnap {
let posst = Post()
if let author = post["author"] as? String, late date = post["date"], let postID = post["postID"] as? String, let userID = post["userID"] as? String {
posst.date = date
posst.author = author
posst.postID = postID
posst.userID = userID
self.posts.sort(by: {$0.postdateprog > $1.postdateprog})
self.posts.append(posst)
}
self.collectionview.reloadData()
}
})
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(posts.count)
return self.posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
cell.authorlabel.text = self.posts[indexPath.row].author
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: self.collectionview.frame.width / 2 - 20, height: self.collectionview.frame.width * 0.7)
}
}
Firebase functions are asynchronous and the code in the closure will be called after the synchronous code runs. In other words, you have this
fetchData()
posts.removeAll()
collectionview.reloadData()
but posts.removeAll() and collectionview.reloadData() will be called before the array is populated.
So here's a truncated overview of the order in which things should be called.
#objc private func refreshData(_ sender: Any) {
self.loadFirebaseData()
}
func loadFirebaseData() {
...
self.posts = []
for (_,post) in postsSnap {
self.posts.append(posst)
}
self.posts.sort(by: {$0.postdateprog > $1.postdateprog}
self.collectionview.reloadData()
}
Notice that I moved the sort and the reloadData outside of the for loop. There's no reason to call those functions over and over as the datasource is being populated. Do it after that task is complete.

How do you display 2 tableViews of the same user? Firebase database

Firebase Database
driver1 Main page
toyota car details
FInal Output
So the problem is that driver1 has 2 cars. how can i make the tableView show for toyota car information and mazda car information.
I was able to show driver 1's car list by this code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "driverRequestCell", for: indexPath)
if let email = Auth.auth().currentUser?.email {
Database.database().reference().child("Driver").queryOrdered(byChild: "email").queryEqual(toValue: email).observe(.childAdded, with: { (snapshot) in
let snapshot = self.driverRequests[indexPath.row]
if let driverRequestDictionary = snapshot.value as? [String:AnyObject] {
if let typeOfCar = driverRequestDictionary["car"] as? String {
cell.textLabel?.text = typeOfCar
}
}
})
}
return cell
}
So my current code for didSelectRowAt is:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let snapshot = driverRequests[indexPath.row]
performSegue(withIdentifier: "carDetailsSegue", sender: snapshot)
}
I think it has something to do with the snapshot, but I can't figure it out. Need help from the pro's
If you want to show all car information in the same cell, first you need a cell with three UILabel, one for all of the information you want to show, This is an example:
I suppose you have your own custom cell class, so you need to drag&drop label outlets into your class.
Now, you have to change you code into cellForRowAt: this way:
if let driverRequestDictionary = snapshot.value as? [String:AnyObject] {
if let typeOfCar = driverRequestDictionary["car"] as? String {
cell.modelLabel?.text = typeOfCar
}
if let colorOfCar = driverRequestDictionary["color"] as? String {
cell.colorLabel?.text = colorOfCar
}
if let plateOfCar = driverRequestDictionary["plate"] as? String {
cell.plateLabel?.text = plateOfCar
}
}
You can use a default value if color and plate doesn't exists adding else statements, or using three operand:
`if let plateOfCar = driverRequestDictionary["plate"] as? String {
cell.plateLabel?.text = plateOfCar
}else{
cell.plateLabel?.text = "Unknown"
}`
ADVICE: Avoid to do a request into cellForRowAt:, Instead of this, make an asynchronous request (on viewDidLoad: for example) using a closure. Then use the result of the closure to fill an array and then reload you table view.
EDIT:
You should struct your nodes this way, where giuseppesapienza is user ID, while note is your car objext:

NSUnknownKeyException on a model variable

'[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key text.'
I'm getting an NSUnknownKeyException at the text variable of my Message model when I attempt to access it in my second tableView() function (at the end of this code). I am still pretty new to swift and don't know how to resolve this. Through my google searches, I found that it could be something to do with storyboards, but I've actually opted to create my UI programmatically so I'm not sure how that could be causing this.
import UIKit
import Firebase
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "chat", style: .plain, target: self, action: #selector(handleChat))
observeMessages()
}
#objc func handleChat() {
let chatController = ChatController(collectionViewLayout: UICollectionViewLayout())
navigationController?.pushViewController(chatController, animated: true)
//present(chatController, animated: true, completion: nil)
}
var messages = [Message]()
func observeMessages() {
let ref = Database.database().reference().child("FalconsVPackers").child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message()
message.setValuesForKeys(dictionary)
self.messages.append(message)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}, withCancel: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "called")
let message = messages[indexPath.row]
cell.textLabel?.text = message.text
return cell
}
}
My Message model is very simple...
import UIKit
class Message: NSObject {
var text: String?
}
Any help or advice would be greatly appreciated!
You are using NSKeyValueCoding to populate Message object so you must add #objc to text property.
message.setValuesForKeys(dictionary) // NSKeyValueCoding
.
class Message : NSObject {
#objc var text: String?
}
If you just want to populate your message object, better just use properties directly instead of setValuesForKeys. And in this case you don't have to use #objc too.
let message = Message()
message.text = dictionary["text"]

Resources