When I try to reload tableview I get an error like
libc++abi.dylib: terminating with uncaught exception of type NSException
My UITableViewController
class NewTableViewController: UITableViewController {
var videos = [Video]()
{
didSet {
self.tableView.reloadData() //error here
}
}
let URL = "https://api.vid.me/channel/1/new"
override func viewDidLoad() {
super.viewDidLoad()
let request = Alamofire.request(.GET, self.URL)
request.responseObject { (response: Response<NewestVideos, NSError>) in
switch response.result {
case .Success(let newest):
self.videos = newest.videos!
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return self.videos.count}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewTableViewCell
let video = videos[indexPath.row]
cell.newVideoNameLabel?.text = video.completeUrl != nil ? video.completeUrl! : "nil"
return cell
}
}
I think it's problem with threading?
In my app I make a .get request and I get a value if I try to print in didSet like print(self.videos)
I tried adding
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
The libc++abi.dylib: terminating with uncaught exception of type NSException error, happens from any ways.
Remove reference from outlet
Bad name in performSegueWithIdentifier
IBActions type.
Check this link for more info
Make sure when you set videos, you are calling it in the Main Queue
You can protect videos when you set in on Main Queue:
dispatch_async(dispatch_get_main_queue()) {
self.videos = newest.videos!
}
Or you can protect it under:
didSet {
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
Related
As I'm writing at the title, I'm using Alamofire to get data and then decode it and append it to the list "articleList" and then try to insert it to TableView, but it seems that TableView is loaded at first, and then the data is collected and inserted to the list.
I would like to make the insertion first, then loading TableView but I cannot find a solution. I just tried it by putting defer into viewDidLoad and make tableView.realodData, but it doesn't work... Could anybody give me any idea for this situation?
import UIKit
import Alamofire
class NewsViewController: UITableViewController {
var urlForApi = "https://newsapi.org/v2/top-headlines?country=jp&category=technology&apiKey=..."
var articleList = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
defer {
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(articleList.count)
return articleList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = articleList[indexPath.row].title
return cell
}
// override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//
// }
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
for article in articleArray {
self.articleList.append(article)
}
print("insert is done")
}
}
func getNewsData(completion: #escaping (ArticlesListResult?) -> Void) {
Alamofire.request(urlForApi, method: .get)
.responseJSON { response in
if response.result.isSuccess {
if let data = response.data {
let articles = try? JSONDecoder().decode(ArticlesListResult.self, from: data)
completion(articles)
}
} else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
Instead of writing tableView.reloadData() in viewDidLoad method, you should write it after you complete appending all articles in the articleList array.
Sample code:
viewDidLoad() should be like:
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
}
updateNewsData() should be like:
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
articleList.append(contentsOf: articleArray)
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
Code inside defer is executed after "return" of method or for example after return of current cycle of some loop
Since viewDidLoad returns Void, this method returns immediately after updateNewsData() is called and it doesn't wait then another method which was called from inside returns or after code inside some closure is executed (defer isn't executed after some closure is executed since inside closure you can't return value for method where closure was declared).
To fix your code, just reload table view data after you append articles
for article in articleArray {
self.articleList.append(article)
}
tableView.reloadData()
Defer block will be executed when execution leaves the current scope, for other hand your request is async block that means that when tableView.reloadData() is called the request maybe is still in process.
You need to call reloadData when the request is finished:
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
}
...
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
for article in articleArray {
self.articleList.append(article)
}
print("insert is done")
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
I'm trying to print the chat array that is declared as a empty global variable in a table. The data that I'm trying to print is received using web sockets. I'm assigning the data in the messageReceived function, and I know that the data is getting to the program because I'm printing in a label, but the moment that I'm trying to print it in the table is simple not working. All of this is in the ViewController.swift:
import UIKit
import Starscream
var messagetext: String = ""
var tabletext: String = ""
var chat = [String] ()
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
var socket = WebSocket(url: URL(string: "ws://localhost:1337/")!, protocols: ["chat"])
#IBOutlet weak var chatMessage: UILabel!
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var tableView: UITableView!
#IBAction func buttonClick(_ sender: Any) {
messagetext = textField.text!
sendMessage(messagetext)
}
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
socket.delegate = self
socket.connect()
navigationItem.hidesBackButton = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
func textFieldDidEndEditing(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return (true)
}
deinit{
socket.disconnect(forceTimeout: 0)
socket.delegate = nil
}
}
// MARK: - FilePrivate
fileprivate extension ViewController {
func sendMessage(_ messager: String) {
socket.write(string: messager)
}
func messageReceived(_ message: String) {
chatMessage.text = message
chat.append(message)
}
}
// MARK: - WebSocketDelegate
extension ViewController : WebSocketDelegate {
public func websocketDidConnect(_ socket: Starscream.WebSocket) {
}
public func websocketDidDisconnect(_ socket: Starscream.WebSocket, error: NSError?) {
performSegue(withIdentifier: "websocketDisconnected", sender: self)
}
public func websocketDidReceiveMessage(_ socket: Starscream.WebSocket, text: String) {
// 1
guard let data = text.data(using: .utf16),
let jsonData = try? JSONSerialization.jsonObject(with: data),
let jsonDict = jsonData as? [String: Any],
let messageType = jsonDict["type"] as? String else {
return
}
// 2
if messageType == "message",
let messageData = jsonDict["data"] as? [String: Any],
let messageText = messageData["text"] as? String {
messageReceived(messageText)
}
}
public func websocketDidReceiveData(_ socket: Starscream.WebSocket, data: Data) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return(chat.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text = chat[indexPath.row] as! String
return(cell)
}
}
Assuming that you are sure about there is data to be received by your view controller, The issue would be: the tableview data source methods are called before receiving any data, which means chat data source array is still empty, thus there is no data to display.
The solution for your case is to make sure to reload the tableview after receiving data (updating the value of chat data source array), which means in your case after appending a message to chat in messageReceived method by calling reloadData() UITableView instance method:
func messageReceived(_ message: String) {
chatMessage.text = message
chat.append(message)
// here we go:
tableView.reloadData()
}
In your message received handler, issue a tableview.reloadData()
Cheers!
You need to tell the tableview that there is new data. You also need to allow for the fact that the network operation probably occurred on a background queue and UI updates must be on the main queue:
func messageReceived(_ message: String) {
DispatchQueue.main.async {
let newRow = IndexPath(row: chat.count, section:0)
chatMessage.text = message
chat.append(message)
tableView.insertRows(at:[newRow],with: .automatic)
}
}
I'm trying to enable Firebase Persistence but my code keeps crashing with:
terminating with uncaught exception of type NSException
I have tried the line of code under my viewDidLoad as well as under DataService but I still get the same crash. what do I need to do to resolve this problem I'm facing
import UIKit
import Firebase
class HomeTeamSelectionVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var club: Clubs!
var player = [Players]()
var playerFirstName = String()
var playerLastName = String()
var playerSelected: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
FIRDatabase.database().persistenceEnabled = true //Correct use of????
CLUB_KEY = ""
CLUB_KEY = club.clubKey
tableView.dataSource = self
tableView.delegate = self
DataService.ds.REF_PLAYERS.queryOrdered(byChild: "clubKey").queryEqual(toValue: club.clubKey).observe(.value, with: { (snapshot) in
print("PLAYERS_COUNT: \(snapshot.childrenCount)")
print("PLAYERS_SNAPSHOT: \(snapshot)")
self.player = []
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let playerDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let players = Players(playerKey: key, dictionary: playerDict)
self.player.append(players)
}
}
}
// self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
print("CHET: local error")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return player.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let players = player[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTeamPlayersCell") as? HomeTeamPlayersCell {
cell.configureCell(players)
return cell
} else {
return HomeTeamPlayersCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let players: Players!
players = player[indexPath.row]
print (players.playerKey)
print (players.playerFirstName)
print (players.playerLastName)
dismiss(animated: true, completion: nil)
}
}
From Firebase documentation for persistenceEnabled property:
Note that this property must be set before creating your first Database reference and only needs to be called once per application.
As such, the standard practice is to set it once in your AppDelegate class. For instance:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FIRApp.configure()
FIRDatabase.database().persistenceEnabled = true
...
return true
}
I am making a cocktail iOS application.
I am adding strings to a tableview (an ingredient to the "cabinet"). The user enters an ingredient and then presses the button ADD. It successfully adds it to the Core Data but it does not appear right away. What am I doing wrong?
Below is my code, thanks!
ViewController:
import UIKit
import CoreData
class CabinetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
var ingredientArray = [String]()
var display = [String]()
var dbIngredients = [String]()
let ingredientFetch = NSFetchRequest(entityName: "Cabinet")
var fetchedIngredient = [Cabinet]()
#IBOutlet weak var TextUI: UITextField!
#IBOutlet weak var Button: UIButton!
#IBOutlet weak var TableView: UITableView!
let moc = DataController().managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
TextUI.delegate = self
TextUI.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
TableView.delegate = self
TableView.dataSource = self
TableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
// fetch Core Data
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
} catch {
fatalError()
}
let postEndpoint: String = "http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on www.thecocktaildb.com")
print(error)
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
var count = 1
if let drinks = post["drinks"] as? [NSDictionary] {
for drink in drinks {
if let strIngredient = drink["strIngredient1"] as? String {
print(String(count) + ". " + strIngredient)
self.dbIngredients.append(strIngredient)
count++
}
}
}
})
task.resume()
TableView.reloadData()
}
func textFieldDidChange(textField: UITextField) {
search(self.TextUI.text!)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
Button.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
return true
}
func buttonPressed(sender: UIButton!) {
//ingredientArray.append(TextUI.text!)
let entity = NSEntityDescription.insertNewObjectForEntityForName("Cabinet", inManagedObjectContext: moc) as! Cabinet
entity.setValue(TextUI.text!, forKey: "ingredient")
do{
try moc.save()
}catch {
fatalError("failure to save context: \(error)")
}
showAlertButtonTapped(Button)
// dispatch_async(dispatch_get_main_queue(), { () -> Void in
// self.TableView.reloadData()
// })
}
#IBAction func showAlertButtonTapped(sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "Added!", message: "You've added " + TextUI.text! + " to your cabinet", preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
func search(str:String) {
display.removeAll(keepCapacity: false)
for s in dbIngredients{
if s.hasPrefix(str){
display.append(s)
print(s)
}
}
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.capacity
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
do{
let fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
} catch {
fatalError("bad things happened: \(error)")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let alert = UIAlertController(title: "Remove " + fetchedIngredient[indexPath.row].ingredient!,
message: "No more " + fetchedIngredient[indexPath.row].ingredient! + " in your cabinet?",
preferredStyle: .Alert)
let deleteAction = UIAlertAction(title: "Remove",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
self.fetchedIngredient.removeAtIndex(indexPath.row)
do{
let fetchedResults = try self.moc.executeFetchRequest(self.ingredientFetch)
if let result = fetchedResults[indexPath.row] as? NSManagedObject {
self.moc.deleteObject(result)
try self.moc.save()
}
}catch{
fatalError()
}
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addAction(cancelAction)
alert.addAction(deleteAction)
presentViewController(alert,
animated: true,
completion: nil)
TableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Since your problem isn't Core Data you need to use Table View beginUpdates and EndUpdates to insert the row. At the end of your buttonPressed function put this:
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
self.tableView.beginUpdates()
let totalIngredients = fetchedIngredient.count
let newItemIndexPath = NSIndexPath(forRow: totalIngredients-1, inSection: 0)
self.tableView.insertRowsAtIndexPaths([newItemIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
} catch {
fatalError()
}
On your number of rows in section:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.count
}
And on the cell for row at index path
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
return cell
}
There are a couple of problems with your code. Firstly, since you're fetching records into an array, calling reloadData will not have any impact unless you update the array. There is no automatic connection between adding a new core data record and your fetchedIngredient array.
There are a few ways to solve this, although the most common is probably to just refetch the records into the same array whenever core data is updated. Alternatively you can change your code to us NSFetchedResultsController instead of an array, which will automatically update the tableView when core data is updated (based on the predicate you provide it). This class provides the automatic connection to core data for you.
Another problem is that you are refetching the records in cellForRowAtIndexPath and didSelectRowAtIndexPath. This should not be done. Instead you should just be referring to the class-level fetchedIngredient array (or the NSFetchedResultsController if you choose to use that).
Furthermore, the call to dataTaskWithRequest runs in the background. It's not clear from the code how you're using it, but the fact that you have reloadData afterwards suggests it should impact the tableView. However because the task runs in the background, the completion handler will run after the table is reloaded. Therefore you should be calling reloadData inside the completion handler. And since it would then be running on another thread, you would have to dispatch it to the main queue, using:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Here is my problem, I'm trying to make a clone of Snapchat, a very simple one, using Parse.com with the default project given in the page, everything was going ok, until I have to take pictures and save them to send them to an user. With this code:
import UIKit
import Parse
class UsersTableViewController: UITableViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var usersArray: [String] = []
var activeRecipient: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
var query = PFUser.query()
query?.whereKey("username", notEqualTo: PFUser.currentUser()!.username!)
var users = query?.findObjects()
if let user = users {
for username in user {
println(username.username!!)
usersArray.append(username.username!!)
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("snapCell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = usersArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
activeRecipient = indexPath.row
pickImage()
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
//Upload image to parse //error come somewhere from here I think
println("Image Selected")
self.dismissViewControllerAnimated(true, completion: nil)
var imageToSend = PFObject(className:"Image")
imageToSend["image"] = UIImageJPEGRepresentation(image, 0.5)
imageToSend["senderUsername"] = PFUser.currentUser()!.username
imageToSend["recipientUsername"] = usersArray[activeRecipient]
imageToSend.save()
}
func pickImage() {
var image = UIImagePickerController()
image.delegate = self
image.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
image.allowsEditing = false
self.presentViewController(image, animated: true, completion: nil)
}
}
The error:
2015-06-24 15:03:13.414 SnapClomSwift[2043:124661] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableData PF_base64EncodedString]: unrecognized selector sent to instance 0x7a648960'
Is not very helpful to debug, any help??
EDIT1: I would think the parse function is the last calling and breaking everything but I'm not sure.
EDIT2: I have I guess fixed but I'm not sure what was in the first place, still. The new code is this:
var imageToSend = PFObject(className:"Image")
//var imageData = UIImagePNGRepresentation(image)
imageToSend["senderUsername"] = PFUser.currentUser()!.username!
imageToSend["recipientUsername"] = usersArray[activeUser]
imageToSend.saveInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
if (success == false) {
// Error.
println("Error horray! \(error?.description)")
} else {
// There was a problem, check error.description
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name: "image-png", data: imageData)
imageToSend["image"] = imageFile
imageToSend.saveInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
if success == false {
println("something is fucked up \(error?.description)")
} else {
println("Cool")
}
})
println("Done")
}
})
I guess this is not a solution but a workaround so I'm going to accept Zaph's answer.
To see the actual statement that is causing the error add an exception breakpoint:
From the Mian Menu Debug:Breakpoints:Create Exception Breakpoint.
Right-click the breakpoint and set the exception to Objective-C.
Add an action: "po $arg1".
Run the app to get the breakpoint and you will be at the line that causes the exception and the error message will be in the debugger console.