I want to grab images from an api which gives the url of the image, do i need another api call to download the images from their url's? How do i grab every image and append it in an array and display them in my collection view cell. I have declared this outside the class scope
var videoArray = [funnyVideos]()
Alamofire.request(_url).responseJSON { (response) in
print(response.result.value!)
if let dict = response.result.value as? Dictionary<String, AnyObject> {
if let result = dict["result"] as? Dictionary<String, AnyObject> {
if let data = result["data"] as? [Dictionary<String, AnyObject>] {
for obj in data {
let mainObj = funnyVideos()
mainObj.parseData(from: obj)
videoArray.append(mainObj)
// print (obj)
}
if let image1 = data[0]["image"] as? String {
print(image1)
}
self.collection.reloadData()
}
}
}
completed()
}
this function is another class: funny videos
func parseData(from: Dictionary<String, AnyObject>){
if let name = from["title"] as? String{
self._name = name.capitalized
}
}
this func configure cell is another class which holds the outlets for the label and imageview of collection cell.
func configureCell(_ funny : funnyVideos) {
self.funny = funny
nameLabel.text = self.funny.name.capitalized
// imageView.image = UIImage()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
fun = videoArray[indexPath.row]
cell.configureCell(fun)
}
what options do I have here to grab them from a url in the UIImage container? and print(image1) prints the url of the image.
Try it,
You can use AlamofireImage for downloading image and set to the collectionview.
Yes, you have to make another api call on image's url. You can use AlamofireImage or sdwebimage library for download images.
First hit Api with normal Alamofire library and get link of all images and save them in dict or array . Then make another api call using AlamofireImage or sdwebimage.
you should try sdwebimage becauz it is easy
You should use a Swift library called Kingfisher
It will handle caching as well as downloading of the images when you provide the URLs.
Here is the link for getting started. Cheat Sheet
Yes you need to call the url of images that fetch from json, to get image from url you can either use
use like this
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let identifier = cellIdentifier else{
fatalError("Cell identifier not provided")
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier ,
for: indexPath) as UICollectionViewCell
let imageUrl = arrayImage[indexPath.row]
cell.imageView.setImage(url:"imageUrl")
return cell
}
https://github.com/rs/SDWebImage
https://github.com/onevcat/Kingfisher
Related
Good day.
I am currently using Alamofire version 4.5 and xcode version 9.3. I am trying to load images from URLrequest with custom headers to a collection view with image from a fileserver using this code
var imageRequest = URLRequest(url: URL(string: "myurl here ")!)
imageRequest.addValue(MyVariables.accountToken, forHTTPHeaderField: "accountToken")
imageRequest.addValue("header value 1", forHTTPHeaderField: "header field 1")
imageRequest.addValue("header value 2", forHTTPHeaderField: "header field 2")
imageRequest.addValue(imageurl from fileserver, forHTTPHeaderField: "file")
After adding headers to the urlrequest I use this alamofire responseimage to set value to the image
Alamofire.request(imageURLRequest).responseImage { response in
if let image = response.result.value {
self.count += 1
print("image \(self.count )", image)
} else {
print("unable to load \(self.count)")
}
}
The problem I am encountering is that not all images are loading at once eventhough I have already looped in the alamofire request with the number of urlrequests I have. Another thing, when I scroll the collectionview the photos I have loaded from the alamofire call jumbles in order not following the indexpath.row. Moreover, I have also tried to append the image response from my alamofire response image and set it to
cell.imageView.image = self.imageArray[indexPath.row]
but it goes out of bounds. When I log the the alamofire imageresponse it only loads until index 7. I have tried different approach like appending the urlrequest to array and loading it to collection view cell via
cell.imageView.af_setImage(withURLRequest: imageURLRquest)
but it only loads the first item in the image array. Sorry for the long question as I have tried most of the solutions I have found so far but it doesnt fix the problem I have right now. Please provide suggestions to fix this. Thank you in advance.
UPDATE:
Here is the code for the datasource from alamofire request. It returns 10 urlrequest that is being appended to the array
var imageRequest = URLRequest(url: URL(string: "myurl here ")!)
imageRequest.addValue(MyVariables.accountToken, forHTTPHeaderField: "accountToken")
imageRequest.addValue("header value 1", forHTTPHeaderField: "header field 1")
imageRequest.addValue("header value 2", forHTTPHeaderField: "header field 2")
imageRequest.addValue(imageurl from fileserver, forHTTPHeaderField: "file")
self.imageArray.append(imageRequest)
This is for the cellForItemAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BooksCell", for: indexPath) as! LibraryCollectionViewCell
cell.imageView.af_setImage(withURLRequest: self.imageArray[indexPath.row])
cell.bookName.text = self.booksObject[indexPath.row].object(forKey: "title") as! String
cell.cosmosRatingView.rating = self.booksObject[indexPath.row].object(forKey: "rating") as! Double
return cell
}
Good day. I have already found the solution for this. You would need to reset the value of the imageView to nil at cellForItemAt then assign it to the indexPath.row
if let image = self.bookImages[indexPath.row] {
cell.imageView.image = image
} else {
Alamofire.request(imageURLRequest).responseImage { response in
if let image = response.result.value {
cell.imageView.image = image
self.bookImages[indexPath.row] = image
} else {
cell.imageView.image = placeholderImage
}
}
}
Thank you for all your help. Hope this also helps someone who encountered the same problem.
You have error at self.imageArray[indexPath.row] will crash with index goes out of bounds because number of items in cell will be equal to self.booksObject.count-1 what if self.booksObject is greater than image array ?!
so you have to make sure that self.imageArray.count = self.booksObject.count
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.booksObject.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BooksCell", for: indexPath) as! LibraryCollectionViewCell
cell.imageView.af_setImage(withURLRequest: self.imageArray[indexPath.row])
cell.bookName.text = self.booksObject[indexPath.row].object(forKey: "title") as! String
cell.cosmosRatingView.rating = self.booksObject[indexPath.row].object(forKey: "rating") as! Double
return cell
}
This is my code —- I am getting error when returning cell1 inside the if statement as it says ” Cannot return a non void return value in void function.I want to return the cell in tableview .. and i have 3 kind of posts .. one for status one for image one for video post. How can i return the cell for each.
P.S. : I have just provided the code for one post type only as if one is solved then all other can be solved.
import UIKit
import Alamofire
class ViewController: UIViewController , UITableViewDelegate ,
UITableViewDataSource{
#IBOutlet weak var feedTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
feedTable.dataSource = self
feedTable.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 376
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
Alamofire.request("https://new.postpfgsdfdsgshfghjoves.com/api/posts/get_all_posts").responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String,AnyObject> {
if let successcode = dict["STATUS_CODE"] as? Int {
if successcode == 1 {
if let postsArray = dict["posts"] as? [Dictionary<String,AnyObject>]
{
for i in 0..<postsArray.count
{
let posttype = postsArray[i]["media_type"] as! String
if posttype == "image"
{
let cell1 : ImageTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.imageURL = postsArray[i]["profile_pic"] as? String
cell1.imageLocation = postsArray[i]["location"] as? String
cell1.content = postsArray[i]["content"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleImagePost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
cell1.numberOfLikes.text = "\(cell1.likes!) Likes"
cell1.numberOfComments.text = "\(cell1.comments!) Comments"
cell1.postTimeOutlet.text = postsArray[i]["posted_on"] as? String
let url = URL(string: cell1.imageURL!)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.profileImage.image = UIImage(data: data!)
let url1 = URL(string: cell1.imageURL!)
let data1 = try? Data(contentsOf: url1!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.postedImage.image = UIImage(data: data1!)
// return cell1
}
else if posttype == "status"
{
let cell1 : StatusTableViewCell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "statusReuse") as! StatusTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.postContent = postsArray[i]["content"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleStatusPost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
cell1.content.text = cell1.postContent
cell1.numberOfLikes.text = "\(cell1.likes!) Likes"
cell1.numberOfComments.text = "\(cell1.comments!) Comments"
cell1.postTimeOutlet.text = "\(cell1.postTime!)"
let url = URL(string: cell1.profileImageURL!)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.profileImage.image = UIImage(data: data!)
// return cell1
}
else if posttype == "video"
{
let cell1 : VideoTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "videoReuse") as! VideoTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
// cell1.profession = postsArray[i]["profession"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.videoURL = postsArray[i]["profile_pic"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleVideoPost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
// return cell1
}
}
}
}
}
}
}
}
}
My answer isn't any different from the others but let me be a little more specific. I'll use a generic example and you'll need to tailor this to your specific needs.
1) Define a model somewhere for your data such as:
class MyDataItem {
var name: String
var title: String
var location: String
init(name: String, title: String, location: String) {
self.name = name
self.title = title
self.location = location
}
}
2) Define an array in your Viewcontroller such as:
var dataArray = [MyDataItem]()
3) Load the data which you could do from the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
feedTable.dataSource = self
feedTable.delegate = self
loadData()
}
4) Implement loadData() function:
func loadData() {
// Here put in your alamo enclosure to retrieve the data and store it into the array you've defined
// When done, call reload data
feedTable.reloadData()
}
5) Your cellForRowAt function will need to be modified to retrieve the data from the array. For example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1 : ImageTableViewCell = tableView.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
cell1.fullName = dataArray[indexPath.row].name
cell1.title = dataArray[indexPath.row].title
cell1.location = dataArray[indexPath.row].location
return cell1
}
Anyway, this is the general idea on how to do what you are attempting. When reloadData is called from your loadData function, it will cause the tableview to reload from the array data correctly.
Hope this helps!
The problem is you do not return the cell, you simply make some async request with alamofire and return an instance of the cell from the closure.
func foo() -> Int { return 1 } ≠ func bar() -> Int { someClosure { return 1 } }
Firstly you need load the the data from https://www.example.com/api/posts/get_all_posts into some data model.
var models: [SomeTypeYouCreate] = []
func loadData() {
Alamofire.request(...).responseJSON { response in
self.models = /* Create array of `SomeTypeYouCreate` objects from response */
self.tableView.reloadData()
}
}
func func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = self.models[indexPath.row]
// configure cell with model
return cell
}
You cannot do it the way you're trying to. You're not returning a cell from cellForRowAt method, you're returning it in Alamofire callback closure. What you should do is to return the cell in your cellForRowAt method, and implement some sort of setup method for your UITableViewCell subclass and make your calls in there
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1 : ImageTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
// put your Alamofire code inside such function in your UITableViewCell subclass
cell.setup()
return cell
}
First and foremost, you are returning value in closure Alamofire.request. If you wanna use cell after you confirm cell values, you want to pass over completion handler to the function and use it in that Alamofire.reqeust...
But if I were you, I would create another function which is called before/after tableView function.
If it is Before then trigger tableview initialization upon alamofire completion.
If it is After then reload when values are loaded correctly in Alamofire.
EDITED:
Like other suggested,it is bad idea to load data in tableView function. Also, by using Alamofire, it means you use Closure. That is, whatever you wanna do in Alamofire happens asynchronously, meaning by the time what you want to achieve in Alamofire is done, your program can be out of the table view function. Also, since it is closure, returning value in Alamofire does not satisfy your tableView return type.
So basically, if you need data via API and verify, you declare function such that do whatever you doing Alamofire and then reload the tableView.
So flow is like this:
1) Make an empty array and put array.count to # of rows.
2) Since it is empty, when tableView first try to generate cells, it doesn't do anything.
3) You call the function which uses Alamofire. If returned values are good, then add the cell(model) to the array.
4) After you are done loading models, do tableView.reload().
5) Tableview calls tableView function now it finds value in array so that will create cells.
I've had a great help in creating a functional image cache for a UITableViewCell in cellForRowAtIndex. Unfortunately, with the code below, only one image is displayed over and over. I was under the impression that cellForRowAtIndexPath was like a for loop, running again for each row. Therefore, I'm wondering why only one image is displayed.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "restaurantcell") as? RestaurantTableCell
var oneRestaurant: Restaurant = tablerestaurantarray[indexPath.row]
if let cachedVersion = cache.object(forKey: "image") {
oneRestaurant = cachedVersion
} else {
cache.setObject(oneRestaurant, forKey: "image")
}
cell?.picture?.image = oneRestaurant.value(forKey: "image") as! UIImage?
let restaurant = restaurantArray[indexPath.row]
cell?.name?.text = restaurant.value(forKey: "Name") as? String
return cell!
}
Update 2:
Results from the added breakpoint
You use the same NSCache key ("image") for different objects. That's why only the first Restaurant object is saved into the cache. For all other cells you look for the object cached for key "image" and get the same previously saved Restaurant object back.
You have to use different keys for caching different Restaurant objects. Try to append the index path to the cache key:
let key = "restaurant \(indexPath)"
if let cachedVersion = cache.object(forKey: key) {
oneRestaurant = cachedVersion
} else {
cache.setObject(oneRestaurant, forKey: key)
}
I don't quite understand why you want to cache restaurant objects though. You already have them in the tablerestaurantarray, so there won't be any benefit caching them. Maybe your intention was caching the images?
I have a tableview which acts as a newsfeed. The cells are filled from an array of newsfeed items. I get the JSON from the Server, create newsfeed items from that input and attach them to my newsfeed array. a newsfeed item contains a title, a description and an imageurl string.
At:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "ImageFeedItemTableViewCell1", for: indexPath) as! ImageFeedItemTableViewCell
var item = self.feed!.items[indexPath.row]
if (item.messageType == 1){
cell = tableView.dequeueReusableCell(withIdentifier: "ImageFeedItemTableViewCell1", for: indexPath) as! ImageFeedItemTableViewCell
cell.title.text = item.title
cell.description.text = item.contentText
if (item.imageURL as URL == URL(string: "noPicture")!)
{
cell.picture.image = UIImage(named:"empty")
}
else{
if (item.cachedImage == UIImage(named:"default-placeholder")){
let request = URLRequest(url: item.imageURL as URL)
cell.picture.image = item.cachedImage
cell.dataTask = self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
OperationQueue.main.addOperation({ () -> Void in
if error == nil && data != nil {
let image = UIImage(data: data!)
if (image != nil){
self.feed!.items[indexPath.row].cachedImage = image!
}
cell.picture.image = image
}
})
})
cell.dataTask?.resume()
}else
{
cell.picture.image = item.cachedImage
}
}
}
the cells from the rows get filled with my newsfeeditem data.
But since i keep all my newsfeeditems inside an array, the memory usage is high and gehts higher for each additional newsfeeditem. I want it to work with endless scrolling like twitter, so i wonder how experienced developers tackle this memory issue.
Your problem is in this lines or wherever you try to hold UIImage inside your array, this is really not advised and will cause crash due to memory since image is very large data and not advised to persist it in your RAM with UIImage inside array:
self.feed!.items[indexPath.row].cachedImage = image!
What you need to do is basically after fetch your image from URL, you save it to your app's documents folder and save the name or it's path that can distinct your image in cachedImage (just change the type to string or sth) and refetch it from your app's document folder when you need to show it in cellForRow
Flow: Fetch image -> save to disk and persist path in array -> refetch from disk with the path in cellForRow
My Program's Flow
I have a Class called News Item. in it, I have a method that goes to a server and fetches JSON Data and creates a NewsItem instance that has each of the JSON data details in a for loop using the SWIFTYJson library as follows:
static func downloadNewsItems(completionBlock block : ([NewsItem]) -> ()) {
var newsItems = [NewsItem]()
let url = NSURL(string: "http://a-url-that-has-stuff")
let networkService = NetworkService(url: url!)
networkService.downloadData { (data) -> Void in
let jsonData = JSON(data:data)
for item in jsonData["stories"].arrayValue {
let newsArticle = NewsItem()
newsArticle.storyCategory = item["category"].string
newsArticle.titleText = item["title"].string
newsArticle.paragraph1 = item["paragraph1"].string
newsArticle.paragraph2 = item["paragraph2"].string
newsArticle.featureImage = item["headerImage"].string
newsArticle.storyDate = item["date"].string
newsArticle.majorReference = item["majorReference"].string
newsArticle.fact = item["fact"].string
newsItems.append(newsArticle)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
block(newsItems)
})
}
}
I have a collectionViewController that calls this method, gets the objects and appends them to an array which i then use to load the collection view data.
My Problem
If you look at the code, at this point, every newsArticle must have all the properties of the news item for this to work. I don't like this behaviour. I'm looking at adding more properties to the NewsItem class that aren't required but if available, should be loaded and properly instantiated. For example, I want to add a second image to the News Item class but not every news item will have a second image. If I add a news item that doesn't have 'titleText' for example, to the backend, the code will break.
I want to add this second image feature, and if a news item has a 'second image', then I want to instantiate a UIImageView and add it to the collectionView with this image.
I know I'm supposed to use optionals somehow but i can't quite crack it. I'm using Swift 3. Optionals, I must admit, have been the bane of my existence since i started swift. Any help would be appreciated. Danke!
EDIT
I'm implementing the newsItem class as follows:
class HomeViewController: UIViewController {
var newsItems = [NewsItem]()
override func viewDidLoad() {
super.viewDidLoad()
NewsItem.downloadNewsItems{ (newsItems) -> () in
self.newsItems = []
self.newsItems = newsItems
self.collectionView.reloadData()
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return newsItems.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! NewsCollectionViewCell
let story = newsItems[indexPath.item]
cell.configureCell(story)
return cell
}
}
The configureCell method just downloads anything that needs to be downloaded and then adds the text/images/links to the cell properties. Nothing special there.