How to use a detail disclosure button in UICollectionViewCell? - ios

I am trying to use the detail disclosure button on the view controller pictured below (PhotoSearchViewController) to show a UIAlertController that shows the photo description from the ImageUrlItem.
When I click the button, it shows that it was pressed, but the alert containing the description does not appear.
My code for the ImageUrlItem:
import Foundation
import Firebase
struct ImageUrlItem {
//Should I add my description in here?
let key: String
let imageUrl: String
let watsonCollectionImageUrl: String
let score: Double
let description: String
let ref: FIRDatabaseReference?
init(imageUrl: String, key: String = "", watsonCollectionImageUrl: String = "", score: Double = 0, description: String) {
self.key = key
self.imageUrl = imageUrl
self.watsonCollectionImageUrl = watsonCollectionImageUrl
self.ref = nil
self.score = score
self.description = description
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
imageUrl = snapshotValue["ImageUrl"] as! String // must map to firebase names
watsonCollectionImageUrl = ""
score = 0
description = snapshotValue["Description"] as! String
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"imageUrl": imageUrl,
"Description": description
]
}
}
My code for the collection view cell (PhotoCell) is as follows:
class PhotoCell: UICollectionViewCell, UIAlertViewDelegate
{
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title:"Ok", style: UIAlertActionStyle.default)
{
(result : UIAlertAction) -> Void in
print("OK")
}
alertController.addAction(okAction)
self.parentViewController?.present(alertController, animated: true, completion: nil)
}
}
Code for the cellForRowAt in my PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
cell.backgroundColor = UIColor(red:0.74, green:0.76, blue:0.78, alpha: 1.0)
// Do any custom modifications you your cell, referencing the outlets you defined in the Custom cell file // if we have a label IBOutlet in PhotoCell we can customize it here
// on page load when we have no search results, show nothing
if similarImageUrls.count > 0 {
//print(indexPath.row)
//print(similarImageUrls.count)
if (indexPath.row < similarImageUrls.count){
let image = self.similarImageUrls[indexPath.row]
// get image asynchronously via URL
let url = URL(string: image.imageUrl)
DispatchQueue.global().async {
// make an asynchonorous call to load the image
DispatchQueue.main.async {
cell.imgPhoto.af_setImage(withURL: url!) // show image using alamofire
}
}
cell.lblScore.isHidden = false
cell.lblScore.text = "Score: \(NSString(format: "%.2f", (image.score * 100)) as String)%"
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
}
}
else
{
// show the placeholder image instead
cell.imgPhoto.image = UIImage(named: "person")
cell.lblScore.isHidden = true
cell.lblScore.text = "0.00%"
// when we get to the last image, and it is not the first time load
if (indexPath.row == 8 && !firstTimeSearch){
// show nothing found alert here
let ac = UIAlertController(title: "Photo Search Completed!", message:"No macthing photo found!", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
return cell
}
}

You can use custom delegate to show the alert
// here is the protocol for creating the delegation:
protocol BtnDesc : class {
func btnDecAlert(alertTitle: String, message: String)
}
class PhotoCell: UICollectionViewCell
{
// MARK:- Delegate
weak var btnDescDelegate : BtnDesc?
var photos: [ImageUrlItem] = []
var ref = FIRDatabase.database().reference(withPath: "Photos")
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var lblScore: UILabel!
#IBAction func btnDesc(_ sender: UIButton)
{
let alertTitle = "Description"
let alertMessage = photos.description
btnDescDelegate?.btnDecAlert(alertTitle: alertTitle, message: alertMessage)
}
}
Code for the cellForRowAt in PhotoSearchViewController:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = "PhotoCell"
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! PhotoCell
//Mark set the delegate.
cell.btnDescDelegate = self
.....
//conform delegate
extension PhotoSearchViewController : BtnDesc {
func btnDecAlert(alertTitle: String, message: String) {
let alert = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: {(_ action: UIAlertAction) -> Void in
// here is your action
print("OK")
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}

Related

UITableViewCell Delete Row Causing Index Out of Range

Here is my code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
#IBOutlet weak var spinner: UIActivityIndicatorView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
DispatchQueue.main.async {
self.scheduleView.reloadData()
}
}
}
var buttonNum : Int?
#IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
showDetail(day : day,dayNum : dayNum)
}
func showDetail(day : String, dayNum : Int) {
if let dayArray = scheduler?[day]
{
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler![self.day]
/////////////THE BELOW STATEMENT THROWS THE ERROR ///////////////////
if let firstLabel = self.scheduleArray?[indexPath.row][0], let secondLabel = self.scheduleArray?[indexPath.row][1] {
DispatchQueue.main.async {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
I am doing the changes in the array locally as data is fetched only once in ViewDidLoad() with getData function. It shows a schedule for each day of the week (7 buttons, one for each day, are linked to an Outlet Collection), with 2 buttons embedded in the custom cell nib - an edit button and a delete button. I have implemented the logic for deleting, button tags are equal to IndexPath.row which works perfectly and I am able to delete the values I want but when I can't seem to get the table reload working. Even after deleting the row data, the table doesn't update itself. I am calling reloadData after successful deletion. What am I doing wrong?
there are 2 issues
when you update something on UI after a request call, you have to push the UI update process back to the main thread (tableview.reloadData() should get triggered on the main thread)
self.scheduleArray?[indexPath.row][0] and self.scheduleArray?[indexPath.row][1] is the issue index out of range. Because you assume it always contains 2 items without safe check.
I've refactored the code a bit as below
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "scheduleCell") else {
return Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
}
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
scheduleArray = scheduler?[self.day]
guard let item = scheduleArray?[indexPath.row], item.count == 2 else {
return cell
}
if let firstLabel = item.first, let secondLabel = item.last {
cell.timeLabel1.text = firstLabel
cell.timeLabel2.text = secondLabel
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
if(statusCode == 200){
// This has to be executed on the main thread to get tableView updated
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.scheduler![self.day]?.remove(at: tag)
self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
Solved it! Updated Code :
class SchedulerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,
scheduleCellDelegate
{
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
var deviceID : String = ""
let retrievedString = KeychainWrapper.standard.string(forKey: "token")
var day = ""
var dayNum = 0
#IBOutlet weak var spinner: UIActivityIndicatorView!
#IBOutlet var buttons: [UIButton]!
#IBOutlet weak var scheduleView: UITableView!
var header : HTTPHeaders? = nil
var ScheduleURL : Dictionary<String, String>?
override func viewDidLoad() {
super.viewDidLoad()
scheduleView.delegate = self
scheduleView.dataSource = self
spinner.isHidden = true
//scheduleView.allowsSelection = false
scheduleView.register(UINib(nibName: "schedulerCell", bundle: nil), forCellReuseIdentifier: "schedulerCell")
self.getData()
}
func getData(){
self.header =
[
"Content-Type" : "application/json",
"Authorization": retrievedString!
]
//scheduleView.register(scheduleCell.self, forCellReuseIdentifier: "scheduleCell")
print(ScheduleURL!["GET"])
AFFunctions.getAFRequest(ofType: ScheduleResponse.self, url: ScheduleURL!["GET"]!) { responseData, statusCode in
print(responseData?.data?.scheduler, statusCode)
self.scheduler = responseData?.data?.scheduler
self.scheduleView.reloadData()
}
}
var buttonNum : Int?
#IBAction func daySelected(_ sender: UIButton) {
self.buttons.forEach { $0.tintColor = ($0 == sender) ? UIColor.orange : UIColor.systemTeal }
self.dayNum = sender.tag
print(sender.tag)
switch dayNum {
case 0 : self.day = "Sunday"
case 1 : self.day = "Monday"
case 2 : self.day = "Tuesday"
case 3 : self.day = "Wednesday"
case 4 : self.day = "Thursday"
case 5 : self.day = "Friday"
case 6 : self.day = "Saturday"
default : self.day = "Sunday"
}
print(day, dayNum)
showDetail(day : day)
}
func showDetail(day : String) {
print(day)
print(scheduler?[day])
if let dayArray = scheduler?[day]
{ print(dayArray)
scheduleArray = dayArray
self.scheduleView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("111 CHECK")
return scheduleArray?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("scheduleCell", owner: self, options: nil)?.first as! scheduleCell
cell.cellDelegate = self
cell.editBtn.tag = indexPath.row
cell.deleteSchedule.tag = indexPath.row
if let item = scheduleArray?[indexPath.row],
item.count > 1 {
DispatchQueue.main.async {
cell.timeLabel1.text = item[0]
cell.timeLabel2.text = item[1]
}
}
return cell
}
func didPressButton(_ tag: Int, btnType: String) {
let deleteURL = K.delURL
print("134", self.day)
print("135", self.scheduleArray?[tag])
print("136", scheduler?[self.day])
print("TAG : ", tag)
print("BTN TYPE: ", btnType)
if(btnType == "delete") {
AFFunctions.deleteAFRequest(ofType: scheduleResponse.self, url: "\(deleteURL)?day=\(self.day)&place=\(tag)") { [self]
responseData, statusCode in
print("\(deleteURL)?day=\(self.day)&place=\(tag)")
print(responseData, statusCode)
if(statusCode == 200){
self.scheduler![self.day]!.remove(at: tag)
DispatchQueue.main.async {
let deleteAlert = UIAlertController(title: "Deleted", message: "Device Schedule Successfully Deleted", preferredStyle: UIAlertController.Style.alert)
deleteAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
self.showDetail(day: self.day)
// self.scheduleView.reloadData()
}))
self.present(deleteAlert, animated: true, completion: nil)
}
}
}
}
}
}
The issue here is you are holding multiple sources of truth and fail at synchronysing them. You have:
var scheduleArray : Array<Array<String>>?
var scheduler : [String : Array<Array<String>>]?
Where both seem to hold the same information in different form. I can´t see why you are doing this from the example code you posted.
You get an error at:
self.scheduleArray?[indexPath.row][0]
because when you delete your item you are removing it from scheduler and reload your tableview. The tableview on the other hand get´s the information how many rows it should render from scheduleArray:
return scheduleArray?.count ?? 0
and these differ at that time because you didn´t assign scheduler to scheduleArray.
So 2 possible solutions here:
assign scheduleArray before you reload your tableview
self.scheduler![self.day]?.remove(at: tag)
scheduleArray = scheduler![self.day]
and remove the assignment in the cellForItemAt function
stop using scheduleArray and scheduler. Use only a single collection to hold the information.

How do I display, update and delete my cells?

I am taking a class to create a client list iOS app and connect it to Realm, I am not a great coder and my online school teaches poorly, so Youtube is my teacher most of the time. I am working off a Realm app video but am unsure how to do things past where they end. The app so far is a tableview, when I select the plus button in the top right corner, it opens a field to enter the client's name, address, city, state and zip code. when I click save it then stores the info to Realm and displays a Subtitle cell with the client's name and city. I would like to be able to delete the cell by swiping and also be able to view the full contents of the cell and update the cell. If anyone can offer any assistance I would be very grateful. Please see my ViewController below:
import UIKit
import RealmSwift
class ContactItem: Object {
#objc dynamic var name = ""
#objc dynamic var addressOne = ""
#objc dynamic var addressTwo = ""
#objc dynamic var city = ""
#objc dynamic var state = ""
#objc dynamic var zipCode = ""
#objc dynamic var _id: ObjectId = ObjectId.generate()
convenience init(name: String, addressOne: String, addressTwo: String, city: String, state: String, zipCode: String, _id: String) {
self.init()
self.name = name
self.addressOne = addressOne
self.addressTwo = addressTwo
self.city = city
self.state = state
self.zipCode = zipCode
}
override static func primaryKey() -> String? {
return "_id"
}
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let mike = ContactItem(name: "Mike", addressOne: "234 Maple Lane", addressTwo: "", city: "Ankeny", state: "Ohio", zipCode: "45665", _id: "0")
var people = try! Realm().objects(ContactItem.self).sorted(byKeyPath: "name", ascending: true)
var realm = try! Realm()
var db: OpaquePointer?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
if realm.isEmpty {
try! realm.write {
realm.add(mike)
}
}
let path = realm.configuration.fileURL?.path
print("Path: \(String(describing: path))")
}
#IBAction func addButtonTapped(_ sender: Any) {
let alertController = UIAlertController(title: "Add Client", message: "", preferredStyle: .alert)
//when the user clicks the add button, present them with a dialog to enter the task name.
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: {
alert -> Void in
let nameField = alertController.textFields![0] as UITextField
let addOneField = alertController.textFields![1] as UITextField
let addTwoField = alertController.textFields![2] as UITextField
let cityField = alertController.textFields![3] as UITextField
let stateField = alertController.textFields![4] as UITextField
let zipField = alertController.textFields![5] as UITextField
//Create a new Task with the text that the user entered.
let newPerson = ContactItem(name: nameField.text!, addressOne: addOneField.text!, addressTwo: addTwoField.text!, city: cityField.text!, state: stateField.text!, zipCode: zipField.text!, _id: "")
//Any writes to the Realm must occur in a write block
try! self.realm.write {
//Add the Task to the Realm. That's it!
self.realm.add(newPerson)
self.tableView.reloadData()
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {
(nameField: UITextField!) -> Void in nameField.placeholder = "Enter Client Name"
})
alertController.addTextField(configurationHandler: {
(addOneField: UITextField!) -> Void in addOneField.placeholder = "Enter First Address Line"
})
alertController.addTextField(configurationHandler: {
(addTwoField: UITextField!) -> Void in addTwoField.placeholder = "Enter Second Address Line"
})
alertController.addTextField(configurationHandler: {
(cityField: UITextField!) -> Void in cityField.placeholder = "Enter City"
})
alertController.addTextField(configurationHandler: {
(stateField: UITextField!) -> Void in stateField.placeholder = "Enter State"
})
alertController.addTextField(configurationHandler: {
(zipField: UITextField!) -> Void in zipField.placeholder = "Enter Zip Code"
})
//Show dialog.
self.present(alertController, animated: true)
}
}
extension ViewController: UITableViewDelegate {
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].city
return cell
}
}
Any help would be greatly appreciated.
PS. There may be some garbage code in there, I was trying a lot of different things before so I might have accidentally left some confusing code. I apologize for the mess.

How to maintain sync between time based OTPs of apps like Google authenticator, Authy in iOS

I am working on an application that generates tops for every 30 seconds using a timer. I am getting secret keys from scanned QR codes and appending them to one model array and for each secret key I am generating tOTP and appending them into another Model array. With that model array, I am populating tableview.
The tableviewcell contains a label for displaying otp and custom circular progress view for tracking the progress. The main issue is I cannot maintain sync with other TOTP apps like Google Authenticator. when I run timer for every second I can generate otps from TOTP generator library every second and can update the label with reloading tableview. But this functionality affects progress view, deleting an editing tableview cells as I am running timer every second to generate otp and reloading tableview.
Hope someone helps...
Here is my code...
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var tOTPModel = [TOTP]()
var secretKeys = [String]()
var generator: OTPGenerator?
var timer: Timer?
var currentTimeInterval = TimeInterval()
#IBOutlet weak var tOtpTableView: UITableView!
#IBOutlet weak var btnInfo: UIBarButtonItem!
#IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if self.tOTPS.isEmpty == false {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
#IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
#objc func generateTOTP() {
self.tOTPModel = []
for tOtpObject in self.tOTPS {
self.generator = Generator.generatorWithSecretKey(key: tOtpObject.secretKey)
let tOtp = (self.generator as! TOTPGenerator).generateOTP()
self.tOTPModel.append(TOTP(secretKey: tOtp!, issuer: tOtpObject.issuer, scheme: tOtpObject.scheme, createdDate: tOtpObject.createdDate))
}
self.tOtpTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPModel[indexPath.section]
cell.lblTOTP.text = tOTP.secretKey.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}
This is model object...
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
}
}
This is generator class which generates TOTP...
class Generator {
static func generatorWithSecretKey(key: String) -> OTPGenerator {
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
let secretKey = MF_Base32Codec.data(fromBase32String: key)
return TOTPGenerator(secret: secretKey, algorithm: OTPGenerator.defaultAlgorithm(), digits: 6, period: 30)
}
}
I can't see why you need the separate tOTPModel array. I would delete that and just use the tOTPS array, and I would put the generator in the TOTP object, where it belongs.
Now you can just reload the visible rows each time the timer ticks.
class TOTP: NSObject, NSCoding {
var secretKey: String
var issuer: String
var scheme: String
var createdDate: String
private var generator: OTPGenerator
var otp: String = {
return generator.generateOTP()
}
init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
self.secretKey = secretKey
self.issuer = issuer
self.scheme = scheme
self.createdDate = createdDate
self.generator = Generator.generatorWithSecretKey(key: secretKey)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(secretKey, forKey: "secretKey")
aCoder.encode(issuer, forKey: "issuer")
aCoder.encode(scheme, forKey: "scheme")
aCoder.encode(createdDate, forKey: "timeInterval")
}
required init?(coder aDecoder: NSCoder) {
secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
issuer = aDecoder.decodeObject(forKey: "issuer") as! String
scheme = aDecoder.decodeObject(forKey: "scheme") as! String
createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
generator = Generator.generatorWithSecretKey(key: secretKey)
}
}
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {
var tOTPS = [TOTP]()
var secretKeys = [String]()
var timer: Timer?
var currentTimeInterval = TimeInterval()
#IBOutlet weak var tOtpTableView: UITableView!
#IBOutlet weak var btnInfo: UIBarButtonItem!
#IBOutlet weak var btnAddQRCode: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.tOtpTableView.tableFooterView = UIView()
self.emptyDataString = "Click + to add new account"
let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
longPressGesture.minimumPressDuration = 1.0
longPressGesture.delegate = self
self.tOtpTableView.addGestureRecognizer(longPressGesture)
setUpViews()
getTotps()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
if !self.tOTPS.isEmpty {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
self.timer?.fire()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getTotps() {
if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
}
}
#IBAction func handleAddQRCode(_ sender: UIButton) {
let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
controllerToPresent.delegate = self
controllerToPresent.tOTPS = self.tOTPS
self.timer?.invalidate()
DispatchQueue.main.async {
self.navigationController?.pushViewController(controllerToPresent, animated: true)
}
}
#objc func generateTOTP() {
tableView.reloadRows(at:tableView.indexPathsForVisibleRows, with:.none)
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.tOTPModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
let tOTP = self.tOTPs[indexPath.section]
cell.lblTOTP.text = tOTP.otp.separate(every: 3, with: " ")
cell.lblIssuer.text = tOTP.issuer
cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"
cell.lblCreatedDate.isHidden = true
cell.issuerConstraint.isActive = true
// let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!
let fromValue = 1
let toValue = 0
cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
let textField = alertController.textFields![0] as UITextField
self.tOTPModel[indexPath.section].issuer = textField.text!
self.tOTPS[indexPath.section].issuer = textField.text!
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.reloadData()
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
let tOTP = self.tOTPModel[indexPath.section]
textField.placeholder = "Enter Issuer"
textField.text = tOTP.issuer
})
self.present(alertController, animated: true, completion: nil)
}
editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)
let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in
let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.tOTPS.remove(at: indexPath.section)
self.tOTPModel.remove(at: indexPath.section)
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
tableView.deleteSections([indexPath.section], with: .automatic)
tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
self.dismiss(animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
deleteAction.backgroundColor = .red
return [editAction,deleteAction]
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Delegate Method that adds tOtp from QRCode scanner controller
func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
}
}

Saving/Fetching CoreData Relationships Swift 3

I am making a budgeting app for the purposes of learning and I have a few questions about storing and fetching entities in CoreData.
I have two entities "Budget" and "Expense".
Every Budget has its own Expenses. As an example I can have an 'Entertainment' budget and it can have expenses such as 'Bowling' and 'Movies' etc.
I can create a Budget and save it. And then add expenses to it.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let expense = Expense(context: context)
.
. // Filling out the expense here
.
budget?.addToExpense(expense)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
I then retrieve the collection of Expenses and display the store name in a TableView
// Inside cellForRowAt
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
let myArray = Array((budget?.expense)!)
cell.textLabel?.text = (myArray[indexPath.row] as! Expense).store
return cell
So far so good. My issue is that when I store an expense it is stored in a Set. Which means the order is random when I retrieve that set and typecast it into an Array.
What I want is to store the Expenses and retrieve them in such a way that I can display the expenses in a FIFO order in the TableView. In other words the first expense I add in the budget should be the first element in the table view and so on and so forth.
There could be several ways to achieve that. The most straightforward would be to use Ordered relation for expense.
To do that,
Open expense relationship properties in DataModel editor.
Check Ordered option
Then budget.expense will be not Set, but OrderedSet, and you won't need to convert it to Array, but access it directly by index.
VIEWCONTROLLER 1:
=====================>
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var txt_user: UITextField!
#IBOutlet weak var txt_password: UITextField!
var result = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
txt_password.isSecureTextEntry = true
}
#IBAction func login_action(_ sender: Any)
{
if(txt_user.text == "" || txt_password.text == "")
{
let alert = UIAlertController(title: "info", message: "fields are empty", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
self.present(alert, animated: true, completion: nil)
}
else
{
self.CheckForUserNameAndPasswordMatch(empName: txt_user.text! , empPwd: txt_password.text!)
}
}
func CheckForUserNameAndPasswordMatch(empName:String,empPwd:String)
{
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let fetchdata = NSFetchRequest<NSManagedObject>(entityName: "Employee")
let predicate = NSPredicate(format: "empName = %#",empName)
fetchdata.predicate = predicate
do
{
self.result = try context.fetch(fetchdata as! NSFetchRequest<NSFetchRequestResult>)as NSArray
if result.count>0
{
let objcetEntity = result.firstObject as! Employee
if objcetEntity.empName == empName && objcetEntity.empPwd == empPwd
{
print("Login Successfully")
// Entered Username & password matched
}
else
{
print("Wrong password/username")
//Wrong password/username
}
}
}
catch let error as NSError
{
print("error",error.localizedDescription)
}
}
#IBAction func unwindToVC1(sender:UIStoryboardSegue)
{
if sender.source is ViewController2
{
let secvc = sender.source as! ViewController2
txt_password.text = secvc.str1! as String
}
}
VIEWCONTROLLER 2:
=====================>
class ViewController2: UIViewController ,UITextFieldDelegate{
#IBOutlet weak var txt_name: UITextField!
#IBOutlet weak var txt_mail: UITextField!
#IBOutlet weak var txt_pwd: UITextField!
#IBOutlet weak var txt_cpwd: UITextField!
#IBOutlet weak var txt_phone: UITextField!
#IBOutlet weak var err: UILabel!
var str1:NSString!
var str2:NSString!
//var update:NSManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
/*if(update != nil)
{
txt_name.text = update.value(forKey: "empName") as? String
txt_mail.text = update.value(forKey: "empMail") as? String
txt_pwd.text = update.value(forKey: "empPwd") as? String
txt_cpwd.text = update.value(forKey: "empCpwd") as? String
txt_phone.text = update.value(forKey: "empPhone") as? String
}*/
txt_pwd.isSecureTextEntry = true
txt_cpwd.isSecureTextEntry = true
}
override func viewWillAppear(_ animated: Bool)
{
txt_name.becomeFirstResponder()
}
#IBAction func regis_clicked(_ sender: Any)
{
if(txt_name.text == "" || txt_mail.text == "" || txt_pwd.text == "" || txt_cpwd.text == "" || txt_phone.text == "" )
{
let alert = UIAlertController(title: "information", message: "fields are empty", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler:
{
(actionsheet) in
if(self.txt_name.text == "")
{
self.txt_name.becomeFirstResponder()
}
if(self.txt_mail.text == "")
{
self.txt_mail.becomeFirstResponder()
}
if(self.txt_pwd.text == "")
{
self.txt_pwd.becomeFirstResponder()
}
if(self.txt_cpwd.text == "")
{
self.txt_cpwd.becomeFirstResponder()
}
if(self.txt_phone.text == "")
{
self.txt_phone.becomeFirstResponder()
}
})
let cancel = UIAlertAction(title: "cancel", style: .default, handler: nil)
alert.addAction(ok)
alert.addAction(cancel)
self.present(alert, animated: true, completion: nil)
}
else if(txt_pwd.text != txt_cpwd.text)
{
let alert1 = UIAlertController(title: "information", message: "password mismatched", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler:nil)
let cancel = UIAlertAction(title: "cancel", style: .default, handler: nil)
alert1.addAction(ok)
alert1.addAction(cancel)
self.present(alert1, animated: true, completion: nil)
}
else
{
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let newuser = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: context)
newuser.setValue(txt_name.text, forKey: "empName")
newuser.setValue(txt_mail.text, forKey: "empMail")
newuser.setValue(txt_pwd.text, forKey: "empPwd")
newuser.setValue(txt_cpwd.text, forKey: "empCpwd")
newuser.setValue(txt_phone.text, forKey: "empPhone")
do
{
try context.save()
}
catch let error as NSError
{
print("error",error.localizedDescription)
}
}
// self.navigationController?.popViewController(animated: true)
performSegue(withIdentifier: "unwind", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if let newlabel = txt_pwd.text
{
str1 = newlabel as NSString
}
}

Download image to tableview from Firebase

class ArtistModel {
var id: String?
var name: String?
var genre: String?
var img: String?
init(id: String?, name: String?, genre: String?, img: String?){
self.id = id
self.name = name
self.genre = genre
self.img = img
}
}
and this my tableviewCell
class addArtistTableViewCell: UITableViewCell {
#IBOutlet weak var lblName: UILabel!
#IBOutlet weak var lblGenre: UILabel!
#IBOutlet weak var img: UIImageView!
and this my viewController
import UIKit
import Firebase
import FirebaseDatabase
class addArtistViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var refArtists: FIRDatabaseReference!
#IBOutlet weak var textFieldName: UITextField!
#IBOutlet weak var textFieldGenre: UITextField!
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var labelMessage: UILabel!
#IBOutlet weak var tableViewArtists: UITableView!
//list to store all the artist
var artistList = [ArtistModel]()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//getting the selected artist
let artist = artistList[indexPath.row]
//building an alert
let alertController = UIAlertController(title: artist.name, message: "Give new values to update ", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
//getting artist id
let id = artist.id
//getting new values
let name = alertController.textFields?[0].text
let genre = alertController.textFields?[1].text
//calling the update method to update artist
self.updateArtist(id: id!, name: name!, genre: genre!)
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding two textfields to alert
alertController.addTextField { (textField) in
textField.text = artist.name
}
alertController.addTextField { (textField) in
textField.text = artist.genre
}
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return artistList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
//creating a cell using the custom class
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! addArtistTableViewCell
//the artist object
let artist: ArtistModel
//getting the artist of selected position
artist = artistList[indexPath.row]
//adding values to labels
cell.lblName.text = artist.name
cell.lblGenre.text = artist.genre
//returning cell
return cell
}
#IBAction func buttonAddArtist(_ sender: UIButton) {
addArtist()
}
override func viewDidLoad() {
super.viewDidLoad()
//getting a reference to the node artists
refArtists = FIRDatabase.database().reference().child("artists")
//observing the data changes
refArtists.observe(FIRDataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.artistList.removeAll()
//iterating through all the values
for artists in snapshot.children.allObjects as! [FIRDataSnapshot] {
//getting values
let artistObject = artists.value as? [String: AnyObject]
let artistName = artistObject?["artistName"]
let artistId = artistObject?["id"]
let artistGenre = artistObject?["artistGenre"]
let artistImg = artistObject?["artistImg"]
//creating artist object with model and fetched values
let artist = ArtistModel(id: artistId as! String?, name: artistName as! String?, genre: artistGenre as! String?, img: artistImg as! String?)
//appending it to list
self.artistList.append(artist)
}
//reloading the tableview
self.tableViewArtists.reloadData()
}
})
}
func addArtist(){
//generating a new key inside artists node
//and also getting the generated key
let key = refArtists.childByAutoId().key
//creating artist with the given values
let artist = ["id":key,
"artistName": textFieldName.text! as String,
"artistGenre": textFieldGenre.text! as String,
] //as [String : Any]
//adding the artist inside the generated unique key
refArtists.child(key).setValue(artist)
//displaying message
labelMessage.text = "Artist Added"
}
func updateArtist(id:String, name:String, genre:String){
//creating artist with the new given values
let artist = ["id":id,
"artistName": name,
"artistGenre": genre
]
//updating the artist using the key of the artist
refArtists.child(id).setValue(artist)
//displaying message
labelMessage.text = "Artist Updated"
}
First I'd cache the image.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromCacheWithUrlSting(urlString: String) {
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
}
DispatchQueue.main.async {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
}
}.resume()
}
This will save you downloading the image every time the view loads. Then add something like this to cellForRow
if let artistImage = dictionary["img"] as? String {
cell?.artistImageView.loadImageFromCacheWithUrlSting(urlString: artistImage)
}

Resources