iOS reloading a UITableView from a Swift class/object - ios

I am trying to build a object oriented iOS app and I am having problems calling a table reload (ui interaction) from one of my swift class files.
If I am working without objects and write all that stuff in my InterfaceController's viewDidLoad method everything works... but not if I put that into classes.
I am using an asynchronous request in order to load json data from a webservice. After receiving the data I am using this data as data source for my table view.
It seems the tableview is initialized after startup with no data, so it is neccessary to call reload() on the tableview after finishing the async request.
So here are the details:
Main TableController
import UIKit;
class DeviceOverview: UITableViewController {
#IBOutlet var myTableView: UITableView!
var myDevices = Array<String>();
var myDevicesSection = NSMutableDictionary()
var deviceObjects = Array<NSDictionary>();
var mappingSectionIndex = Array<String>();
var sharedUserDefaults = NSUserDefaults(suiteName: "group.barz.fhem.SharingDefaults")
// THIS IS AN ATTEMPT TO give the class itself as PARAMETER
// ALSO TRIED TO USE myTableView (IBOutlet)
var fhem :FHEM = FHEM(tableview : self)
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fhem.sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var devicesInSection : Array<NSDictionary> = self.fhem.sections[self.fhem.mappingSectionIndex[section]] as! Array<NSDictionary>
return devicesInSection.count;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("device", forIndexPath: indexPath) as! TableViewCell
let sectionName : String = fhem.mappingSectionIndex[indexPath.section]
if let devices : Array<NSDictionary> = self.fhem.sections.objectForKey(sectionName) as? Array<NSDictionary> {
let device = devices[indexPath.row]
var alias : NSString?;
if let attr : NSDictionary = device.objectForKey("ATTR") as? NSDictionary {
alias = attr.objectForKey("alias") as? String
}
if (alias != nil) {
cell.deviceLabel.text = alias as? String
}else{
if let deviceName : String = device.objectForKey("NAME") as? String {
cell.deviceLabel.text = deviceName
}
}
}
return cell
}
FHEM Class
import UIKit
class FHEM: NSObject {
var devices : [NSDictionary]
var sections : NSMutableDictionary
var deviceNames : [String]
var mappingSectionIndex : [String]
var myTableViewController : DeviceOverview
init(tableview : DeviceOverview){
self.devices = Array<NSDictionary>()
self.sections = NSMutableDictionary()
self.deviceNames = Array<String>()
self.mappingSectionIndex = Array<String>()
self.myTableViewController = tableview
super.init()
let url = NSURL(string: "xyz");
var request = NSURLRequest(URL: url!);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) in
if let jsonData: NSData? = data {
// do some great stuff
//
// this is an attempt to call the table view to reload
self.myTableViewController.myTableView.reloadData()
}
}
}
}
It throws compile errors if I try to put the self variable into the constructor of my constructor
var fhem :FHEM = FHEM(tableview : self)
It also does not work if I try to put the UITableView directly into the constructor
var fhem :FHEM = FHEM(tableview : myTableView)
Am I walking along complete wrong path using objects and interacting with the ui?

You can just post a Notification when your async task finishes:
NSNotificationCenter.defaultCenter().postNotificationName("refreshMyTableView", object: nil)
Add an observer to that notification to your DeviceOverview class method viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshList:", name:"refreshMyTableView", object: nil)
and add the method that will be fired at your DeviceOverview class
func refreshList(notification: NSNotification){
myTableView.reloadData()
}

Hi You can use Notification as suggested in Above answers otherwise you can use delegation to resolve this problem
Delegate is doing something like that you done but in easy manner.
Here you pass table view controller in you custom class.this thing you can do with delegate. create custom delegate method in your custom class. set delegate with table view controller object.
here you don't required to take IBOutlet of your table view because your controller inherited from table view controller so it's view is table view
import UIKit
class DeviceOverview: UITableViewController,FHEMDelegate {
#IBOutlet var myTableView: UITableView!
var myDevices = Array<String>();
var myDevicesSection = NSMutableDictionary()
var deviceObjects = Array<NSDictionary>();
var mappingSectionIndex = Array<String>();
var sharedUserDefaults = NSUserDefaults(suiteName: "group.barz.fhem.SharingDefaults")
var fhem :FHEM?
// THIS IS AN ATTEMPT TO give the class itself as PARAMETER
// ALSO TRIED TO USE myTableView (IBOutlet)
override func viewDidLoad() {
super.viewDidLoad()
fhem = FHEM ()
fhem?.delegate = self
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// var devicesInSection : Array<NSDictionary> = self.fhem!.sections[self.fhem!.mappingSectionIndex[section]] as Array<NSDictionary>
return 5;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("device", forIndexPath: indexPath) as UITableViewCell
let sectionName : String = fhem!.mappingSectionIndex[indexPath.section]
if let devices : Array<NSDictionary> = self.fhem!.sections.objectForKey(sectionName) as? Array<NSDictionary> {
let device = devices[indexPath.row]
var alias : NSString?;
if let attr : NSDictionary = device.objectForKey("ATTR") as? NSDictionary {
alias = attr.objectForKey("alias") as? String
}
if (alias != nil) {
// cell.deviceLabel.text = alias as? String
}else{
if let deviceName : String = device.objectForKey("NAME") as? String {
// cell.deviceLabel.text = deviceName
}
}
}
return cell
}
func reloadDataOfTable()
{
self.tableView.reloadData()
}
}
FHEM
import UIKit
protocol FHEMDelegate{
func reloadDataOfTable()
}
class FHEM : NSObject {
var devices : [NSDictionary]
var sections : NSMutableDictionary
var deviceNames : [String]
var mappingSectionIndex : [String]
//var myTableViewController : DeviceOverview
var delegate :FHEMDelegate?
override init(){
self.devices = Array<NSDictionary>()
self.sections = NSMutableDictionary()
self.deviceNames = Array<String>()
self.mappingSectionIndex = Array<String>()
self.delegate = nil
super.init()
let url = NSURL(string: "http://google.com");
var request = NSURLRequest(URL: url!);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response, data, error) in
if let jsonData: NSData? = data {
// do some great stuff
//
// this is an attempt to call the table view to reload
self.delegate?.reloadDataOfTable()
}
}
}
}
Here in FHEM class create one custom delegate method reloadDataOfTable that implement in your viewcontroller class so when got response of code it call that method and this method (reloadDataOfTable) content code for reload table data.

I don't know much Swift, but I've been writing Objective-C for iOS for a while now. (In Objective-C at least) you must call init on super before accessing self, otherwise you're object isn't initialized correctly. You're also not assigning self to the result of super.init(), which is necessary in Objective-C, looks like it isn't in Swift though.
tl;dr -- Move the call to super.init() to the very first line in your FHEM class' init method.

Related

table view cannot be displayed on device

I am developing iOS app where I want to display table content one my device.but unable fetch and display...but contents are being displayed on console view.
I am using json getmethod() to fetch the details
also using view controller and view table and swift language
import UIKit
class UpdateSheetManagementViewController:
UIViewController, UITableViewDelegate, UITableViewDataSource, WebserviceDelegate {
//var FinalArray = [[String:Any]]()
// class func instantiateFromStoryboard() -> UpdateSheetManagementViewController {
// let storyboard = UIStoryboard(name: "Management", bundle: nil)
// return storyboard.instantiateViewController(withIdentifier: String(describing: self)) as! UpdateSheetManagementViewController
// }
#IBOutlet weak var managementTableView: UITableView!
var controllerType : String!
var array = [[String : Any]]()
override func viewDidLoad() {
super.viewDidLoad()
title = controllerType
updatesheetWebserviceCall()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// updatesheetWebserviceCall()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 320.0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = tableView.dequeueReusableCell(withIdentifier: "UpdateSheetManagementTableViewCell", for: indexPath) as! UpdateSheetManagementTableViewCell
let cellIdentifier : String = "UpdateSheetManagementTableViewCell";
let cell : UpdateSheetManagementTableViewCell =
tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! UpdateSheetManagementTableViewCell
let dict = array[indexPath.section]
cell.classname.text = dict["Cls_Name"] as? String
cell.dateFrom.text = dict["Date_From"] as? String
cell.date_To.text = dict["Date_To"] as? String
cell.downloadButton.tag = indexPath.row
cell.downloadButton.addTarget(self, action: #selector(onDownloadButtonClicked(button:)), for: .touchUpInside)
return cell
}
func onDownloadButtonClicked(button : UIButton) {
let postion = button.tag
let data = array[postion]
let attachment = data["Attachment"] as? String
if(attachment != nil && attachment!.characters.count > 0){
let userid = UserDefaults.standard.string(forKey: USER_ID)
let finalString = "https://skillskool.mycit.co.in/PagesParentApp/Today-ClassNotes.aspx?FilePath=" + attachment! + "&UserId=" + userid! + "&PageName=UpdateSheet"
let url = URL(string: finalString)
if(url != nil){
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
}
func updatesheetWebserviceCall() {
let str = ""
let webClass = WebserviceClass()
webClass.delegate = self
webClass.fireRequest(functionName: "MgmtUpdateSheet.php", requestString: str, view: view)
}
func webserviceDidFinishWith(response: [String : Any], functionName: String) {
parseUpdateSheetWebservice(response: response)
}
func parseUpdateSheetWebservice(response : [String : Any]) {
let arr = response["homework"] as? [[String : Any]]
if(arr != nil){
// array.removeAll()
self.array.append(contentsOf: arr!)
// managementTableView.reloadData()
}
}
}
Add managementTableView.reloadData() after updating the data and make sure to set the delegate and datasource in either view controller or storyboard
Set tableview delegate and datasource in viewDidLoad and reload tableview in web service response.
// set tableview delegate and datasource in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
title = controllerType
managementTableView.dataSource = self
//managementTableView.delegate = self
updatesheetWebserviceCall()
}
// reload tableview upon web service response
func parseUpdateSheetWebservice(response : [String : Any]) {
let arr = response["homework"] as? [[String : Any]]
if(arr != nil){
// array.removeAll()
self.array.append(contentsOf: arr!)
self.managementTableView.reloadData()
/*
// or reload table using main queue, if you web service operation is in background queue
DispatchQueue.main.async {
self.managementTableView.reloadData()
}
*/
}
}
In the beginning, you need to set tableview dataSource and delegate and after loading data you also need to reload data.
self.managementTableView.delegate = self;
self.managementTableView.datasource = self;
Reload data:
self.array.append(contentsOf: arr!)
self.managementTableView.reloadData()
You have to set in viewDidLoad
managementTableView.dataSource = self
and un comment managementTableView.reloadData()
func parseUpdateSheetWebservice(response : [String : Any]) {
let arr = response["homework"] as? [[String : Any]]
if(arr != nil){
// array.removeAll()
self.array.append(contentsOf: arr!)
DispatchQueue.main.async {
managementTableView.reloadData() }
}
}
I think delegate and datasource connections are missed, please add the following code in viewDidLoad function:
self.managementTableView.delegate = self;
self.managementTableView.dataSource = self;

Swift 3, how to parse first and show it on next TableViewController

I am really new to developing iOS apps. So I also apologize in advance if my coding is not close to being optimal or good written...
I am trying to create fairly simple app only for showing retrieving and showing data, but I hit a bump which I am trying to solve it for past few days, but I need help...
So the whole functionality of the app is like this: when opened(displying viewcontroller with textfield and button), user should enter username and push button. After button is pushed app should then do 2 things in next order:
combine certain URL address with entered username, retrieve data
(what kind of data, depends on given username - I gave few examples
on the bottom of that post) and pass them to next
tableviewcontroller
display tableviewcontroller and show parsed data.
But, this does not happen, what I noticed is, that my app opens new tableviewconotrller first, and after tableviewcontroller is open, it parses data, which causes that my table has no data (but I can see that data has been parsed, using print())
I am using Swift 3.
ViewController with textfield and button for "login":
import UIKit
class ViewController: UIViewController {
var zavodi = [[String]]()
#IBOutlet weak var uporabnik: UITextField!
#IBAction func vstop(_ sender: Any) {
self.parse { (completed) in
if (completed){
let zavodiView = self.storyboard?.instantiateViewController(withIdentifier: "zavodiController") as! ZavodiController
zavodiView.niz = self.uporabnik.text!
zavodiView.zavodi = self.zavodi
self.navigationController?.pushViewController(zavodiView, animated: true)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.title="Vstop"
}
func parse( completion: #escaping (_ completed: Bool)-> () ){
let uporabnikIme = uporabnik.text!
//parsing
let shramba = UserDefaults.standard
let zavodiUrl = "https://oaza.rrc.si/eZaporiMobi/kapStSta/read?pUporabniskoIme="+uporabnikIme;
var zavodiRequest = URLRequest(url: URL(string: zavodiUrl)!)
zavodiRequest.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: zavodiRequest) { (data, response, error) in
if (error != nil) {
print("Error")
}
else {
var zavodiTemp = [Zavod]()
do {
let fetchedData = try JSONSerialization.jsonObject(with: data!) as! NSArray
//print(fetchedData)
zavodiTemp.removeAll()
for najdenZavod in fetchedData {
let vsakZavod = najdenZavod as! [String : Any]
let zavodId = vsakZavod["zaiId"] as! Int
let naziv = vsakZavod["kratekNaziv"] as! String
let ureditev = vsakZavod["ureditev"] as! Int
let zasedenost = vsakZavod["zasedenost"] as! String
let kapaciteta = vsakZavod["kapaciteta"] as! Int
let stStanje = vsakZavod["stStanje"] as! Int
let naBegu = vsakZavod["naBegu"] as! Int
let prekinitev = vsakZavod["prekinitev"] as! Int
zavodiTemp.append(Zavod(zavodId: zavodId, naziv: naziv, ureditev: ureditev, zasedenost: zasedenost,kapaciteta: kapaciteta, stStanje: stStanje, naBegu: naBegu, prekinitev: prekinitev))
}
zavodiTemp = zavodiTemp.sorted(by: {$0.ureditev < $1.ureditev})
self.zavodi.removeAll()
for e in zavodiTemp {
var temp = [String]()
temp.append(String(e.zavodId)) //0
temp.append(e.naziv) //1
temp.append(String(e.ureditev)) //2
temp.append(e.zasedenost) //3
temp.append(String(e.kapaciteta)) //4
temp.append(String(e.stStanje)) //5
temp.append(String(e.naBegu)) //6
temp.append(String(e.prekinitev)) //7
self.zavodi.append(temp)
}
let steviloZavodov = self.zavodi.count
shramba.set(self.zavodi, forKey:"zavodi")
shramba.set(steviloZavodov, forKey:"steviloZavodov")
var s = [[String]]()
s = shramba.array(forKey: "zavodi") as! [[String]]
for e in s{
print(e[2]+" "+e[1])
}
}
catch {
print()
}
}
}
task.resume()
completion(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
class Zavod {
var zavodId : Int
var naziv : String
var ureditev : Int
var zasedenost : String
var kapaciteta : Int
var stStanje : Int
var naBegu : Int
var prekinitev : Int
init(zavodId : Int, naziv : String, ureditev : Int, zasedenost : String, kapaciteta : Int, stStanje : Int, naBegu : Int, prekinitev : Int) {
self.zavodId = zavodId
self.naziv = naziv
self.ureditev = ureditev
self.zasedenost = zasedenost
self.kapaciteta = kapaciteta
self.stStanje = stStanje
self.naBegu = naBegu
self.prekinitev = prekinitev
}
}
}
TableViewController where should parsed data be displayed:
import UIKit
class ZavodiController: UITableViewController {
var niz = ""
var zavodi = [[String]]()
override func viewDidLoad() {
super.viewDidLoad()
print(niz)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("število zavodov"+String(self.zavodi.count))
return self.zavodi.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "zavodCelica", for: indexPath) as! ZavodCelica
cell.nazivZavoda.text = self.zavodi[indexPath.row][1]
cell.kapaciteta.text = self.zavodi[indexPath.row][4]
cell.zasedenost.text = self.zavodi[indexPath.row][3]
cell.stStanje.text = self.zavodi[indexPath.row][5]
cell.naBegu.text = self.zavodi[indexPath.row][6]
cell.prekinitev.text = self.zavodi[indexPath.row][7]
return cell
}
}
I also tried to use UserDefaults, but it just seems that tableviewcontroller just simply gets loaded before actual parsing is done.
For usernames, you can use:
-"manj", returns 1 record
-"mref", returns 3 records
-"mmli", returns 14 records
I would really appreciate any help.
There can be a lot of ways to achieve this. Reloading the table is also a fix. But to be very accurate with the data your should wait for the function completion before moving to the next screen. Look at the code
func parse()( completion: #escaping (_ completed: Bool)-> () ){
// parse logic goes in here
// after the processing finishes return true like following
completion(true) // you can also have logic to return failures.
}
This will be called like
self.parse { (completed) in
if (completed){
let zavodiView = storyboard?.instantiateViewController(withIdentifier: "zavodiController") as! ZavodiController
zavodiView.niz = uporabnik.text!
zavodiView.zavodi = self.zavodi
navigationController?.pushViewController(zavodiView, animated: true)
}
}
Afetr appending data to the table array add this
DispatchQueue.main.async {
self.tableview.reloadData()
}

Array printing same results from API call

I am building an app whereby you enter ingredients and you return a bunch of recipes based on your input. I'm making the calls to the API using alamofire and these seem to be successful. The problem I'm having is the 6 results in my test call are repeating 1 recipe 6 times rather than returning all the results in separate cells. This is the API call code:
import Alamofire
class RecipeAp: NSObject{
var concoctions = [RecipeDetails]()
func provideRecipeDetailsForName(name: String, completed:#escaping ([RecipeDetails]) -> Void) {
let urlSearchString = URL_FULL + "onion" + "soup"
Alamofire.request(urlSearchString).responseJSON(completionHandler: { response in
let details = RecipeDetails()
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let matches = dict["matches"] as? [[String: Any]] {
for ingredient in matches {
if let name = ingredient["ingredients"] as? [String] {
details.ingredients = name
self.concoctions.append(details)
}
}
for recipeName in matches {
if let name = recipeName["recipeName"] as? String {
details.recipeTitle = name
print("the recipe name = \(name.debugDescription)")
self.concoctions.append(details)
}
}
}
completed(self.concoctions)
}
})
}
}
This is my model:
class RecipeDetails: NSObject {
var recipeID: String?
var recipeImageURL: String?
var recipeTitle: String?
var recipeSourceURL: String?
var recipePublisher: String?
var ingredients: [String]?
}
This is my customCell setup
import UIKit
class RecipeListCustomCell: UITableViewCell {
#IBOutlet weak var recipeTitle: UILabel!
#IBOutlet weak var recipeUrl: UILabel!
var recipe: RecipeDetails? {
didSet {
updateView()
}
}
func updateView() {
recipeTitle.text = recipe?.recipeTitle
recipeUrl.text = recipe?.recipeSourceURL
}
}
And finally this is my viewController
import UIKit
class MainVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var recipe = RecipeAp()
var results = [RecipeDetails]()
override func viewDidLoad() {
super.viewDidLoad()
loadRecipes()
}
func loadRecipes() {
recipe.provideRecipeDetailsForName(name: "onion" + "soup") { (response) in
self.results = response
self.tableView.reloadData()
}
}
}
extension MainVC: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return results.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:
"RecipeListCustomCell", for: indexPath) as! RecipeListCustomCell
let recipe = results[indexPath.row]
cell.recipe = recipe
return cell
}
}
Not sure how to display all the recipes separately in each cell. I have also attached some screen shots on what I am getting back from the API and the display in the simulator.
You create only one instance of RecipeDetails for each response. So, you add exactly the same reference into your self.concoctions repeatedly.
You may need to write something like this:
func provideRecipeDetailsForName(name: String, completed: #escaping ([RecipeDetails]) -> Void) {
let urlSearchString = URL_FULL + "onion" + "soup"
Alamofire.request(urlSearchString).responseJSON(completionHandler: { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let matches = dict["matches"] as? [[String: Any]] {
for match in matches {
//### Create a new instance for each iteration.
let details = RecipeDetails()
if let ingredients = match["ingredients"] as? [String] {
details.ingredients = ingredients
}
if let recipeName = match["recipeName"] as? String {
details.recipeTitle = recipeName
print("the recipe name = \(recipeName.debugDescription)")
}
//### Add the instance once in the iteration
self.concoctions.append(details)
}
}
completed(self.concoctions)
}
})
}

Parsing JSON into tableview

I am receiving a JSON file from a remote server and I can display the result in a label. The JSON data is working fine when I call function processJSONData() and the tableview works fine with a simple array. How can I incorporate both to display the result from the JSON file in the tableview? Kindly look at the code below and edit. Many thanks:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var countryLabel: UILabel!
#IBOutlet weak var capitalLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//processJSONData()
self.myTableView.registerClass(UITableViewCell.self,forCellReuseIdentifier: "cell")
self.myTableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func processJSONData(){
let urlPath = "http://dubaisinan.host22.com/service1.php"
let url : NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url,completionHandler: {(data, respose, error) -> Void in
if error != nil {
println(error)
}
else {
self.abc(data)
}
})
task.resume()
}
func abc(data:NSData)
{
var parseError: NSError?
let result:AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &parseError);
if(parseError == nil){
if let dictResult = result as? NSArray{
dispatch_async(dispatch_get_main_queue()) {
self.countryLabel.text = dictResult[2]["Capital"] as? String
}
}
}
}
#IBOutlet weak var myTableView: UITableView!
var items = ["One","Two", "Three","Four"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.myTableView
.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
}
I don't see you assign your parsing result to global "items" and reload tableview with new data anywhere.
could be done here
if let dictResult = result as? NSArray{
self.items = dictResult
self.myTableView.reloadData()
///the rest of the code
}
You have to save the JSON data into a class-level variable, which you will define outside of any function, similar to how you defined "items". Assuming you have a list of countries with the capital of each, this might look like so:
var countryAndCapitalData = [(country: String, capital: String)]()
This could be improved by first defining a struct to contain your data:
struct CountryInfo
{
name: String
capital: String
init(name:String, capital:String)
{
self.name = name
self.capital = capital
}
}
which lets you define your data array as an array of CountryInfo:
var countryAndCapitalData = [CountryInfo]()
Then in your "abc" function (which I insist you rename to something like processCountryData), store the pairs of country name + capital name strings in countryAndCapitalData. For example:
countryAndCapitalData.append(CountryInfo(countryName, capitalName))
Use a For loop to loop through values in dictResult. Creating countryName and capitalName depends on the structure of your JSON, but from your example it might look like this:
for countryDictionary in dictResult[2]
{
if let countryName = countryDictionary["country"], let capitalName = countryDictionary["capital"]
{
countryAndCapitalData.append(CountryInfo(countryName, capitalName))
}
}
Then in tableView.cellForRowAtIndexPath, populate the cell label(s) with countryAndCapitalData[indexPath.row].name and countryAndCapitalData[indexPath.row].capital.
And finally, be sure to reload the table after the loop (thanks Eugene):
dispatch_async(dispatch_get_main_queue()) {
self.myTableView.reloadData()
}
Apologies for any compilation errors, as I'm typing this from a Windows machine.
You should update your items property in abc method call and then refresh the table:
func abc(data: NSData) {
// Do something with data
items = .. // processed data
}
var items: [String]? {
didSet {
NSOperationQueue.mainQueue.addOperationWithBlock {
self.tableView.reloadData()
}
}
}

Same method works in ViewDidLoad, but doesn't work in custom TableViewCell button action

I am learning iOS swift and creating an application to learn about getting JSON data and saving this data to CoreData while working with Itunes search api. I have a table view and am using a custom table view cell, it has some labels, an image and a download button. My purpose is to able to get album and all songs in that album information to CoreData after clicking the button of the cell. Here is the list of what is working and what is not working:
Clicking the button gives me the correct CollectionId for the album.
The album information is successfully added to CoreData.
I'm NOT able to fill my songs array after calling the api in my download action method. It stays empty. Note that when I call the api in ViewDidLoad with a manually entered collection id, the songs array is filled.
Codes:
API Controller to get the song information.
import Foundation
protocol songAPIControllerForCoreDataProtocol {
func didReceiveAPISongResults(results: NSDictionary)
}
class songAPIControllerForCoreData {
var delegate: songAPIControllerForCoreDataProtocol
init(delegate: songAPIControllerForCoreDataProtocol) {
self.delegate = delegate
}
func searchItunesForSongsBelongingTo(searchTerm: String) {
// The iTunes API wants multiple terms separated by + symbols, so I'm replacing spaces with + signs
let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
// Escape anything else that isn't URL-friendly
if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
// Using Itunes search api to find people that has a music album with the entered search term
let urlPath = "https://itunes.apple.com/lookup?id=\(escapedSearchTerm)&entity=song"
let url: NSURL = NSURL(string: urlPath)!
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error != nil) {
// If there is an error in the web request, print it to the console
println(error.localizedDescription)
}
var err: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as! NSDictionary
println(jsonResult[0])
if(err != nil) {
// If there is an error parsing JSON, print it to the console
println("JSON Error \(err!.localizedDescription)")
}
self.delegate.didReceiveAPISongResults(jsonResult)
println(jsonResult)
})
task.resume()
}
}
}
Song class (Not CoreData):
import Foundation
class Song {
var title: String
var previewURL: String
var collectionID: Int
init(title: String, previewURL: String, collectionID: Int) {
self.title = title
self.previewURL = previewURL
self.collectionID = collectionID
}
class func songsWithJSON(allResults: NSArray) -> [Song] {
// Create an empty array of Albums to append to from this list
var songs = [Song]()
// Store the results in our table data array
if allResults.count>0 {
// Sometimes iTunes returns a collection, not a track, so we check both for the 'name'
for result in allResults {
var title = result["trackName"] as? String
if title == nil {
title = result["collectionName"] as? String
}
if title == nil {
title = result["collectionName"] as? String
}
let previewURL = result["previewUrl"] as? String ?? ""
let collectionID = result["collectionId"] as? Int ?? 0
var newSong = Song(title: title!, previewURL: previewURL, collectionID: collectionID)
songs.append(newSong)
}
}
return songs
}
}
Finally AlbumViewController:
import UIKit
import CoreData
class AlbumViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, searchAPIControllerProtocol, songAPIControllerForCoreDataProtocol {
#IBOutlet
var tableView: UITableView!
#IBOutlet weak var artistNameOutlet: UILabel!
var songapi : songAPIControllerForCoreData?
var api : searchAPIController?
var albums = [Album]()
var songs = [Song]()
var imageCache = [String : UIImage]()
//Variables that take the values after segue from uTableViewController
var artistID, artistName: String?
let cellIdentifier: String = "albumCell"
//for CoreData
var error:NSError?
let managedObjectContext = (UIApplication.sharedApplication().delegate
as! AppDelegate).managedObjectContext
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.albums.count
}
func download(sender: AnyObject){
var senderButton : UIButton = sender as! UIButton
let newAlbum = NSEntityDescription.insertNewObjectForEntityForName("Albums", inManagedObjectContext: managedObjectContext!) as! Albums
let newSong = NSEntityDescription.insertNewObjectForEntityForName("Songs", inManagedObjectContext: managedObjectContext!) as! Songs
songapi!.searchItunesForSongsBelongingTo((String)(self.albums[senderButton.tag].collectionID))
newAlbum.albumArt = self.albums[senderButton.tag].largeImageURL
newAlbum.albumID = (String)(self.albums[senderButton.tag].collectionID)
newAlbum.albumName = self.albums[senderButton.tag].title
newAlbum.albumPrice = self.albums[senderButton.tag].price
newAlbum.artistID = self.artistID!
newAlbum.artistName = self.artistName!
newAlbum.numberOfSongs = (String)(self.albums[senderButton.tag].trackCount)
newAlbum.has = []
println(self.songs)
for(var i = 1; i < self.albums[senderButton.tag].trackCount - 1; i++){
newSong.collectionID = String(self.songs[i].collectionID)
newSong.previewURL = self.songs[i].previewURL
newSong.songName = self.songs[i].title
}
self.managedObjectContext?.save(&self.error)
println(newAlbum)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: AlbumTableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! AlbumTableViewCell
cell.albumCellButton.tag = indexPath.row
cell.albumCellButton.addTarget(self, action: "download:", forControlEvents: .TouchUpInside)
let album = self.albums[indexPath.row]
cell.albumName.text = album.title
cell.artistImage.image = UIImage(named: "user7.png")
cell.numberOfSongs.text = (String)(album.trackCount) + " Songs"
// Get the formatted price string for display in the subtitle
let formattedPrice = album.price
// Grab the artworkUrl60 key to get an image URL for the app's thumbnail
let urlString = album.thumbnailImageURL
// Check our image cache for the existing key. This is just a dictionary of UIImages
var image = self.imageCache[urlString]
if( image == nil ) {
// If the image does not exist, we need to download it
var imgURL: NSURL = NSURL(string: urlString)!
// Download an NSData representation of the image at the URL
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
image = UIImage(data: data)
// Store the image in to our cache
self.imageCache[urlString] = image
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as?AlbumTableViewCell {
cellToUpdate.artistImage.image = image
}
})
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
else {
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) as?AlbumTableViewCell {
cellToUpdate.artistImage.image = image
}
})
}
cell.priceOfAlbum.text = formattedPrice
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func didReceiveAPIResults(results: NSDictionary) {
var resultsArr: NSArray = results["results"] as! NSArray
dispatch_async(dispatch_get_main_queue(), {
self.albums = Album.albumsWithJSON(resultsArr)
self.tableView!.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
func didReceiveAPISongResults(results: NSDictionary) {
var resultsArr: NSArray = results["results"] as! NSArray
dispatch_async(dispatch_get_main_queue(), {
self.songs = Song.songsWithJSON(resultsArr)
self.tableView!.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = artistName
artistNameOutlet.text = " Albums"
api = searchAPIController(delegate: self)
songapi = songAPIControllerForCoreData(delegate: self)
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
api!.searchItunesForAlbumsBelongingTo(self.artistName!, id: self.artistID!)
// Do any additional setup after loading the view.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let songsController = segue.destinationViewController as! SongsViewController
var albumCollectionID = self.albums
var albumIndex = tableView!.indexPathForSelectedRow()!.row
var collectionID = self.albums[albumIndex].collectionID
var albumName = self.albums[albumIndex].title
songsController.albumName = albumName
songsController.collectionID = collectionID
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need to write the definition of your protocol like follows:
protocol songAPIControllerForCoreDataProtocol : class {
func didReceiveAPISongResults(results: NSDictionary)
}
This will make it class only protocol and will force the confirming type to have reference semantics. If no 'class' keyword is specified it will have value semantics.
Without the 'class' keyword the issue here I assume is setting the delegate via initializer. When you pass delegate like:
songapi = songAPIControllerForCoreData(delegate: self)
This will assume the delegate param to be on value type and copy the value rather than send a reference of it. So when you set that value in init() the delegate member will point to a new object rather than the UIViewController passed.
If you set the delegate like:
songapi.delegate = self
it will work without the 'class' keyword in protocol definition.

Resources