Swift: Trying to retrieve the data from a dataSource Struct - ios

Here is my code below:
class DataSource: NSObject {
var categories = [String]()
var items = [Item]()
private override init() {
super.init()
}
class var sharedDataSource: DataSource {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: DataSource!
}
dispatch_once(&Static.onceToken) {
let dataSource = DataSource()
Static.instance = dataSource
let urlPath = "myUrlPathString"
let endpoint = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: endpoint!)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
dataSource.items.append(item)
print(dataSource.items)
print("STOP")
}
}).resume()
}
return Static.instance
}
}
I am trying to use the result of this dataSource in a UICollectionView, by assigning its result to an Item array. I am successfully grabbing the data in my NSURLSession, and its local list, 'items', is being populated.
In my UICollectionView, in my viewDidLoad, I am assigning my local variable as follows:
let dataSource = DataSource()
items = dataSource.sharedInstance.items
Printing the value within the viewDidLoad always results in an empty array with no values, but I know the values are there by the time NURLSession is finished. I'm not sure how to write a completionhandler for this. This is my first time doing this kind of thing with a sharedDataSource that is a struct.
Any ideas anyone?
Thanks,
Sean

If you are doing this in viewDidLoad then it's likely the NSURL session has not completed by the time you try to get your items array. Send a notification in your dataSource class after the data has been received and then set items.
Eg
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
dataSource.items.append(item)
print(dataSource.items)
print("STOP")
}
NSNotificationCenter.defaultCenter().postNotificationName("dataRecievedNotification", object: nil)
}).resume()
But you also need to be able to receive this notification in your ViewController with the collectionView. So in viewDidLoad add
NSNotificationCenter.defaultCenter().addObserver(self, selector: "dataRecieved", name: "dataRecievedNotification", object: nil)
Then add a function in the same ViewController:
func dataRecieved() {
print("data received")
items = dataSource.sharedInstance.items
collectionView.reloadData()
}
Where dataSource is a variable declared above viewDidLoad:
let dataSource = DataSource()
Don't forget that if you are using observers you need to remove them when the class is removed from memory, so in the ViewController add this deinit function:
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
EDIT: I think your singleton pattern can be simplified using modern swift. You don't have to use a struct or dispatch_once any more, just declaring a static let shared instance will handle all that for you. with that in mind, I tried to simplify your dataSource class:
class DataSource {
static let sharedInstance = DataSource()
var items = [Item]()
private init() {}
func retrieveItems() {
let urlPath = "myUrlPathString"
let endpoint = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: endpoint!)
NSURLSession.sharedSession().dataTaskWithRequest(request,completionHandler: { (data, response, error) in
let json = JSON(data: data!)
print(json)
for obj in json.arrayValue {
let item: Item = Item()
item.itemID = obj["item"].stringValue
item.price = obj["price"].floatValue
item.title = obj["title"].stringValue
item.category = obj["category"].stringValue
item.available = obj["available"].boolValue
item.image = obj["image"].stringValue
print(item.title)
items.append(item)
print(items)
print("STOP")
}
NSNotificationCenter.defaultCenter().postNotificationName("dataRecievedNotification", object: nil)
}).resume()
}
}
where you, in viewDidLoad, add the following logic:
if DataSource.sharedInstance.items.count == 0 {
DataSource.sharedInstance.retrieveItems()
}
then change dataRecieved to
func dataRecieved() {
print("data received")
items = DataSource.sharedInstance.items //Notice the capital
collectionView.reloadData()
}
and delete your declaration of var dataSource = DataSource() above viewDidLoad

Related

How can I remove all array data when using nested SnapshotListeners?

So whenever I add new data, both of my snapshot listeners are executed and the new data gets appended to 'dataArray' which is located outside of my LoadData Function. So what I figured I had to do is reset the data whenever new data get's added so not to duplicate my tableview data. So what I did is I added self.dataArray.removeAll() inside of my top listener, but how would I go about reseting the data for when the other listener gets executed as well?
struct Days: Decodable {
var dow : String
var workouts : [Workouts]
}
struct Workouts: Decodable {
var workout : String
}
var dayCount = 0
var dataArray = [Days]()
//MARK: - viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
vcBackgroundImg()
navConAcc()
picker.delegate = self
picker.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
tableView.tableFooterView = UIView()
Auth.auth().addStateDidChangeListener { (auth, user) in
self.userIdRef = user!.uid
self.rootCollection = Firestore.firestore().collection("/users/\(self.userIdRef)/Days")
self.loadData { (Bool) in
if Bool == true {
self.dayCount = self.dataArray.count
self.tableView.reloadData()
}
}
}
}
//MARK: - Load Data
func loadData(completion: #escaping (Bool) -> ()){
let group = DispatchGroup()
self.rootCollection.addSnapshotListener ({ (snapshot, err) in
if let err = err
{
print("Error getting documents: \(err.localizedDescription)");
}
else {
guard let dayDocument = snapshot?.documents else { return }
self.dataArray.removeAll()
for day in dayDocument {
group.enter()
self.rootCollection.document(day.documentID).collection("Workouts").addSnapshotListener {(snapshot, err) in
var workouts = [Workouts]()
let workoutDocument = snapshot!.documents
try! workoutDocument.forEach({doc in
let tester: Workouts = try doc.decoded()
let workoutString = tester.workout
let newWorkout = Workouts(workout: workoutString)
workouts.append(newWorkout)
})
let dayTitle = day.data()["dow"] as! String
let newDay = Days(dow: dayTitle, workouts: workouts)
self.dataArray.append(newDay)
group.leave()
}
}
}
group.notify(queue: .main){
completion(true)
}
})
}

Swift: How to find Realm database contains custom string

I had a problem to filter realm database. I search movie name and wrote values to realm database from Json. After written, I assign to tableview cell to show results. At the second search, I always get same values because of the getting all result from the database. I need to filter new values from the database and set to tableview. Please help me !
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.counter = 0
aranacak_kelime = searchBar.text!
let url = URL(string: "https://www.omdbapi.com/?s=" + aranacak_kelime + "&apikey=c6e----")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if(error != nil)
{
print("error")
}
else{
if let content=data
{
do
{
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableLeaves) as AnyObject
if let search = json["Search"] as? [NSDictionary]
{
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
print(Realm.Configuration.defaultConfiguration.fileURL)
for result in search
{
let movie = Movie()
movie.name = result["Title"] as! String
movie.type = result["Type"] as! String
movie.year = result["Year"] as! String
movie.poster = result["Poster"] as! String
try! realm.write {
realm.add(movie)
}
self.counter += 1
DispatchQueue.main.async {self.tableView.reloadData()}
}
}
}
}
}
catch{
print("error in JSONSerialization")
}
}
}}task.resume()}
and tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "textCell", for: indexPath) as! TableViewCell
let row = indexPath.row
let realm = try! Realm()
//let results = realm.objects(Movie.self).filter("ANY name.contains('aranacak_kelime')")
let results = realm.objects(Movie.self)
cell.movieName.text = results[row].name
cell.movieYear.text = results[row].year
cell.movieType.text = results[row].type
let imageUrl = results[row].poster
}
and my Movie class
class Movie: Object{
#objc dynamic var name:String = ""
#objc dynamic var type:String = ""
#objc dynamic var year:String = ""
#objc dynamic var poster:String = ""
}
As I understand, you have a couple of issues. If you search for the same movie twice, you will be saving the same result to your realm DB. So maybe use some sort of ID and when saving use realm.add(movie, update: true) this will prevent from storing the same movie twice.
The other issue is that you are not filtering the results you load on the tableview.
You should have an auxiliar method where you use the search bar text to filter your results before reloading your table view, something like:
func reloadContent() {
self.data = realm.objects(Movie.self).filter("ANY name.contains(YOUR_SEARCHBAR.text)")
self.tableView.reloadData()
}
You should change this line DispatchQueue.main.async {self.tableView.reloadData()} for DispatchQueue.main.async {self.reloadContent()}
and finally have a property of var data: Results<Movie>? where you will store the filtered movies. You will have to change your table view datasource delegate methods to use this data instead.
Hope it helps.

Swift - Load/save from CoreData generates duplicate entries

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

Swift 2.0 iOS9 UITableView update does not render

When i load a JSON file inside my UITableViewController it loads and updates my datasource and view, but only renders the update when i touch my screen.
The loading and parsing code i'm using looks like this:
func fetchData() {
let jsonUrl = 'http://myrestserver.com/apicall'
let session = NSURLSession.sharedSession()
let urlObject = NSURL(string: jsonUrl)
let task = session.dataTaskWithURL(urlObject!) {
(data, response, error) -> Void in
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
var items :[Article] = []
let jsonItems = jsonData["channel"] as! NSArray
for (_, item) in jsonItems.enumerate() {
let article = Article()
article.id = item["id"] as? String
article.title = item["title"] as? String
article.guid = item["guid"] as? String
items.append(article)
}
self.articles.insertContentsOf(items, at: 0)
} catch {
print("error fetchData")
}
}
task.resume()
self.tableView.reloadData()
}
Is there a method i'm not aware of to handle this re-rendering?
I've tried render methods for UITableViewCell like described here:
setNeedsLayout and setNeedsDisplay
But there is no luck, can someone explain what is the best practice for rendering new records?
Best regards,
Jos
#nwales is correct, though I would recommend getting familiar with property observers for reloading your data. Once your data is reloaded simply update your property and it will automatically fire your update.
var data: [String] = [""] {
didSet {
// you could call a function or just reload right here
self.tableView.reloadData()
}
}
using #nwales method:
var data: [String] = [""] {
didSet {
dispatch_async(dispatch_get_main_queue(),{
myTableView.reloadData()
})
}
}
After you've parsed the JSON try adding the following
dispatch_async(dispatch_get_main_queue(),{
myTableView.reloadData() //myTableView = your table view instance
})

swift - JSON to NSManagedObject

I have a JSON data which I want to map to CoreData when my tableview loaded at first launch.
I found a way to do it in cellForRowAtIndexPath, but this way I can only save the data to CoreData when the cell is displayed.
I want to do it once for all cells.
var yazarMakaleListesi: [JSON]? = []
var authorList = [AuthorList]() // My CoreData
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("YazarCell") as! YazarTableViewCell
cell.yazar = self.yazarMakaleListesi?[indexPath.row]
cell.yazar = self.yazarMakaleListesi?[indexPath.row]
let authorName = self.yazarMakaleListesi?[indexPath.row]["author_name"].string
let authorID = self.yazarMakaleListesi?[indexPath.row]["author_id"].int
let authorImage = self.yazarMakaleListesi?[indexPath.row]["author_image"].string
let newspaperID = self.yazarMakaleListesi?[indexPath.row]["newspaper_id"].string
let newspaperName = self.yazarMakaleListesi?[indexPath.row]["newspaper"].string
let newsPaperImage = self.yazarMakaleListesi?[indexPath.row]["author_image"].string
let articleEntity = NSEntityDescription.entityForName("AuthorList", inManagedObjectContext: self.context!)
let newAuthor = AuthorList(entity: articleEntity!, insertIntoManagedObjectContext: self.context!)
newAuthor.authorName = authorName!
newAuthor.authorImage = authorImage!
newAuthor.newspaperName = newspaperName!
newAuthor.newsPaperImage = newsPaperImage!
newAuthor.authorID = authorID!
var saveError: NSError?
self.context!.save(&saveError)
if let _error = saveError {
println("\(_error.localizedDescription)")
} else {
println("Author Saved!")
}
var error: NSError?
let request = NSFetchRequest(entityName: "AuthorList")
let results = self.context!.executeFetchRequest(request, error: &error) as! [AuthorList]
return cell
}
I get the JSON data here:
func loadYazar(){
if (gazeteid != nil){
let url = "http:myapi.com" + String(gazeteid)
Alamofire.request(.GET, url).responseJSON { (Request, response, json, error) -> Void in
if (json != nil){
var jsonObj = JSON(json!)
if let data = jsonObj["authors"].arrayValue as [JSON]?{
self.yazarMakaleListesi = data
self.tableView.reloadData()
}
}
}
}
}
EDIT : I get my jsonresponse here, implemented # thefredelement's recommendation.
But I get " 'JSON' does not have a member named 'valueForKey'" from line:
newFakeCoreDataObject.authorName = jsonResult.valueForKey("authorName") as! String
Alamofire.request(.GET, url).responseJSON { (Request, response, json, error) -> Void in
if (json != nil){
var jsonObj = JSON(json!)
if let jsonResults = jsonObj["authors"].arrayValue as [JSON]?{
self.yazarMakaleListesi = jsonResults
var error : NSError?
for jsonResult in jsonResults {
let newFakeCoreDataObject = FakeCoreDataObject()
newFakeCoreDataObject.authorName = jsonResult.valueForKey("authorName") as! String
self.context!.save(&error)
}
self.tableView.reloadData()
}
}
I would highly suggest taking that work out of cellForRowAtIndex path and making a separate function that will iterate through your JSON results and save them each, then load the data from core data as a custom object and put that object into an instance array, then use that array for your table data.
Edit: I wouldn't use this code exactly obviously, it's just an example of what I was trying to explain.
import UIKit
import CoreData
class FakeCoreDataObject : NSObject {
// this would really be your NSManagedObject subclass
var authorName = ""
var authorImage = NSData()
var newspaperName = ""
var newspaperImage = NSData()
var authorId = 0 as NSNumber
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var myEntries = [FakeCoreDataObject]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchJson()
prepareTableData()
}
func fetchJson() {
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
let context = appDel.managedObjectContext!
var error : NSError?
// Get your json reuslts like you are already
var jsonResults = [AnyObject]() // just for an example
for jsonResult in jsonResults {
let newFakeCoreDataObject = FakeCoreDataObject()
newFakeCoreDataObject.authorName = jsonResult.valueForKey("authorName") as! String
// etc
context.save(&error)
// save whatever else you want for other entities, etc, if you need track out of scope you can do that and then save after the loop
}
}
func prepareTableData() {
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
let context = appDel.managedObjectContext!
var error : NSError?
let fetchTableDataRequest = NSFetchRequest(entityName: "whateverItIsCalled")
fetchTableDataRequest.returnsObjectsAsFaults = false
myEntries = context.executeFetchRequest(fetchTableDataRequest, error: &error) as! [FakeCoreDataObject]
// If you need to do this often, reload the table data here too and you can call it from notifications, etc.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myEntries.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// cell stuff you already have but just use the array
// just a tip to set set values to "" or nil if you're creating a big table so you don't get duplciate data while scrolling
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
you should parse the json you receive inside responseJSON:
let jsonObj = NSJSONSerialization.JSONObjectWithData(json, options:NSJSONReadingOptions(rawValue: 0), error: &err) as! NSDictionary
callback(jsonObj as! Dictionary<String, AnyObject>, nil)
var recs:NSArray = jsonObj.objectForKey("authors") as! NSArray
var ct:Int = 0
for item in recs{
var dict:NSDictionary = item as! NSDictionary
// use self.yazarMakaleListesi[ct] here
// process into the dict "author_name" , "author_id", etc
// increment ct
}
self.tableView.reloadData()

Resources