Click on cell is opening new ViewController with proper value from each cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getCredits(movieId: singleMovie.id)
var directorName = ""
creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing{
directorName = singleCredit.name
}
})
let detailVc = DetailViewController(title: singleMovie.title, imageUrl: singleMovie.posterPath,description: singleMovie.overview, groups: checkGroups(groups: singleMovie.genreIds), director: directorName)
navigationController?.pushViewController(detailVc, animated: true)
}
With function below, I am adding value to creditResponse which is CreditsResponse type.
func getCredits(movieId: Int) {
networkManager.getDirector(from: "https://api.themoviedb.org/3/movie/", movieId: movieId) { (creditResponse) in
guard let credit = creditResponse else {
return
}
self.creditResponse = credit
}
}
With function below I am fetching data from URL.
func getDirector(from url: String, movieId: Int, _ completed: #escaping (CreditsResponse?) -> Void){
guard let safeUrl = URL(string: url + "\(String(movieId))/credits" + apiid) else {return}
URLSession.shared.dataTask(with: safeUrl){ data, urlResponse, error in
guard let safeData = data, error == nil, urlResponse != nil else {
completed(nil)
return
}
if let decodedObject: CreditsResponse = SerializationManager().parse(jsonData: safeData){
completed(decodedObject)
}else{
completed(nil)
}
}.resume()
}
Problem is that when I select first cell creditReponse is nil, and after selecting second cell there are value from first cell that I select (it presents always previous value)
Also, when new ViewController is pushed, its content is shown in transition before root controller is moved like in picture below:
Image
Change 1
Add a completion parameter to following.
func getCredits(movieId: Int, completion: #escaping (() -> Void)) {
networkManager.getDirector(from: "https://api.themoviedb.org/3/movie/", movieId: movieId) { (creditResponse) in
guard let credit = creditResponse else {
return
}
self.creditResponse = credit
DispatchQueue.main.async {
completion()
}
}
}
Change 2
Wait for the API call to complete before moving to next screen.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let singleMovie = movieList[indexPath.row]
getCredits(movieId: singleMovie.id, completion: { [weak self] in
guard let self = self else { return }
var directorName = ""
self.creditResponse?.crew.forEach({ singleCredit in
if singleCredit.knownForDepartment == .directing {
directorName = singleCredit.name
}
})
let detailVc = DetailViewController(title: singleMovie.title, imageUrl: singleMovie.posterPath, description: singleMovie.overview, groups: checkGroups(groups: singleMovie.genreIds), director: directorName)
self.navigationController?.pushViewController(detailVc, animated: true)
})
}
The problem is creditResponse gets set, but it gets set too late you are making an async call and then leaving. Local var directorName then gets copied as "" and goes out of scope. When the network call comes back you need to update the detailVC. Consider passing just singleMovie and handling your async calls in detailVC. Keep in mind you can only update the view from the main thread when the async block returns.
Related
I've been researching and wrecking my brain attempting to get my JSON data to load into my tableview. I've tried placing the data in a Variable & I'm able to see the data in the console when I print it, however unable to push it to my table view.
Am I doing something wrong on the data page or am I not properly accessing the data within the loop?
I've tried putting the loop in the viewdidload but haven't been successful either.
// ViewController
import Foundation
import UIKit
import SDWebImage
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var A = EntertainmentApi()
var data = [EntertainmentPageData]()
var AA = EntertainmentApi().userFeedPosts
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
func showTable() {
}
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
DispatchQueue.main.async {
self.entPostTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
customCell1.profileDisplayName.text = AA[indexPath.row].postDisplayName
self.AA.forEach({ (EntertainmentPageData) in
customCell1.configue(with: EntertainmentPageData.postDisplayName, PostImage: EntertainmentPageData.imageURLString, PostDescription: EntertainmentPageData.postDescription)
})
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func item(for index: Int) -> EntertainmentPageData {
return data[index]
}
func numberOfItems() -> Int {
return data.count
}
}
//Data
import SwiftUI
import SDWebImage
public protocol EntertainmentPagePostItem {
/// The image for the card.
var imageURLString: String { get }
/// Rating from 0 to 5. If set to nil, rating view will not be displayed for the card.
var postDescription: String? { get }
/// Will be displayed in the title view below the card.
var postDisplayName: String { get }
}
public protocol EntertainmentPagePostDataSource: class {
/// CardSliderItem for the card at given index, counting from the top.
func item(for index: Int) -> EntertainmentPagePostItem
/// Total number of cards.
func numberOfItems() -> Int
}
struct HomePagePost: Codable {
var displayName: String
var cityStatus: String
var displayDescription: String
var displayImageURL: String
var lookingFor: String
var profileImager1: String?
var profileImager2: String?
var profileImager3: String?
var profileImager4: String?
}
struct EntertainmentPageData: Codable {
let postDisplayName: String
let imageURLString: String
let postDescription: String?
}
public class entPostFly: Codable {
let postDisplayName, imageURLString, postDescription: String
}
struct eItem: EntertainmentPagePostItem {
var postDisplayName: String
var imageURLString: String
var postDescription: String?
}
public class EntertainmentApi {
var userFeedPosts = [EntertainmentPageData]()
init() {
load()
}
func load() {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
self.userFeedPosts = entPostData
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getFeedPosts(completion: #escaping ([EntertainmentPageData]) -> () ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(entPostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
}
class Api {
func getHomePagePosts(completion: #escaping ([HomePagePost]) -> Void ) {
guard let apiURL = URL(string: "https://api.quickques.com/.....") else {
return
}
let task: () = URLSession.shared.dataTask(with: apiURL) { Data, apiResponse, error in
guard let Data = Data else { return }
do {
let homePostData = try JSONDecoder().decode([HomePagePost].self, from: Data)
completion(homePostData)
}
catch {
let error = error
print(error.localizedDescription)
}
}.resume()
}
func getImageData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
}
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
First you have an empty function showTable inside your viewDidLoad - This does nothing. Presumably it is something hanging around from your various attempts. Delete that.
As you have probably worked out, your network fetch operation is going to occur asynchronously and you need to reload the table view once the data has been fetched.
You have some code in viewDidLoad that kind of tries to do this, but it isn't related to the fetch operation. It is just dispatched asynchronously on the next run loop cycle; This is probably still before the data has been fetched.
However, even if the data has been fetched, it won't show up because you are assigning userFeedPosts from a second instance of your API object to AA at initialisation time. This array is empty and will remain empty since Swift arrays are value types, not reference types. When userFeedPosts is updated, AA will hold the original empty array.
To load the data you need to
Start a load operation when the view loads
Pass a completion handler to that load operation to be invoked when the load is complete
Reload your table view with the new data
class EntertainmentViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var data = [EntertainmentPageData]()
#IBOutlet weak var entPostTableView: UITableView!
override func viewDidLoad() {
entPostTableView.register(EntertainmentViewrTableViewCell.nib(), forCellReuseIdentifier: EntertainmentViewrTableViewCell.identifier)
entPostTableView.delegate = self
entPostTableView.dataSource = self
super.viewDidLoad()
EntertainmentAPI.getFeedPosts { result in
DispatchQueue.main.async { // Ensure UI updates on main queue
switch result {
case .error(let error):
print("There was an error: \(error)")
case .success(let data):
self.data = data
self.entPostTableView.reloadData
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell1 = tableView.dequeueReusableCell(withIdentifier: EntertainmentViewrTableViewCell.identifier, for: indexPath) as! EntertainmentViewrTableViewCell
let post = data[indexPath.row)
customCell1.profileDisplayName.text = data[indexPath.row].postDisplayName
customCell1.configure(with: post.postDisplayName, PostImage: post.imageURLString, PostDescription: post.postDescription)
return customCell1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
public class EntertainmentAPI {
static func getFeedPosts(completion: #escaping ((Result<[EntertainmentPageData],Error>) -> Void) ) {
guard let apiURL = URL(string: "https://api.quickques.com/....") else {
return
}
let task = URLSession.shared.dataTask(with: apiURL) { data, apiResponse, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
/// TODO - Invoke the completion handler with a .failure case
return
}
do {
let entPostData = try JSONDecoder().decode([EntertainmentPageData].self, from: Data)
completion(.success(entPostData))
}
catch {
completion(.failure(error))
}
}.resume()
}
}
I am currently trying to set data I get from parsing my json to a variable in my ViewController from RawDataCategory file, which decodes json.
Here is how I call a static method in CategoryViewController
class CategoryViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var categoryProducts: [Datum]? // this is the variable i want with data i get
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
Datum.fetchProducts { (categoryProductsFromJSON) -> () in // here i call a static function.. i will paste the code below
self.categoryProducts = categoryProductsFromJSON //here i set value
print(self.categoryProducts?[0].name) // if i try to print the value in the scope i can access it. and it shows with no problem
}
print(categoryProducts?[0].name) //here is the problem. when i get out of the scope it return nil.
}
}
I can access the variable in the scope but somehow when i try to call it outside it prints nil.
Here is the static function (method) i call to get decoded json:
static func fetchProducts(_ completionHandler: #escaping ([Datum]) -> ()) {
guard let url = URL(string: "http://localhost:8888/dayhandan/public/api/v1/category/1") else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
if error != nil {
print("error")
return
}
do {
print("no error so far")
guard let data = data else { return }
let rawData = try JSONDecoder().decode(CategoryRawData.self, from: data)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(rawData.data ?? [])
})
} catch let err {
print(err)
}
})
task.resume()
}
I don't get any errors while i decode json. In fact I can pass it to my ViewContoller. There are no errors overall. categoryProducts just becomes nil when i call it outside the scope.
Is this a good way to use decoded data. if it is can someone help me to solve the issue. or can someone point at a good way to use decoded data. Thanks for your time
You should only try to use the result of fetchProducts in the completion handler because the completion handler will be executed at some point in the future. Anything outside of the completion handler will be executed immediately.
Reload your tableView from within the completion handler:
Datum.fetchProducts { (categoryProductsFromJSON) -> () in
self.categoryProducts = categoryProductsFromJSON
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Then you should have some logic to populate the tableView:
extension CategoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categoryProducts?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// something like this
guard let category = categoryProducts?[indexPath.row] else { return UITableViewCell() }
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell.titleLabel?.text = category.title
return cell
}
}
I am picking files from iCloud and uploading into AWS S3. I am listing selected files with progress bar uploading status in tableview cell. Each cell separate file title and loading progress I am maintaining. Here, everything almost done but If I upload two files tableview first cell got freezed and second cell doing upload progress.
My upload function
private func upload(file url: URL, keyname : String, exten: String) {
let bucket = S3BucketName
let key = keyname
let contentType = "text/\(exten)"
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = progressBlock
let task = transferUtility.uploadFile(url,
bucket: bucket,
key: key,
contentType: contentType,
expression: expression,
completionHandler: completionHandler)
task.continueWith { (task) -> Any? in
if let error = task.error {
DispatchQueue.main.async {
//self.infoLabel.text = "Error: \(error.localizedDescription)"
}
return nil
}
if let uploadTask = task.result {
self.uploadTask = uploadTask
DispatchQueue.main.async {
//self.infoLabel.text = "Generating Upload File"
//self.uploadRequests.append(self.uploadTask)
self.tableView_util.reloadData()
}
}
return nil
}
}
Tableview cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellutil", for: indexPath) as! UtilityTableViewCell
let item = tableArray[indexPath.row]
cell.name_label_util.text = item.title
cell.control_button_util.tag = indexPath.row
cell.control_button_util.addTarget(self, action: #selector(playpause), for: .touchUpInside)
// MARK - Upload process
progressBlock = { [weak self] task, progress in
guard let strongSelf = self else { return }
DispatchQueue.main.async {
cell.loader_Line_util.progress = Float(progress.fractionCompleted)
//NSLog(#"fraction completed: %f", progress.fractionCompleted);
let percentageUploaded:Float = Float(progress.fractionCompleted) * 100
cell.statusLabel_util.text! = NSString(format:"Uploading: %.0f%%",percentageUploaded) as String
// Need to change
if cell.statusLabel_util.text == "Uploading: 100%" {
cell.statusLabel_util.text = "File Successfully Uploaded!"
cell.loader_Line_util.progress = 1;
self?.tableView_util.reloadData()
}
}
}
completionHandler = { [weak self] task, error in
guard let strongSelf = self else { return }
if let error = error {
DispatchQueue.main.async {
}
return
}
Ok, I had to look at AWS TransferUtility, never used it before. I think you'll need to refactor some stuff. I wrote this in notepad so likely so syntax errors and I left some functions empty, since you can add what goes there.
//create a class to track all your uploads in progress
Class UploadTaskTracker {
let shared = uploadTaskTracker()
private var tasks = [UploadTask]()
func addTask( _ task: UploadTask){
tasks.append(task)
}
fund updateTask( id: String, progress: Double){
// get task from array and update progress value
}
func completeTask(id: String) {
// remove task from tasks array
// send out a notification to trigger tableview to reload data
}
func activeTasks() -> Int {
return tasks.count
}
func taskAt(_ indexPath: IndexPath) -> UploadTask{
// check for out of bounds
// return task
}
}
Class UploadTask {
var id : String
var progress : Double
}
// and when you start an upload task, change your expression progress block
// (and remove it from the cell. This should all be happening in another
// class and not be associated with your tableview
let guid = UUID()
let uploadTask = UploadTask.init(id: guid, progress: 0.0)
UploadTaskTracker.shared.addTask(uploadTask_
expression.progressBlock = {(task, progress) in
UploadTaskTracker.shared.updateTask(id: guid, progress: progress)
}
// then in table view controller
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return UploadTaskTracker.shared.activeTasks()
}
//And then in
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// dequeue custom uitableviewcel
let uploadTask = UploadTaskTracker.shared. taskAt( indexPath)
cell.load(uploadTask)
}
// create a custom uitableviewcell class
class customCell: UITableViewCell {
#IBOutlet weak var someLabel: UILabel!
var observers = [NSKeyValueObservation]()
override func prepareForReuse() {
// clear previous UI to reduce chance of user seeing obsolete text or image
super.prepareForReuse()
stopObservers()
}
func stopObservers(){
for observer in observers{
observer.invalidate()
}
observers.removeAll()
}
func load(_ uploadTask: UploadTask ) {
let observer = uploadTask.observe(\.progress, options: [.initial, .new]) { [weak self] (uploadTask, change) in
DispatchQueue.main.async {
someLabel.text = “\(uploadTask.progress)”
}
}
observers.append(observer)
}
So I have a tableview controller which has headers and cells which are supposed to be present under those headers. I have already done the job of correctly creating the headers. I currently have prepared the datasource for the cells which takes its content from the a list of currentEvents that a user is currently attending. The issue isn't the pulling of the data it seems to be the completion block. It takes so long to come back that the currentEvents array never gets appended when it is supposed to. It also keeps appending events onto the original array even after it should reset upon entering the new section. I have tried many things and moved code around many places but nothing seems to be having any effect
The key function in all this is
self.fetchEventsFromServer()
specifically the part where the EventService.show takes place
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
print(followingId.key)
ref.child("users").child(followingId.key).observeSingleEvent(of: .value, with: { (userInfoSnapShot) in
guard let followingUserInfo = userInfoSnapShot.children.allObjects as? [DataSnapshot] else {
return
}
//validating if proper data was pulled for each follower
for currentUserInfo in followingUserInfo {
//will add this back when I want to event implementation
if currentUserInfo.key == "Attending" {
guard let eventKeys = currentUserInfo.children.allObjects as? [DataSnapshot] else{return}
for event in eventKeys {
print(event.key)
EventService.show(forEventKey: event.key, completion: { (event) in
guard let currentEvent = event else{
return
}
self.attendingEvents.append(currentEvent)
print(self.attendingEvents.count)
})
}
}
if currentUserInfo.key == "profilePic"{
self.currentUserPic = currentUserInfo.value as! String
//print(self.currentUserPic)
}
if currentUserInfo.key == "username"{
self.currentUserName = currentUserInfo.value as! String
//print(self.currentUserName)
var friend = Friend(friendName: self.currentUserName!, events: self.attendingEvents, imageUrl: self.currentUserPic!)
print(friend.events.count)
self.friends.append(friend)
//print(self.friends.count)
}
}
group.leave()
let result = group.wait(timeout: .now() + 0.01)
completion(nil)
}, withCancel: { (err) in
completion(err)
print("Couldn't grab info for the current list of users: \(err)")
})
}
completion(nil)
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
}
If anyone sees something I don't please dont hesitate to say something
How would I alter my dispatch group implementation to accomplish this goal
EventService Function
struct EventService {
static func show(forEventKey eventKey: String, completion: #escaping (Event?) -> Void) {
// print(eventKey)
let ref = Database.database().reference().child("events").child(eventKey)
// print(eventKey)
//pull everything
ref.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot,eventKey) in
// print(snapshot.value ?? "")
guard let event = Event(snapshot: snapshot) else {
return completion(nil)
}
completion(event)
})
}
}
I have found examples showing two different ways of solving this scenario:
Option A: responsible for networking to be the UITableViewController
It would be something like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard results.count > 0 else {
return UITableViewCell()
}
let myCell = tableView.dequeueReusableCell(withIdentifier: MyCell.cellIdentifier, for: indexPath) as! MyCell
imageProvider.getImage(imageUrl: imageUrl, completion: {[weak self] (image, error) in
DispatchQueue.main.async {
if error == nil && image != nil {
if let updateCell = tableView.cellForRow(at: indexPath) as? MyCell {
updateCell.updateImage(image!)
}
}
}
})
return myCell
}
where imageProvider.getImage has an URLSessionDownloadTask and calls let task = session.downloadTask() and task.resume().
Option B: responsible for networking to be the UITableViewCell
I have seen an example similar to this:
class MyCell: UITableViewCell {
// Several IBOutlets
static let cellIdentifier = "myCell"
let imageProvider = ImageProvider()
var model: MyModel? {
willSet {
activityIndicator.startAnimating()
configureImage(showImage: false, showActivity: true)
}
didSet {
guard let modelUrlStr = model?.imageUrlStr, let imageUrl = URL(string: modelUrlStr) else {
activityIndicator.stopAnimating()
configureImage(showImage: false, showActivity: false)
return
}
imageProvider.getImage(imageUrl: imageUrl, completion: {[weak self] (image, error) in
DispatchQueue.main.async {
guard error == nil else {
self?.activityIndicator.stopAnimating()
self?.configureImage(showImage: false, showActivity: false)
return
}
self?.imageView.image = image
self?.activityIndicator.stopAnimating()
self?.configureImage(showCoverImage: true, showActivity: false)
}
})
}
}
override func awakeFromNib() {
super.awakeFromNib()
configureImage(showCoverImage: false, showActivity: false)
}
override func prepareForReuse() {
super.prepareForReuse()
model = nil
}
private func configureImage(showImage: Bool, showActivity: Bool) {
// Update image view
}
}
Here, imageProvider.getImage also has an URLSessionDownloadTask and calls let task = session.downloadTask() and task.resume().
On the other hand, in this approach, I think that the cell shouldn't have a reference to the model object, but it is appropriate to do the networking task in the cell?
Additional considerations:
I need the N images I need to download, one per each cell that is shown, to be downloaded concurrently.
I also need to be able to cancel all the downloads that could be in progress if the user wants to stop them.
Which option/approach should I go for? (Could be maybe one different from these)