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

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()
}

Related

TableView is not loading any JSON data Swift 4

I have spent about three weeks trying to figure this out. I can get the section title to view, but none of the JSON data is showing. When I do a standard "array" contained in the file, it displays.
I have followed every tip and trick out there and I am stuck.
I think this may have something to do with AnyObject and String, but I am missing something. Please see my code below:
import UIKit
import Alamofire
import SwiftyJSON
class UserTableViewCell: UITableViewCell {
#IBOutlet weak var userFirstname: UILabel!
#IBOutlet weak var userLastname: UILabel!
}
class Profile2VC: UITableViewController {
#IBOutlet var userTable: UITableView!
var usertitles = ["First Name", "Last Name", "Email", "Mobile Number"]
var userinfo = [[String:AnyObject]]() //Array of dictionary
override func viewDidLoad() {
super.viewDidLoad()
let defaultValues = UserDefaults.standard
let URL_USER_LOGIN = "https://www.myapp.com/myphp.php"
let userid = "13"
let parameters: Parameters=["id":coolid]
Alamofire.request(URL_USER_LOGIN, method: .get, parameters:
parameters).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar)
if let userData = swiftyJsonVar["user"].arrayObject {
self.userinfo = userData as! [[String:AnyObject]]
//debugPrint(userData)
}
if self.userinfo.count > 0 {
self.userTable.reloadData()
}
}
}
self.userTable.reloadData()
// Uncomment the following line to preserve selection between
presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the
navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in 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 userinfo.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection
section: Int) -> String? {
return "Section \(section)"
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell",
for: indexPath) as! UserTableViewCell
//let userTitles = usertitles[indexPath.row]
let userInfo = userinfo[indexPath.row]
cell.userFirstname?.text = userInfo["first_name"] as? String
cell.userLastname?.text = userInfo["last_name"] as? String
//cell.imageView?.image = UIImage(named: fruitName)
//cell.textLabel?.text = usertitles[indexPath.row]
return cell
}
}
First of all you need to reload your table view in main queue. Check below code:
DispatchQueue.main.async {
self.userTable.reloadData()
}
And you are reloading it multiple times which is not good so Remove unwanted reload code and you final code will be:
Alamofire.request(URL_USER_LOGIN, method: .get, parameters: parameters).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar)
if let userData = swiftyJsonVar["user"].arrayObject {
self.userinfo = userData as! [[String:AnyObject]]
//debugPrint(userData)
}
if self.userinfo.count > 0 {
DispatchQueue.main.async {
self.userTable.reloadData()
}
}
}
}
//self.userTable.reloadData() //Remove this code
And once your API call done, Make sure debugPrint(userData) is printing some data and then when you are reloading userTable put a breakpoint in cellForRowAt and confirm that it's calling.
Then if its calling and data is there from server, You are good to go.
But if cellForRowAt method didn't called then you need to check your userTable DataSource and Delegate if it's correctly connected or not.
Try this code :
let API = URL(string:"http://www.myapp.com/myphp.php")
let request = URLRequest(url:API!)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let data = data {
if String(data: data, encoding: String.Encoding.utf8) != nil {
let data = data
let json = try? JSONSerialization.jsonObject(with: data, options: [])
let jsonData = json as! [[String:Any]]
DispatchQueue.main.sync {
let user = jsonData.flatMap { $0["user"] as? String }
print(user)
self.annocumentTableView.reloadData()
}
}
}
})
task.resume()

Tableview doesn't display data in swift 2

I'm working on the tableView in swift 2.2 in xcode 7.3.1 and I'm sure from my code because it's not the first time for me to deal with tableView , I'm pulling data correctly from server and stored it in array but I notice the two function that is related to table view is not called so the table view appear empty for me ! I added cell and linked tableview with view also from layout.
I don't know where is the problem!
class studentTeacherList: UIViewController , UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var studentParentTable: UITableView!
#IBOutlet weak var loadIndicator: UIActivityIndicatorView!
var username:String!
var fromSender: String?
var toRec: String?
var student_id = [Int]()
var parent_id = [String]()
var student_names = [String]()
var parent_name = [String]()
//Sent Data
var s_id:Int = 0
var s_name = ""
var p_id = ""
var p_name = ""
override func viewDidLoad() {
super.viewDidLoad()
studentParentTable.delegate = self
studentParentTable.dataSource = self
let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()
username = prefs.objectForKey("user")as! String
fromSender = prefs.objectForKey("Sender")as! String
toRec = prefs.objectForKey("Receiver")as! String
self.loadIndicator.startAnimating()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
self.loadList()
//self.studentParentTable.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.loadIndicator.stopAnimating()
})
});
studentParentTable.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return student_names.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//This method to define each cell at Table View
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = student_names[indexPath.row]
cell.detailTextLabel?.text = parent_name[indexPath.row]
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func backButton(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil )
}
func loadList()
{
var normallink = "mylinkhere"
normallink = normallink + "?teacherid=" + self.username
var studentParentURL:NSURL = NSURL (string: normallink)!
let data = NSData(contentsOfURL: studentParentURL)!
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
if let alldata = json["data"] as? [[String: AnyObject]] {
for onedata in alldata {
if let stu_id = onedata["id"] as? Int {
student_id.append(stu_id)
}
if let stu_name = onedata["studentName"] as? String {
student_names.append(stu_name)
}
if let par_id = onedata["parentId"] as? String {
parent_id.append(par_id)
}
if let par_name = onedata["parentName"] as? String {
parent_name.append(par_name)
}
}
}
} catch {
print("Error Serializing JSON: \(error)")
}
print(student_names.count)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
s_id = student_id[indexPath.row]
s_name = student_names[indexPath.row]
p_id = parent_id[indexPath.row]
p_name = parent_name[indexPath.row]
}
}
It looks like you aren't reloading after this call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
self.loadList()
//self.studentParentTable.reloadData()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.loadIndicator.stopAnimating()
})
});
So you should add studentParentTable.reloadData() after self.loadIndicator.stopAnimating().

IOS 9 TableViewCell Not Visible Until Selected

I use a service in a background thread to fetch a post request. Then I use NSJSONSerialization to turn that into an array. I loop thorough the array to create an array of teams. Then i go back to the main queue and call the completion handler.
Team:
class Team
{
private (set) var id: Int
private (set) var city: String
private (set) var name: String
private (set) var abbreviation: String
init(data: JSONDictionary)
{
id = data["team_id"] as? Int ?? 0
city = data["city"] as? String ?? ""
name = data["team_name"] as? String ?? ""
abbreviation = data["abbreviation"] as? String ?? ""
}
}
Service:
func getTeams(urlString: String, completion: [Team] -> Void)
{
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = NSURL(string: urlString)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
print(data)
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? JSONArray {
var teams = [Team]()
for team in json {
let team = Team(data: team as! JSONDictionary)
teams.append(team)
}
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
}
} catch {
print("error in NSJSONSerialization")
}
}
}
task.resume()
}
I then try to use data to populate a tableView. I also loop through and print out all the team names to the console with success. The problem I am having It populate the tableView but everything is all white. I cant see any txt from my labels until I touch it. While the table cell is selected I can see the contents of the labels which are in black. But if i touch another one only the currently selected label is showing. It seems they should all just show up visible once the data is loaded.
custom cell:
class TeamTableViewCell: UITableViewCell {
var team: Team? {
didSet {
updateCell()
}
}
#IBOutlet weak var title: UILabel!
#IBOutlet weak var abbreviation: UILabel!
func updateCell()
{
title.text = team?.name ?? ""
abbreviation.text = team?.abbreviation ?? ""
}
}
Controller:
var teams = [Team]()
override func viewDidLoad() {
super.viewDidLoad()
title = "Teams"
let service = NBAService()
service.getTeams("https://probasketballapi.com/teams?api_key=\(Constants.API.APIKey)", completion: didLoadTeams )
}
func didLoadTeams(teams: [Team])
{
self.teams = teams
tableView.reloadData()
// This actuall works returns an list of team names to the console.
for team in teams {
print("Team: \(team.name)")
}
}
// 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 teams.count
}
struct Storyboard {
static let TeamCell = "TeamCell"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.TeamCell, forIndexPath: indexPath) as! TeamTableViewCell
// Configure the cell...
cell.team = self.teams[indexPath.row]
return cell
}
When i print the teams names to the console that prints fine so I know that I have successfully got the data back from the request. And one team at a time is visible when the cell is selected. What am I missing
This is kind of strange:
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}
}
I would replace this with:
dispatch_async(dispatch_get_main_queue()) {
completion(teams)
}

How to return value from Swift closure?

I'm new at swift closures.
I have this code and i can't extract the values from the forloop into the arrays outside the closure.
Sorry about this question but i have already searched stackflow but without success in this specific case.
Thanks in advance.
var namesArray: [String] = []
var imagesArray: [String] = []
var timesArray: [String] = []
var placesArray: [String] = []
func getData(){
//DATA SOURCE
// Create request for user's Facebook data
let request = FBSDKGraphRequest(graphPath: cmp.pageId, parameters: ["fields": "events"])
// Send request to Facebook
request.startWithCompletionHandler {
(connection, result, error) in
if error != nil {
// Some error checking here
print(error.debugDescription)
} else
if let pageData = result["events"] {
print(result["events"]!!["data"]!![0]["name"])
if let eventDetails = pageData!["data"] {
// EVENT DETAILS FETCH
for var i = 0; i < eventDetails!.count; i++
{
let fetchedEventName = eventDetails![i]["name"] as? String!
let fetchedEventTime = eventDetails![i]["start_time"] as? String!
if eventDetails?[i]["place"]??["name"] != nil {
if let fetchedEventPlace = eventDetails?[i]["place"]??["name"] {
self.placesArray.append(fetchedEventPlace! as! String)
}
} else {
self.placesArray.append("Lisbon")
}
self.namesArray.append(fetchedEventName!)
self.timesArray.append(fetchedEventTime!)
}
print("Name of event: \(self.namesArray)")
}
} else {
print("Error.")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getData()
}
EDIT: I want to show the fetched result into a tableview that's already set.
Heres the tableview code.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("previewCell") as? PreviewCell {
var img: UIImage!
let url = NSURL(string: imagesArray[indexPath.row])!
if let data = NSData(contentsOfURL: url){
img = UIImage(data: data)
} else {
img = UIImage(named: "ant")
}
cell.configureCell(img, title: namesArray[indexPath.row], time: timesArray[indexPath.row], place: placesArray[indexPath.row])
return cell
} else {
return PreviewCell()
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return namesArray.count
}
func addNewEvent(name: String, image: String, time: String, place: String)
{
namesArray.append(name)
imagesArray.append(image)
timesArray.append(time)
placesArray.append(place)
}
You are already getting the number in to the arrays so what you need to do is just reload the tableview after the for loop.
//end of for-loop
self.tableView.reloadData()
The reason for this is the asynchronous execution of the request.startWithCompletionHandler. The code inside that block will most likely execute after your tableView already loaded and it therefor needs a reload after the data has been fetched
In case if you need to return data from closure you can define completion on your getData() method like this
func getData(completion:([DataType]) -> Void) {
//process data
let dataArray:DataType = []
completion(dataArray)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getData { (completionData) -> Void in
// process returned data
}
}
Hope this helps

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