Insert information from REST API in a table view - xcode

I after asking another question today and fixed my Swift 4.2 code, I realized that I can't debug the app because of some profiles.
Errors shown when Testing application
The app also crashes on launch so there is not much I can do for now. I believe that the issue is how I am getting the information from the webserver (currently my computer). I am quite new to this, so i might have some mistakes in my code, so bear with me.
import UIKit
class InfoTableViewController: UITableViewController {
//MARK: Properties
class Produs {
var nume: String!
var codBare: String!
init(nume: String, codBare: String) {
self.nume = nume
self.codBare = codBare
}
}
var produse = [Produs]()
override func viewDidLoad() {
super.viewDidLoad()
//Load elements from server, theoretically
loadProducts()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return produse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "InfoTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InfoTableViewCell else {
fatalError("The dequeued cell is not an instance of InfoTableViewCell.")
}
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.nume
cell.nameInfo.text = produs.codBare
return cell
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
//MARK: Private Methods
private func loadProducts() { //This function retrieves information in a JSON format from the server
var request = URLRequest(url: URL(string: "192.168.0.145")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
let decoder = JSONDecoder()
let json = try decoder.decode([[String]].self, from: data!)
print(json)
for produs in json {
print(produs)
var nume_prod: String = produs[0] // Numele produsului
var cod_ext: String = produs[1] // Codul de bare al produsului
var prod_obj = Produs(nume: nume_prod, codBare: cod_ext)
self.produse.append(prod_obj)
}
} catch {
print("JSON Serialization error")
}
}).resume()
}
}
I am getting the information from the server in a JSON file that is an array of arrays and looks like this:
[
[
"product1",
"code1"
],
[
"product2",
"code2"
],
[
"product3",
"code3"
]
]
Thank you for your help!

Don't send the JSON as array of arrays, send it as array of dictionaries. It simplifies the decoding considerably.
[
{"name":"product1", "code":"code1"},
{"name":"product2", "code":"code2"},
{"name":"product3", "code":"code3"}
]
Then declare the model as struct and never declare properties as implicit unwrapped optional which are initialized with non-optional values. If you need optionals declare them as regular optional (?) otherwise non-optional
struct Produs {
let name: String
let code: String
}
Replace loadProducts with
private func loadProducts() { //This function retrieves information in a JSON format from the server
let url = URL(string: "192.168.0.145")!
URLSession.shared.dataTask(with: url, completionHandler: {[unowned self] data, response, error in
if let error = error { print(error); return }
do {
self.produse = try JSONDecoder().decode([Produs].self, from: data!)
print(self.produse)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}).resume()
}
And replace cellForRowAt with
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InfoTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InfoTableViewCell
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.name
cell.nameInfo.text = produs.code
return cell
}

Related

Searchbar only searches in first section - actionGoal or goalTitle

This is the data model I used to import a json file.
struct ActionResult: Codable {
let data: [Datum]
}
struct Datum: Codable {
let goalTitle, goalDescription, goalImage: String
let action: [Action]
}
struct Action: Codable {
let actionTitle: String
let actionGoal: String
Now I am trying to create a searchbar, but it only searches in one section, although I have not defined a section in the data model but it is picking either the goalTitle or actionGoal.
var index = 0
var action: Action? // this is for the segue
var result: ActionResult? {
didSet {
guard let result = result else { return }
allSectionDataActionMap = Dictionary(uniqueKeysWithValues: result.data.enumerated().map { ($0.0, ($0.1, $0.1.action)) })
updateFilteredData()
}
}
var allSectionDataActionMap = [Int: (datum: Datum, actions: [Action])]()
// Maps the section index to the Datum & filtered [Action]
var filteredSectionDataActions = [Int: (datum: Datum, actions: [Action])]()
let searchController = UISearchController()
This is part of the setup code.
override func numberOfSections(in tableView: UITableView) -> Int {
return filteredSectionDataActions.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredSectionDataActions[section]?.actions.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ActionTableView.dequeueReusableCell(withIdentifier: "ActionCell", for: indexPath) as! ActionTableCell
let action = filteredSectionDataActions[indexPath.section]?.actions[indexPath.row]
// setup cell for action
cell.actionItem.text = action?.actionTitle
cell.actionImage.image = UIImage(named: action!.actionImage)
cell.actionImage.layer.cornerRadius = cell.actionImage.frame.size.width / 2
cell.actionGoal.text = action?.actionGoal
// cell.actionBenefit.text = action?.actionBenefit
// cell.actionCalculator.text = action?.actionCalculator
This is the table view code
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateFilteredData(for: searchText.lowercased())
tableView.reloadData()
}
func updateFilteredData(for searchText: String = String()) {
if searchText.isEmpty {
filteredSectionDataActions = allSectionDataActionMap
} else {
for (index, (datum, actions)) in allSectionDataActionMap {
let filteredActions = actions.filter { $0.actionTitle.lowercased().contains(searchText) }
if filteredActions.isEmpty {
filteredSectionDataActions[index] = (datum, actions)
} else {
filteredSectionDataActions[index] = (datum, filteredActions)
}
This is for the searchbar.

Realm note based App not properly deleting the Correct Object

When a cell is deleted, the item at the end of the list takes the place of the item that just got deleted. This only happens when there are more than 3 items in the list.
In the gif below I delete numbers 3 and 4 which leaves me with numbers 1,2,5 in the simulator. HOWEVER in the Realm file I have numbers 1,2,4. I have no clue why it does this?
Data Model
import Foundation
import RealmSwift
class Item: Object {
#objc dynamic var name = ""
}
View Controller
import UIKit
import RealmSwift
class ListViewController: UITableViewController {
let realm = try! Realm()
var itemArray : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.itemArray = realm.objects(Item.self)
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Item", message: "", preferredStyle: .alert)
alert.view.tintColor = UIColor.red
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
let newItem = Item()
newItem.name = textField.text!
try! self.realm.write {
self.realm.add(newItem)
}
self.tableView.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.itemArray!.count//Size of the Array
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath)//Asigns the Protocol Cell
let data = self.itemArray![indexPath.row]
cell.textLabel?.text = data.name
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let item = itemArray?[indexPath.row] {
try! self.realm.write {
self.realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
You are retrieving an unsorted result set from Realm, which as per documentation does not necessarily retain insertion order after deletions (basically when you remove 3, then 5 is shifted in its place):
Note that the order of Results is only guaranteed to stay consistent when the query is sorted. For performance reasons, insertion order is not guaranteed to be preserved.
So there are two things you can do:
1.) sort the result set
2.) instead of assuming you're only deleting a single object and otherwise have no movements of any sort, you can rely on Realm's own diffing + change set evaluation with a notification token so that you receive a change set for any possible change that happens to the result set.
// see https://realm.io/docs/swift/latest/#collection-notifications
class ViewController: UITableViewController {
var notificationToken: NotificationToken? = nil
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Person.self).filter("age > 5")
// Observe Results Notifications
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}

How to download name and images on one row in ascending order

I have researched this and nothing seems to be working. I am trying to build a recipe app and the the image of the dish & names of the dish (appetizer) are not downloading in order. How can I do this?
Code:
class Appetizers: UITableViewController {
var valueToPass: String!
var valuePassed: String!
var appetizer = [String]()
var images = [UIImage]()
func refresh() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
override func viewDidLoad() {
super.viewDidLoad()
// Parse - class - column
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
let load = object.objectForKey("appetizer") as! String
self.appetizer.append(load)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error != nil {
print(error)
} else {
if let data = imageData {
self.images.append(UIImage(data: data)!)
self.tableView.reloadData()
}
}
})
self.tableView.reloadData()
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
sleep(1)
refresh()
}
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return appetizer.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = appetizer[indexPath.row]
// add image to table
if images.count > indexPath.row {
cell.imageView?.image = images[indexPath.row]
}
return cell
}
// when user taps on cell ...
func getCellLabel () {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
valueToPass = currentCell.textLabel!.text
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
getCellLabel()
self.performSegueWithIdentifier("0", sender: self)
}
}
When performing asynchronous queries, you have no assurances regarding the order they complete. So, the concept of two separate arrays, one an array of strings and another an array of images will always be problematic.
You could, for example, replace images with a dictionary indexed by the appetizer name, and thus it wouldn't matter what order they complete.
var appetizer = [String]()
var images = [String: UIImage]()
Thus, it might look like:
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { objects, error in
guard error == nil, let objects = objects else {
print(error)
return
}
for (index, object) in objects.enumerate() {
let appetizerName = object.objectForKey("appetizer") as! String
self.appetizer.append(appetizerName)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock { imageData, error in
guard error == nil, let data = imageData else {
print(error)
return
}
// when the image comes in, asynchronously update only that one row
self.images[appetizerName] = UIImage(data: data)
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade)
}
}
// reload the table only once, after all of the `appetizer` entries are created (but likely before the images come in)
self.tableView.reloadData()
}
}
And
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let name = appetizer[indexPath.row]
cell.textLabel?.text = name
cell.imageView?.image = images[name]
return cell
}
Or you could just replace these two separate properties with one that is an array of custom objects (e.g. an Appetizer object that has both a name property and an image property).
But any way you do that, you want to name sure you're not dealing with two separate array.
By the way, but this process of loading all of the images can be problematic if you have a lot of rows. This code is employing "eager" loading of images (loading them whether they're currently required or not). The problem is that images are relatively large assets (in comparison to the string values) and you can run into memory issues, network bandwidth issues, etc.
One generally likes to employ "lazy" loading (e.g. let cellForRowAtIndexPath request the image only when it's needed. For example, let's say you have 200 rows, of which only 12 are visible at one point. You shouldn't be requesting 200 images, but rather only that for the 12 visible ones. If you take the image retrieval out of viewDidLoad and, instead, have cellForRowAtIndexPath request them one at a time, you'll have much better network performance and less demanding memory characteristics.
If you're going to save the images in some structure like the code currently does, at the very least make sure you purge those images upon receiving notification of a memory warning (and, obviously, gracefully handle the re-requesting them in a JIT manner as needed).
I figured out the problem with my table not loading without sleep() ...
I had 'self.tableView.reloadData()' outside of the block.
Rob was very helpful :)

Two functions with one variable in Swift

I have two functions in a view controller. The first function parses JSON and makes an array; another generates a table with the array data. The problem is that it seems that the first function cannot send its array data to the second function.
Here is the code:-
class secondViewController: UIViewController, UITableViewDataSource {
let chartTitle:[String] = ["Name",......]
func parseJSON(){
let url = NSURL(string: "http://00000.us-west-2.elasticbeanstalk.com/index.php?000000")
let request = NSURLRequest(URL: url!)
do {
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
var name = json["Name"]
var chartContent:[String] = ["\(name)",.....] //Contents of current chart contents
} catch{
//Handle Exception
}
} catch{
//Handle Exception
}
}
override func viewDidLoad() {
parseJSON()
...
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //currnet table information.
let cell = UITableViewCell()
cell.textLabel?.text = chartTitle[indexPath.row] + "\t\t\t\t\t here comes info" + chartContent[indexPath.row]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return chartTitle.count
}
}
This code has an error at the tableView function:
Use of unresolved identifier 'chartContent'
I tried to declare the variables outside the first function which is right under the class secondViewController but there was another error on UITableViewDataSource.
Any solution for these?
Charttitle is defined outside any procedure, so it's available everywhere. Chartcontent is defined in a block, so it's usable just in it's block
Its because chartContent is a local variable just available to parseJson func only and its scope is till that func block. You have to create this variable the same way you dis chartTitle to be available throughout the class.

How to implant a sectioned tableview with Parse in Swift

I am struggling to figure out how to implement a sectioned table view using parse. I am able to correctly section the table view if I don't load the names I want. However, when I use parse the names don't load in time and therefore are missed as it is async.
When I reload the table in queryFriends() it doesn't show up. My theory is that the table isn't sectioned again.
How do I make the table section again?
Or does anyone have any ideas?
Thanks for your help
import UIKit
import Parse
import Foundation
class MyFriendsTableView: UITableViewController, UITableViewDataSource, UITableViewDelegate{
var names:[String] = []
/*var names: [String] = [
"AAAA",
"Clementine",
"Bessie",
"Yolande",
"Tynisha",
"Ellyn",
"Trudy",
"Fredrick",
"Letisha",
"Ariel",
"Bong",
"Jacinto",
"Dorinda",
"Aiko",
"Loma",
"Augustina",
"Margarita",
"Jesenia",
"Kellee",
"Annis",
"Charlena",
"!##",
"###"
]*/
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl?.addTarget(self, action: "queryFriends", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl!)
}
override func viewDidAppear(animated: Bool) {
UIApplication.sharedApplication().statusBarHidden = false
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent
queryFriends()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func queryFriends() {
let currentUser = PFUser.currentUser()?.username
let predicate = NSPredicate(format: "status = 'Friends' AND fromUser = %# OR status = 'Friends' AND toUser = %#", currentUser!, currentUser!)
var query = PFQuery(className: "FriendRequest", predicate: predicate)
var friends:[String] = []
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects as? [PFObject] {
for object in objects {
if object["toUser"] as? String != currentUser {
friends.append(object["toUser"] as! String)
} else if object["fromUser"] as? String != currentUser {
friends.append(object["fromUser"] as! String)
}
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
self.names = friends
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
}
/* type to represent table items
`section` stores a `UITableView` section */
class User: NSObject {
let name: String
var section: Int?
init(name: String) {
self.name = name
}
}
// custom type to represent table sections
class Section {
var users: [User] = []
func addUser(user: User) {
self.users.append(user)
}
}
// `UIKit` convenience class for sectioning a table
let collation = UILocalizedIndexedCollation.currentCollation()
as! UILocalizedIndexedCollation
// table sections
var sections: [Section] {
// return if already initialized
if self._sections != nil {
return self._sections!
}
// create users from the name list
var users: [User] = names.map { name in
var user = User(name: name)
user.section = self.collation.sectionForObject(user, collationStringSelector: "name")
return user
}
// create empty sections
var sections = [Section]()
for i in 0..<self.collation.sectionIndexTitles.count {
sections.append(Section())
}
// put each user in a section
for user in users {
sections[user.section!].addUser(user)
}
// sort each section
for section in sections {
section.users = self.collation.sortedArrayFromArray(section.users, collationStringSelector: "name") as! [User]
}
self._sections = sections
return self._sections!
}
var _sections: [Section]?
// table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].users.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let user = self.sections[indexPath.section].users[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = user.name
return cell
}
/* section headers
appear above each `UITableView` section */
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// do not display empty `Section`s
if !self.sections[section].users.isEmpty {
return self.collation.sectionTitles[section] as? String
}
return ""
}
/* section index titles
displayed to the right of the `UITableView` */
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject] {
return self.collation.sectionIndexTitles
}
override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
return self.collation.sectionForSectionIndexTitleAtIndex(index)
}
}
You can try to clear the _sections in queryFriends:
self._sections = nil
self.names = friends
self.tableView.reloadData()
self.refreshControl?.endRefreshing()

Resources