Binding to a UIRefreshControl after network call - ios

I am new to RxSwift and I was wondering how I would be able to "reactively" use a UIRefreshControl with a UITableView instead of the normal way of creating a target, and manually calling beginRefreshing() and endRefreshing().
For instance, say I am loading some strings from an API:
class TableViewController: UITableViewController {
var data : [String] = []
let db = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
//I don't want to use
//refreshControl?.addTarget(self, action: #selector(getData), forControlEvents: .ValueChanged)
//Do something to refreshControl.rx_refreshing?
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let str = data[indexPath.row]
cell.textLabel?.text = str
return cell
}
//MARK: - Requests
private func getData() {
let myData = MyAPI.getData() //Returns Observable<[String]>
myData
.subscribe({ [weak self] (event) in
switch event {
case .Next(let strings):
self?.data = strings
self?.tableView.reloadData()
break
case .Error(let err):
print(err)
break
case .Completed:
break
}
// self?.refreshControl?.endRefreshing()
})
.addDisposableTo(db)
}
}
MyAPI sends a request for some string values, how can I bind the refreshControl to call getData() and also stop refreshing when it's finished (or error'd) the network request? Do I need to bind to refreshControl.rx_refreshing?

RxSwift's example app provides an interesting class to handle this kind of logic: ActivityIndicator.
Once you have ActivityIndicator in, code for binding rx_refreshing to the request becomes really easy.
let activityIndicator = ActivityIndicator()
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
// When refresh control emits .ValueChanged, start fetching data
refreshControl.rx_controlEvent(.ValueChanged)
.flatMapLatest { [unowned self] _ in
return self.getData()
.trackActivity(activityIndicator)
}
.subscribeNext { [unowned self] strings in
self.data = strings
self.tableView.reloadData()
}
.addDisposableTo(db)
// Bind activity indicator true/false to rx_refreshing
activityIndicator.asObservable()
.bindTo(refreshControl.rx_refreshing)
.addDisposableTo(db)
}
// getData only needs to return an observable, subscription is handled in viewDidLoad
private func getData() -> Observable<[String]> {
return myData = MyAPI.getData() //Returns Observable<[String]>
}

Related

RxSwift Subscriber receiving multiple events

Consider the below code.
On tapButton, we subscribe to an Observable isFetched and then call fetchPopularMovies().
fetchPopularMovies() in turn calls an API. When the response is received, we will send OnNext(true) event.
Problem is, I receive multiple events on 2nd button tap onwards. If I add onCompleted(), I don't even receive events on 2nd button tap onwards. My expectation is that one event will be triggered on each button tap. What am I missing here?
class ViewController: UIViewController {
let popularMoviesURL = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=API_KEY")
var isFetched = BehaviorSubject<Bool?>(value:nil)
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func tapButton(_ sender: Any) {
let observable = isFetched.asObservable()
observable.subscribe(onNext: { observer in
guard let result = observer else { return }
print(result)
print("onNext Recieved")
}, onError: { _ in
print("onError Recieved")
}).disposed(by: disposeBag)
fetchPopularMovies()
}
func fetchPopularMovies() {
let task = URLSession.shared.dataTask(with: popularMoviesURL!) {(data, response, error) in
guard let _ = data else { return }
self.isFetched.onNext(true)
//self.isFetched.onCompleted()
}
task.resume()
}
}
Reactive code is declarative. It's "setup" code. So it should be placed where the comment says "Do any additional setup after loading the view."
The simplest change you can do to fix the problem you are having is to move the subscription into the viewDidLoad method as Satish Patel referenced in his comment.
override func viewDidLoad() {
super.viewDidLoad()
isFetched.subscribe(onNext: { observer in
guard let result = observer else { return }
print(result)
print("onNext Recieved")
}, onError: { _ in
print("onError Recieved")
}).disposed(by: disposeBag)
}
#IBAction func tapButton(_ sender: Any) {
fetchPopularMovies()
}
(Note that Subjects should always be held with lets never vars.)
If you use RxCocoa, you can simplify this code even more:
class ViewController: UIViewController {
let button = UIButton()
let isFetched = BehaviorSubject<Bool?>(value:nil)
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let popularMoviesURL = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=API_KEY")!
let fetchedData = button.rx.tap
.flatMapLatest {
URLSession.shared.rx.data(request: URLRequest(url: popularMoviesURL))
.catch { error in
print("onError Recieved")
return Observable.empty()
}
}
fetchedData
.map { _ in true }
.bind(to: isFetched)
.disposed(by: disposeBag)
}
}
Now, all of your code is "setup code" so it all goes in viewDidLoad.

By Alamofire, would like to get data and insert it to TableView, but before completing of inserting, TableView is loaded

As I'm writing at the title, I'm using Alamofire to get data and then decode it and append it to the list "articleList" and then try to insert it to TableView, but it seems that TableView is loaded at first, and then the data is collected and inserted to the list.
I would like to make the insertion first, then loading TableView but I cannot find a solution. I just tried it by putting defer into viewDidLoad and make tableView.realodData, but it doesn't work... Could anybody give me any idea for this situation?
import UIKit
import Alamofire
class NewsViewController: UITableViewController {
var urlForApi = "https://newsapi.org/v2/top-headlines?country=jp&category=technology&apiKey=..."
var articleList = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
defer {
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(articleList.count)
return articleList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = articleList[indexPath.row].title
return cell
}
// override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//
// }
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
for article in articleArray {
self.articleList.append(article)
}
print("insert is done")
}
}
func getNewsData(completion: #escaping (ArticlesListResult?) -> Void) {
Alamofire.request(urlForApi, method: .get)
.responseJSON { response in
if response.result.isSuccess {
if let data = response.data {
let articles = try? JSONDecoder().decode(ArticlesListResult.self, from: data)
completion(articles)
}
} else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
Instead of writing tableView.reloadData() in viewDidLoad method, you should write it after you complete appending all articles in the articleList array.
Sample code:
viewDidLoad() should be like:
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
}
updateNewsData() should be like:
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
articleList.append(contentsOf: articleArray)
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
Code inside defer is executed after "return" of method or for example after return of current cycle of some loop
Since viewDidLoad returns Void, this method returns immediately after updateNewsData() is called and it doesn't wait then another method which was called from inside returns or after code inside some closure is executed (defer isn't executed after some closure is executed since inside closure you can't return value for method where closure was declared).
To fix your code, just reload table view data after you append articles
for article in articleArray {
self.articleList.append(article)
}
tableView.reloadData()
Defer block will be executed when execution leaves the current scope, for other hand your request is async block that means that when tableView.reloadData() is called the request maybe is still in process.
You need to call reloadData when the request is finished:
override func viewDidLoad() {
super.viewDidLoad()
updateNewsData()
}
...
func updateNewsData() {
getNewsData() { (articles) in
guard let articleArray = articles?.articles else
{fatalError("cannot get articles.")}
for article in articleArray {
self.articleList.append(article)
}
print("insert is done")
DispatchQueue.main.async {
tableView.reloadData()
}
}
}

Loading the server response into the UICollectionView

I have a UIViewController class where I'm trying to populate the data into my UICollectionView
// HomeController.swift
import UIKit
import SwiftyJSON
class HomeController: UICollectionViewController,
UICollectionViewDelegateFlowLayout, TaskServiceManagerDelegate {
func didUpdate(sender: TaskServiceManager) {
self.collectionView?.reloadData()
}
var taskList:JSON? = nil
override func viewDidLoad() {
super.viewDidLoad()
// getting the task list to populate the UICollectionView
let taskServiceManager:TaskServiceManager = TaskServiceManager()
taskList = taskServiceManager.getAllTasks()
collectionView?.dataSource = self;
collectionView?.delegate = self;
navigationItem.title = "Test"
collectionView?.backgroundColor = UIColor.lightGray
collectionView?.register(QuestionCell.self, forCellWithReuseIdentifier: "cellId")
}
override func viewDidAppear(_ animated: Bool) {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// getting the task list to populate the UICollectionView
let taskServiceManager:TaskServiceManager = TaskServiceManager()
taskList = taskServiceManager.getAllTasks()
collectionView?.dataSource = self;
collectionView?.delegate = self;
}
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return (taskList?.count)!
}
}
I have another class TaskServiceManager where I'm making a REST api call and getting the data.
// TaskServiceManager.swift
import UIKit
import Alamofire
import SwiftyJSON
class TaskServiceManager : NSObject{
var delegate: TaskServiceManagerDelegate?
func getAllTasks() -> JSON {
var swiftyJsonVar: JSON = []
Alamofire.request(url, headers: getHeaders()).responseJSON { (responseData) -> Void in
print("Status Code: \(responseData.response?.statusCode)")
if((responseData.result.value) != nil) {
swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar)
print("Task List Count: \(swiftyJsonVar.array?.count)")
}
}
return swiftyJsonVar
}
}
protocol TaskServiceManagerDelegate {
func didUpdate(sender: TaskServiceManager)
}
The taskList?.count in the numberOfItemsInSection returns 0 even though I get a success from the api response with count as 6. Why is it so?
How do I refer the HomeController from the TaskServiceManager class?
Also, why does my TaskServiceManagerDelegate doesn't reflect the UICollectionView?
No need of delegate to fetch data from API, completion handlers are better choice for this kind of task.
Make this change in your function :
func getAllTasks(completion:#escaping(JSON)->()) {
var swiftyJsonVar: JSON = []
Alamofire.request(url, headers: getHeaders()).responseJSON { (responseData) -> Void in
print("Status Code: \(responseData.response?.statusCode)")
if((responseData.result.value) != nil) {
swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar)
print("Task List Count: \(swiftyJsonVar.array?.count)")
completion(swiftyJsonVar)
}
}
}
To call it from your another class:
TaskServiceManager().getAllTasks { (tasks) in
print("tasks : \(tasks)")
//here reload your collection view
}
You are doing everything right except return statement. API calls are asynchronous and so your return statement is getting called before your network request is over. You need a completion callback to return the data when you get response.
And your delegation is incomplete. If you want to the delegation method then you need add these lines.
In viewDidLoad() after :
let taskServiceManager:TaskServiceManager = TaskServiceManager()
you need to assign your HomeController's instance to delegate variable of TaskServiceManager class
taskServiceManager.delegate = self
And in TaskServiceManager class when you get data from server you need to pass it using your delegate method, I don't know why you are trying to pass instance of TaskServiceManager in delegate method ?
protocol TaskServiceManagerDelegate {
func didUpdate(sender: TaskServiceManager) // Why ?
// func didUpdate(serverData: JSON) // Maybe this is what you need
}
To call delegate method one last step without any return statement :
func getAllTasks() {
var swiftyJsonVar: JSON = []
Alamofire.request(url, headers: getHeaders()).responseJSON { (responseData) -> Void in
print("Status Code: \(responseData.response?.statusCode)")
if((responseData.result.value) != nil) {
swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar)
print("Task List Count: \(swiftyJsonVar.array?.count)")
delegate.didUpdate(serverData: swiftyJsonVar) // this is to pass data
}
}
}
Now this function in your HomeController will be called (I changed the data to serverData instead of TaskServiceManager) :
func didUpdate(serverData: dataFormServer) {
print("dataFormServer : \(dataFormServer)")
self.collectionView?.reloadData()
}
you forgot to call the didUpdate function after successful API call. You need to reload the collectionView after the successful API call. The reason is API calls are asynchronous in nature

Update Table View in iOS (Swift)

I am making a cocktail iOS application.
I am adding strings to a tableview (an ingredient to the "cabinet"). The user enters an ingredient and then presses the button ADD. It successfully adds it to the Core Data but it does not appear right away. What am I doing wrong?
Below is my code, thanks!
ViewController:
import UIKit
import CoreData
class CabinetViewController: UIViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {
var ingredientArray = [String]()
var display = [String]()
var dbIngredients = [String]()
let ingredientFetch = NSFetchRequest(entityName: "Cabinet")
var fetchedIngredient = [Cabinet]()
#IBOutlet weak var TextUI: UITextField!
#IBOutlet weak var Button: UIButton!
#IBOutlet weak var TableView: UITableView!
let moc = DataController().managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
TextUI.delegate = self
TextUI.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
TableView.delegate = self
TableView.dataSource = self
TableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
// fetch Core Data
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
} catch {
fatalError()
}
let postEndpoint: String = "http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on www.thecocktaildb.com")
print(error)
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
var count = 1
if let drinks = post["drinks"] as? [NSDictionary] {
for drink in drinks {
if let strIngredient = drink["strIngredient1"] as? String {
print(String(count) + ". " + strIngredient)
self.dbIngredients.append(strIngredient)
count++
}
}
}
})
task.resume()
TableView.reloadData()
}
func textFieldDidChange(textField: UITextField) {
search(self.TextUI.text!)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
Button.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
return true
}
func buttonPressed(sender: UIButton!) {
//ingredientArray.append(TextUI.text!)
let entity = NSEntityDescription.insertNewObjectForEntityForName("Cabinet", inManagedObjectContext: moc) as! Cabinet
entity.setValue(TextUI.text!, forKey: "ingredient")
do{
try moc.save()
}catch {
fatalError("failure to save context: \(error)")
}
showAlertButtonTapped(Button)
// dispatch_async(dispatch_get_main_queue(), { () -> Void in
// self.TableView.reloadData()
// })
}
#IBAction func showAlertButtonTapped(sender: UIButton) {
// create the alert
let alert = UIAlertController(title: "Added!", message: "You've added " + TextUI.text! + " to your cabinet", preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
// show the alert
self.presentViewController(alert, animated: true, completion: nil)
}
func search(str:String) {
display.removeAll(keepCapacity: false)
for s in dbIngredients{
if s.hasPrefix(str){
display.append(s)
print(s)
}
}
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.capacity
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
do{
let fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
} catch {
fatalError("bad things happened: \(error)")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let alert = UIAlertController(title: "Remove " + fetchedIngredient[indexPath.row].ingredient!,
message: "No more " + fetchedIngredient[indexPath.row].ingredient! + " in your cabinet?",
preferredStyle: .Alert)
let deleteAction = UIAlertAction(title: "Remove",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
self.fetchedIngredient.removeAtIndex(indexPath.row)
do{
let fetchedResults = try self.moc.executeFetchRequest(self.ingredientFetch)
if let result = fetchedResults[indexPath.row] as? NSManagedObject {
self.moc.deleteObject(result)
try self.moc.save()
}
}catch{
fatalError()
}
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addAction(cancelAction)
alert.addAction(deleteAction)
presentViewController(alert,
animated: true,
completion: nil)
TableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
Since your problem isn't Core Data you need to use Table View beginUpdates and EndUpdates to insert the row. At the end of your buttonPressed function put this:
do{
fetchedIngredient = try moc.executeFetchRequest(ingredientFetch) as! [Cabinet]
self.tableView.beginUpdates()
let totalIngredients = fetchedIngredient.count
let newItemIndexPath = NSIndexPath(forRow: totalIngredients-1, inSection: 0)
self.tableView.insertRowsAtIndexPaths([newItemIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
} catch {
fatalError()
}
On your number of rows in section:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedIngredient.count
}
And on the cell for row at index path
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = fetchedIngredient[indexPath.row].ingredient
return cell
}
There are a couple of problems with your code. Firstly, since you're fetching records into an array, calling reloadData will not have any impact unless you update the array. There is no automatic connection between adding a new core data record and your fetchedIngredient array.
There are a few ways to solve this, although the most common is probably to just refetch the records into the same array whenever core data is updated. Alternatively you can change your code to us NSFetchedResultsController instead of an array, which will automatically update the tableView when core data is updated (based on the predicate you provide it). This class provides the automatic connection to core data for you.
Another problem is that you are refetching the records in cellForRowAtIndexPath and didSelectRowAtIndexPath. This should not be done. Instead you should just be referring to the class-level fetchedIngredient array (or the NSFetchedResultsController if you choose to use that).
Furthermore, the call to dataTaskWithRequest runs in the background. It's not clear from the code how you're using it, but the fact that you have reloadData afterwards suggests it should impact the tableView. However because the task runs in the background, the completion handler will run after the table is reloaded. Therefore you should be calling reloadData inside the completion handler. And since it would then be running on another thread, you would have to dispatch it to the main queue, using:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}

Pull to Refresh in Swift not Reloading UITableView

I've got JSON filling my UITableView successfully, but the JSON is often updated so I need the ability to refresh. I followed THIS TUTORIAL to implement a pull to refresh control. Visually, it seems like it all works correctly, but when I call tableView.reloadData() the table doesn't reload. However, if I leave the ViewController and return, the table is updated. Why would tableView.reloadData() work in viewDidAppear and viewWillAppear but not in my custom refresh() function?
MainVC.swift file
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var dataArray: NSArray = NSArray()
#IBOutlet var Controller: UISegmentedControl!
var refreshControl:UIRefreshControl!
func refresh(sender:AnyObject)
{
refreshBegin("Refresh",
refreshEnd: {(x:Int) -> () in
self.tableView .reloadData()
println("Table Reloaded")
self.refreshControl.endRefreshing()
})
}
func refreshBegin(newtext:String, refreshEnd:(Int) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
println("refreshing")
sleep(2)
dispatch_async(dispatch_get_main_queue()) {
refreshEnd(0)
}
}
}
override func viewWillAppear(animated: Bool) {
self.tableView .reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = UIImageView(image: UIImage(named: "logojpg.jpg"))
startConnectionAt("http://www.domain.com/json.php")
refreshControl = UIRefreshControl()
refreshControl.backgroundColor = UIColor.orangeColor()
refreshControl.tintColor = UIColor.whiteColor()
refreshControl.attributedTitle = NSAttributedString(string: "Pull to Refresh")
refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl)
}
//MARK: JSON Loading
var data: NSMutableData = NSMutableData()
func startConnectionAt(urlPath: String){
var url: NSURL = NSURL(string: urlPath)
var request: NSURLRequest = NSURLRequest(URL: url)
var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)
connection.start()
}
func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {
println("Connection failed.\(error.localizedDescription)")
}
func connection(connection: NSURLConnection, didRecieveResponse response: NSURLResponse) {
println("Recieved response")
}
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
self.data = NSMutableData()
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {
self.data.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection!) {
var dataAsString: NSString = NSString(data: self.data, encoding: NSUTF8StringEncoding)
var err: NSError
var json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var results: NSArray = json["needs"] as NSArray
self.dataArray = results
tableView.reloadData()
println("success")
}
//End loading of JSON
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count;
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:CustomCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as CustomCell
var rowData: NSDictionary = dataArray[indexPath.row] as NSDictionary
var firstName=rowData["needFirstname"] as String
var descrip=rowData["needDescription"] as String
var poster=rowData["needPoster"] as String
var city=rowData["needCity"] as String
var state=rowData["needState"] as String
var country=rowData["needCountry"] as String
cell.needFirstName.text = firstName
cell.needDescription.text = descrip
cell.needDescription.numberOfLines = 0
cell.needPoster.text = poster
cell.needCity.text = city
cell.needState.text = state
cell.needCountry.text = country
return cell
}
#IBAction func Change(sender: AnyObject) {
if Controller.selectedSegmentIndex == 0 {
startConnectionAt("http://www.domain.com/localJSON.php")
}
else if Controller.selectedSegmentIndex == 1 {
startConnectionAt("http://www.domain.com/intlJSON.php")
}
self.tableView .reloadData()
}
}
Your last comment is right-on in my view.
During your pull to refresh function, you call tableView.reloadData(), however, reloadData() does not inherently do any repopulating the elements in the data source (in your case, dataArray). It simply reloads all the data that's currently in the table view's data source at the time it is called.
So my recommendation would be to construct your refresh function such that the following happens:
Initiate a request to your web service.
When the response comes back (ie, connectionDidFinishLoading is executed), parse the JSON results and assign that result to the dataArray instance. You seem to be doing this already in connectionDidFinishLoading, so it's just a matter of sending the request to your web service, I'd think.
Call tableView.reloadData() to display any new elements that have been added since the last time the tableView's data was displayed. Again, you're doing this already in connectionDidFinishLoading, so #1 is the primary thing that I think needs to happen.
Referring to https://stackoverflow.com/a/25957339
Not sure but maybe the connection is run on a different thread, if so you need to run the table update on the main UI thread
// using Swift's trailing closure syntax:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}

Resources