Loading the server response into the UICollectionView - ios

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

Related

Protocol-Delegate pattern not notifying View Controller

My Model saves data to Firestore. Once that data is saved, I'd like it to alert my ViewController so that a function can be called. However, nothing is being passed to my ViewController.
This is my Model:
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
func createUserAddedRecipe(
docId:String,
completion: #escaping (Recipe?) -> Void) {
let db = Firestore.firestore()
do {
try db.collection("userFavourites").document(currentUserId).collection("userRecipes").document(docId).setData(from: recipe) { (error) in
print("Data Saved Successfully") // THIS OUTPUTS TO THE CONSOLE
// Notify delegate that data was saved to Firestore
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
}
}
catch {
print("Error \(error)")
}
}
}
The print("Data Saved Successfully") outputs to the console, but the delegate method right below it doesn't get called.
And this is my ViewController:
class ViewController: UIViewController {
private var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
}
}
extension ViewController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
if dataSavedSuccessfully == true {
print("Result is true.")
}
else {
print("Result is false.")
}
print("Protocol-Delegate Pattern Works")
}
}
Is there something I'm missing from this pattern? I haven't been able to notice anything different in the articles I've reviewed.
So I test your code and simulate something like that
import UIKit
protocol ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully:Bool)
}
class Model {
var delegate:ProtocolModel?
// I use this timer for simulate that firebase store data every 3 seconds for example
var timer: Timer?
func createUserAddedRecipe(
docId:String) {
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
self.delegate?.wasDataSavedSuccessfully(dataSavedSuccessfully: true)
})
}
}
class NavigationController: UINavigationController {
var model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
// Call this method to register for network notification
model.createUserAddedRecipe(docId: "exampleId")
}
}
extension NavigationController: ProtocolModel {
func wasDataSavedSuccessfully(dataSavedSuccessfully: Bool) {
print(#function)
}
}
so you can see the result as image below, my delegate update controller that conform to that protocol.

ios Swift Protocol Data

I don't use storyboards.
I want to send protocol data using #objc button action.
However, the sent view controller does not run the protocol function.
May I know what the reason is?
In fact, there's a lot more code.
Others work, but only protocol functions are not executed.
The didUpdataChampion function is
Data imported into a different protocol.
I have confirmed that there is no problem with this.
protocol MyProtocolData {
func protocolData(dataSent: String)
func protocolCount(dataInt: Int)
}
class PickViewController: UIViewController,ChampionManagerDelegate{
static let identifier = "PickViewController"
var count = 0
var urlArray = [URL]()
var pickDelegate : MyProtocolData?
override func viewDidLoad() {
super.viewDidLoad()
champions.riot(url: "myURL")
}
#objc func topHand(){
pickDelegate?.protocolData(dataSent: "top")
print(count)
pickDelegate?.protocoCount(dataInt: count)
let cham = ChampViewController()
cham.modalPresentationStyle = .fullScreen
present(cham, animated: true, completion: nil)
}
//Data imported to another protocol
func didUpdataChampion(_ championManager: ChampionManager, champion: [ChampionRiot]) {
print(#function)
count = champion.count
for data in champion {
let id = data.id
guard let url = URL(string: "https://ddragon.leagueoflegends.com/cdn/11.16.1/img/champion/\(id).png") else { return }
urlArray.append(url)
count = urlArray.count
}
}
func didFailWithError(error: Error) {
print(error)
}
}
class ChampViewController: UIViewController,MyProtocolData {
var pickData = ""
var arrayCount = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func protocolData(dataSent: String) {
print(#function)
pickData = dataSent
print(pickData)
}
func protocoCount(dataInt: Int) {
print(#function)
arrayCount = dataInt
print(arrayCount)
}
}
i don't see full code, for instance how you call bind to topHand(), my advice is:
check that topHand - is called
check that pickDelegate isn't nil inside topHand
Create Object fo your PickViewController class and set its delegate to self.
var yourObj = PickViewController()
override func viewDidLoad() {
super.viewDidLoad()
yourObj.delegate = self
}

function call from viewmodel to view controller class

I am trying to call one function in ViewController class which was written in view model class.But unfortunately that function is not getting called.I am not getting that where I did mistake.If anyone helps me ,Would be great. Thankyou!
//ViewController Class
import UIKit
class getDetailsViewController: UIViewController {
var getviewmodel: getDetaisViewModel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func getDetailsAction(_ sender: Any) {
getviewmodel?.getdetailscall()
}
}
//ViewModel Class
import Foundation
import Alamofire
import SwiftyJSON
class getDetaisViewModel{
var url: URL!
func getdetailscall(){
let headers:HTTPHeaders = ["Authorization":"application/json"]
print(headers)
url = URL(string: Constants.apiForGetDetails)
print(url)
let vc = getDetailsViewController.init(nibName: "getDetailsViewController", bundle: nil)
getWebService.requestService(url: url, method: .post, headers: headers, showLoaderFlag: true, viewController: vc, completion: { response in
guard response["code"].int != 503 else {
print("No internet connection")
return
}
guard response != .null else {
return
}
})
}
}
Becuase you haven't initialized the getviewmodel object
Change
var getviewmodel: getDetaisViewModel?
to
var getviewmodel = getDetaisViewModel()
Or initialize getviewmodel object in viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
self.getviewmodel = getDetaisViewModel()
}
Change your class name to GetDetaisViewModel

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()
}
}
}

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