When tableview is first loaded, the index print(indexPath.row) shows index 0,1,2. When the tableview is loaded again the indexpath repeats itself 3 times, in the debugger it will be 0,1,2,0,1,2,0,1,2. This happens within two seconds, but if you scroll the tableview before this there is a fatal error: Index out of range error.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! EstablishmentCell
let object = Model.sharedInstance.items[indexPath.row]
cell.venueDistance.text = object.venueDistance
cell.titleLabel.text = object.name
object.loadCoverPhoto { image in
dispatch_async(dispatch_get_main_queue()) {
cell.coverPhotoView.image = image
}
}
let myCustomSelectionColorView = UIView()
myCustomSelectionColorView.backgroundColor = UIColor(red:0.16, green:0.65, blue:0.64, alpha:1.0)
cell.selectedBackgroundView = myCustomSelectionColorView
}
}self.activityIndicatorView.stopAnimating()
return cell
}}
EDIT1:
Establishment Class
class Establishment: NSObject {
//Properties
var record: CKRecord!
var name: String!
var establishmentDescription: String!
var establishmentOpening: String!
var venueLocation: CLLocation!
weak var database: CKDatabase!
var assetCount = 0
var establishmentDistance: String!
let locationManager = CLLocationManager()
//Map Annotation Properties
// var coordinate: CLLocationCoordinate2D {
// return venueLocation.coordinate
// }
var title: String? {
return name
}
var venueDescription: String? {
return establishmentDescription
}
var venueDistance: String? {
return establishmentDistance
}
//Initializers
init(record: CKRecord, database: CKDatabase) {
self.record = record
self.database = database
//let venueLocation = record["Location"] as? CLLocation
//let location = CLLocation()
//let userLocation = CLLocation(latitude: 51.211963, longitude: -0.769298)
self.name = record["Name"] as? String
self.establishmentDescription = record["Description"] as? String
self.establishmentOpening = record["OpeningTimes"] as? String
self.venueLocation = record["Location"] as? CLLocation
let location = locationManager.location
let distanceBetween: CLLocationDistance = (venueLocation!.distanceFromLocation(location!))
self.establishmentDistance = String(format: "%.f", distanceBetween)
}
func fetchPhotos(completion: (assets: [CKRecord]!) ->()) {
let predicate = NSPredicate(format: "Establishment == %#", record)
let query = CKQuery(recordType: "EstablishmentPhoto", predicate: predicate)
// Intermediate Extension Point - with cursors
database.performQuery(query, inZoneWithID: nil) { [weak self] results, error in
defer {
completion(assets: results)
}
guard error == nil,
let results = results else {
return
}
self?.assetCount = results.count
}
}
func loadCoverPhoto(completion:(photo: UIImage!) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
var image: UIImage!
defer {
completion(photo: image)
}
guard let asset = self.record["CoverPhoto"] as? CKAsset,
path = asset.fileURL.path,
imageData = NSData(contentsOfFile: path) else {
return
}
image = UIImage(data: imageData)
}
}
Model Class:
protocol ModelDelegate {
func errorUpdating(error:NSError)
func modelUpdated() class Model{
//Properties
let EstablishmentType = "Establishment"
static let sharedInstance = Model()
var delegate: ModelDelegate?
var items: [Establishment] = []
let userInfo: UserInfo
let locationManager = CLLocationManager()
var latitude : String!
var longitude : String!
var isLoading = Bool()
//Define Databases
// Represents the default container specified in the iCloud section of the Capabilities tab for the project.
let container: CKContainer
let publicDB: CKDatabase
let privateDB: CKDatabase
//Indicator
// Ask for Authorisation from the User.
func requestLocation() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.distanceFilter = 10
self.locationManager.startUpdatingLocation()
// Check if this app has permission to use location services
let authorizationStatus:CLAuthorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus == CLAuthorizationStatus.Denied {
// TODO: Tell user that app functionality may be limited
}
else if authorizationStatus == CLAuthorizationStatus.NotDetermined {
// Manually prompt the user for permission
self.locationManager.requestWhenInUseAuthorization()
}
else if authorizationStatus == CLAuthorizationStatus.AuthorizedWhenInUse {
self.locationManager.startUpdatingLocation()
}
}
//Initializers
init () {
container = CKContainer.defaultContainer()
publicDB = container.publicCloudDatabase
privateDB = container.privateCloudDatabase
userInfo = UserInfo(container: container)
}
#objc func refresh() {
let location = locationManager.location
let radius = CGFloat(1000)
let predicate = NSPredicate(format: "distanceToLocation:fromLocation:(%K,%#) < %f", "Location", location!, radius)
let query = CKQuery(recordType: "Establishment", predicate: predicate)
let sort = CKLocationSortDescriptor(key: "Location", relativeLocation: location!)
query.sortDescriptors = [sort]
publicDB.performQuery(query, inZoneWithID: nil) { [unowned self] results, error in
guard error == nil else {
dispatch_async(dispatch_get_main_queue()) {
self.delegate?.errorUpdating(error!)
print("Cloud Query Error - Refresh: \(error)")
}
return
}
self.items.removeAll(keepCapacity: true)
for record in results! {
let establishment = Establishment(record: record, database: self.publicDB)
self.items.append(establishment)
}
dispatch_async(dispatch_get_main_queue()) {
self.delegate?.modelUpdated()
}
}
}
func establishment(ref: CKReference) -> Establishment! {
let matching = items.filter { $0.record.recordID == ref.recordID }
return matching.first
}}
Mention all datasource method here which are added in your code .
And also check your following method :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return Model.sharedInstance.items.count
}
Related
I want to use the latitude and logitude values in the structure array to represent polylines.
You can use the list[1].latitude value from json in the view, but if you try to use the list[1].latitude value from the override func viewDidLoad () above, you get an error.
How can I use this value as a polyline's latitude and longitude value?
Swift Source
import UIKit
import GoogleMaps
class TableController: UIViewController, GMSMapViewDelegate , UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var viewMap: GMSMapView!
#IBOutlet weak var listTableView: UITableView!
//JSON담을 구조
var list:[MyStruct] = [MyStruct]()
struct MyStruct
{
var index = ""
var ip = ""
var latitude = ""
var longitude = ""
var zonename = ""
init(_ index:String, _ ip:String, _ latitude:String, _ longitude:String, _ zonename:String)
{
self.index = index
self.ip = ip
self.latitude = latitude
self.longitude = longitude
self.zonename = zonename
}
}
override func viewDidLoad() {
super.viewDidLoad()
// set initial location
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: 37.209037, longitude: 126.976370, zoom: 16.0)
viewMap.camera = camera
let count: Int = 0
//폴리라인
let path = GMSMutablePath()
path.add(CLLocationCoordinate2DMake(37.209037, 126.976370))
path.add(CLLocationCoordinate2DMake(37.211643, 126.972444))
path.add(CLLocationCoordinate2DMake(37.211457, 126.972147))
let polyline = GMSPolyline(path: path)
polyline.strokeWidth = 6.0
polyline.map = viewMap
listTableView.dataSource = self
listTableView.delegate = self
get_data("http://192.168.0.11/fence.php")
}
func get_data(_ link:String)
{
let url:URL = URL(string: link)!
let session = URLSession.shared
let request = URLRequest(url: url)
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
self.extract_data(data)
})
task.resume()
}
func extract_data(_ data:Data?)
{
let json:Any?
if(data == nil)
{
return
}
do{
json = try JSONSerialization.jsonObject(with: data!, options: [])
}
catch
{
return
}
guard let data_array = json as? NSArray else
{
return
}
for i in 0 ..< data_array.count
{
if let data_object = data_array[i] as? NSDictionary
{
if let data_index = data_object["index"] as? String,
let data_ip = data_object["Ip"] as? String,
let data_latitude = data_object["Latitude"] as? String,
let data_longitude = data_object["Longitude"] as? String,
let data_Zonename = data_object["ZoneName"] as? String
{
list.append(MyStruct(data_index, data_ip, data_latitude, data_longitude, data_Zonename))
}
}
}
refresh_now()
}
func refresh_now()
{
DispatchQueue.main.async(
execute:
{
self.listTableView.reloadData()
})
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return list.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = list[indexPath.row].zonename + " : " + list[indexPath.row].latitude
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I try to sort my tableView by distance form the current location. I search on the website but is it very difficult for me to adapte on my projet what I find
Can you help me please?
Here is my first files for data "Cables.swift":
import Foundation
import MapKit
class Person {
var identifier:Int
var name:String
var country:String
var email:String
var website:String
var facebook:String
var adress:String
var phone:String
var latitude:Double
var longitude:Double
var lac:Int
var poulie1:Int
var rotation1:String
var module1:Int
var poulie2:Int
var rotation2:String
var module2:Int
var distance:Double
var smallPhotoUrl:URL! {
return URL(string: "http://wakefinder.16mb.com/full/\(self.identifier).jpg")
}
var largePhotoUrl:URL! {
return URL(string: "http://wakefinder.16mb.com/full/\(self.identifier).jpg")
}
//var coordinate: CLLocationCoordinate2D
init?(fromData personData:[String:AnyObject]) {
guard let identifier = personData["id"] as? Int,
let name = personData["name"] as? String,
let country = personData["country"] as? String,
let email = personData["email"] as? String,
let website = personData["website"] as? String,
let facebook = personData["facebook"] as? String,
let adress = personData["adress"] as? String,
let phone = personData["phone"] as? String,
let latitude = personData["lat"] as? Double,
let longitude = personData["lng"] as? Double,
let lac = personData["lac"] as? Int,
let poulie1 = personData["poulie1"] as? Int,
let rotation1 = personData["rotation1"] as? String,
let module1 = personData["module1"] as? Int,
let poulie2 = personData["poulie2"] as? Int,
let rotation2 = personData["rotation2"] as? String,
let module2 = personData["module2"] as? Int
else {
return nil
}
self.identifier = identifier
self.name = name
self.country = country
self.email = email
self.website = website
self.facebook = facebook
self.adress = adress
self.phone = phone
self.latitude = latitude
self.longitude = longitude
self.lac = lac
self.poulie1 = poulie1
self.rotation1 = rotation1
self.module1 = module1
self.poulie2 = poulie2
self.rotation2 = rotation2
self.module2 = module2
}
// Function to calculate the distance from given location.
func calculateDistance(fromLocation: CLLocation?) {
let location = CLLocation(latitude: self.latitude, longitude: self.longitude)
distance = location.distance(from: fromLocation!)
}
}
and the file for my TableView:
import UIKit
import Alamofire
import CoreLocation
class cableViewController: UITableViewController, UISearchResultsUpdating {
var _personList:[Person] = []
var _personFiltered:[Person] = []
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request("http://wakefinder.16mb.com/users.json")
.validate()
.responseJSON { (response) in
if response.result.isSuccess {
let rawPersonList = response.result.value as! [[String:AnyObject]]
for personData in rawPersonList {
if let personObject = Person(fromData: personData) {
self._personList.append(personObject)
}
}
self.tableView.reloadData()
} else {
print(response.result.error as Any)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.isActive && searchController.searchBar.text != "" {
return _personFiltered.count
}
return _personList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:cablesTableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cable_cell") as! cablesTableViewCell
if searchController.isActive && searchController.searchBar.text != "" {
let person:Person = _personFiltered[indexPath.row]
cell.display(person: person)
//_personList[indexPath.row] = _personFiltered[indexPath.row]
} else {
let person:Person = _personList[indexPath.row]
cell.display(person: person)
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let cell = sender as? UITableViewCell {
if let indexPath = self.tableView.indexPath(for: cell) {
let selectedPerson: Person
if searchController.isActive && searchController.searchBar.text != "" {
selectedPerson = _personFiltered[indexPath.row]
} else {
selectedPerson = _personList[indexPath.row]
}
let personViewController:fichesViewController = segue.destination as! fichesViewController
personViewController._person = selectedPerson
}
}
}
}
func updateSearchResults(for searchController: UISearchController) {
filterContent(searchText: self.searchController.searchBar.text!)
}
func filterContent(searchText:String) {
_personFiltered = _personList.filter { user in
let username = user.name
return(username.lowercased().contains(searchText.lowercased()))
}
self.tableView.reloadData()
}
}
I am trying to get an array of strings from JSON and I'm trying to figure out how to deal with it if the returned array is empty. In some cases, the returned value is [] and for other cases, the array contains string values. It is crashing because of unexpectedly finding a nil value.
For clarification, the gyms array is passed from another class and everything here works without the code for the images.
Here is my relevant code:
var gyms = [AnyObject]()
var imageArrays = [[String]?]()
In viewDidLoad()
getGymImages()
Methods to get JSON data:
func getGymImages() {
var index = 0
for dictionary in gyms {
let id = dictionary["id"] as! String
let urlString = String("https://gyminyapp.azurewebsites.net/api/GymImage/\(id)")
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for imageArray in json as! [AnyObject] {
imageArrays.append((imageArray as? [String])!)
}
} catch {
print("Error")
}
index += 1
}
addImagesToGyms()
}
func addImagesToGyms() {
var index = 0;
for array in imageArrays {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["images"] = array
gyms[index] = dictionary
index += 1
}
}
In cellForRowAtIndexPath()
let gymImages = dictionary["images"] as! [String]
if gymImages.count > 0 {
let firstImageURL = gymImages[0] as String
cell.cellImageView.sd_setImageWithURL(NSURL(string: firstImageURL))
}
EDIT: I was asked to show more of the file, so here it is.
import UIKit
class GymListTableViewController: UITableViewController {
var gyms = [AnyObject]()
var gymName: String?
var gymAddress: String?
var gymPhoneNumber: String?
var gymWebsite: String?
var gymID: String?
var gymLatitude: String?
var gymLongitude: String?
var maxDistance: Double?
var myLocation: CLLocation?
var milesArray = [Double]()
var imageArrays = [[String]?]()
var segmentedControl: UISegmentedControl?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Gyms"
tableView.registerNib(UINib(nibName: "GymListTableViewCell", bundle: nil), forCellReuseIdentifier: "gymCell")
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "waypoint_map"), style: .Done, target: self, action: #selector(showMapView))
self.navigationItem.rightBarButtonItem?.tintColor = BarItems.greenTintColor
segmentedControl = UISegmentedControl(items: ["A-Z", "Z-A", "Rating", "Distance"])
segmentedControl?.sizeToFit()
segmentedControl?.selectedSegmentIndex = 0
segmentedControl!.setTitleTextAttributes([NSFontAttributeName: UIFont(name:"Helvetica-Light", size: 15)!],
forState: UIControlState.Normal)
segmentedControl?.addTarget(self, action: #selector(changeSelectedSegmentIndex), forControlEvents: .ValueChanged)
self.navigationItem.titleView = segmentedControl
sortAlphabetically()
let backgroundImage = UIImage(named: "gray_background")
let backgroundImageView = UIImageView(image: backgroundImage)
tableView.backgroundView = backgroundImageView
addDistancesToGyms()
getGymImages()
// geocodeAddresses()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func addDistancesToGyms() {
var index = 0
for distance in milesArray {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["distance"] = distance
gyms[index] = dictionary
index += 1
}
}
func getGymImages() {
var index = 0
for dictionary in gyms {
let id = dictionary["id"] as! String
let urlString = String("https://gyminyapp.azurewebsites.net/api/GymImage/\(id)")
let url = NSURL(string: urlString)
let data = NSData(contentsOfURL: url!)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
for imageArray in json as! [AnyObject] {
imageArrays.append((imageArray as? [String])!)
}
} catch {
print("Error")
}
index += 1
}
addImagesToGyms()
}
func addImagesToGyms() {
var index = 0;
for array in imageArrays {
var dictionary = gyms[index] as! [String:AnyObject]
dictionary["images"] = array
gyms[index] = dictionary
index += 1
}
}
func changeSelectedSegmentIndex() {
let segmentTouched = segmentedControl?.selectedSegmentIndex
if segmentTouched == 0 {
sortAlphabetically()
} else if segmentTouched == 1 {
sortReverseAlphabetically()
} else if segmentTouched == 2 {
sortByRatingAscending()
} else {
sortByDistanceAscending()
}
}
func sortAlphabetically() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["name"] as? String) < (($1 as! Dictionary<String, AnyObject>)["name"] as? String)
}
tableView.reloadData()
}
func sortReverseAlphabetically() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["name"] as? String) > (($1 as! Dictionary<String, AnyObject>)["name"] as? String)
}
tableView.reloadData()
}
func sortByRatingAscending() {
// TODO: Sort by rating
}
func sortByDistanceAscending() {
gyms.sortInPlace{
(($0 as! Dictionary<String, AnyObject>)["distance"] as? Double) < (($1 as! Dictionary<String, AnyObject>)["distance"] as? Double)
}
tableView.reloadData()
}
func showMapView() {
self.performSegueWithIdentifier("displayMapSegue", sender: self.navigationController)
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gyms.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 131
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("gymCell", forIndexPath: indexPath) as! GymListTableViewCell
let dictionary = gyms[indexPath.row]
let addressDictionary = dictionary["address"]
let street = addressDictionary!!["streetAddress"] as! String
let city = addressDictionary!!["city"] as! String
let state = addressDictionary!!["state"] as! String
let zipInt = addressDictionary!!["zipCode"] as! Int
let zipCode = String(zipInt)
let addressString = String("\(street) " + "\(city), " + "\(state) " + "\(zipCode)")
cell.backgroundColor = UIColor.clearColor()
cell.gymNameLabel.text = dictionary["name"] as? String
cell.gymAddressLabel.text = addressString
let miles = dictionary["distance"] as! Double
let milesString = String(format: "%.1f miles", miles)
let milesLabelString = milesString
cell.milesLabel.text = milesLabelString
let gymImages = dictionary["images"] as! [String]
if gymImages.count > 0 {
let firstImageURL = gymImages[0] as String
cell.cellImageView.sd_setImageWithURL(NSURL(string: firstImageURL))
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dictionary = gyms[indexPath.row]
if dictionary["name"] as? String != nil {
self.gymName = dictionary["name"] as? String
}
let cell = tableView.cellForRowAtIndexPath(indexPath) as! GymListTableViewCell
self.gymAddress = cell.gymAddressLabel.text
if dictionary["phone"] as? String != nil {
self.gymPhoneNumber = dictionary["phone"] as? String
}
if dictionary["website"] as? String != nil {
self.gymWebsite = dictionary["website"] as? String
}
if dictionary["id"] as? String != nil {
self.gymID = dictionary["id"] as? String
}
if dictionary["latitude"] as? String != nil {
self.gymLatitude = dictionary["latitude"] as? String
}
if dictionary["longitude"] as? String != nil {
self.gymLongitude = dictionary["longitude"] as? String
}
self.performSegueWithIdentifier("detailFromListSegue", sender: self.navigationController)
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailFromListSegue" {
let gymDetailVC = segue.destinationViewController as! DetailTableViewController
if self.gymName != nil {
gymDetailVC.gymName = self.gymName
} else {
gymDetailVC.gymName = nil
}
if self.gymAddress != nil {
gymDetailVC.gymAddress = self.gymAddress
} else {
gymDetailVC.gymAddress = nil
}
if self.gymPhoneNumber != nil {
gymDetailVC.gymPhoneNumber = self.gymPhoneNumber
} else {
gymDetailVC.gymPhoneNumber = nil
}
if self.gymWebsite != nil {
gymDetailVC.gymWebsite = self.gymWebsite
} else {
gymDetailVC.gymWebsite = nil
}
if self.gymID != nil {
gymDetailVC.gymID = self.gymID!
} else {
// gymDetailVC.gymID = nil
}
if self.gymLatitude != nil {
gymDetailVC.gymLatitude = self.gymLatitude!
}
if self.gymLongitude != nil {
gymDetailVC.gymLongitude = self.gymLongitude!
}
}
}
}
numberOfRowsInSection should be returning gymImages.count if it isn't already.
Then as a safeguard you can always do
if indexPath.row < gymImages.count {
}
before accessing the content in cellForRowAtIndexPath
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.
I'd like to update a record in my database but I can't figure out how. Specifically I am trying to fetch a record and then update/save that record with a new value when a user taps the UISwitch.
the method where I am attempting this is in my ViewController.Swift and is called
didChangeSwitchState(#sender: SettingCell, isOn: Bool)
I am using the boiler plate CoreData template which puts the managedObjectContext in the AppDelegate.
this is my model:
and the resulting code plus a method for creating new entries:
LogItem.swift
class LogItem: NSManagedObject {
#NSManaged var settingLabel: String
#NSManaged var switchState: Bool
class func createInManagedObjectContext(moc: NSManagedObjectContext, label: String, state: Bool) -> LogItem {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as! LogItem
newItem.settingLabel = label
newItem.switchState = state
return newItem
}
}
ViewController.swift
class ViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate, SettingCellDelegate {
// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
// Create the table view as soon as this class loads
//var logTableView = UITableView(frame: CGRectZero, style: .Plain)
var logItems = [LogItem]()
override func viewDidLoad() {
super.viewDidLoad()
if let moc = self.managedObjectContext {
let fetchRequest = NSFetchRequest(entityName:"LogItem")
var error: NSError?
let fetchedResults = moc.executeFetchRequest(fetchRequest, error: &error) as! [NSManagedObject]?
if (fetchedResults?.count == 0) {
// Create some dummy data to work with
var items = [
("Best Animal", true),
("Best Language", true),
("Derp", false),
("Applesauce", false)
]
for (settingLabel, switchState) in items {
LogItem.createInManagedObjectContext(moc,
label: settingLabel, state: switchState)
}
} else {
println("data already exists")
}
fetchLog()
}
}
func fetchLog() {
let fetchRequest = NSFetchRequest(entityName: "LogItem")
let sortDescriptor = NSSortDescriptor(key: "settingLabel", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
logItems = fetchResults // question... this seems like it would store the entire table as one item in the array... huh?
}
}
func save() {
var error : NSError?
if(managedObjectContext!.save(&error) ) {
println(error?.localizedDescription)
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let logItem = logItems[indexPath.row]
println(logItem.switchState)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomSettingCell") as! SettingCell
let logItem = logItems[indexPath.row]
cell.settingsLabel?.text = logItem.settingLabel
cell.settingsSwitch.on = logItem.switchState
cell.cellDelegate = self
return cell
}
func didChangeSwitchState(#sender: SettingCell, isOn: Bool) {
let indexPath = self.tableView.indexPathForCell(sender)
managedObjectContext!.save(nil)
var context = managedObjectContext
let fetchRequest = NSFetchRequest()
var entityName = NSEntityDescription.entityForName("LogItem", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entityName
var error: NSError?
if let cellName = sender.settingsLabel.text {
fetchRequest.predicate = NSPredicate(format: "settingLabel = %#", cellName)
}
var fetchedResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) as? [LogItem]
if let setting = fetchedResults {
if error != nil {
println("An error occurred loading the data")
} else {
var saveError : NSError? = nil
if !managedObjectContext!.save(&saveError) {
println("Could not update record")
} else {
tableView.reloadData()
}
}
}
tableView.reloadData()
}
SettingCell.swift
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
var cellDelegate: SettingCellDelegate?
#IBAction func handledSwitchChange(sender: UISwitch) {
self.cellDelegate?.didChangeSwitchState(sender: self, isOn:settingsSwitch.on)
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
SettingItem.swift
class SettingItem: NSObject {
var settingName : String?
var switchState : Bool?
override init() {
super.init()
}
init (settingName: String?, switchState : Bool?) {
super.init()
self.settingName = settingName
self.switchState = switchState
}
}
SettingCellDelegate.swift
protocol SettingCellDelegate {
func didChangeSwitchState(# sender: SettingCell, isOn: Bool)
}
finally this is my output,
You're not changing the value of the LogItem before saving. Please do it accordingly, also this code may generate compiler errors as I'm not well-versed in the swift language, I just write the pseudo code that may help you.
var fetchedResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) as? [LogItem]
if let setting = fetchedResults {
if error != nil {
println("An error occurred loading the data")
}
else {
//now you have array of LogItem in your case it's one (assuming that you have unique name
//change the item's switch state and then save it
//Please make a count check here before getting the object
/*
if count == 0 -->> record doesn't exist
else -->> record exist
*/
var settingLogItem = setting[0]
settingLogItem.switchState = isOn
var saveError : NSError? = nil
if !managedObjectContext!.save(&saveError) {
println("Could not update record")
}
else {
tableView.reloadData()
}
}
}