How to cache the image in swift using SDWebImage? - swift

I am fetching the image from firebase storage and want to cache it using SDWebImage, but in my ViewController it gets keep on downloaded. Please guide me how to cache the images
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! demoCell
cell.textDemo.text = images[indexPath.row]
var images_list = [String]()
images_list.append(images[indexPath.row])
images_list.append(images[indexPath.row] + "1")
let storage = Storage.storage().reference()
var imagesarray = [URL]()
for x in images_list{
let storageRef = storage.child("images/\(x).jpg")
storageRef.downloadURL { (url, error) in
if let error = error{
print(error.localizedDescription)
}
else if let downloadURL = url?.absoluteString{
cell.imageDemo.image = SDImageCache.shared().imageFromCache(forKey: downloadURL)
print("Image Cached")
}
else{
cell.imageDemo.sd_setImage(with: url!, completed: nil)
}
}
}
return cell
}

Rather than writing :
cell.imageDemo.image = SDImageCache.shared().imageFromCache(forKey: downloadURL)
Try this :
cell.imageDemo.sd_setImage(with: URL(string: downloadURL), placeholderImage: UIImage(named: "Your Default Image"))
SDWebImage will handle the caching part here.

This was my solution to the issue
var value : Any?
var vc = ViewController()
var images = [String]()
var downloads_array = [URL]()
override func viewDidLoad() {
super.viewDidLoad()
getImageNames()
downloadImages()
}
func downloadImages(){
let storage = Storage.storage().reference()
for x in images{
let storageRef = storage.child("images/\(x).jpg")
storageRef.downloadURL { (url, error) in
if let error = error{
print(error.localizedDescription)
}
else{
self.downloads_array.append(url!)
self.tableView.reloadData()
}
}
}
}
func getImageNames(){
images = vc.images
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return downloads_array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! demoCell
cell.textDemo.text = images[indexPath.row]
if let downloadURL = SDImageCache.shared().imageFromCache(forKey: downloads_array[indexPath.row].absoluteString){
cell.imageDemo.image = downloadURL
}
else{
cell.imageDemo.sd_setImage(with: downloads_array[indexPath.row], completed: nil)
}
return cell
}

Related

Cannot fetch data from google-cloud-firestore correctly

No problems with fetching Title and Subtitle. Problems are with downloading of images and streaming the video. I really appreciate your help friends. I just want to learn how to implement it correctly and what to use, get data method or listener? Does it need grammatical changes of my code or a pair of code lines?
import UIKit
import AVKit
import AVFoundation
import FirebaseFirestore
import Combine
class ListOfVideoLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let pinchGesture = UIPinchGestureRecognizer()
private var viewModel = VideosViewModel()
private var cancellable: AnyCancellable?
var player = AVPlayer()
var playerViewController = AVPlayerViewController()
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.fetchData()
self.title = "Video Lessons"
table.delegate = self
table.dataSource = self
cancellable = viewModel.$videos.sink { _ in
DispatchQueue.main.async{
self.table.reloadData()
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("videos count = ", viewModel.videos.count)
return viewModel.videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let video = viewModel.videos[indexPath.row]
tableView.tableFooterView = UIView()
//configure cell
cell.textLabel?.text = video.name
cell.detailTextLabel?.text = video.lessonName
cell.accessoryType = .disclosureIndicator
let image = UIImage(data: video.imageData)
cell.imageView?.image = image...
imageName!.withRenderingMode(.alwaysTemplate)
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(named: "VideoLessonsCellHighlighted")
cell.selectedBackgroundView = backgroundView
cell.textLabel?.font = UIFont(name: "Helvetica-Bold", size: 14)
cell.detailTextLabel?.font = UIFont(name: "Helvetica", size: 12)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
playVideo(at: indexPath)
}
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = UIColor(named: "VideoLessonsCellHighlighted")
cell.textLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
cell.detailTextLabel?.highlightedTextColor = UIColor(named: "textHighlighted")
}
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.contentView.backgroundColor = nil
}
}
func playVideo(at indexPath: IndexPath){
let selectedVideo = viewModel.videos[indexPath.row]
let videoURL = URL(string: selectedVideo.fileURL)
player = AVPlayer(url: videoURL!)
playerViewController.player = player
self.present(playerViewController, animated: true, completion: {
self.playerViewController.player?.play()
})
}
}
This is the VideosViewModel which supposed to download data from firestore database and translate into local file names if I am not mistaken.
import Foundation
import FirebaseFirestore
class VideosViewModel: ObservableObject {
#Published var videos = [Video]()
private var db = Firestore.firestore()
static func downloadImage(withURL url: URL, completion: #escaping (_ _image:UIImage?)->()) {
let dataTask = URLSession.shared.dataTask(with: url) { data, url, error in
var downloadedImage:UIImage?
if let data = data {
downloadedImage = UIImage(data: data)
}
DispatchQueue.main.async {
completion(downloadedImage)
}
}
dataTask.resume()
}
func fetchData() {
db.collection("videos").addSnapshotListener { [self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No Documents")
return
}
self.videos = documents.map { (queryDocumentSnapshot) -> Video in
let data = queryDocumentSnapshot.data()
let name = data["name"] as? String ?? ""
let imageName = data["imageName"]as? URL ?? ""
let lessonName = data["lessonName"] as? String ?? ""
let fileURL = data["fileURL"] as? String ?? ""
print(data)
return Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL)
}
}
}
}
import Foundation
struct Video {
var name: String
var imageName: Data
var lessonName: String
var fileURL: String
}```
[1]: https://i.stack.imgur.com/YIU5O.png

Issue with TableView and Firebase implementation (Swift)

I want to connect my TableView to what I query from Firestore. The query works, but I can't get the TableView to show the content. Right now its just a blank tableView. The TableViewCell file also has no issues, since it worked before without the firebase implementation (The Cell is registered correctly).
I suspect that the issue is in cellForRowAt and tried played around in there, but couldn't get anything to work.
Can you find the issue?
import UIKit
import Firebase
class popularViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet var table: UITableView!
var texttt = [TextPost]()
override func viewDidLoad() {
super.viewDidLoad()
gettingPosts()
table.register(textTableViewCell.nib(), forCellReuseIdentifier: textTableViewCell.identifier)
table.delegate = self
table.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
gettingPosts()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let alle = models.count + texttt.count
return alle
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: textTableViewCell.identifier, for: indexPath) as! textTableViewCell
cell.configure(with: texttt[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
func gettingPosts(){
let db = Firestore.firestore()
let postsRef = db.collection("posts")
postsRef.addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added){
let data = diff.document.data()
let Name = data["username"] as! String
let text = data["description"] as! String
let likes = data["likes"] as! Int
let typ = data["postType"] as! Int
let pfp = data["profileImage"] as! String
let uid = data["uid"] as! String
let pic = data["picture"]
let time = data["time"] as! String
if typ == 0{ // Text post
let dasDing = TextPost(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text)
self.texttt.append(dasDing)
}
}
}
}
}
}
struct TextPost {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let textName: String
}
You need to reload data once you get data from firebase
func gettingPosts(){
let db = Firestore.firestore()
let postsRef = db.collection("posts")
postsRef.addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added){
let data = diff.document.data()
let Name = data["username"] as! String
let text = data["description"] as! String
let likes = data["likes"] as! Int
let typ = data["postType"] as! Int
let pfp = data["profileImage"] as! String
let uid = data["uid"] as! String
let pic = data["picture"]
let time = data["time"] as! String
if typ == 0{ // Text post
let dasDing = TextPost(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text)
self.texttt.append(dasDing)
}
}
}
DispatchQueue.main.async {
tableView.reloadData()
}
}
}

Index out of range custom cell TableViewController

I'm trying to populate 3 custom cells into a TableViewController.
but I always get index out of range error. I`m not sure whats wrong with my code. anyone can help me, I'm newbie in swift.
but when i use 0 for numberOfRowsInSection return, the output is the first cell.
here's my code :
class testResize: UITableViewController {
#objc var comments = [AnyObject]()
#objc var images = [UIImage]()
var getImg = [String]()
override func viewDidLoad() {
super.viewDidLoad()
loadPosts()
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let getCom = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testResizeHeadCell.self), for: indexPath) as! testResizeHeadCell
let user = getCom["nickname"] as! String
let ava = getCom["ava"] as! String
if ava != "" {
let resource = ImageResource(downloadURL: URL(string: ava)!, cacheKey: ava)
cell.avaImg.kf.setImage(with: resource)
}
cell.username.text = user
return cell
}else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testResizeCell.self), for: indexPath) as! testResizeCell
cell.setCustomImage(image: images[indexPath.row])
return cell
}else {
let getCom = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testRezieTextCell.self), for: indexPath) as! testRezieTextCell
let text = getCom["text"] as! String
cell.explaination.text = text
return cell
}
}
here is my load function :
#objc func loadPosts() {
let uuid = "959D1073"
let url = URL(string: "some/url.php")!
self.tableView.reloadData()
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "uuid=\(uuid)"
//print(body)
request.httpBody = body.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async(execute: {
if error == nil {
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
self.comments.removeAll(keepingCapacity: false)
self.images.removeAll(keepingCapacity: false)
self.tableView.reloadData()
guard let parseJSON = json else {
print("Error While Parsing")
return
}
guard let posts = parseJSON["posts"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
self.comments = posts.reversed()
print(self.comments)
for i in 0 ..< self.comments.count {
let path = self.comments[i]["path"] as? String
self.getImg = [path!]
if !path!.isEmpty {
let url = NSURL(string: path!)!
let imageData = try? Data(contentsOf: url as URL)
let image = UIImage(data: imageData! as Data)!
self.images.append(image)
} else {
let image = UIImage()
self.images.append(image)
}
}
self.tableView.reloadData()
//print(posts)
} catch {
print(error)
}
}else{
print(error!)
}
})
}.resume()
}
i think you have a single comment and 3 cell type and when you use indexPath.row happen some thing like this :
for example :
comments = {[{nickname : "mahdi" , ava : "url"} ]}
if indexPath.row == 0 {
let getCom = comments[0]
let user = getCom["nickname"] as! String
let ava = getCom["ava"] as! String
}else if indexPath.row == 1 {
images[1]
}else {
let getCom = comments[2]
let text = getCom["text"] as! String
}
but you have just one comment and when you call comments[1] or commens [2] , you get index out of range error
please try this code :
class testResize:UITableViewController {
#objc var comments = [AnyObject]()
#objc var images = [UIImage]()
var getImg = [String]()
override func viewDidLoad() {
super.viewDidLoad()
loadPosts()
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (self.comments.count == 0 ? 0 : self.comments.count + 2)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let getCom = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testResizeHeadCell.self), for: indexPath) as! testResizeHeadCell
let user = getCom["nickname"] as! String
let ava = getCom["ava"] as! String
if ava != "" {
let resource = ImageResource(downloadURL: URL(string: ava)!, cacheKey: ava)
cell.avaImg.kf.setImage(with: resource)
}
cell.username.text = user
return cell
}else if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testResizeCell.self), for: indexPath) as! testResizeCell
cell.setCustomImage(image: images[indexPath.row - 1])
return cell
}else {
let getCom = comments[indexPath.row - 2]
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: testRezieTextCell.self), for: indexPath) as! testRezieTextCell
let text = getCom["text"] as! String
cell.explaination.text = text
return cell
}
}
and change your numberOfRowInSection :
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return (self.comments.count == 0 ? 0 : self.comments.count + 2)
}
I am assuming that you load your posts asynchronously.
But you do not check if there are actually enough elements in the array. You should check if there are actually enough elements in the array before you access it with a fixed index.
Additionally, you should change your numberOfRows method to the following:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.comments.count
}
After you have loaded your posts, you can then call
self.tableView.reloadData()

I want to get firestore data in dequeueReusableCell

I rewrote all the text and now I got the code I wanted to realize.
It can not be displayed on the tableCell, and the layout also collapses. I am sorry that the code and the body I wrote are not explained enough.
guard let userID = Auth.auth (). currentUser? .uid I want to always acquire userID with else {return}.
// guard let docSnapshot = querySnapshot, document.exists else {return}
Since an error occurs, it is commented out.
Within viewidLoad of UIViewController
var profDict: [ProfDic] = [] is in the UIViewController.
profUIView is being added to UIViewController.
func getFirebaseData() {
db = Firestore.firestore()
guard let userID = Auth.auth().currentUser?.uid else {return}
let ref = db.collection("users").document(userID)
ref.getDocument{ (document, error) in
if let document = document {
// guard let docSnapshot = querySnapshot, document.exists else {return}
if let prof = ProfDic(dictionary: document.data()!) {
self.profDict.append(prof)
print("Document data \(document.data())")
}
}else{
print("Document does not exist")
}
self.profUIView.tableView1.reloadData()
}
}
tableView1 has been added to ProfUIView.
class ProfUIView: UIView, UITableViewDelegate, UITableViewDataSource {
//omission...
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .blue
addSubview(tableView1)
tableView1.anchor(top: //omission...
sections = [
Section(type: .prof_Sec, items: [.prof]),
Section(type: .link_Sec, items: [.link]),
Section(type: .hoge_Sec, items: [.hoge0])
]
tableView1.register(TableCell0.self, forCellReuseIdentifier: TableCellId0)
tableView1.register(TableCell3.self, forCellReuseIdentifier: TableCellId3)
tableView1.register(TableCell5.self, forCellReuseIdentifier: TableCellId5)
tableView1.delegate = self
tableView1.dataSource = self
}
var tableView1:UITableView = {
let table = UITableView()
table.backgroundColor = .gray
return table
}()
//omission
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (baseVC?.profDict.count)!//sections[section].items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sections[indexPath.section].items[indexPath.row] {
case .prof:
let cell0 = tableView.dequeueReusableCell(withIdentifier: TableCellId0, for: indexPath) as? TableCell0
cell0?.nameLabel.text = baseVC?.profDict[indexPath.row].userName
return cell0!
}
//omission...
}
}
Additional notes
import Foundation
import FirebaseFirestore
struct ProfDic {
var userName :String
var dictionary:[String:Any] {
return
["userName" : userName
]
}
}
extension ProfDic {
init?(dictionary:[String:Any]) {
guard let userName = dictionary["userName"] as? String
else {return nil}
self.init(userName: userName as String)
}
}
enter image description here
First create an empty array of ProfDic elements:
var profDict: [ProfDic] = []
Then create a function to load your Firebase Data:
func getFirebaseData() {
db = Firestore.firestore()
let userRef = db.collection("users").getDocuments() {
[weak self] (querySnapshot, error) in
for document in querySnapshot!.documents {
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
if let prof = ProfDic(dictionary: docSnapshot.data()!) {
profDict.append(prof)
}
}
tableView.reloadData()
}
}
Call this function in viewDidLoad or viewDidAppear.
Then in tableView cellForRowAt you access your data like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sections[indexPath.section].items[indexPath.row] {
case .prof:
let cell = tableView.dequeueReusableCell(withIdentifier: TableCellId, for: indexPath) as? TableCell
cell?.nameLabel.text = profDict[indexPath.row].userName
return cell!
}
}
EDIT:
Also in numberOfRowsInSection:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profDict.count
}

How do I use SDImageView to download URL within a TableViewCell?

I am trying to use the SDImageView Cocoapod with a table view to retrieve a URL from a database and turning it into a viewable image. When I use the code bellow I don't get images in return, what is wrong with my code? Thanks!
var posts = [postStruct]()
override func viewDidLoad() {
super.viewDidLoad()
let ref = Database.database().reference().child("Posts")
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount)
for rest in snapshot.children.allObjects as! [DataSnapshot] {
guard let value = rest.value as? Dictionary<String,Any> else { continue }
guard let title = value["Title"] as? String else { continue }
guard let downloadURL = value["Download URL"] as? String else { continue }
let post = postStruct(title: title, downloadURL: downloadURL)
self.posts.append(post)
}
self.posts = self.posts.reversed(); self.tableView.reloadData()
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let imageView = cell?.viewWithTag(200) as! UIImageView
imageView.sd_setImage(with: URL(string: "downloadURL"), placeholderImage: UIImage(named: "placeholder.png"))
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
return cell!
}
You need to change SDWebimage syntax as ->
var posts = [postStruct]()
var downloadURL : String = ""
override func viewDidLoad() {
super.viewDidLoad()
let ref = Database.database().reference().child("Posts")
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount)
for rest in snapshot.children.allObjects as! [DataSnapshot] {
guard let value = rest.value as? Dictionary<String,Any> else { continue }
guard let title = value["Title"] as? String else { continue }
downloadURL = value["Download URL"] as? String ?? ""
let post = postStruct(title: title, downloadURL: downloadURL)
self.posts.append(post)
}
self.posts = self.posts.reversed(); self.tableView.reloadData()
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let imageView = cell?.viewWithTag(200) as! UIImageView
imageView.sd_setImage(with: URL(string: downloadURL), placeholderImage: UIImage(named: "placeholder.png"))
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
return cell!
}
Where downloadURL is url String.
You first need to pick the postStruct from the array and then the downloadURL. Change your override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method with this.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let imageView = cell?.viewWithTag(200) as! UIImageView
let post = self.posts[indexPath.row];
imageView.sd_setImage(with: URL(string: post.downloadURL), placeholderImage: UIImage(named: "placeholder"))
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
return cell!
}