initial view of view control where we start the download Once the download started it look like this I were working in a mp3 application where I need to download the mp3 from the list shown when the user click the download button it shows the control for pause, stop and a progress view. my download is working but I could not show the progress for the download all over the application. For example: if I am downloading some "x.mp3" song if the user click x.mp3 in VC1 the progress should also shown in VC3. Then the big problem I am facing is after downloading the download control should hide. Due to tableview reload function I cannot handle the UIrefresh which should show the download control and also the download button accordingly.
I have attached both my ViewController class the cell class and also the design
I have attached the "CELLFORROWATINDEXPATH" function for your reference and I have enclosed all the download functionality in the cell class.
viewcontroller Cellforrowatindexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let table_cell = tableView.dequeueReusableCell(withIdentifier: "LecturesVcCellID", for: indexPath) as! LecturesVcCell
let trackdata = searchResults[indexPath.row]
constantsList.playTypeArray.boolArray.append(false)
table_cell.configure(track: trackdata, index: indexPath.row)
table_cell.downloadbtn.tag = indexPath.row
table_cell.pauseButton.tag = indexPath.row
table_cell.cancelButton.tag = indexPath.row
table_cell.progressLabel.tag = indexPath.row
let x : Int = trackdata.classNumber as! Int
let className = String(x)
if index == indexPath.row {
table_cell.img_play.image=#imageLiteral(resourceName: "playing_track")
table_cell.lbl_SubTitle.text = "Now playing"
table_cell.lbl_SubTitle.textColor = #colorLiteral(red: 0.968627451, green: 0.7490196078, blue: 0.4823529412, alpha: 1)
}else {
table_cell.img_play.image=#imageLiteral(resourceName: "play_track")
table_cell.lbl_SubTitle.text = String(format: "Class - %# | %#", className,(trackdata.timeoftrack!))
table_cell.lbl_SubTitle.textColor = UIColor.lightGray
}
str_image = "https://www.imaginetventures.name/swamiji/wp-content/uploads/2019/01/introved.png"
constantsList.playTypeArray.imageLecture = str_image
table_cell.img_Show.sd_setImage(with: URL(string: str_image), placeholderImage: UIImage(named: "placeholder.png"))
table_cell.navigationController = self.navigationController
if str_Type .isEqual(to: "Playlist") {
table_cell.downloadbtn.isHidden = true
// table_cell.playlistAdditionImageview.isHidden = false
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
table_cell.playlistAdditionImageview.isHidden = true
}else {
table_cell.playlistAdditionImageview.isHidden = false
}
}else {
table_cell.downloadbtn.setImage(UIImage(named: "DATA11"), for: .normal)
// table_cell.playlistAdditionImageview.isHidden = true
}
table_cell.selectionStyle = .none
return table_cell
}
viewcontrollercell classfile:
override func awakeFromNib() {
super.awakeFromNib()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
entity = NSEntityDescription.entity(forEntityName: "DownloadList", in: context)
}
#IBAction func pauseBtnAction (_ sender: UIButton) {
if(downloadTask != nil && isDownload) {
downloadTask!.cancel(byProducingResumeData: { (resumeData) -> Void in
self.downloadData = resumeData
})
isDownload = false
downloadTask = nil
pauseButton.setImage(UIImage(named: "start-button"), for: .normal)
}else
if(!isDownload && downloadData != nil) {
if let resumeData = self.downloadData {
downloadTask = urlSession?.downloadTask(withResumeData: resumeData)
} else {
let url:URL = URL(string: "\((constantsList.playTypeArray.arr_subCatagriesLecture.object(at: sender.tag) as AnyObject).value(forKey: "mp3") as! String)")!
downloadTask = downloadsSession?.downloadTask(with: url)
}
downloadTask!.resume()
isDownload = true
pauseButton.setImage(UIImage(named: "x"), for: .normal)
}
}
#IBAction func cancelBtnClicked (_ sender: UIButton) {
downloadTask?.cancel()
downloadData = nil
isDownload = false
downloadTask = nil
progress.progress = 0
progressLabel.text = "0%"
progress.setProgress(0.0, animated: true)
cancelButton.isHidden = true
pauseButton.isHidden = true
downloadbtn.isHidden = false
progressLabel.isHidden = true
progress.isHidden = true
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
#IBAction func downloadBtnAction(_ sender: UIButton) {
// sender.pulse()
self.reachability = Reachability.init()
if ((self.reachability?.connection) != .none) {
switch self.reachability?.connection {
case .wifi?:
print("Wifi")
print(sender.tag)
constantsList.playTypeArray.typeofnetwork = true
downloadTapped(sender: sender.tag)
case .cellular?:
print("mobile data")
print(sender.tag)
let alert = UIAlertController(title: "Mobile Data usage Alert!", message: "Downloads will be using mobile data!!", preferredStyle: UIAlertController.Style.alert);
let action1 = UIAlertAction(title: "Ok", style: .default) { (action:UIAlertAction) in
self.downloadTapped(sender: sender.tag)
}
let action2 = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
print("Cancelled")
}
alert.addAction(action1)
alert.addAction(action2)
self.navigationController?.present(alert, animated: true, completion: nil)
case .none:
print("Network Not reachable")
case .some(.none):
print("Some")
}
}else {
print("No Internet")
}
}
func downloadTapped(sender : Int) {
constantsList.playTypeArray.arrayDownloadSort.append(sender as NSNumber)
print(constantsList.playTypeArray.arrayDownloadSort)
constantsList.playTypeArray.boolArray[sender] = true
showDownloadControls = true
downloadbtn.isHidden = true
pauseButton.isHidden = false
cancelButton.isHidden = false
progressLabel.isHidden = true
progress.isHidden = false
progressLabel.text = "Downloading..."
var urlConfiguration:URLSessionConfiguration!
urlConfiguration = URLSessionConfiguration.default
let queue:OperationQueue = OperationQueue.main
urlSession = URLSession.init(configuration: urlConfiguration, delegate: self, delegateQueue: queue)
let url:NSURL = NSURL(string: "\((constantsList.playTypeArray.arr_subCatagriesLecture.object(at: sender) as AnyObject).value(forKey: "mp3") as! String)")!
downloadTask = urlSession.downloadTask(with: url as URL)
downloadTask.resume()
}
//DOWNLOADING THE FILE UPDATE THE PROGRESS
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 2
let progress1 = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite);
// 3
print(progress1)
// 4
// OperationQueue.main.addOperation
DispatchQueue.main.async (execute: {
self.isDownload = true
// 2
constantsList.playTypeArray.isDownloading = true
self.progress.setProgress(progress1, animated: false)
self.progressLabel.text = String(progress1 * 100) + "%"
constantsList.playTypeArray.setprogress = progress1
let i = 0
if progress1 == 1.0 {
constantsList.playTypeArray.isDownloading = false
print(i + 1)
}
})
}
//AFTER DOWNLOADING SAVE THE FILE TO APP DATA
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Finish Downloading")
let filemanager = FileManager()
let directoryURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0]
print(directoryURL)
let docDirectoryURL = NSURL(fileURLWithPath: "\(directoryURL)")
print(docDirectoryURL)
let destinationFileName = downloadTask.originalRequest?.url?.lastPathComponent
print(destinationFileName!)
let destinationURL = docDirectoryURL.appendingPathComponent("\(destinationFileName!)")
print(destinationURL!)
if let path = destinationURL?.path {
if filemanager.fileExists(atPath: path) {
do {
try filemanager.removeItem(at: destinationURL!)
}catch let error as NSError{
print(error.localizedDescription)
}
}
}
do {
try filemanager.copyItem(at: location, to: destinationURL!)
print(destinationURL!)
print(downloadbtn.tag)
newUser = NSManagedObject(entity: entity!, insertInto: context)
dic66 = constantsList.playTypeArray.arr_subCatagriesLecture.object(at: downloadbtn.tag) as? NSDictionary
newUser.setValue(dic66?.value(forKey: "classname") as! NSNumber, forKey: "classname")
newUser.setValue("\(String(describing: dic66?.value(forKey: "time") as! String))", forKey: "time")
newUser.setValue("\(String(describing: dic66?.value(forKey: "title") as! String))", forKey: "songtitle")
newUser.setValue("\(destinationURL!.path)", forKey: "mp3Url")
newUser.setValue("\(String(describing: constantsList.playTypeArray.imageLecture!))", forKey: "imageurl")
do {
try context.save()
print(context)
self.cancelButton.isHidden = true
self.pauseButton.isHidden = true
self.downloadbtn.isHidden = true
self.progressLabel.isHidden = true
self.progress.isHidden = true
print("Successful")
} catch {
print("failed")
}
}catch {
print("Error while copying file")
}
downloadData = nil;
}
func configure(track: Track,index: Int) {
var filefound: Bool = false
var shopwControls: Bool = true
if(constantsList.playTypeArray.boolArray[index]) {
filefound = true
shopwControls = false
self.progress.setProgress(constantsList.playTypeArray.setprogress, animated: true)
}
lbl_Title.text = track.songTitle
//CLASS AND VOLUME UPDATION
let x : Int = track.classNumber as! Int
let className = String(x)
lbl_SubTitle.text = String(format: "Class - %# | %#", className,(track.timeoftrack!))
//core Data checking for already downloaded file
request.returnsObjectsAsFaults = false
request.resultType = .dictionaryResultType
do {
let result = try context.fetch(request)
dic66 = constantsList.playTypeArray.arr_subCatagriesLecture.object(at: index) as? NSDictionary
for data in result as! [[String:Any]] {
print(data["classname"] as! Int)
print(dic66?.value(forKey: "classname") as! Int)
if data["classname"] as! Int == (dic66?.value(forKey: "classname") as! Int) {
filefound = true
}
}
} catch {
print("Failed")
}
// If the track is already downloaded, enable cell selection and hide the Download button
selectionStyle = filefound ? UITableViewCell.SelectionStyle.gray : UITableViewCell.SelectionStyle.none
downloadbtn.isHidden = filefound
pauseButton.isHidden = shopwControls
cancelButton.isHidden = shopwControls
progress.isHidden = shopwControls
}
}
Related
Every time when I use pull to refresh, the Active Indicator and the application itself freeze for a moment. I found which method is causing this problem - getTracks(), if I remove it then everything works well (except that the table is not updated), but I can't figure out what's the matter, I've tried many options, but no result. Can anyone help me?
My code:
class LibraryViewController: UIViewController {
private let fetchManager = FetchManager()
private var tracks: [TrackModelProtocol] = []
...
private let refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshTableView), for: .valueChanged)
return refreshControl
}()
...
override func viewDidLoad() {
super.viewDidLoad()
...
getTracks()
setupTableView()
tableView.refreshControl = refreshControl
}
...
private func getTracks() {
guard let tracks = fetchManager.setupTrackModels() else { return }
self.tracks = tracks
}
//pull to refresh
#objc private func refreshTableView(sender: UIRefreshControl) {
getTracks()
tableView.reloadData()
sender.endRefreshing()
}
...
}
...
class FetchManager {
var content: [URL] = []
private var currentDirectory : URL?
private var playerQueue: [AVPlayerItem] = []
private let audioPlayer = AudioPlayer()
private func fetchDataFromDeviceMemory() -> [URL]? {
var content: [URL] = []
if currentDirectory == nil {
currentDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}
do {
let allURLs = try FileManager.default.contentsOfDirectory(at: currentDirectory!, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants])
content = allURLs.filter{ $0.pathExtension == "mp3" || $0.pathExtension == "aac" || $0.pathExtension == "wav" }
} catch {
content = []
}
return content
}
func setupTrackModels() -> [TrackModelProtocol]? {
guard let content = fetchDataFromDeviceMemory() else { return [] }
self.content = content
playerQueue = audioPlayer.createPlayerQueue(from: content)
var tracks: [TrackModelProtocol] = []
for index in 0..<playerQueue.count {
var artwork: UIImage?
let playerItem = playerQueue[index]
let metadataList = playerItem.asset.metadata
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "artwork" where value is Data:
artwork = UIImage(data: value as! Data)
default:
continue
}
}
let track = TrackFromDeviceMemory(
fileName: content[index].lastPathComponent,
duration: playerQueue[index].asset.duration.seconds,
artwork: artwork,
url: content[index].absoluteURL)
tracks.append(track)
}
return tracks
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if isSearchActive {
let deletedTrackURL = filteredTracks[indexPath.row].url
print(deletedTrackURL)
do {
try FileManager.default.removeItem(at: deletedTrackURL)
} catch {
print(error.localizedDescription)
}
getTracks()
searchController.isActive = false
// tableView.reloadData()
tableView.delegate?.tableView!(tableView, didSelectRowAt: IndexPath(row: 0, section: indexPath.section))
} else {
let deletedTrackURL = tracks[indexPath.row].url
print(deletedTrackURL)
do {
try FileManager.default.removeItem(at: deletedTrackURL)
} catch {
print(error.localizedDescription)
}
getTracks()
tableView.deleteRows(at: [indexPath], with: .automatic)
// tableView.reloadData()
Instead of returning an array with setupTrackModels, have you tried using a completion handler and perform the fetch on a global queue? Performing the fetch on the main queue itself might be the reason for freeze.
func setupTrackModels(completion: (_ models: [TrackModelProtocol])->Void) {
DispatchQueue.global().async {
guard let content = fetchDataFromDeviceMemory() else {
DispatchQueue.main.async {
completion([])
return
}
}
self.content = content
playerQueue = audioPlayer.createPlayerQueue(from: content)
var tracks: [TrackModelProtocol] = []
for index in 0..<playerQueue.count {
var artwork: UIImage?
let playerItem = playerQueue[index]
let metadataList = playerItem.asset.metadata
for item in metadataList {
guard let key = item.commonKey?.rawValue, let value = item.value else{
continue
}
switch key {
case "artwork" where value is Data:
artwork = UIImage(data: value as! Data)
default:
continue
}
}
let track = TrackFromDeviceMemory(
fileName: content[index].lastPathComponent,
duration: playerQueue[index].asset.duration.seconds,
artwork: artwork,
url: content[index].absoluteURL)
tracks.append(track)
}
DispatchQueue.main.async {
completion(tracks)
}
}
}
At the call site:
fetchManager.setupTrackModels { tracks in
refreshControl.endRefreshing()
self.tracks = tracks
tableView.reloadData()
}
I am not able to load the documents in chat application in Swift IOS using Firestore database, though able to successfully retrieve the data from the Firestore database, I have added the deinit method as well please assist further to resolve the error, I have added the complete view controller , please help me
Error
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (47) must be equal to the number of rows contained in that section before the update (23), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Code
let kBannerAdUnitID = "ca-app-pub-3940256099942544/2934735716"
#objc(FCViewController)
class FCViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,
UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// Instance variables
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var sendButton: UIButton!
var ref : CollectionReference!
var ref2: DocumentReference!
var messages: [DocumentSnapshot]! = []
var msglength: NSNumber = 10
fileprivate var _refHandle: CollectionReference!
var storageRef: StorageReference!
var remoteConfig: RemoteConfig!
private let db = Firestore.firestore()
private var reference: CollectionReference?
private let storage = Storage.storage().reference()
// private var messages = [Constants.MessageFields]()
//snapshot private var messages: [Constants.MessageFields] = []
private var messageListener: ListenerRegistration?
// var db:Firestore!
#IBOutlet weak var banner: GADBannerView!
#IBOutlet weak var clientTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.clientTable.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
// clientTable.delegate = self
//clientTable.dataSource = self
//db = Firestore.firestore()
ref = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages")
ref2 = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").document()
configureDatabase()
configureStorage()
configureRemoteConfig()
fetchConfig()
loadAd()
}
deinit {
if let refhandle = _refHandle {
let listener = ref.addSnapshotListener { querySnapshot, error in
}
listener.remove()
}
}
func configureDatabase() {
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
/* let name = documents.map { $0["name"]!}
let text = documents.map { $0["text"]!}
let photourl = documents.map { $0["photoUrl"]!}
print(name)
print(text)
print(photourl)*/
self.messages.append(contentsOf: documents)
// self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
//self.clientTable.reloadData()
}
}
func configureStorage() {
storageRef = Storage.storage().reference()
}
func configureRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
let remoteConfigSettings = RemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if self.remoteConfig.configSettings.isDeveloperModeEnabled {
expirationDuration = 0
}
remoteConfig.fetch(withExpirationDuration: expirationDuration) { [weak self] (status, error) in
if status == .success {
print("Config fetched!")
guard let strongSelf = self else { return }
strongSelf.remoteConfig.activateFetched()
let friendlyMsgLength = strongSelf.remoteConfig["friendly_msg_length"]
if friendlyMsgLength.source != .static {
strongSelf.msglength = friendlyMsgLength.numberValue!
print("Friendly msg length config: \(strongSelf.msglength)")
}
} else {
print("Config not fetched")
if let error = error {
print("Error \(error)")
}
}
}
}
#IBAction func didPressFreshConfig(_ sender: AnyObject) {
fetchConfig()
}
#IBAction func didSendMessage(_ sender: UIButton) {
_ = textFieldShouldReturn(textField)
}
#IBAction func didPressCrash(_ sender: AnyObject) {
print("Crash button pressed!")
Crashlytics.sharedInstance().crash()
}
func inviteFinished(withInvitations invitationIds: [String], error: Error?) {
if let error = error {
print("Failed: \(error.localizedDescription)")
} else {
print("Invitations sent")
}
}
func loadAd() {
self.banner.adUnitID = kBannerAdUnitID
self.banner.rootViewController = self
self.banner.load(GADRequest())
}
// UITableViewDataSource protocol methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot: DocumentSnapshot! = self.messages[indexPath.row]
guard let message = messageSnapshot as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let text = textField.text else { return true }
textField.text = ""
view.endEditing(true)
let data = [Constants.MessageFields.text: text]
sendMessage(withData: data)
return true
}
func sendMessage(withData data: [String: String]) {
var mdata = data
mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
if let photoURL = Auth.auth().currentUser?.photoURL {
mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
}
// Push data to Firebase Database
self.ref.document().setData(mdata, merge: true) { (err) in
if let err = err {
print(err.localizedDescription)
}
print("Successfully set newest city data")
}
}
// MARK: - Image Picker
#IBAction func didTapAddPhoto(_ sender: AnyObject) {
let picker = UIImagePickerController()
picker.delegate = self
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
picker.sourceType = .camera
} else {
picker.sourceType = .photoLibrary
}
present(picker, animated: true, completion:nil)
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion:nil)
guard let uid = Auth.auth().currentUser?.uid else { return }
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceURL = info[.originalImage] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
guard let strongSelf = self else { return }
strongSelf.storageRef.child(filePath)
.putFile(from: imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
let nsError = error as NSError
print("Error uploading: \(nsError.localizedDescription)")
return
}
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
})
} else {
guard let image = info[.originalImage] as? UIImage else { return }
let imageData = image.jpegData(compressionQuality:0.8)
let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
guard let strongSelf = self else { return }
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
#IBAction func signOut(_ sender: UIButton) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
dismiss(animated: true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError.localizedDescription)")
}
}
func showAlert(withTitle title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title,
message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
}
Edit
perform this block of code on main thread
for doc in documents {
self.messages.append(doc)
self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
}
This should work..
I am currently loading images from my server. The images are plenty depending on the number of images it return. I have been able to display these images successfully and I can scroll down to view all images. Now my problem is I want a way which I can click on a specific image i will be able to zoom it. I have shared my code below:
//Declaration of variables
var albumID: String?
var imagePath: String?
var path: String?
var getClickImage = UIImageView()
var zoomscrollV = UIScrollView()
var imageArray = [String]()
//view did load
override func viewDidLoad() {
super.viewDidLoad()
setUpViewsAlbumPhotos()
zoomscrollV.delegate = self
if !CheckInternet.Connection(){
showAlert(title: "No Internet", message: "Please connect your device to an internet connection")
}else{
fetchPhotos(albumID: albumID)
}
}
//viewDidAppear
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
zoomscrollV.isHidden = true
zoomscrollV.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height - 50)
zoomscrollV.minimumZoomScale=1
zoomscrollV.maximumZoomScale=10
zoomscrollV.bounces=false
self.view.addSubview(zoomscrollV)
getClickImage=UIImageView()
getClickImage.frame = CGRect(x:0, y:0, width:zoomscrollV.frame.width, height:zoomscrollV.frame.height)
getClickImage.backgroundColor = .black
getClickImage.contentMode = .scaleAspectFit
zoomscrollV.addSubview(getClickImage)
}
//This code makes an async call to download images and details from the server
let async_call = URL(string: "\(String.api_albumPhotos)\(albumID ?? "")")
let request = NSMutableURLRequest(url: async_call!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil {
print("error is:: \(error!.localizedDescription)")
DispatchQueue.main.async {
self.showAlert(title: "Error", message: "Sorry try again later")
self.stopActivityLoader()
}
return
}
do {
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = myJSON {
var responseCode: String!
var message: NSArray!
responseCode = parseJSON["responseCode"] as! String?
if responseCode == "200" {
DispatchQueue.main.async {
self.stopActivityLoader()
message = parseJSON["message"] as? NSArray
self.path = parseJSON["path"] as? String
if let message = message {
let totalMessage = message.count
let viewHeight = self.view.frame.height
var scrollHeight = 0
var contentViewTopConstraint: CGFloat = 20
// self.preference.set(parseJSON, forKey: UserDefaultKeys.albums.rawValue)
for obj in message{
if let dict = obj as? NSDictionary {
self.imagePath = dict.value(forKey: "path") as? String
let imageID = dict.value(forKey: "id") as? String
let albumThumbnail = photos()
self.scrollView.addSubview(albumThumbnail)
albumThumbnail.translatesAutoresizingMaskIntoConstraints = false
albumThumbnail.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor).isActive = true
albumThumbnail.topAnchor.constraint(equalTo: self.scrollView.topAnchor, constant: contentViewTopConstraint).isActive = true
albumThumbnail.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor).isActive = true
albumThumbnail.heightAnchor.constraint(equalToConstant: 150).isActive = true
albumThumbnail.isUserInteractionEnabled = true
albumThumbnail.contentMode = .scaleAspectFit
let touchRec = UITapGestureRecognizer(target: self, action: #selector(self.myImageTapped(_:)))
albumThumbnail.addGestureRecognizer(touchRec)
if let path = self.path{
if let imagePath = self.imagePath{
let strippedPath = path.replacingOccurrences(of: "\\", with: "")
let strippedImagePath = imagePath.replacingOccurrences(of: "\\", with: "")
print("\(strippedPath)\(strippedImagePath)")
albumThumbnail.sd_setImage(with: URL(string: "\(strippedPath)\(strippedImagePath)"), placeholderImage: UIImage(named: "default_profile"), options: [.continueInBackground, .progressiveDownload])
if let wrapped = self.path {
self.imageArray.append("\(strippedPath)\(strippedImagePath)")
// print(self.imageArray.append(wrapped))
}
}
}
contentViewTopConstraint = contentViewTopConstraint + 170
}
}
scrollHeight = totalMessage * 170
if totalMessage <= 1 {
self.scrollView.contentSize.height = viewHeight + 20
}else{
self.scrollView.contentSize.height = CGFloat(scrollHeight)
}
}
}
}else{
//Show alert
DispatchQueue.main.async {
self.showAlert(title: "Error", message: "Sorry could not update album. Try again")
self.stopActivityLoader()
}
}
}
}catch{
print("you:: \(error.localizedDescription)")
//Show alert
DispatchQueue.main.async {
self.showAlert(title: "Error", message: "Sorry could not update album. Try again")
self.stopActivityLoader()
}
}
}
task.resume()
}
#objc func myImageTapped(_ sender: UITapGestureRecognizer) {
zoomscrollV.isHidden = false
let myImage = imageArray[(sender.view?.tag)!]
print("myImage \(myImage)")
// RESPECTIVE IMAGE
getClickImage.image = UIImage(named: myImage)
let closeButton: UIButton = UIButton(type: .custom)
closeButton.frame = CGRect(x:40.0, y:self.view.frame.height - 50, width:self.view.frame.width - 80, height:50.0)
closeButton.addTarget(self, action: #selector(self.closeZoom), for: .touchUpInside)
closeButton.setTitle("CLOSE ZOOM", for: .normal)
closeButton.setTitleColor(UIColor.white, for: .normal)
// CLOSE BUTTON
self.view.addSubview(closeButton)
}``
#objc func closeZoom(sender: AnyObject) {
zoomscrollV.isHidden = true
zoomscrollV.setZoomScale(1.0, animated: false)
sender.removeFromSuperview()
}
//SCROLLVIEW DELEGATE
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return getClickImage
}
========================== Edit Jan 24 ============================
CONVERT URL TO IMAGE
#objc func myImageTapped(_ sender: UITapGestureRecognizer) {
zoomscrollV.isHidden = false
let myImageURL = imageArray[(sender.view?.tag)!]
if let url = URL( string: myImageURL)
{
DispatchQueue.global().async {
if let data = try? Data( contentsOf:url){
DispatchQueue.main.async {
let myImage = UIImage( data:data)
print("myImage \(myImage)")
// RESPECTIVE IMAGE
getClickImage.image = UIImage(named: myImage)
let closeButton: UIButton = UIButton(type: .custom)
closeButton.frame = CGRect(x:40.0, y:self.view.frame.height - 50, width:self.view.frame.width - 80, height:50.0)
closeButton.addTarget(self, action: #selector(self.closeZoom), for: .touchUpInside)
closeButton.setTitle("CLOSE ZOOM", for: .normal)
closeButton.setTitleColor(UIColor.white, for: .normal)
// CLOSE BUTTON
self.view.addSubview(closeButton)
}
}
}
}
===============================================================
On image click, U have create scrollview and add respective image as subview, then zoom will work out.
var getClickImage = UIImageView()
var zoomscrollV = UIScrollView()
override func viewDidLoad() {
super.viewDidLoad()
zoomscrollV.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
zoomscrollV.isHidden = true
zoomscrollV.frame = CGRect(x:0, y:0, width:self.view.frame.width, height:self.view.frame.height - 50)
zoomscrollV.minimumZoomScale=1
zoomscrollV.maximumZoomScale=10
zoomscrollV.bounces=false
self.view.addSubview(zoomscrollV)
getClickImage=UIImageView()
getClickImage.frame = CGRect(x:0, y:0, width:zoomscrollV.frame.width, height:zoomscrollV.frame.height)
getClickImage.backgroundColor = .black
getClickImage.contentMode = .scaleAspectFit
zoomscrollV.addSubview(getClickImage)
}
// ON CLICKING IMAGE ACTION
#objc func myImageTapped(_ sender: UITapGestureRecognizer) {
zoomscrollV.isHidden = false
// RESPECTIVE IMAGE
getClickImage.image = BG_CARD_IMGS[(sender.view?.tag)!]
let closeButton: UIButton = UIButton(type: .custom)
closeButton.frame = CGRect(x:40.0, y:self.view.frame.height - 50, width:self.view.frame.width - 80, height:50.0)
closeButton.addTarget(self, action: #selector(self.closeZoom), for: .touchUpInside)
closeButton.setTitle("CLOSE ZOOM", for: .normal)
closeButton.setTitleColor(UIColor.red, for: .normal)
// CLOSE BUTTON
self.view.addSubview(closeButton)
}
#objc func closeZoom(sender: AnyObject) {
zoomscrollV.isHidden = true
zoomscrollV.setZoomScale(1.0, animated: false)
sender.removeFromSuperview()
}
//SCROLLVIEW DELEGATE
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return getClickImage
}
OUTPUT
I am using UITableview and it looks similar with the feed of Instagram. The issue I have
I have a like function in each tableviewCell
when tap like button, it needs to update the screen and the screen blinks
In tableview cellForRowAt function, I have a network call to check the like and its number.
Please let me know if there is a way to avoid this blink. Should I avoid network call in this function or is there any other way?
U can see some part of my code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! StoryReviewTableViewCell
let review = ReviewArray[indexPath.row]
// 프로필 이미지랑 닉네임 설정
if let user = review.creator {
let nickname = user.getProperty("nickname") as! String
cell.profileName.text = nickname
if let profileURL = user.getProperty("profileURL") {
if profileURL is NSNull {
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
} else {
let url = URL(string: profileURL as! String)
DispatchQueue.main.async {
cell.profileImage.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "imageLoadingHolder"), options: [.transition(.fade(0.2))], progressBlock: nil, completionHandler: nil)
}
}
}
} else {
// 삭제된 유저의 경우
cell.profileName.text = "탈퇴 유저"
cell.profileImage.image = #imageLiteral(resourceName: "user_profile")
}
// 장소 이름
if let store = ReviewArray[indexPath.row].store {
cell.storeName.text = "장소: \(String(describing: store.name!))"
} else {
cell.storeName.text = "가게 이름"
}
// 라이크버튼 설정 - 라이크 모양은 여기서 컨트롤, delegate에서 user 라이크 컨트롤
DispatchQueue.global(qos: .userInteractive).async {
let likeStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
let dataQuery = BackendlessDataQuery()
let objectID = review.objectId!
let userID = UserManager.currentUser()!.objectId!
// print("objectID & userID: \(objectID) & \(userID)")
// 여기서 by가 현재 유저의 objectId이어야 하고, to는 이 리뷰의 objectId이어야 한다
dataQuery.whereClause = "by = '\(userID)' AND to = '\(objectID)'"
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}
// 좋아요 개수 세기
let countQuery = BackendlessDataQuery()
// to가 story의 objectID와 일치하면 땡
countQuery.whereClause = "to = '\(objectID)'"
let queryOptions = QueryOptions()
queryOptions.pageSize = 1
countQuery.queryOptions = queryOptions
DispatchQueue.global(qos: .userInteractive).async {
let matchingLikes = likeStore?.find(countQuery)
let likeNumbers = matchingLikes?.totalObjects
DispatchQueue.main.async {
if likeNumbers == 0 {
cell.likeLabel.text = "라이크 없음 ㅠ"
} else {
cell.likeLabel.text = "\(String(describing: likeNumbers!))개의 좋아요"
}
}
}
}
// 리뷰 평점 배당
cell.ratingView.value = review.rating as! CGFloat
// 리뷰 바디
cell.reviewBody.text = review.text
// 코멘트 개수 받아오기
DispatchQueue.global(qos: .userInteractive).async {
// 댓글수 찾기
let tempStore = Backendless.sharedInstance().data.of(ReviewComment.ofClass())
let reviewId = review.objectId!
let dataQuery = BackendlessDataQuery()
// 이 리뷰에 달린 댓글 모두 몇 개인지 찾기
dataQuery.whereClause = "to = '\(reviewId)'"
DispatchQueue.main.async {
tempStore?.find(dataQuery, response: { (collection) in
let comments = collection?.data as! [ReviewComment]
cell.replyLabel.text = "댓글 \(comments.count)개"
}, error: { (Fault) in
print("서버에서 댓글 얻어오기 실패: \(String(describing: Fault?.description))")
})
}
}
cell.timeLabel.text = dateFormatter.string(from: review.created! as Date)
return cell
}
U can see the button action here
#IBAction func likeButtonClicked(_ sender: UIButton) {
likeButton.isUserInteractionEnabled = false
// delegate action
delegate?.actionTapped(tag: likeButton.tag)
// image change
if sender.image(for: .normal) == #imageLiteral(resourceName: "like_bw") {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
} else {
UIView.transition(with: sender, duration: 0.2, options: .transitionCrossDissolve, animations: {
sender.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}, completion: nil)
self.likeButton.isUserInteractionEnabled = true
}
}
This is part of my delegate function which reload only for the row
func changeLike(_ row: Int, _ alreadyLike: Bool, completionHandler: #escaping (_ success:Bool) -> Void) {
let selectedReview = ReviewArray[row]
let reviewId = selectedReview.objectId
// 그냥 유저 객체로 비교는 안되고 objectId로 체크를 해야 함
let objectID = Backendless.sharedInstance().userService.currentUser.objectId
let dataStore = Backendless.sharedInstance().data.of(ReviewLikes.ofClass())
// 좋아요 - alreadyLike가 true이면
if !alreadyLike {
// 객체 생성
let like = ReviewLikes()
like.by = objectID! as String
like.to = reviewId
dataStore?.save(like, response: { (response) in
DispatchQueue.main.async {
let indexPath = IndexPath(row: row, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
let cell = self.tblView.cellForRow(at: IndexPath(row: index, section: 0)) as! UITableViewCell
cell.btnLike.setImage(UIImage(named: (isReviewed! ? IMG_LIKE : IMG_UNLIKE)), for: .normal)
Just update the cell, instead of reloading whole data in the tableview.
Try to call data on button click instead of cellForRowAtIndexpath and use that button which clicked instead of reload data
#IBAction func likeButtonClicked(_ sender: UIButton) {
let buttonPosition:CGPoint = sender.convert(CGPointZero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
let cell = tableView.cellForRow(at: indexPath) as! StoryReviewTableViewCell
DispatchQueue.main.async {
likeStore?.find(dataQuery, response: { (collection) in
let likes = collection?.data as! [ReviewLikes]
// 하트를 안 눌렀을 때
if likes.count == 0 {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_bw"), for: .normal)
}
} else {
DispatchQueue.main.async {
cell.likeButton.setImage(#imageLiteral(resourceName: "like_red"), for: .normal)
}
}
}, error: { (Fault) in
print("라이크 불러오기에서 에러: \(String(describing: Fault?.description))")
})
}
How do automatically stop activity indicator after the refresh button is pressed and loading of content is done in UITableview JSON
I already have to the code to start the spinning and the button
Here is full code
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var json_data_url = "http://www.howtotechworld.com/json_table_view_images%20(1).json"
var image_base_url = ""
var isProgressShowing = true;
var TableData:Array< datastruct > = Array < datastruct >()
enum ErrorHandler:ErrorType
{
case ErrorFetchingResults
}
struct datastruct
{
var imageurl:String?
var description:String?
var image:UIImage? = nil
init(add: NSDictionary)
{
imageurl = add["url"] as? String
description = add["description"] as? String
}
}
#IBOutlet var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
get_data_from_url(json_data_url)
}
override func viewWillAppear(animated: Bool) {
let barButtonItem = UIBarButtonItem(title: "Refresh", style: .Plain, target: self, action: "refreshTapped");
self.navigationItem.rightBarButtonItem = barButtonItem;
}
func refreshTapped() {
addProgressIndicator(isProgressShowing);
get_data_from_url(json_data_url)
}
func addProgressIndicator(show : Bool) {
isProgressShowing = !show;
if(show) {
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle:UIActivityIndicatorViewStyle.Gray)
myActivityIndicator.startAnimating()
let barButtonItem = UIBarButtonItem(customView: myActivityIndicator)
self.navigationItem.rightBarButtonItem = barButtonItem
} else {
self.navigationItem.rightBarButtonItem = nil;
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let data = TableData[indexPath.row]
cell.textLabel?.text = data.description
if (data.image == nil)
{
cell.imageView?.image = UIImage(named:"image.jpg")
load_image(image_base_url + data.imageurl!, imageview: cell.imageView!, index: indexPath.row)
}
else
{
cell.imageView?.image = TableData[indexPath.row].image
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return TableData.count
}
func get_data_from_url(url:String)
{
let url:NSURL = NSURL(string: url)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let task = session.dataTaskWithRequest(request) {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
dispatch_async(dispatch_get_main_queue(), {
self.extract_json(data!)
return
})
}
task.resume()
}
func extract_json(jsonData:NSData)
{
let json: AnyObject?
do {
json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
} catch {
json = nil
return
}
if let list = json as? NSArray
{
for (var i = 0; i < list.count ; i++ )
{
if let data_block = list[i] as? NSDictionary
{
TableData.append(datastruct(add: data_block))
}
}
do
{
try read()
}
catch
{
}
do_table_refresh()
}
}
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
return
})
}
func load_image(urlString:String, imageview:UIImageView, index:NSInteger)
{
let url:NSURL = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
let task = session.downloadTaskWithURL(url) {
(
let location, let response, let error) in
guard let _:NSURL = location, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let imageData = NSData(contentsOfURL: location!)
dispatch_async(dispatch_get_main_queue(), {
self.TableData[index].image = UIImage(data: imageData!)
self.save(index,image: self.TableData[index].image!)
imageview.image = self.TableData[index].image
return
})
}
task.resume()
}
func read() throws
{
do
{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "Images")
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest)
for (var i=0; i < fetchedResults.count; i++)
{
let single_result = fetchedResults[i]
let index = single_result.valueForKey("index") as! NSInteger
let img: NSData? = single_result.valueForKey("image") as? NSData
TableData[index].image = UIImage(data: img!)
}
}
catch
{
print("error")
throw ErrorHandler.ErrorFetchingResults
}
}
func save(id:Int,image:UIImage)
{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entity = NSEntityDescription.entityForName("Images",
inManagedObjectContext: managedContext)
let options = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext:managedContext)
let newImageData = UIImageJPEGRepresentation(image,1)
options.setValue(id, forKey: "index")
options.setValue(newImageData, forKey: "image")
do {
try managedContext.save()
} catch
{
print("error")
}
}
}
You will need to keep a reference to myActivityIndicator and stop it in do_table_refresh() as shown below.
func do_table_refresh()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableview.reloadData()
myActivityIndicator.stopAnimating()
return
})
}
Edit: Just had a quick thought after looking at your code in the question.
If you would like to hide myActivitIndicator using addProgressIndicator(isProgressShowing) , you will have to make sure that isProgressShowing is set to FALSE, then in the code that I have given above, replace myActivityIndicator.stopAnimating() with addProgressIndicator(isProgressShowing).
You will also need to update the addProgressIndicator function and add myActivityIndicator.stopAnimating() to the else condition.
If I understand your question right then I think below is what you want.
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
//end of loading
//for example [activityIndicator stopAnimating];
}
}
and for swift it is like this
func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?{
if(indexPath.row = tableView.indexPathsForVisibleRows().lastObject().row{
//end of loading
//for example [activityIndicator stopAnimating];
}
return yourCell;
}
I don't know swift much, but i guess you will have got the idea how to do it.
Add this code after do_table_refresh(): addProgressIndicator(!isProgressShowing);