I'm using Alamofire to fetch data from server and then put them in an array of CarType objects which CarType is my struct. what I get from server is name , id and iconUrl. from iconUrls i want to download icons and put them in icon. after that I'll use icon and name in a collection view. my Alamofire request is:
var info = [CarType]()
Alamofire.request(.GET,"url")
.responseJSON { response in
for (_,subJson):(String, JSON) in json["result"]
{
let name = subJson["name"].string
let iconUrl = subJson["icon"].string
let id = subJson["id"].int
info.append(CarType(id: id!, name: name!, iconUrl: iconUrl! , image: UIImage()))
}
my struct is:
import Foundation
import UIKit
struct CarType {
var name : String
var id : Int
var iconUrl : String
var icon : UIImage
}
I want to download images before using them in collectionView.
How can i download images (using AlamofireImage) and put them in related carType icon property?
What you are asking is really bad practice in mobile app. Just a case, for example, you made a request and got like 20 items in an array, and in order to put all UIImages in your models, you have to make 20 more requests, also you even don't know if your users will eventually use (view) those icons or no.
Instead, you could fetch the images when the cell (I guess, you will be displaying those icons in a cell) is displayed, for this purpose you could use libs like SDWebImage(objective c) or Kingfisher(swift) which have extensions for UIImageView to make it simple to fetch and display image on. Those libs can also cache the downloaded image.
Also, another suggestion for object mapping. Currently, you are mapping json to your model manually. There are a lot of good libs to handle that for you, which could automate your object mapping process, for example - ObjectMapper
Hope, this was helpful. Good Luck!
I have done functionalities thing in UITableview, added following in CellForRowIndex method:
getDataFromUrl(urlString){(data,response,error) -> Void in
if error == nil {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data!)
// Store the image in to our cache
if((image) != nil){
// Store the image in to our cache
self.imageCacheProfile[urlString] = image
// Update the cell
DispatchQueue.main.async(execute: {
cell.imgvwProfile?.image = image
})
}
else{
cell.imgvwProfile!.image = UIImage(named: "user")
}
}
}
func getDataFromUrl(_ strUrl:String, completion: #escaping ((_ data: Data?, _ response: URLResponse?, _ error: NSError? ) -> Void)) {
let url:URL = URL(string: strUrl)!
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {data, response, err in
print("Entered the completionHandler")
}.resume()
}
You also need to declare imageCache to store downloaded images.
var imageCache = [String:UIImage]()
You can use above code in your method and it should work just.
Related
I'm quite new to Swift and currently dealing with the Firebase-Database.
I managed to realise the functions that I want to have, but my implementation feels not right.
Most I am struggling with the closures, that I need to get data from Firebase.
I tried to follow the MVC approach and have DataBaseManager, which is getting filling my model:
func getCollectionData(user: String, callback: #escaping([CollectionData]) -> Void) {
var dataArray: [CollectionData] = []
var imageArray:[String] = []
let db = Firestore.firestore()
db.collection(user).getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let album = document.get("album") as! String
let artist = document.get("artist") as! String
let genre = document.get("genre") as! String
let location = document.get("location") as! String
var a = CollectionData(album: album, artist: artist, imageArray: imageArray, genre: genre, location: location)
a.imageArray!.append(document.get("fronturl") as? String ?? "No Image")
a.imageArray!.append(document.get("backurl") as? String ?? "No Image")
a.imageArray!.append(document.get("coverlurl") as? String ?? "No Image")
dataArray.append(a)
}
callback(dataArray)
}
}
}
With this I'm getting the information and the downloadlinks, which I later use in a gallery.
Is this the right way?
I feel not, because the fiddling starts, when I fetch the data from my ViewController:
var dataArray = []
dataBaseManager.getCollectionData(user: user) { data in
self.dataArray = data
I can see, that I sometimes run into problems with timing, when I use data from dataArray immediately after running the closure.
My question is, this a valid way to handle the data from Firebase or is there a more elegant way to achieve this?
You are on the right track. However, using dataArray immediately is where the issue could be.
Let me provide a high level example with some pseudo code as a template:
Suppose you have an ToDo app; the user logs in and the first view they see is all of their current To Do's
class viewController
var dataArray = [ToDo]() //a class property used as a tableViewDataSource
#IBOutlet toDoTableView
viewDidLoad {
loadToDos()
}
func loadToDos() {
thisUsersToDoCollection.getDocuments() { documents in
self.array.append( the to Do Documents)
self.toDoTableView.reloadData()
}
}
}
With the above template you can see that within the Firebase .getDocuments function, we get the To Do documents from the colleciton, populate the dataSource array and THEN reload the tableView to display that data.
Following this design pattern will alleviate this issue
I sometimes run into problems with timing, when I use data from
dataArray immediately after running the closure
Because the user cannot interact with the data until it's fully loaded and populated within the tableView.
You could of course do a callback within the loadToDos function if you prefer so it would then be called like this - only reload the tableView once the loadToDos function has completed
viewDidLoad {
loadToDos {
toDoTableView.reloadData()
}
}
The big picture concept here is that Firebase data is ONLY VALID WITHIN THE CLOSURE following the Firebase call. Let that sequence provide pacing to your app; only display info if it's valid, only allow the user to interact with the data when it's actually available.
I'm working my way through the Stanford CS193P Course on iTunesU. Stuck on this one part - There's one section where the instructor has the following comment:
When a drop happens, you'll have to collect both the aspect ratio (from
the UIImage) and the URL before you can add an item. You could do this
straightforwardly with a couple of local variables that are captured by
the closures used to load up the drag and drop data
I assume this to mean that I have to update my model item in the performDrop method of the UICollectionViewDropDelegate protocol. Therefore I will have to call loadObject for both the NSURL item and the UIImage item. However, since the completion handlers for loadObject are off the main thread, I don't have access to the same local variable for both. Therefore if I first load the UIImage, get the aspect ratio, and save it to a local variable in the performDrop method, that variable is still empty when I call loadObject on the URL, which is when I create the model based on the URL and the aspect ratio. I tried nesting the loadObject calls, but the 2nd call was not firing. My code is below:
let placeholderContext = coordinator.drop(item.dragItem, to:
UICollectionViewDropPlaceholder(insertionIndexPath: destinationIndexPath, reuseIdentifier: placeholderCellReuseIdentifier))
var aspectRatio: [CGFloat] = []
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) { (provider, error) in
if var image = provider as? UIImage {
aspectRatio.append(image.size.width / image.size.height)
}
DispatchQueue.main.async {
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self, completionHandler: { (provider, error) in
print(provider)
if let error = error {
print(error.localizedDescription)
}
print("load urls in completion")
})
}
}
print("main thread: \(aspectRatio)")
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self) { (provider, error) in
print("loaded urls")
DispatchQueue.main.async {
if let url = provider as? URL {
placeholderContext.commitInsertion(dataSourceUpdates: { (insertionIndexPath) in
self.galleryItems.items.insert(GalleryItem(imageURL: url.imageURL, aspectRatio: 1.0), at: insertionIndexPath.item)
})
} else {
placeholderContext.deletePlaceholder()
}
}
}
Am I misguided here or is there something simple I am missing? How can I get the aspect ratio from loading the UIImage first, and then use that in the URL load object completion handler, in the way as is described in the homework?
I have imported my Parse db to Backendless and I'm seeing that my image content is in relation with data in string format, so when I try to fetch it shows only as string(URL).How can I call or convert url to UIImage.Thank you
See script below :
func fetching() {
let backendless = Backendless()
let query = BackendlessDataQuery()
backendless.persistenceService.of(Menu.ofClass()).find(query, response: { ( menu : BackendlessCollection!) -> () in
let currentPage = menu.getCurrentPage()
print("Loaded \(currentPage.count) Menu objects")
for menu in currentPage as! [Menu] {
print("Menu name = \(menu.name)")
self.name.text = menu.name
self.type.text = menu.type
print("Type = \(menu.type)")
print(menu.image)
}
},
error: { ( fault : Fault!) -> () in
print("Server reported an error: \(fault)")
})
}
You need to download the image file, in a similar manner to how you used to download the PFFile. It's generally a good idea to use a library like SDWebImage to manage the download and caching of the image for you.
I'm working on app to do paged photos inside scrollViews like in the photos app to swipe right to get the old photos and swipe left to get the new photos until the end of photos.
Alright,so i am getting the photos as a multi-diminutional from the web :
imageArray[indexPath.row][0] as? String
Thats in the ViewController1 to show all the images in CollectionView .
When the user press on a photo i do segue and show the image larger in ViewController2 so if swipe left it show the new photos and right to show the old photos which is stored in the array.
but i need to covert my two-dimensional array to one and use it dynamically to be something like this :
pageImages = [UIImage(named:"photo1.png")!,
UIImage(named:"photo2.png")!,
UIImage(named:"photo3.png")!,
UIImage(named:"photo4.png")!,
UIImage(named:"photo5.png")!]
how is it possible to do ?
i could say like :
pageImages = [UIimage(named:thewholearray)] ?
i tried first to convert to one-diminutional array but i failed :
var imageArray : NSArray = []
var mybigarray : NSArray = []
for (var i=0; i<=self.imageArray.count; i++) {
self.mybigarray = self.imageArray[i][0] as! NSArray
}
which generate this casting error :
Could not cast value of type '__NSCFString' (0x196806958) to 'NSArray' (0x196807308).
You can use the map-function to extract the image names from your multi-dimensional array.
var imageNames:[String] = imageArray.map({
$0[0] as! String
})
The map function iterates through all array entries like a for-in-loop.
The statement in the closure determines the new entry of your imageNames array.
EDIT:
If you don't use Swift-Arrays:
var imageNames:NSMutableArray = []
for image in imageArray{
imageNames.addObject(image[0] as! String)
}
I looked at the tutorial and I think this will help with the answer from Daniel. Keep Daniel's Answer and use the URLs from the array with the extension.
Add this to create you images from a URL
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if data != nil {
self.image = UIImage(data: data)
}
else {
self.image = UIImage()
}
}
}
}
}
Use this in the PagedImageScrollViewController:
newPageView.imageFromUrl(pageImages[page])
pageImages is your array of Strings.
I'm creating a app which has a UITableView. When the user selects one row, it transitions to a content page that has an image slider and its content. There can be minimum of 0 and maximum of 4 images per item.
When loading the inner page, I'm checking the image availability using this method:
func validateUrl (stringURL : NSString) -> Bool {
let url = NSURL(string: stringURL as String)
if (NSData(contentsOfURL: url!) == nil){
return false
}
else{
return true
}
}
It's a really simple method to get the proper output and it always works, but it takes lot of time and because of it I have to load images twice (one for this method and second time to display them).
Is there any better way to do this?
I would add to this method some code to catch the data that was received from
NSData(contentsOfURL: url!)
For example a map with the url as key and data as object. this way, to display the picture you first check the map and only if it's not there you download it
try:
if let data = NSData(contentsOfURL: url) {
return true
}else{
return false
}