Swift - Load/save from CoreData generates duplicate entries - ios

I have run into a problem where I can save and load into and from CoreData in Swift for my iOS app, but I run into a problem where I have tried to guard for duplicate entries, but it does not seem to work. can anyone tell me where I went wrong? Thanks!
My ViewController class:
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate,
UITableViewDataSource {
#IBOutlet weak var headerLabel:UILabel!
#IBOutlet weak var myTableView: UITableView!
var lenders = [LenderData]()
var lendersTemp = [LenderData]()
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.rowHeight = 90
myTableView.delegate = self
myTableView.dataSource = self
let fetchRequest: NSFetchRequest<LenderData> = LenderData.fetchRequest()
do {
let lenders = try PersistenceService.context.fetch(fetchRequest)
self.lenders = lenders
} catch {
// Who cares....
}
downloadJSON {
for tempLender in self.lendersTemp {
if !self.lenders.contains(where: {$0.id == tempLender.id}) {
self.lenders.append(tempLender)
}
}
self.lendersTemp.removeAll()
PersistenceService.saveContext()
self.myTableView.reloadData()
}
}
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://api.kivaws.org/v1/loans/newest.json")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("JSON not downloaded")
} else {
if let content = data {
do {
let myJSONData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
var imageID:Int64 = -1
var country:String = "N/A"
var latLongPair:String = "0.000000 0.000000"
var town:String = "N/A"
if let loans = myJSONData["loans"] as? NSArray {
for i in 0...loans.count-1 {
if let lender = loans[i] as? NSDictionary {
if let imageData = lender["image"] as? NSDictionary { imageID = imageData["id"] as! Int64 }
if let countryData = lender["location"] as? NSDictionary {
country = countryData["country"] as! String
town = countryData["town"] as! String
if let geo = countryData["geo"] as? NSDictionary {
latLongPair = geo["pairs"] as! String
}
}
let newLender = LenderData(context: PersistenceService.context)
newLender.id = lender["id"] as! Int64
newLender.name = lender["name"] as? String
newLender.image_id = imageID
newLender.activity = lender["activity"] as? String
newLender.use = lender["use"] as? String
newLender.loan_amount = lender["loan_amount"] as! Int32
newLender.funded_amount = lender["funded_amount"] as! Int32
newLender.country = country
newLender.town = town
newLender.geo_pairs = latLongPair
self.lendersTemp.append(newLender)
}
}
}
DispatchQueue.main.async {
completed()
}
} catch {
print("Error occured \(error)")
}
}
}
}
task.resume()
}
}
EDIT
Added the part of the code where I populate the lendersTemp array

I quote matt on this one from the comments:
So... You are appending to self.lendersTemp on a background thread but reading it on the main thread. Instead, get rid of it and just pass the data right thru the completed function.
Which is exactly what I did. And this worked

Related

How to display a playlist of a YouTube channel using YouTube API in Swift3

Question 1 :
I am using YouTube API to display a playlist of videos in a UITableView but it's not working. It's working fine when I make it for a single video, one video appears in the UITableView.
How can I display a playlist of any YouTube channel? I am using this code in my UITableView.
My UITableView code :
import UIKit
import AVFoundation
class YTViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
#IBOutlet weak var txtSearch: UITextField!
#IBOutlet weak var searchResultTableView: UITableView!
// Set up a network session
let session = URLSession.shared
// ReST GET static String parts
let BASE_URL: String = "https://www.googleapis.com/youtube/v3/"
let SEARCH_VIDEO: String = "channels?part=snippet&q="
let VIDEO_TYPE: String = "&id=UCJIc9yX_3iHE2CfmUqoeJKQ&key="
let API_KEY: String = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#IBAction func btnSearchClicked(_ sender: UIButton) {
}
func getVideoList() {
let methodArguments: [String: AnyObject] = [
"query": txtSearch.text! as AnyObject
]
// Format the search string (video title) for http request
let videoTitle: String = escapedParameters(methodArguments)
// Make the query url
// sample: https://www.googleapis.com/youtube/v3/search?part=snippet&q=werewolf&type=video&key=AIzaSyDDqTGpVR7jxeozoOEjH6SLaRdw0YY-HPQ
let searchVideoByTitle = BASE_URL + SEARCH_VIDEO + videoTitle + VIDEO_TYPE + API_KEY
print("#####################\(searchVideoByTitle)")
if let url = URL(string: searchVideoByTitle) {
let request = URLRequest(url: url)
// Initialise the task for getting the data
initialiseTaskForGettingData(request, element: "items")
}
}
// Array to store all the desired values dictionaries
var videosArray: Array<Dictionary<String, AnyObject>> = [[String: AnyObject]]()
func initialiseTaskForGettingData(_ request: URLRequest, element: String) {
// Initialize task for getting data
// Refer to http://www.appcoda.com/youtube-api-ios-tutorial/
let task = session.dataTask(with: request, completionHandler: {(data, HTTPStatusCode, error) in
// Handler in the case of an error
if error != nil {
print(error as Any)
return
}
else {
// Parse that data received from the service
let resultDict: [String: AnyObject]!
do {
// Convert the JSON data to a dictionary
resultDict = try JSONSerialization.jsonObject(with: data! as Data, options: .allowFragments) as! [String: AnyObject]
print("***************************\(resultDict)")
// Get the first item from the returned items
if let itemsArray = (resultDict as AnyObject).value(forKey: element) as? NSArray {
// Remove all existing video data
self.videosArray.removeAll()
for index in 0..<itemsArray.count {
// Append the desiredVaules dictionary to the videos array
self.videosArray.append(self.unwrapYoutubeJson(arrayToBeUnwrapped: itemsArray, index: index))
}
// Asynchronously reload the data and display on the tableview
DispatchQueue.main.async {
// Reload the tableview
self.searchResultTableView.reloadData()
}
}
} catch let jsonError {
print(jsonError)
}
}
})
// Execute the task
task.resume()
}
func unwrapYoutubeJson(arrayToBeUnwrapped: NSArray, index: Int) -> [String: AnyObject]{
let firstItemDict = arrayToBeUnwrapped[index] as! [String: AnyObject]
// Get the snippet dictionary that contains the desired data
let snippetDict = firstItemDict["snippet"] as! [String: AnyObject]
// Dictionary to store desired video contents for display on tableview
// desired values - "Title", "Description", "Thumbnail"
var desiredValuesDict = [String: AnyObject]()
desiredValuesDict["title"] = snippetDict["title"]
desiredValuesDict["description"] = snippetDict["description"]
// Further unwrap to get the Thumbnail default URL
let thumbnailDict: [String: AnyObject]
thumbnailDict = snippetDict["thumbnails"] as! [String: AnyObject]
let defaultThumbnailDict = thumbnailDict["default"] as! [String: AnyObject]
desiredValuesDict["thumbnail"] = defaultThumbnailDict["url"]
//Get the id dictionary that contains videoId
let idDict = firstItemDict["id"] as? [String: AnyObject]
desiredValuesDict["videoId"] = idDict?["videoId"]
return desiredValuesDict
}
// Helper function: Given a dictionary of parameters, convert to a string for a url
func escapedParameters(_ parameters: [String : AnyObject]) -> String {
var urlVars = [String]()
for (key, value) in parameters {
// Make sure that it is a string value
let stringValue = "\(value)"
// Escape it
let escapedValue = stringValue.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
//Append it
urlVars += [key + "=" + "\(escapedValue!)"]
}
return (!urlVars.isEmpty ? "" : "") + urlVars.joined(separator: "&")
}
// MARK: UITableView method implementation
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SearchResultTableViewCell
let videoSelected = videosArray[indexPath.row]
cell.updateIU(video: videoSelected)
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
let id = videosArray[indexPath.row]["videoId"] as? String
print("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\(id)")
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videosArray.count
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? playerViewController {
if let selectedRowIndexPath = searchResultTableView.indexPathForSelectedRow?.row {
destination.mytitle = videosArray[selectedRowIndexPath]["title"] as! String
destination.mydescript = videosArray[selectedRowIndexPath]["description"] as! String
destination.myvideoId = videosArray[selectedRowIndexPath] ["videoId"] as? String
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
getVideoList()
searchResultTableView.dataSource = self
searchResultTableView.delegate = self
}
}
Question 2 :
When I am trying to play a video using YTPlayerHelper it's not working:
fatal error: unexpectedly found nil while unwrapping an Optional value and the video ID appears as nil.
How can I play the video using the YTPlayerHelper? This is how I am playing the video:
import UIKit
import youtube_ios_player_helper
class playerViewController: UIViewController {
#IBOutlet weak var MyPlayer: YTPlayerView!
#IBOutlet weak var txtTitle: UITextView!
#IBOutlet weak var txtDescript: UITextView!
var mytitle: String!
var mydescript: String!
var myvideoId : String!
override func viewDidLoad() {
super.viewDidLoad()
print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\(myvideoId)")
MyPlayer.load(withVideoId: myvideoId!)
txtTitle.text = mytitle
txtDescript.text = mydescript
}
}
Here is my Alamofire implementation; you have to adjust the names to match yours:
func callAlamo(url : String) {
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData : Data) {
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
pageToken = readableJSON["nextPageToken"] as! String
if previousPageButton.isEnabled {
previousPageToken = readableJSON["prevPageToken"] as? String
}
if previousPageToken == nil {
previousPageButton.isEnabled = false
}
if let items = readableJSON["items"] as? [JSONStandard] {
for i in 0..<items.count {
let item = items[i]
var name = String()
var previewURL1 = String()
if let id = item["id"] as? JSONStandard {
let url = id["videoId"] as! String
previewURL1 = url
}
let previewURL = previewURL1
if let snippet = item["snippet"] as? JSONStandard {
let title = snippet["title"] as! String
name = title
if let thumbnails = snippet["thumbnails"] as? JSONStandard {
if let images = thumbnails["high"] as? JSONStandard {
let mainImageURL = URL(string: images["url"] as! String)
imageURL = images["url"] as! String
let mainImageData = NSData(contentsOf: mainImageURL!)
let mainImage = UIImage(data: mainImageData! as Data)
posts.append(post.init(mainImage: mainImage, name: name, previewURL: previewURL, imageURL: imageURL))
self.tableView.reloadData()
nextPageButton.isEnabled = true
}
}
}
}
}
} catch {
print(error)
}
}
Then make a request by using callAlamo(url: yourURL), replacing yourURL with the actual URL.
For the second question, you have a great tutorial here: http://www.appcoda.com/youtube-api-ios-tutorial/
In the tutorial is another way to update UITableView with YouTube videos, but personally I prefer the Alamofire one, as it is much faster and easier to write. I recommend to view just the playing videos part.

How to sent / return values from dispatch_sync(dispatch_get_main_queue(), {} to another page

I have created a project that will retrieve the extracted JSON data and display it in UITableview. I don't want to burden the app by downloading everything. So, only when user selected a row, will it retrieve the employee details. I'm using a page view controller so that the user is able to navigate each page by sliding the page. How can I sent the value I sent for page in dispatch_sync to detailviewcontroller page?
This is my code from managePageviewController
func viewDetailViewController(index: Int) -> DetailViewController? {
if let storyboard = storyboard,
page = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController {
let currentEmployee = employeeStore.searchEmployee[index]
getJson().testsearchJSON(currentEmployee.id, handler: {(employeeDetails) -> Void in
dispatch_sync(dispatch_get_main_queue(), {
page.employee = employeeDetails
page.employeeIndex = index
return page //fail here
})
})
}
return nil
}
This is my getJSON().testSearchJSON fund
func testsearchJSON(id:String, handler: (Employee) -> Void) {
let requestURL: NSURL = NSURL(string: (favUrl + id))!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
//retrieve data successfully
if (statusCode == 200) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if data!.length > 0 && error == nil {
guard let name = json["firstName"] as? String,
let title = json["title"] as? String,
let id = json["id"]!,
let manager = json["managerName"] as? String,
let oa = json["oa"] as? String,
let email = json["email"] as? String,
let department = json["department"] as? String,
let division = json["division"] as? String,
let company = json["company"] as? String
else {
return;
}
let newEmployee = Employee(id: String(id), name: name, title: title, manager: manager, oa: oa, email: email, department: department, division: division, company: company)
//test
handler(newEmployee)
}
} catch {
print("Error with JSON: \(error)")
}
}
}
task.resume()
}
}
This is my page for DetailviewController
class DetailViewController: UIViewController, UITextFieldDelegate {
// MARK:- Propertise
#IBOutlet var employeePic: UIImageView! //employee picture
#IBOutlet var employeeName: UILabel! // name
#IBOutlet var employeeTitle: UILabel! //job title
#IBOutlet var dateCreated: UILabel!
#IBOutlet var managerName: UITextField!
#IBOutlet var oaName: UITextField!
#IBOutlet var emailField: UITextField!
#IBOutlet var departmentField: UITextField!
#IBOutlet var divisionField: UITextField!
#IBOutlet var companyField: UITextField!
var employee: Employee! {
//add applicataion name
didSet {
navigationItem.title = employee.name
}
}
//current employee index
var employeeIndex: Int!
let dateFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
formatter.timeStyle = .NoStyle
return formatter
}()
//MARK:- assign values
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
employeeName.text = employee.name
employeeTitle.text = "( " + employee.title + " )"
emailField.text = employee.email
managerName.text = employee.manager
dateCreated.text = dateFormatter.stringFromDate(employee.dateCreated)
oaName.text = employee.oa
departmentField.text = employee.department
divisionField.text = employee.division
companyField.text = employee.company
//retrieve image
employeePic.thumbnails()
employeePic.image = UIImage(named: "Default Image")
}
I think is this case it would be better to write data fetching in viewDidLoad or viewWillAppear function in DetailViewController. Something like that:
In MainViewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
viewDetailViewController(index : indexPath.row, employee: employeeStore.searchEmployee[indexPath.row])
}
func viewDetailViewController(index: Int, employee: Employee) {
let detailController = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController
detailController.currentEmployee = employee
// present/push/etc detail controller
present(detailViewController, animated: true, completion: nil)
}
In DetailViewController:
var employee : Employee?
...
override func viewDidLoad() {
super.viewDidLoad()
if let employee = currentEmployee {
getJson().testsearchJSON(employee.id, handler: {(employeeDetails) -> Void in
dispatch_sync(dispatch_get_main_queue(), {
//reload UI for employeeDetails
})
})
}
}
Also you can use GCD to wait for block loading, for example GCD groups.
Try to like this
func viewDetailViewController(index: Int) -> DetailViewController? {
if let storyboard = storyboard,
page = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController {
let currentEmployee = employeeStore.searchEmployee[index]
getJson().testsearchJSON(currentEmployee.id, handler: {(employeeDetails) -> Void in
page.employee = employeeDetails
page.employeeIndex = index
return page //fail here
})
}
return nil
}
func testsearchJSON(id:String, handler: (Employee) -> Void) {
let requestURL: NSURL = NSURL(string: (favUrl + id))!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let semaphore = dispatch_semaphore_create(0);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
//retrieve data successfully
if (statusCode == 200) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if data!.length > 0 && error == nil {
guard let name = json["firstName"] as? String,
let title = json["title"] as? String,
let id = json["id"]!,
let manager = json["managerName"] as? String,
let oa = json["oa"] as? String,
let email = json["email"] as? String,
let department = json["department"] as? String,
let division = json["division"] as? String,
let company = json["company"] as? String
else {
dispatch_semaphore_signal(semaphore);
return;
}
let newEmployee = Employee(id: String(id), name: name, title: title, manager: manager, oa: oa, email: email, department: department, division: division, company: company)
//test
handler(newEmployee)
dispatch_semaphore_signal(semaphore);
}
} catch {
dispatch_semaphore_signal(semaphore);
print("Error with JSON: \(error)")
}
}
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
}

How can I do an HTTP Post before reading the JSON?

I am reading JSON from a URL and that has been working correctly. This is my code:
#IBOutlet weak var ProfilesCell: UITableView!
let cellspacing: CGFloat = 50
var names = [String]()
var posts = [String]()
var locations = [String]()
var votes = [String]()
var comments = [String]()
override func viewDidLoad() {
super.viewDidLoad()
ProfilesCell.dataSource = self
let url:URL = URL(string: "http://"+Connection_String+":8000/profile_view")!
URLSession.shared.dataTask(with:url, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Profile = parsedData["Profile"] as! [AnyObject]?
{
for Stream in Profile {
if let fullname = Stream["fullname"] as? String {
self.names.append(fullname)
}
if let post = Stream["post"] as? String {
self.posts.append(post)
}
if let location = Stream["location"] as? String {
self.locations.append(location)
}
if let vote = Stream["votes"] as? String {
self.votes.append(vote.appending(" Votes"))
}
if let comment = Stream["comments"] as? String {
self.comments.append(comment.appending(" Comments"))
}
DispatchQueue.main.async {
self.ProfilesCell.reloadData()
}
}
}
} catch let error as NSError {
print(error)
}
}
}).resume()
}
That code above correctly parses the JSON and the data is returned to the TableView. I now want to do an HTTP Post before reading that JSON and the parameter name is profile_id and I know that is something wrong in my code because if I do an HTML form with the parameter, things work correctly.
This is the new code that I now have:
#IBOutlet weak var ProfilesCell: UITableView!
let cellspacing: CGFloat = 50
var names = [String]()
var posts = [String]()
var locations = [String]()
var votes = [String]()
var comments = [String]()
override func viewDidLoad() {
super.viewDidLoad()
ProfilesCell.dataSource = self
let url:URL = URL(string: "http://"+Connection_String+":8000/profile_view")!
let ss = "32"
var request = URLRequest(url:url)
let paramString = "profile_id=\(ss)"
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
request.httpBody = paramString.data(using: .utf8)
URLSession.shared.dataTask(with:url, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Profile = parsedData["Profile"] as! [AnyObject]?
{
for Stream in Profile {
if let fullname = Stream["fullname"] as? String {
self.names.append(fullname)
}
if let post = Stream["post"] as? String {
self.posts.append(post)
}
if let location = Stream["location"] as? String {
self.locations.append(location)
}
if let vote = Stream["votes"] as? String {
self.votes.append(vote.appending(" Votes"))
}
if let comment = Stream["comments"] as? String {
self.comments.append(comment.appending(" Comments"))
}
DispatchQueue.main.async {
self.ProfilesCell.reloadData()
}
}
}
} catch let error as NSError {
print(error)
}
}
}).resume()
}
Now with this extra code the URL is still being hit but profile_id is showing null even though I have hardcoded the number 32. I also get this message displayed:
Error Domain=NSCocoaErrorDomain Code=3840 "No value." UserInfo={NSDebugDescription=No value.}

Exception thrown if no data are received

I am using following Class to receive data from an external database:
import Foundation
protocol HomeModelProtocal: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocal!
var data : NSMutableData = NSMutableData()
var mi_movil: String = ""
let misDatos:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var urlPath: String = "http:...hidden here.."
let parametros = "?id="
func downloadItems() {
mi_movil = misDatos.stringForKey("ID_IPHONE")!
print ("mi_movil en HOMEMODEL:",mi_movil)
urlPath = urlPath + parametros + mi_movil
let url: NSURL = NSURL(string: urlPath)!
var session: NSURLSession!
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
print ("LA URL ES: ",url)
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
let task = session.dataTaskWithURL(url)
task.resume()
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
self.data.appendData(data);
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON()
}
}
func parseJSON() {
var jsonResult: NSMutableArray = NSMutableArray()
do{
jsonResult = try NSJSONSerialization.JSONObjectWithData(self.data, options:NSJSONReadingOptions.AllowFragments) as! NSMutableArray
} catch let error as NSError {
print(error)
}
var jsonElement: NSDictionary = NSDictionary()
let locations: NSMutableArray = NSMutableArray()
for(var i = 0; i < jsonResult.count; i++)
{
jsonElement = jsonResult[i] as! NSDictionary
print (jsonElement)
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
if let id_mis_autos = jsonElement["id_mis_autos"] as? String,
let modelo = jsonElement["modelo"] as? String,
let ano = jsonElement["ano"] as? String,
let id_movil = jsonElement["id_movil"] as? String
{
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
}
locations.addObject(location)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.itemsDownloaded(locations)
})
}
}
If there are received data, it works fine but if there are no data an exception is thrown:
Could not cast value of type '__NSArray0' (0x1a0dd2978) to 'NSMutableArray' (0x1a0dd3490)
What should I change to detect if there are no data to avoid the exception?
Since you don't seem to be modifying jsonResult anywhere, the obvious choice is to make it an NSArray instead of an NSMutableArray, and change the downcasting to match that.
I'm not sure why you're using NSDictionary and NSMutableArray but this is how I would do it:
for result in jsonResult {
guard let jsonElement = result as? [String:AnyObject] else { return }
let locations: [MiAutoModel] = []
let location = MiAutoModel()
//the following insures none of the JsonElement values are nil through optional binding
let id_mis_autos = jsonElement["id_mis_autos"] as? String ?? ""
let modelo = jsonElement["modelo"] as? String ?? ""
let ano = jsonElement["ano"] as? String ?? ""
let id_movil = jsonElement["id_movil"] as? String ?? ""
location.id_mis_autos = id_mis_autos
location.modelo = modelo
location.ano = ano
location.id_movil = id_movil
locations.append(location)
}
You might have to change some of the code depending on your situation.

Update a UILabel using string value from another class in Swift

How do I used a string value from a function in a another class to update an UILabel on my ViewController?
Here is my code:
View controller:
import UIKit
class ViewController: UIViewController, dataEnterdDelegate {
#IBOutlet weak var auaTempLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let weather2 = WeatherService2()
weather2.getWeatherData("Oranjestad,AW")
}
**func userDidEnterInformation(info: NSString)
{
testLabel!.text = info as String
}**
func setLabel2(information: String)
{
auaTempLabel.text = information
}
The other class named WeatherService2 contain the following codes:
**protocol dataEnterdDelegate{
func userDidEnterInformation(info:NSString)
}**
Class WeatherService2{
var currentTempeture:String?
let targetVC = ViewController()
**var delegate: dataEnterdDelegate?**
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint))
getData(testString)
}
func getData(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.setLabel(data!)
}
})
}
task.resume()
}
func setLabel(weatherData:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(weatherData, options: .AllowFragments)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["results"] as? NSDictionary
{
if let channel = name["channel"] as? NSDictionary
{
if let item = channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let temp = condition["temp"] as? String
{
setTemp(temp)
**delegate!.userDidEnterInformation(temp)**
}
}
}
}
}
}
}
catch {
print("Failed to load JSON Object")
}
}
func setTemp(tempeture:String)
{
self.currentTempeture = tempeture
}
func getTemp() ->String
{
return self.currentTempeture!
}
}
The code runs fine and everything but I get an error "Fatal error: unexpectedly found nil while unwrapping an Optional value" when I try to update the UILabel in my ViewController.
When I used the print("The return value is: "+information) in the view controller class it print the return value correctly.
This is the reason I'm confused right now because I don't know why I still getting the "Fatal error: unexpectedly found nil while unwrapping an Optional value" when trying to use this value to update my UILabel.
Can anyone help me with this problem?
Thanks in advance
For that you have to create delegate method.
In viewController you create delegate method and call it from where you get response and set viewController.delegate = self
I could not explain more you have to search for that and it will works 100% .
All the best.
I manage to fix this issue by doing the following:
I create the following class
- Item
- Condition
- Channel
These classes implement the JSONPopulator protocol.
The JSONPopulator protocol:
protocol JSONPopulator
{
func populate(data:AnyObject)
}
Item class:
class Item: JSONPopulator
{
var condition:Condition?
func getCondition() ->Condition
{
return condition!
}
func populate(data: AnyObject)
{
condition = Condition()
condition?.populate(data)
}
}
Condition class:
class Condition:JSONPopulator
{
var arubaTemp:String?
var channel:NSDictionary!
func getArubaTemp()->String
{
return arubaTemp!
}
func getBonaireTemp() ->String
{
return bonaireTemp!
}
func getCuracaoTemp()->String
{
return curacaoTemp!
}
func populate(data: AnyObject)
{
if let query = data["query"] as? NSDictionary
{
if let results = query["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
self.channel = channel
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
if city.containsString("Oranjestad")
{
switch city
{
case "Oranjestad":
arubaTemp = getTemp()
print(arubaTemp)
default:
break
}
}
}
}
}
}
}
}
func getTemp() ->String
{
var temp:String?
if let item = self.channel["item"] as? NSDictionary
{
if let condition = item["condition"] as? NSDictionary
{
if let tempeture = condition["temp"] as? String
{
print(tempeture)
temp = tempeture
}
}
}
print(temp)
return temp!
}
}
Channel class:
class Channel: JSONPopulator
{
var item:Item?
var unit:Unit?
var request_city:String?
func setRequestCity(request_city:String)
{
self.request_city = request_city
}
func getRequestCity() ->String
{
return request_city!
}
func getItem() -> Item
{
return item!
}
func getUnit() -> Unit
{
return unit!
}
func populate(data: AnyObject)
{
item = Item()
item?.populate(data)
}
}
The WeatherService class that handles the function of parsing the JSON object. This class implement a WeatherServiceCallBack protocol.
The WeatherServiceCallBack protocol:
protocol WeatherServiceCallBack
{
func arubaWeatherServiceService( channel:Channel)
func arubaWeatherServiceFailure()
}
WeatherService class:
class WeatherService
{
var weatherServiceCallBack:WeatherServiceCallBack
var requestCity:String?
init(weatherServiceCallBack: WeatherServiceCallBack)
{
self.weatherServiceCallBack = weatherServiceCallBack
}
internal func checkCity(city:String)
{
switch (city)
{
case "Oranjestad,AW":
requestCity = city
getWeatherData(requestCity!)
default:
break
}
}
func getWeatherData(urlString:String)
{
let url = NSURL(string: urlString)!
let sqlQuery = "select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"\(url)\")"
let endpoint = "https://query.yahooapis.com/v1/public/yql?q=\(sqlQuery)&format=json"
let testString = (String(endpoint)
executeTask(testString)
}
func executeTask(request_data: String)
{
let requestString:NSString = request_data.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let url_with_data = NSURL(string: requestString as String)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url_with_data){
(data, response, error) in dispatch_async(dispatch_get_main_queue(), {
if data == nil
{
print("Failed loading HTTP link")
}else{
self.onPost(data!)
}
})
}
task.resume()
}
func onPost(data:NSData)
{
enum JSONErrors: ErrorType
{
case UserError
case jsonError
}
do{
let jsonResults = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
print(jsonResults)
if let city = jsonResults["query"] as? NSDictionary
{
if let name = city["count"] as? Int
{
if name == 0
{
weatherServiceCallBack.arubaWeatherServiceFailure()
}
}
}
if let requestCity_check = jsonResults["query"] as? NSDictionary
{
if let results = requestCity_check["results"] as? NSDictionary
{
if let channel = results["channel"] as? NSDictionary
{
if let location = channel["location"] as? NSDictionary
{
if let city = location["city"] as? String
{
requestCity = city
let channel = Channel()
channel.setRequestCity(requestCity!)
channel.populate(jsonResults)
weatherServiceCallBack.arubaWeatherServiceService(channel)
}
}
}
}
}
}catch {
print("Failed to load JSON Object")
}
}
}
In the ViewController class (I add some animation to the UILabel so it can flip from Fahrenheit to Celsius):
class ViewController: UIViewController, WeatherServiceCallBack
{
var weather:WeatherService?
var aua_Tempeture_in_F:String?
var aua_Tempeture_in_C:String?
var timer = NSTimer()
#IBOutlet var aua_Temp_Label: UILabel!
let animationDuration: NSTimeInterval = 0.35
let switchingInterval: NSTimeInterval = 5 //10
override func viewDidLoad() {
super.viewDidLoad()
weather = WeatherService(weatherServiceCallBack: self)
weather?.checkCity("Oranjestad,AW")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func animateTemptext()
{
self.timer = NSTimer.scheduledTimerWithTimeInterval(7.0, target: self, selector: Selector("tempConvertionTextSwitch"), userInfo: nil, repeats: true)
}
func setTempinCelsius(temp_string:String)
{
aua_Tempeture_in_F = "\(temp_string)°F"
let convertedString = convertFahrenheittoCelsius(temp_string)
aua_Tempeture_in_C = "\(convertedString)°C"
aua_Temp_Label.text = aua_Tempeture_in_C
animateTemptext()
}
func convertFahrenheittoCelsius(currentTemp:String) ->String
{
let tempTocelsius = (String(((Int(currentTemp)! - 32) * 5)/9))
return tempTocelsius
}
#objc func tempConvertionTextSwitch()
{
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock{
let delay = dispatch_time(DISPATCH_TIME_NOW,Int64(self.switchingInterval * NSTimeInterval(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue())
{
}
}
let transition = CATransition()
transition.type = kCATransitionFade
if aua_Temp_Label.text == aua_Tempeture_in_F
{
aua_Temp_Label.text = aua_Tempeture_in_C
}else if aua_Temp_Label.text == aua_Tempeture_in_C
{
aua_Temp_Label.text = aua_Tempeture_in_F
}else if aua_Temp_Label == ""
{
aua_Temp_Label.text = aua_Tempeture_in_C
}
aua_Temp_Label.layer.addAnimation(transition, forKey: kCATransition)
CATransaction.commit()
}
func arubaWeatherServiceFailure() {
}
func arubaWeatherServiceService(channel: Channel)
{
let requested_city = channel.getRequestCity()
let items = channel.getItem()
let aua_Temp = items.getCondition().getArubaTemp()
setTempinCelsius(aua_Temp)
}
}
Reference:
iOS 8 Swift Programming Cookbook Solutions Examples for iOS Apps book
iOS 8 Programming Fundamentals with Swift Swift, Xcode, and Cocoa Basics book
Hope it help the once that had the same problem

Resources