XML data save in NSUserDefaults or in Documents Directory - ios

I am making an simple news app, and trying to save (title, description, date, image with its data) in NSUserDefaults for offline read. I want when it save data in NSUserDefaults, it shows in offline and also when new data is available it rewrite or replace with previous data.
I know how to save string arrays in NSUserDefaults but did't know properly about how image saved in NSUserDefaults. I am trying to make logic of saving data and check it if new data is available but did't get success, also splash screen is take more time to disappear , is it due to loading data from server or due to slow internet connection?
Can anyone please check my code for fix it.
Thanks
class ViewController2: UIViewController ,NSXMLParserDelegate {
let newsDefaults = NSUserDefaults.standardUserDefaults()
#IBOutlet weak var tableView: UITableView!
var parser = NSXMLParser()
var posts = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var title1 = NSMutableString()
var date = NSMutableString()
var link = NSMutableString()
var des = NSMutableString()
var img2 = NSMutableString()
var NSUserDefaultsTitle : [NSString] = []
var NSUserDefaultsDate : [NSString] = []
var NSUserDefaultsDes : [NSString] = []
var NSUserDefaultsImage : [UIImage] = []
typealias CompletionHandler = (image: UIImage) -> Void
var attrsUrl : [String : NSString]!
var urlPic : NSString?
var postLink: String = String()
override func viewDidLoad() {
super.viewDidLoad()
self.configure()
self.beginParsing()
}
override func viewWillAppear(animated: Bool) {
// if let HaveTitle = newsDefaults.objectForKey("t"){
// NSUserDefaultsTitle = HaveTitle.mutableCopy() as! [NSString]
// }
// if let HaveDate = newsDefaults.objectForKey("d"){
// NSUserDefaultsDate = HaveDate.mutableCopy() as! [NSString]
// }
// if let HaveDes = newsDefaults.objectForKey("des"){
// NSUserDefaultsDes = HaveDes.mutableCopy() as! [NSString]
// }
// if let imageData = newsDefaults.objectForKey("imgData"){
// NSUserDefaultsImage = imageData.mutableCopy() as! [UIImage]
// }
//
// print(newsDefaults.objectForKey("d"))
}
func beginParsing()
{
posts = []
parser = NSXMLParser(contentsOfURL:(NSURL(string: "http://www.voanews.com/api/zq$omekvi_"))!)!
parser.delegate = self
parser.parse()
tableView!.reloadData()
}
//XMLParser Methods
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
{
element = elementName
if (elementName as NSString).isEqualToString("item")
{
elements = NSMutableDictionary()
elements = [:]
title1 = NSMutableString()
title1 = ""
date = NSMutableString()
date = ""
link = NSMutableString()
link = ""
des = NSMutableString()
des = ""
img2 = NSMutableString()
img2 = ""
}
if elementName == "enclosure" {
attrsUrl = attributeDict as [String: NSString]
urlPic = attrsUrl["url"]
print(urlPic!, terminator: "")
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if (elementName as NSString).isEqualToString("item") {
if !title1.isEqual(nil) {
elements.setObject(title1, forKey: "title")
}
if !date.isEqual(nil) {
elements.setObject(date, forKey: "pubDate")
}
if !link.isEqual(nil) {
elements.setObject(link, forKey: "link")
}
if !des.isEqual(nil){
elements.setObject(des, forKey: "description")
}
if !img2.isEqual(nil){
elements.setObject(urlPic!, forKey: "enclosure")
}
posts.addObject(elements)
//
// if let HaveData = newsDefaults.objectForKey("post"){
//
// }else{
//
//// newsDefaults.setObject(self.posts.valueForKey("title"), forKey: "t")
//// newsDefaults.setObject(self.posts.valueForKey("pubDate"), forKey: "d")
//// newsDefaults.setObject(self.posts.valueForKey("description"), forKey: "des")
//
// newsDefaults.setObject(posts, forKey: "post")
//
print("elementName")
// }
}
print("didEndElement")
}
func parser(parser: NSXMLParser, foundCharacters string: String)
{
if element.isEqualToString("title") {
title1.appendString(string)
} else if element.isEqualToString("pubDate") {
date.appendString(string)
print(date)
}
else if element.isEqualToString("link"){
link.appendString(string)
}else if element.isEqualToString("description"){
des.appendString(string)
}else if element.isEqualToString("enclosure"){
img2.appendString(string)
}
print("foundCharacter")
}
private func configure() {
self.navigationController?.navigationBar.titleTextAttributes = [NSFontAttributeName: UIFont.boldSystemFontOfSize(20.0), NSForegroundColorAttributeName: UIColor.whiteColor()]
self.tableView.registerNib(UINib(nibName: "2ImageCell", bundle: nil), forCellReuseIdentifier: "imageCell")
self.tableView.delegate = self
self.tableView.dataSource = self
self.fillNavigationBar(color: UIColor(red: 252.0/255.0, green: 0, blue: 0, alpha: 1.0))
}
private func fillNavigationBar(color color: UIColor) {
if let nav = self.navigationController {
nav.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
nav.navigationBar.shadowImage = UIImage()
for view in nav.navigationBar.subviews {
if view.isKindOfClass(NSClassFromString("_UINavigationBarBackground")!) {
if view.isKindOfClass(UIView) {
(view as UIView).backgroundColor = color
}
}
}
}
}
}
extension ViewController2: UITableViewDelegate, UITableViewDataSource {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
let view = ImageModalView2.instantiateFromNib()
view.des.text = posts.objectAtIndex(indexPath.row).valueForKey("description") as? String
downloadFileFromURL(NSURL(string: self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String)!, completionHandler:{(img) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
view.image = img
})
})
let window = UIApplication.sharedApplication().delegate?.window!
let modal = PathDynamicModal.show(modalView: view, inView: window!)
view.closeButtonHandler = {[weak modal] in
modal?.closeWithLeansRandom()
return
}
}
#IBAction func printData(sender: AnyObject) {
print(NSUserDefaultsImage)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80.0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("imageCell", forIndexPath: indexPath) as! ImageCell2
// if let picURL = user["picture"].string, url = NSURL(string: picURL) {
// if let data = NSData(contentsOfURL: url) {
// cell!.imageView?.image = UIImage(data: data)
// }
cell.titleLabel.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
// if NSUserDefaultsImage {
// cell.sideImageView.image = NSUserDefaultsImage[indexPath.row]
// }else{
downloadFileFromURL(NSURL(string: self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String)!, completionHandler:{(img) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.sideImageView.image = img
// self.NSUserDefaultsImage.append(img)
// print(img)
// self.newsDefaults.setObject(self.NSUserDefaultsImage, forKey: "imgData")
})
})
// }
//cell.date.text = posts.objectAtIndex(indexPath.row).valueForKey("pubDate") as? String
//cell.sideImageView?.contentMode = UIViewContentMode.ScaleAspectFit
//cell.sideImageView?.image = image
cell.titleLabel.userInteractionEnabled = false
cell.titleLabel.editable = false
cell.titleLabel.selectable = false
return cell
}
func downloadFileFromURL(url1: NSURL?,completionHandler: CompletionHandler) {
// download code.
if let url = url1{
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let data = NSData(contentsOfURL: url)
if data != nil {
print("image downloaded")
completionHandler(image: UIImage(data: data!)!)
}
}
}
}
}

In order to save images to NSUserDefaults you will need to turn it into NSData as there are only certain types that are allowed in there.
As for the idea of storing information in there for offline viewing.... That sounds more like a job for the Applications Document directory. You should be able to use the NSCoding protocol in your objects and write all of your information to the disk by calling
NSKeyedArchiver:archiveRootObject:toFile on the main object and it will call encodeWithCoder on all the child objects.

Saving large data in NSUserDefault in not recommended by Apple and we should not save large data. NSUserDefault or Keychain we use to store "Username", "Password" like less information.
You can use CoreData or Sqlite to do operatation with Data.

Related

Not able to refresh my JSON data through pull to refresh in tableview controller

I am having the problem in refreshing my JSON data through pull to refresh.When I launch my application, then it is displaying the data but when I pull to refresh then it is not refreshing.
I have implemented the Refresh Control to refresh data. I am able to see the wheel icon of the pull to refresh, but it's not updating the JSON data.
Here is my code:
struct JSONData {
let country: String
let indicename: String
let currPrice: String
let chg: String
let perChg: String
init?(dictionary: [String:Any]) {
guard let country = dictionary["Country"] as? String,
let indicename = dictionary["Indicename"] as? String,
let currPrice = dictionary["CurrPrice"] as? String,
let chg = dictionary["Chg"] as? String,
let perChg = dictionary["perChg"] as? String else {
return nil
}
self.country = country
self.indicename = indicename
self.currPrice = currPrice
self.chg = chg
self.perChg = perChg
}
}
class SouthAViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,IndicatorInfoProvider {
var datas = [JSONData]()
var refreshControl = UIRefreshControl()
#IBOutlet var tableview: UITableView!
var arrowupimage : UIImage = UIImage(named : "arrowup")!
var arrowdownimage : UIImage = UIImage(named : "arrowdown")!
// I haven't shared the URL of parsing for security reasons
let url=NSURL(string:"*******************************")
let stringurl = "***************************"
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 10.0, *) {
tableview.refreshControl = refreshControl
} else {
tableview.addSubview(refreshControl)
}
self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl.addTarget(self, action: #selector(SouthAViewController.refresh), for: UIControlEvents.valueChanged)
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
self.downloadJsonWithURL()
tableview.delegate = self
tableview.dataSource = self
}
func refresh(){
self.downloadJsonWithURL()
}
// downloading JSON data to display on this class
func downloadJsonWithURL() {
let task = URLSession.shared.dataTask(with: URL(string: stringurl)!) { (data, response, error) in
if error != nil {
// print(error?.localizedDescription)
return
}
if let contdata = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [String:Any] {
if let arrJSON = contdata["data"] as? [[String:Any]] {
self.datas = arrJSON.flatMap(JSONData.init)
//Reload tableView and endRefreshing the refresh control
DispatchQueue.main.async {
self.tableview.reloadData()
self.refreshControl.endRefreshing()
}
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas.count
}
//setting data on the table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "southacell", for: indexPath) as! southacell
cell.Indicename?.text = datas[indexPath.row].indicename
cell.Country?.text = datas[indexPath.row].country
cell.CurrPrice?.text = datas[indexPath.row].currPrice
return cell
}
}
json sample :
{ "data":[
{
"Country":"China",
"Indicename":"SZSE COMPONENT INDEX",
"date":"2017-06-23 14:53:57",
"zone":"GMT+8",
"CurrPrice":"10355.3",
"Chg":"90.07",
"PerChg":"0.88",
"prev_close":"10265.2"
},
{
"Country":"China",
"Indicename":"Shanghai Composite",
"date":"2017-06-23 14:52:54",
"zone":"GMT+8",
"CurrPrice":"3155.9",
"Chg":"8.44",
"PerChg":"0.27",
"prev_close":"3147.45"
}
]
}
Don't use Data(contentsOf:) to retrieve data from URL what you need to use is URLSession with datatask to retrieve data from URL after that you need to reload the tableView and ending animation of RefreshControl inside the completion block of datatask(with:). Also instead of handling multiple array what you need to do is create one array of custom class or struct
struct Data {
var country: String
var indicename: String
var currPrice: String
var chg: String
var perChg: String
init?(dictionary: [String:Any]) {
guard let country = dictionary["Country"] as? String,
let indicename = dictionary["Indicename"] as? String,
let currPrice = dictionary["CurrPrice"] as? String,
let chg = dictionary["Chg"] as? String,
let perChg = dictionary["PerChg"] as? String else {
return nil
}
self.country = country
self.indicename = indicename
self.currPrice = currPrice
self.chg = chg
self.perChg = perChg
}
}
Now declare simply one array of type [Data] and use this will your tableView methods.
var datas = [Data]()
Now simply use this single array to store all the data and display data in tableView.
func refresh(){
self.downloadJsonWithURL()
}
func downloadJsonWithURL() {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error?.localizedDescription)
return
}
if let contdata = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [String:Any] {
if let arrJSON = contdata["data"] as? [[String:Any]] {
self.datas = arrJSON.flatMap(Data.init)
//Reload tableView and endRefreshing the refresh control
DispatchQueue.main.async {
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
}
}
}
task.resume()
}
//tableView methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
//setting data on the table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "southacell", for: indexPath) as! southacell
let curpricefloat : Double = Double(datas[indexPath.row].currPrice)!
let Chgfloat : Double = Double(datas[indexPath.row].chg)!
let perchgfloat : Double = Double(datas[indexPath.row].perChg)!
cell.Indicename?.text = datas[indexPath.row].indicename
cell.Indicename.font = UIFont.boldSystemFont(ofSize: 19.0)
cell.Country?.text = datas[indexPath.row].country
cell.PerChg?.text = "(" + String(format: "%.2f", perchgfloat) + "%)"
cell.CurrPrice?.text = String(format: "%.2f", curpricefloat)
cell.Chg?.text = String(format: "%.2f", Chgfloat)
if Float((cell.Chg?.text)!)! < 0 {
datas[indexPath.row].chg = datas[indexPath.row].chg.replacingOccurrences(of: "-", with: "")
datas[indexPath.row].perChg = datas[indexPath.row].perChg.replacingOccurrences(of: "-", with: "")
cell.PerChg?.text = "(" + datas[indexPath.row].perChg + "%)"
cell.Chg?.text = datas[indexPath.row].chg
cell.Chg?.textColor = UIColor.red
cell.PerChg?.textColor = UIColor.red
cell.arrow?.image = UIImage(named : "arrowdown")
} else
{
cell.Chg?.textColor = UIColor.green
cell.PerChg?.textColor = UIColor.green
cell.arrow?.image = UIImage(named : "arrowup")
}
if cell.Country!.text! == "Argentina"{
cell.countryFlag?.image = UIImage(named : "argentina")
}
else if cell.Country!.text! == "Brazil"{
cell.countryFlag?.image = UIImage(named : "brazil")
}
else if cell.Country!.text! == "Peru"{
cell.countryFlag?.image = UIImage(named : "peru")
}
else{
cell.countryFlag?.image = UIImage(named : "unknown")
}
return cell
}
Try the following:
override func viewDidLoad() {
super.viewDidLoad()
// MARK: Refresh control
updateData.backgroundColor = .black
updateData.tintColor = .white
updateData.attributedTitle = NSAttributedString(string: "Updating Tap
List...", attributes: [NSForegroundColorAttributeName: UIColor(red:
255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0)])
updateData.addTarget(self, action:
#selector(ViewController.loadNewData), for: UIControlEvents.valueChanged)
tapListTableView.addSubview(updateData)
tapListTableView.sendSubview(toBack: updateData)
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}

Waiting to fill in a tableview when you data is downloaded from a url

I am trying to get a table filled when after I downloaded information from a url.This data will fill in different labels and a image view in the table view which is nested in a view controller. I got the data from a local json file to parse out right and also fill in the labels with the right values. The problem is the table methods that fill the table get called before I can download the data from the url. Any help with this will be appreciated.
thanks
Here is what I have so far:
var titleArray = [String]()
var descriptionArray = [String]()
var amountArray = [Int]()
var typeArray = [String]()
var startDateArray = [String]()
var endDateArray = [String]()
var barcodeArray = [String]()
#IBOutlet weak var myTableView: UITableView!
// MARK: - UIViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
downloadCouponData(couponUrl)
}
// MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Running Table view that counts the number of rows")
return titleArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Coupon", forIndexPath: indexPath) as! CouponTableViewCell
print("Running TableView that fills the table")
var amountText : String
amountText = String( amountArray[indexPath.row])
var typeType : String = ""
if(typeArray[indexPath.row] == "PERCENT_OFF")
{
typeType = "%"
}else
{
typeType = "¥ off"
}
cell.couponTitle.text = titleArray[indexPath.row]
cell.couponDescription.text = descriptionArray[indexPath.row]
cell.couponAmount.text = amountText + typeType
cell.couponStartDate.text = startDateArray[indexPath.row]
cell.couponEndDate.text = endDateArray[indexPath.row]
cell.couponBarcodeNumber.text = barcodeArray[indexPath.row]
let img = Barcode.fromString(barcodeArray[indexPath.row])
cell.couponBarcode.image = img
return cell
}
class Barcode {
class func fromString(string : String) -> UIImage? {
let data = string.dataUsingEncoding(NSASCIIStringEncoding)
let filter = CIFilter(name: "CICode128BarcodeGenerator")
filter!.setValue(data, forKey: "inputMessage")
return UIImage(CIImage: filter!.outputImage!)
}
}
//Function to log into the server and retrive data
func downloadCouponData(myUrl : String)
{
print("Downloading Coupon Data")
Alamofire.request(.GET, myUrl)
.authenticate(user: "admin", password: "admin")
.validate()
.responseString { response in
print("Success: \(response.result.isSuccess)")
self.parseCoupons(response.result.value!)
}
}
func parseCoupons(response : String)
{
print("Starting to parse the file")
let data = response.dataUsingEncoding(NSUTF8StringEncoding)
var myJson : NSArray
myJson = []
do {
myJson = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as! NSArray
print("MyJson lenght" , myJson.count)
}
catch {
print("Error")
}
for i in 0..<myJson.count
{
titleArray.append((myJson[i]as! NSDictionary)["name"] as! String)
descriptionArray.append((myJson[i]as! NSDictionary)["description"] as! String)
amountArray.append((myJson[i]as! NSDictionary)["amount"] as! Int)
typeArray.append((myJson[i]as! NSDictionary)["type"] as! String)
startDateArray.append((myJson[i]as! NSDictionary)["start_date"] as! String)
endDateArray.append((myJson[i]as! NSDictionary)["end_date"] as! String)
barcodeArray.append((myJson[i]as! NSDictionary)["barcode"] as! String)
}
for gus in descriptionArray{
print("descr array: " + gus)
}
for gus in amountArray{
print("Amount array: " , gus)
}
for gus in typeArray{
print("Type array: " + gus)
}
for gus in startDateArray{
print("Start array: " + gus)
}
for gus in endDateArray{
print("End array: " + gus)
}
for gus in barcodeArray{
print("Bar array: " + gus)
}
for gus in titleArray{
print("Title array: " + gus)
}
}
}
Call reloadData on the table view once you have downloaded all your data.

Passing data from TableViewController to parser

During my app development I've encountered a specific problem.
I've a database which contains following columns: Dossier_Title, Dossier_Name, Dossier_Category and Dossier_Description. The Description is uniqe for every Dossier.
First of all I'm calling my WebService which selects unique Dossier_Title and forms an xml page. After that app parse this page and forms a number of unique cells in table View. Also it forms an array which consists of Dossier_Title and Dossier_category.
Now I want to form a new tableView which would consist of Dossier_Name and Dossier_Description based on the Dossier_Category I've achieved on previous step. For this I want to call a new WebService and parse it using that Category as condition.
My question is: how can I pass Dossier_category to my second parser so I can use it as condition?
Here is my first parser code I assume the second one would be pretty much the same with addition of some new conditions
class DossierParser: NSObject, NSXMLParserDelegate {
var parser = NSXMLParser()
var feeds = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var ftitle = NSMutableString()
var link = NSMutableString()
var fdescription = NSMutableString()
var fdate = NSMutableString()
var fcategory = NSMutableString()
// initilise parser
/*
func initWithURL(url :NSURL) -> AnyObject {
startParse(url)
return self
}
*/
init(URL: NSURL){
super.init()
startParse(URL)
}
func startParse(url :NSURL) {
feeds = []
parser = NSXMLParser(contentsOfURL: url)!
parser.delegate = self
parser.shouldProcessNamespaces = false
parser.shouldReportNamespacePrefixes = false
parser.shouldResolveExternalEntities = false
parser.parse()
}
func allFeeds() -> NSMutableArray {
return feeds
}
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
self.element = elementName
if self.element == "News" {
elements = NSMutableDictionary()
elements = [:]
ftitle = NSMutableString()
ftitle = ""
fdescription = NSMutableString()
fdescription = ""
fdate = NSMutableString()
fdate = ""
fcategory = NSMutableString()
fcategory = ""
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if (elementName as NSString).isEqualToString("News") {
if ftitle != "" {
elements.setObject(ftitle, forKey: "DosNum")
}
if fcategory != "" {
elements.setObject(fcategory, forKey: "Dossier_number")
}
if fdate != "" {
elements.setObject(fdate, forKey: "Date_Text")
}
feeds.addObject(elements)
}
}
func parser(parser: NSXMLParser, foundCharacters string: String?) {
if element.isEqualToString("DosNum") {
ftitle.appendString(string!)
}else if element.isEqualToString("Date_Text") {
fdate.appendString(string!)
}else if element.isEqualToString("Dossier_number"){
fcategory.appendString(string!)
}
}
And here is my first TableView code.
var myFeed : NSArray = []
var url: NSURL = NSURL()
override func viewDidLoad() {
super.viewDidLoad()
// Cell height.
self.tableView.rowHeight = 70
self.tableView.dataSource = self
self.tableView.delegate = self
//url = NSURL(string: "https://www.kpmg.com/_layouts/feed.aspx?xsl=1&web=/RU/ru/IssuesAndInsights/RSSFeeds&page=207b36b2-20f7-407f-a9ec-a09f191fd84b&wp=9810a349-6086-489d-ad03-40c06f6669f6")!
//url = NSURL(string: "http://www.skysports.com/rss/0,20514,11661,00.xml")!
// url = NSURL(scheme: "https", host: "www.kpmg.com", path: "/_layouts/feed.aspx?xsl=1&web=/RU/ru/IssuesAndInsights/RSSFeeds&page=207b36b2-20f7-407f-a9ec-a09f191fd84b&wp=9810a349-6086-489d-ad03-40c06f6669f6")!
//(scheme: "http", host: "10.207.203.216", path: "/AppWebservice/Service1.asmx/getNewsData")!
url = NSURL(string: "http://10.207.206.74/AppWebservice/Service1.asmx/getUniqDossier")!
// Call custom function.
loadRss(url);
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = ""
let backImg:UIImage! = UIImage(named: "backPicture.png")
self.navigationItem.backBarButtonItem =
UIBarButtonItem(image:backImg, style:.Plain, target:self, action:nil);
let navBgImage:UIImage = UIImage(named: "express.png")!
self.navigationController?.navigationBar.setBackgroundImage(navBgImage, forBarMetrics: .Default)
}
func loadRss(data: NSURL) {
let myParser = DossierParser(URL: data)
myFeed = myParser.feeds
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "openPage" {
let indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow!
let selectedFTitle: String = myFeed[indexPath.row].objectForKey("DosNum") as! String
let selectedFContent: String = myFeed[indexPath.row].objectForKey("Dossier_number") as! String
// Instance of our feedpageviewcontrolelr
let fpvc: FeedPageViewController = segue.destinationViewController as! FeedPageViewController
fpvc.selectedFeedTitle = selectedFTitle
fpvc.selectedFeedFeedContent = selectedFContent
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myFeed.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let chevron = UIImage(named: "Next4.png")
cell.accessoryType = .DisclosureIndicator
cell.accessoryView = UIImageView(image: chevron!)
cell.textLabel?.textColor = UIColor.blackColor()
// Feeds dictionary.
var dict : NSDictionary! = myFeed.objectAtIndex(indexPath.row) as! NSDictionary
// Set cell properties.
cell.textLabel?.text = myFeed.objectAtIndex(indexPath.row).objectForKey("DosNum") as? String
cell.detailTextLabel?.text = myFeed.objectAtIndex(indexPath.row).objectForKey("Date_text") as? String
return cell
}
I would appreciate any sort of help. My guessing there should be easier way around it, but I don't know it, so if someone has suggestion it would be nice!
Thanks!
I believe you would want to retain an instance of fCategory and pass it to the second parser class via init.
The second parser class should have the following to allow for the fcategory to be passed in and stored locally for use by the parser.
let fcategory = NSMutableString()
let URL = NSURL
init(URL: NSURL, category: String){
super.init()
self.URL = URL
self.fcategory = category
self.startParse(URL, fcategory)
}

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.

TableView shows blank cells while parsing XML

I worked on my XML feed reader with a TableViewControllerand it was working perfectly but as it's not flexible, I wanted to move to another ViewController which includes `TableView'. However, even though I think I made the connections right (creating IBOutlet from tableview, using the right objects in custom cell in CustomCell class etc), it shows blank cells.
You can find the code below. I am using CustomCell class to create my custom cell. What am I missing here?
class originalViewController: UIViewController,UITableViewDelegate, NSXMLParserDelegate, UIScrollViewDelegate{
#IBOutlet var tableView: UITableView!
var parser = NSXMLParser()
var feeds = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var ftitle = NSMutableString?()
var link = NSMutableString?()
var fdescription = NSMutableString?()
var fIMG = NSMutableString?()
var fAuthor = NSMutableString?()
var fDate = NSMutableString?()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
feeds = []
var url = NSURL(string: "http://www.marketoloji.com/?feed=rss2")
parser = NSXMLParser(contentsOfURL: url)!
parser.delegate = self
parser.shouldProcessNamespaces = false
parser.shouldReportNamespacePrefixes = false
parser.shouldResolveExternalEntities = false
parser.parse()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "openPage" {
var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!
let wvc: WebViewController = segue.destinationViewController as WebViewController
var selectedURL: String = feeds[indexPath.row].objectForKey("link") as String
selectedURL = selectedURL.stringByReplacingOccurrencesOfString(" ", withString: "")
selectedURL = selectedURL.stringByReplacingOccurrencesOfString("\n", withString: "")
wvc.selectedLink = selectedURL
}
}
func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
element = elementName
if (element as NSString).isEqualToString("item"){
elements = NSMutableDictionary.alloc()
elements = [:]
ftitle = ""
link = ""
fdescription = ""
fAuthor = ""
fDate = ""
fIMG = ""
} else if element.isEqualToString("enclosure") {
var imgLink = attributeDict["url"] as String
imgLink = imgLink.stringByReplacingOccurrencesOfString(" ", withString: "")
imgLink = imgLink.stringByReplacingOccurrencesOfString("\n", withString: "")
fIMG?.appendString(imgLink)
println(imgLink)
}
}
func parser(parser: NSXMLParser!, didEndElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!){
if (elementName as NSString).isEqualToString("item"){
if ftitle != nil {
elements.setObject(ftitle!, forKey: "title")
}
if link != nil {
elements.setObject(link!, forKey: "link")
}
if fdescription != nil {
elements.setObject(fdescription!, forKey: "description")
}
if fAuthor != nil {
elements.setObject(fAuthor!, forKey: "creator")
}
if fDate != nil {
elements.setObject(fDate!, forKey: "date")
}
if fIMG != nil {
elements.setObject(fIMG!, forKey: "imageLink")
}
feeds.addObject(elements)
}
}
func parser(parser: NSXMLParser!, foundCharacters string: String!) {
if element.isEqualToString("title") {
ftitle?.appendString(string)
} else if element.isEqualToString("link") {
link?.appendString(string)
} else if element.isEqualToString("description") {
fdescription?.appendString(string)
} else if element.isEqualToString("dc:creator") {
fAuthor?.appendString(string)
} else if element.isEqualToString("pubDate") {
fDate?.appendString(string)
}
}
func parserDidEndDocument(parser: NSXMLParser!) {
self.tableView.reloadData()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("Cell") as CustomCell
let cell:CustomCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as CustomCell
cell.setCell(feeds.objectAtIndex(indexPath.row).objectForKey("title") as String, fDescription: feeds.objectAtIndex(indexPath.row).objectForKey("description") as String, fAuthor: feeds.objectAtIndex(indexPath.row).objectForKey("creator") as String, fDate: feeds.objectAtIndex(indexPath.row).objectForKey("date") as String, fImage: feeds.objectAtIndex(indexPath.row).objectForKey("imageLink") as String)
//cell.backgroundColor = UIColor.clearColor()
return cell
}
}
Figured out that I didnt connect the delegate and datasource of the table to the view controller, that was the main problem.

Resources