Images not showing in collectionView Swift - swift

In console there is printing of empty imagesArray but I am downloading it in downloadImages function. And in simulator the images won't load
import UIKit
import PinterestLayout
import ProgressHUD
class MainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
var postsArray = [Post]()
var imagesArray = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
loadItems()
downloadPhoto()
}
func loadItems() {
ref.child("Posts").observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String,Any>
if let title = snapshotValue["title"], let price = snapshotValue["price"], let downloadUrl = snapshotValue["downloadUrl"], let category = snapshotValue["category"], let senderUid = snapshotValue["senderUid"] {
let post = Post()
post.title = title as! String
post.price = price as! String
post.downloadUrl = downloadUrl as! String
post.category = category as! String
post.senderUid = senderUid as! String
self.postsArray.append(post)
self.collectionView.reloadData()
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.shadowImage = UIImage()
tabBarController?.tabBar.backgroundImage = UIImage()
}
func downloadPhoto(){
DispatchQueue.global().async {
self.imagesArray.removeAll() // this is the image array
for i in 0..<self.postsArray.count {
guard let url = URL(string: self.postsArray[i].downloadUrl) else {
continue
}
let group = DispatchGroup()
print(url)
print("-------GROUP ENTER-------")
group.enter()
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
print(response?.suggestedFilename ?? url.lastPathComponent)
if let imgData = data, let image = UIImage(data: imgData) {
DispatchQueue.main.async() {
self.imagesArray.append(image)
self.collectionView.reloadData()
}
} else if let error = error {
print(error)
}
group.leave()
}).resume()
group.wait()
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imagesArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell
cell.imgView.downloadImage(from: self.postsArray[indexPath.row].downloadUrl)
return cell
}
}
extension MainViewController: PinterestLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat {
let image = imagesArray[indexPath.item]
let height = image.size.height
return height
}
}
extension UIImageView {
func downloadImage(from url: String){
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) {
(data,response,error) in
if error != nil {
print(error ?? error!)
return
}
if let data = data {
DispatchQueue.main.async {
self.image = UIImage(data: data)
}
}
}
task.resume()
}
}
UPDATED CODE:
import UIKit
import PinterestLayout
import ProgressHUD
import Kingfisher
class MainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
var postsArray = [Post]()
var imagesArray = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView.collectionViewLayout as? PinterestLayout {
layout.delegate = self
}
collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
loadItems()
collectionView.reloadData()
}
func loadItems() {
ref.child("Posts").observe(.childAdded) { (snapshot) in
let snapshotValue = snapshot.value as! Dictionary<String,Any>
if let title = snapshotValue["title"], let price = snapshotValue["price"], let downloadUrl = snapshotValue["downloadUrl"], let category = snapshotValue["category"], let senderUid = snapshotValue["senderUid"] {
let post = Post()
post.title = title as! String
post.price = price as! String
post.downloadUrl = downloadUrl as! String
post.category = category as! String
post.senderUid = senderUid as! String
self.postsArray.append(post)
self.downloadPhoto()
print(self.imagesArray.count)
self.collectionView.reloadData()
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.shadowImage = UIImage()
tabBarController?.tabBar.backgroundImage = UIImage()
}
func downloadPhoto(){
DispatchQueue.global().async {
self.imagesArray.removeAll() // this is the image array
for i in 0..<self.postsArray.count {
guard let url = URL(string: self.postsArray[i].downloadUrl) else {
continue
}
let group = DispatchGroup()
print(url)
print("-------GROUP ENTER-------")
group.enter()
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
print(response?.suggestedFilename ?? url.lastPathComponent)
if let imgData = data, let image = UIImage(data: imgData) {
DispatchQueue.main.async() {
self.imagesArray.append(image)
let post = Post()
post.image = image
self.collectionView.reloadData()
}
} else if let error = error {
print(error)
}
group.leave()
}).resume()
group.wait()
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return postsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell
let resource = ImageResource(downloadURL: URL(string: postsArray[indexPath.row].downloadUrl)!, cacheKey: postsArray[indexPath.row].downloadUrl)
cell.imgView.kf.setImage(with: resource)
return cell
}
}
extension MainViewController: PinterestLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, heightForPhotoAtIndexPath indexPath: IndexPath) -> CGFloat {
let image = imagesArray[indexPath.row]
let height = image.size.height / 6
return height
}
}
//AND POST CLASS
import UIKit
class Post {
var title : String = ""
var category : String = ""
var downloadUrl : String = ""
var price : String = ""
var senderUid : String = ""
var image = UIImage()
}

You need to call
downloadPhoto()
inside
loadItems()
as both are asynchronous , here
self.postsArray.append(post)
downloadPhoto()
Note: I recommend having a cache to check before downloading the image or better use SDWebImage

As Sh_Khan said, you need to call downloadPhoto after loadItems has completed, otherwise there wont be any posts to loop over.
Also, a few points to consider here...
You are not reloading any cells once the image download has completed (downloadPhoto)
You are not caching images, so you will end up downloading images often. Once you scroll your collectionView and cells get reused you will download the same images, again.
You are not using DispatchGroup effectively here (in downloadPhoto anyway), you appear to be downloading one image at a time (or trying to), not taking advantage of parallel downloads. If you intend to do this, use a serial queue. But this will slow down loading images considerably.
I prefer to use KingFisher for downloading and caching images, the library already manages most of this for you and leaves you to focus on your app.
If you dont want to use a library, something like this should help...
var imageCache = [String: UIImage]()
func downloadImage(from url: String){
if let image = imageCache[url] as? UIImage {
self.image = image
return
}
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) {
(data,response,error) in
if error != nil {
print(error ?? error!)
return
}
if let data = data {
DispatchQueue.main.async {
let image = UIImage(data: data)
imageCache[url] = image
self.image = image
}
}
}
task.resume()
}
Kingfisher example:
imageView.kf.setImage(with: url, completionHandler: {
(image, error, cacheType, imageUrl) in
// image: Image? `nil` means failed
// error: NSError? non-`nil` means failed
// cacheType: CacheType
// .none - Just downloaded
// .memory - Got from memory cache
// .disk - Got from disk cache
// imageUrl: URL of the image
})
so have a postsArray and images array
var postsArray = [Post]()
var images = [String: UIImage]() // dictionary, maps url to image
Then when you receive a Post:
let post = Post()
post.title = title as! String
post.price = price as! String
post.downloadUrl = downloadUrl as! String
post.category = category as! String
post.senderUid = senderUid as! String
self.postsArray.append(post)
imageView.kf.setImage(with: url, completionHandler: { (image, error, cacheType, imageUrl) in
// check image is not nil etc
images[url] = image
collectionView.reloadData()
}
CellForRowAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell
cell.imgView.image = images[postsArray[indexPath.row].downloadUrl]
return cell
}

Related

Dowload image from Firebase

Please help me. Explain how to set the image in the cell. In my Database I have: title, description and imageURL (url from Firebase Storage). Can you write me a code and explain.
class TrainingProgram
{
var description = ""
var title = ""
var imageURL = ""
init(description: String, title: String, imageURL: String) {
self.description = description
self.title = title
self.imageURL = imageURL
}
function to get data from firebase.
func fetchPrograms() {
Database.database().reference().child("programs").observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
let newTitle = dict["title"] as! String
let newDescription = dict["description"] as! String
let newImageURL = dict["imageURL"] as! String
let trainingCell = TrainingProgram(description: newDescription, title: newTitle, imageURL: newImageURL)
self.trainingPrograms.append(trainingCell)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
}
And this is how I set title and description to my cells.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TrainingProgramCollectionViewCell", for: indexPath) as! TrainingProgramCollectionViewCell
//cell.featuredImageView.setImage(from: indexPath.item)
cell.titleLabel.text = trainingPrograms[indexPath.item].title
cell.descriptionLabel.text = trainingPrograms[indexPath.item].description
What I need to write in the function for receiving data to get images and how to set images in cells
Install SDWebImage pod from this link https://github.com/SDWebImage
And in Your CollectioView cellForRow method
var images_list = [String]()
var imagesArray = [URL]()
images_list.append(itemModel[indexPath.row]. imageURL)
let storage = Storage.storage().reference()
for x in images_list{
print(x)
let storageRef = storage.child("images/\(x).jpg")
storageRef.downloadURL { (url, error) in
if let error = error{
print(error.localizedDescription)
}
else{
imagesArray.append(url!)
}
if let x = images_list.last{
cell.itemImage.sd_setImage(with: URL(string: x), placeholderImage: UIImage(named: "default"))
}
}
}
Full code
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TrainingProgramCollectionViewCell", for: indexPath) as! TrainingProgramCollectionViewCell
//cell.featuredImageView.setImage(from: indexPath.item)
cell.titleLabel.text = trainingPrograms[indexPath.item].title
cell.descriptionLabel.text = trainingPrograms[indexPath.item].description
var images_list = [String]()
var imagesArray = [URL]()
for x in images_list{
print(x)
let storageRef = storage.child("images/\(x).jpg")
storageRef.downloadURL { (url, error) in
if let error = error{
print(error.localizedDescription)
}
else{
imagesArray.append(url!)
}
if let x = images_list.last{
cell.itemImage.sd_setImage(with: URL(string: x), placeholderImage: UIImage(named: "default"))
}
}
}
}
Try this for single image downloadJust take your imageUrl from firebase
let x: String = "yourImageUrl"
let storageRef = storage.child("images/\(x).jpg")
storageRef.downloadURL { (url, error) in
if let error = error{
print(error.localizedDescription)
}
else{
imagesArray.append(url!)
}
if let x = images_list.last{
cell.itemImage.sd_setImage(with: URL(string: x), placeholderImage: UIImage(named: "default"))
}
}

Collectionview in TableviewCell, data repeat

I've a collectionview inside my resizable tablviewCells. Tableview has one cell in each 'n' number of sections. Datasource and delegate of collectionview are set to the tableviewCell. There is an API called on tablview's cellForRowAt, and the result is rendered on the collectionview for each cell. After the result is fetched, a delegate tells the tableview that collectionview is loaded and it should reload that cell without calling the API this time. But the problem is that my collectionview data is repeated after every 2 tableviewCells.
I know prepareForReuse should be override to get rid of cell reuse problems. I've implemented prepareForReuse in my collectionviewCells and set my label.text and imageView.image to nil. However i'm not sure what to add to prepareForReuse for my tableviewCell.
// TableView class
override func viewDidLoad() {
super.viewDidLoad()
storiesSections = [....]
tableView.register(UINib(nibName: "RWFeedTableViewCell", bundle: nil), forCellReuseIdentifier: "reuseIdentifier")
tableView.estimatedRowHeight = 1
tableView.rowHeight = UITableView.automaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
return storiesSections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! RWFeedTableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "reuseIdentifier") as! RWFeedTableViewCell
}
cell.delegate = self
cell.fetchData(feedSection: storiesSections[indexPath.section], indexPath: indexPath)
return cell
}
// delegate for tableview reload
func collectionViewDidEnd(updatedFeedSection: FeedSection, indexPath: IndexPath) {
storiesSections[indexPath.section] = updatedFeedSection
tableView.beginUpdates()
tableView.endUpdates()
}
// TableViewCell class
override func awakeFromNib() {
super.awakeFromNib()
initializeCode()
}
func initializeCode() {
// Set layout
self.collectionView.collectionViewLayout = RWWaterfallLayout2()
self.collectionView.register(UINib(nibName: "\(ImageThenTitleViewCell.self)", bundle: nil), forCellWithReuseIdentifier: kImageThenTitleCellID)
self.collectionView.register(UINib(nibName: "\(LeftImageCell.self)", bundle: nil), forCellWithReuseIdentifier: kLeftImageCellID)
self.collectionView.contentInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
self.collectionView.isScrollEnabled = false
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
if feedSection.isLoadComplete {
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
var updatedFeedSection = feedSection
updatedFeedSection.storiesArray? = (todo["data"]! as! Array)
updatedFeedSection.isLoadComplete = true
self.feedSection = updatedFeedSection
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: updatedFeedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.feedSection == nil {
return 0
} else {
return (self.feedSection?.storiesArray?.count)!
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let indexForTen = indexPath.item%10
let story = self.feedSection?.storiesArray?[indexPath.item]
if indexForTen == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kImageThenTitleCellID, for: indexPath) as! ImageThenTitleViewCell
cell.setupData(story: story!)
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kLeftImageCellID, for: indexPath) as! LeftImageCell
cell.setupData(story: story!)
return cell
}
}
override func prepareForReuse() {
super.prepareForReuse()
}
// Collectionview Cell
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setupData(story: Dictionary<String, Any>){
self.storyImage.image = nil // reset the image
let thumbImage = story["image"] as! Dictionary<String, String>
self.storyTitle.text = story["t"] as? String
self.storyImage.downloaded(from: (thumbImage["m"])!)
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 1
self.layer.cornerRadius = 8
}
override func prepareForReuse() {
super.prepareForReuse()
storyImage.image = nil
storyTitle.text = nil
}
// FeedSection struct
struct FeedSection {
var categoryID: String?
var storiesArray : [Dictionary<String, Any>]?
var isLoadComplete: Bool
init(categoryID: String) {
self.categoryID = categoryID
self.storiesArray = []
self.isLoadComplete = false
}
}
Currently the 3rd tableviewCell repeats the data of 1st tablviewCell. How to avoid repeating cell data?
Only problem was the feedSection object in TableViewCell. It should be initialized at the time fetchData() is called. And just reload the collectionView if isLoadComplete is true.
Also since isLoadComplete is set on completion handler of URLSession, I set it to true the time API is called. So the same api will not be called while waiting for response. Maybe an enum could be set for api call and api response events on FeedSection. But for now this works.
func fetchData(feedSection: FeedSection, indexPath: IndexPath) {
self.feedSection = feedSection
if self.feedSection.isLoadComplete {
self.collectionView.reloadData()
return
}
if let catID = feedSection.categoryID {
let urlString = URL(string: <urlString>)
let urlRequest = URLRequest(url: urlString!)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
self.feedSection.isLoadComplete = true
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
let task = session.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
} else {
print(error?.localizedDescription as Any)
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
print("success convert data to JSON")
DispatchQueue.main.async {
self.feedSection.storiesArray? = (todo["data"]! as! Array)
self.feedSection.isLoadComplete = true
self.collectionView.reloadData()
self.collectionView.performBatchUpdates({
}, completion: { (complete) in
self.collectionViewHeightConstraint.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height + self.collectionView.contentInset.top + self.collectionView.contentInset.bottom
self.delegate?.collectionViewDidEnd(updatedFeedSection: self.feedSection, indexPath: indexPath)
})
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
}
Set UITableView.reloadData() after get the data.
And check if you set CollectionView.reloadData(), if yes then remove reloadData() of UICollectionView. Only set UITableView.reloadData()

swift different textview url

I have a tableview.
Each cell opens a new ViewController and loads a .txt file to a textview from my github repo.
But now i want to make each cells to load a different .txt file from the same repo.
So like the first cell loads the first.txt, the second loads the second.txt and so on
is this even possible?
It would be like a news reader app where you click on a title and can read the article.
Here is my current code which is bad but im a beginner :(
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
let content = self.downloadContent()
DispatchQueue.main.async {
self.myTextView.text = content
}
}
}
func downloadContent() -> String {
var data : Data!
data = try? Data(contentsOf: URL(string: "https://raw.githubusercontent.com/SiposPtr/umszkiapp/master/cikk2.txt")!)
let data_str = String(data: data, encoding: .utf8)
return data_str!
}
I upload my files to a new github repo so you can see the whole stuff:
https://github.com/SiposPtr/stackoverflow
Could you please check if this is working?
import UIKit
class TableViewController: UITableViewController {
var TableData: Array<String> = Array<String>()
var currentCell: String?
var selectedValue: String?
var numberOfFileToLoad: Int = 1
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("https://raw.githubusercontent.com/SiposPtr/umszkiapp/master/cimek.json")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = TableData[indexPath.row]
cell.textLabel!.numberOfLines = 2;
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90.0;
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedValue = TableData[indexPath.row]
numberOfFileToLoad = indexPath.row + 1
}
func get_data_from_url(_ link:String)
{
let url:URL = URL(string: link)!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.httpMethod = "GET"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(
data, response, error) in
guard let _:Data = data, let _:URLResponse = response , error == nil else {
return
}
self.extract_json(data!)
})
task.resume()
}
func extract_json(_ data: Data) {
let json: Any?
do
{
json = try JSONSerialization.jsonObject(with: data, options: [])
}
catch
{
return
}
guard let data_list = json as? NSArray else
{
return
}
if let hir_lista = json as? NSArray {
for i in 0 ..< data_list.count
{
if let hir_obj = hir_lista[i] as? NSDictionary
{
if let cim_nev = hir_obj["cim"] as? String
{
if let hir_code = hir_obj["datum"] as? String
{
TableData.append(cim_nev + "\n(" + hir_code + ")")
}
}
}
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
// ez működik, de a run utáni első cikknek üres a titléje és akk csúszik minden cím eggyel
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "gotoSentences"{
let nextViewController = segue.destination as! HirNezetViewController
nextViewController.title = selectedValue
print(selectedValue)
nextViewController.numberOfFileToLoad = numberOfFileToLoad
print(numberOfFileToLoad)
}
}
#IBAction func reloadButton(_ sender: Any) {
let alert = UIAlertController(title: "A cikkeket újratöltöttem", message: "Kattints a gombra az eltüntetéshez", preferredStyle: .alert)
let ok = UIAlertAction(title: "Rendben", style: .default, handler: { action in
})
alert.addAction(ok)
get_data_from_url("https://raw.githubusercontent.com/SiposPtr/umszkiapp/master/cimek.json")
tableView.reloadData()
}
}
import UIKit
class HirNezetViewController: UIViewController {
var data:String!
var content: String?
var numberOfFileToLoad: Int = 1
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
let content = self.downloadContent()
DispatchQueue.main.async {
self.myTextView.text = content
}
}
}
func downloadContent() -> String {
var data : Data!
data = try? Data(contentsOf: URL(string: "https://raw.githubusercontent.com/SiposPtr/umszkiapp/master/cikk\(String(numberOfFileToLoad)).txt")!)
let data_str = String(data: data, encoding: .utf8)
return data_str!
}
}

Returning UIImage Async using Task

I have a UICollectionView of cells each including an image.
I want to load the cell's images.
My code that instantiate the image :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = prodInCell.GetProductImage()
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
My Product's GetProductImage function:
public func GetProductImage() -> UIImage
{
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
var prodImg = #imageLiteral(resourceName: "DefaultProductImage")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: // Up to 10 MB pictures
{
(data, error) in
if let data = data
{
if let img = UIImage(data: data)
{
prodImg = img
}
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
return prodImg
}
I want a UIImage to be retrieved from Firebase Storage, or return a DefaultProductImage if none exists. Current implementation stucks my UI and seems to not really load anything from Firebase.
How do I Make this work ? I would also like for it to not take so much time - so perhaps using a couple of tasks to each load an image would be a good solution.
Edit :
This is my code now :
accept
You can use a completion block to return the UIImage asynchronously.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = #imageLiteral(resourceName: "DefaultProductImage")
prodInCell.GetProductImage() { image in
cell.ProductImageView.image = image
}
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
public func GetProductImage(completion: ((UIImage?) -> Void)) {
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: { (data, error) in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
}
And now I get
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
In function
- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data
error:(GTM_NULLABLE NSError *)error {
// Callbacks will be released in the method stopFetchReleasingCallbacks:
GTMSessionFetcherCompletionHandler handler;
#synchronized(self) {
GTMSessionMonitorSynchronized(self);
handler = _completionHandler;
if (handler) {
[self invokeOnCallbackQueueUnlessStopped:^{
handler(data, error);
// Post a notification, primarily to allow code to collect responses for
// testing.
//
// The observing code is not likely on the fetcher's callback
// queue, so this posts explicitly to the main queue.
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
if (data) {
userInfo[kGTMSessionFetcherCompletionDataKey] = data;
}
if (error) {
userInfo[kGTMSessionFetcherCompletionErrorKey] = error;
}
[self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification
userInfo:userInfo
requireAsync:NO];
}];
}
} // #synchronized(self)
In line handler(data,error);
With error error NSError * domain: #"com.google.HTTPStatus" - code: 404
You can use a completion block to return the UIImage asynchronously.
E.g. you could update your code to the following:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "product_collection_cell", for: indexPath) as! ProductsCollectionViewCell
let prodInCell = searchActive ? filtered[indexPath.row] : products[indexPath.row]
// Set fields
cell.ProductImageView.image = #imageLiteral(resourceName: "DefaultProductImage")
prodInCell.GetProductImage() { image in
cell.ProductImageView.image = image
}
cell.ProductName.text = prodInCell.Name()
cell.ProductPrice.text = String(prodInCell.Price())
cell.productUniqueID = prodInCell.UniqueID()
return cell
}
And:
public func GetProductImage(completion: ((UIImage?) -> Void)) {
let prodID = self.UniqueID()
let dbRef = Storage.storage().reference().child(prodID).child("pic0.jpg")
let imgTask = dbRef.getData(maxSize: 10*1024*1024, completion: { (data, error) in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
})
imgTask.observe(.progress, handler: {(snapshot) in
print (snapshot.progress ?? "NO MORE PROGRESS")
})
imgTask.resume()
}

Swift Images place in wrong cell and keep chaging in UICollectionview

I have searched but I was not successful to find the answer to my question, I am downloading images from internet and put them into collection view but when I scroll the places are changing even without scrolling they places on the wrong cell here is my code :
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
var Label = cell.viewWithTag(2) as! UILabel
Label.text = namesArray[indexPath.row]
var image = cell.viewWithTag(1) as! UIImageView
let URLString = imgLink[indexPath.row]
let imgUrl = URL(string: URLString)
image.downloadedFrom(url: imgUrl!, contentMode: UIViewContentMode.scaleAspectFit)
UIimg.insert(image.image!, at: indexPath.row)
return cell
}
public extension UIImageView {
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { () -> Void in
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode)
}
}
UPDATE:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
var image1 = cell.viewWithTag(1) as! UIImageView
let URLString = imgLink[indexPath.row]
let imgUrl = URL(string: URLString)
var Label = cell.viewWithTag(2) as! UILabel
Label.text = namesArray[indexPath.row]
getImage(urlString: URLString) { (success:Bool, data:NSData?, errorDescription:String?) in
if success {
DispatchQueue.main.async() {
let image = UIImage(data: data! as Data)
image1.image = image
}
}
}
return cell
}
func getImage(urlString:String, completionHandler:#escaping (_ success:Bool, _ data:NSData?, _ errorDescription:String?) -> Void) -> Void {
let url = NSURL(string: urlString)
let request = NSMutableURLRequest(url: url! as URL)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data, error == nil else {
completionHandler(false,nil, error?.localizedDescription)
return
}
completionHandler(true, data as NSData?, nil)
}
task.resume()
}
This function is an asynchronous function which will take some time to complete
image.downloadedFrom(url: imgUrl!, contentMode: UIViewContentMode.scaleAspectFit)
So this line..
UIimg.insert(image.image!, at: indexPath.row)
Will run before the above function call has finished downloading the image. This will be causing your issue.
Your downloadedFrom function should use a completion handler to run some code after the image has downloaded for it to work properly.
I usually use a function like the one below for fetching images
func getImage(urlString:String, completionHandler:(success:Bool, data:NSData?, errorDescription:String?) -> Void) -> Void {
let url = NSURL(string: urlString)
let request = NSMutableURLRequest(URL: url!)
let task = session.dataTaskWithRequest(request) { (data, response, error) in
guard let data = data where error == nil else {
completionHandler(success: false,data: nil, errorDescription: error?.localizedDescription)
return
}
completionHandler(success: true, data: data, errorDescription: nil)
}
task.resume()
}
Which can be used in a tableCell/collectionCell like this:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let photo = fetchedResultsController.objectAtIndexPath(indexPath) as! Photo
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! AlbumCell
cell.backgroundColor = UIColor.grayColor()
cell.imageView.image = UIImage(named: "placeholder")
if let image = photo.image {
cell.imageView.image = image
} else {
VTClient.sharedInstance().getImage(photo.url) { (success:Bool, data:NSData?, errorDescription:String?) in
if success {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data: data!)
cell.imageView.image = image
FlickrClient.Caches.imageCache.storeImage(image, withIdentifier: photo.id)
}
}
}
}
return cell
}
}
See my project here https://github.com/martinjkelly/virtual-tourist/blob/master/Virtual Tourist for more information on how this is used