I have appended an array with URL's like so
[FirebaseTest.storiesContent(storyUrl: https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/Content%2F20170525130622.jpg?alt=media&token=1e654c60-2f47-43c3-9298-b0282d27f66c), FirebaseTest.storiesContent(storyUrl: https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/20170525131400.mp4?alt=media&token=30fd962d-c305-4fa4-955d-dbb06ef91623), FirebaseTest.storiesContent(storyUrl: nil)]
In order to create this array, I am using a structure as a basis in order to append the content
struct storiesContent {
var storyUrl : String!
}
However, I am unsure how I would grab these URL's from this array in order to repeatedly download each image with SDWebImage in order to append each image to an array of UIImage's. I'm very new to Swift so my understanding is limited.
I really haven't used Firebase, but for what I understand here, you want to download the image from each link and save all the images in an array. You can achieve that by doing this:
//Get all URLS in an NSArray
let urlsArray:NSArray = ["https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/Content%2F20170525130622.jpg?alt=media&token=1e654c60-2f47-43c3-9298-b0282d27f66c","https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/20170525131400.mp4?alt=media&token=30fd962d-c305-4fa4-955d-dbb06ef91623"]
//Create a NSMutableArray where the final images will be saved.
let imagesArray:NSMutableArray! = NSMutableArray()
//Create a for that checks every link in the urlsArray.
for x in 0..<urlsArray.count
{
//Set the urlsArray content at position x as a URL
let imageUrl:URL = URL(string: urlsArray.object(at: x) as! String)!
//Generate a request with the current imageUrl.
let request:URLRequest = URLRequest.init(url: imageUrl)
//Start a NSURLConnection and get a Data that represents your image.
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main, completionHandler: { (response, imageDta, error) in
//Store the received data as an UIImage.
let imageReceived:UIImage = UIImage(data: imageDta!)!
//Save the image to our imagesArray.
imagesArray.add(imageReceived)
})
//The Process loops until you get all the images.
}
UPDATE
Sure you can, the only thing here is that I removed your last object from your array because it contains a nil object and Swift can't candle nil objects:
//Using the NSArray style you're using.
let yourFireBaseArray = [FirebaseTest.storiesContent(storyUrl: https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/Content%2F20170525130622.jpg?alt=media&token=1e654c60-2f47-43c3-9298-b0282d27f66c), FirebaseTest.storiesContent(storyUrl: https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/20170525131400.mp4?alt=media&token=30fd962d-c305-4fa4-955d-dbb06ef91623)]
//Create a NSMutableArray where the final images will be saved.
let imagesArray:NSMutableArray! = NSMutableArray()
//Create a for that checks every link in the yourFireBaseArray.
for x in 0..<yourFireBaseArray.count
{
//Get your current array position string as a storiesContent object
let fireBaseString:storiesContent = yourFireBaseArray.object(at: x) as! storiesContent
//Use your fireBaseString object, get the storyURL string and set it in an URL.
let imageUrl:URL = URL(string: fireBaseString.storyURL)!
//Generate a request with the current imageUrl.
let request:URLRequest = URLRequest.init(url: imageUrl)
//Start a NSURLConnection and get a Data that represents your image.
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main, completionHandler: { (response, imageDta, error) in
//Store the received data as an UIImage.
let imageReceived:UIImage = UIImage(data: imageDta!)!
//Save the image to our imagesArray.
imagesArray.add(imageReceived)
})
//The Process loops until you get all the images.
}
UPDATE 2
Okay, this is my example project, copy and paste and it will give you the resulted image.
import UIKit
class ViewController: UIViewController {
#IBOutlet var image:UIImageView!
var urlArray:NSMutableArray! = NSMutableArray()
var imagesArray:NSMutableArray! = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
urlArray = NSMutableArray.init(array: ["https://firebasestorage.googleapis.com/v0/b/motive-73352.appspot.com/o/Content%2F20170525130622.jpg?alt=media&token=1e654c60-2f47-43c3-9298-b0282d27f66c"])
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
for x in 0..<urlArray.count
{
let imageUrl:URL = URL(string: "\(urlArray.object(at: x) as! String)")!
let request:URLRequest = URLRequest.init(url: imageUrl)
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main, completionHandler: { (response, imageDta, error) in
if (error == nil)
{
self.imagesArray.add(UIImage(data: imageDta!)!)
if self.imagesArray.count > 0
{
self.image.image = self.imagesArray.object(at: 0) as! UIImage
}
}else{
print("ERROR - \(error!)")
}
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
To retrive Data from Firebase add propertylist file to your project it will hold the reference to your data base in firbase.
//Declare an array of string
var myArray = [String]()
//call this when your view load or in viewDidLoad()
ref = FIRDatabase.database().reference()
ref?.child("path").observe(.value, with: { (snapshot) in
if let snap = snapshot.value as? [String]{
self.myArray = snap
}
})
Then menupulate your array as you like.
Related
I am downloading a couple of images and cache them. Then I am recalling the cached images and add them as a subView to my scrollView (which is horizontal using a page controller). I want to do both of that when view appears without any other actions required. Unfortunately, it already adds the subview before the images were cached, because it goes through the code while the images are getting downloaded. It follows that nothing is added as a subView.
It works when I download and cache the images when the view appears and I add them as a subView when I press a button, but I would like for that to work automatically.
I am not sure if my code helps for a better understanding but here it is.
This is my code to download the images
func downloadImage(url: URL) {
let dataTask = URLSession.shared.dataTask(with: url) { data, responseURL, error in
var downloadedImage:UIImage?
if let data = data {
downloadedImage = UIImage(data: data)
}
// if download actaully got an image
if downloadedImage != nil {
self.sum += 1
self.cache.setObject(downloadedImage!, forKey: url.absoluteString as NSString) // cache the image
// add the url as value to the dictionary
self.imageURLS[self.sum] = url.absoluteString
}
}
dataTask.resume()
}
And this is what I use to add the subviews
func appendImages(){
// sort the values of the dictionary by the greatest
let sortedImageURLs = Array(imageURLS.keys).sorted(by: >)
var stringURLs = [String]() // empty string array to append the URL's
// for loop which goes through all integers in "creation Date" array
for keys in sortedImageURLs {
let url: String? = imageURLS[keys] // url which is connected to the key
stringURLs.append(url!) // append the url to the url array
}
// -> the url string array starts with the latest loaded url
var originSubview = 0 // counter of the origin of the subviews
for urls in stringURLs {
// 1.
frame.origin.x = scrollView.frame.size.width * CGFloat(originSubview)
frame.size = scrollView.frame.size
// 2.
let imageView = UIImageView(frame: frame)
// get the image which is cahed under the url
let image = cache.object(forKey: urls as NSString)
// set the image of image view as the cached image
imageView.image = image
// and add that image view as a subview to the scrollView
scrollView.addSubview(imageView)
// increase counter variable by one
//-> next images origin is at the end of the image before
originSubview += 1
}
// 3.
scrollView.contentSize = CGSize(width: ((scrollView.frame.size.width) * CGFloat(specialsCounter)), height: (scrollView.frame.size.height))
scrollView.delegate = self
pageControl.numberOfPages = specialsCounter
}
You can use a DispatchGroup to notify when all your downloads have completed.
Basically you enter() before you start a download and leave when the download has finished. notify will trigger when all entered tasks has left
Here is an example on how to do it. I made a completion block for your downloadImage function.
let dispatchGroup = DispatchGroup()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for url in imageUrls {
self.dispatchGroup.enter()
downloadImage(url: url) {
self.dispatchGroup.leave()
}
}
self.dispatchGroup.notify(queue: .main) {
//All images has been downloaded here
}
}
downloadImage function with completion:
func downloadImage(url: URL, completion: () -> ()) {
let dataTask = URLSession.shared.dataTask(with: url) { data, responseURL, error in
var downloadedImage:UIImage?
if let data = data {
downloadedImage = UIImage(data: data)
}
// if download actaully got an image
if downloadedImage != nil {
self.sum += 1
self.cache.setObject(downloadedImage!, forKey: url.absoluteString as NSString) // cache the image
// add the url as value to the dictionary
self.imageURLS[self.sum] = url.absoluteString
completion()
} else {
completion()
}
}
dataTask.resume()
}
I got an error while reading json content from remote url and printing on main interface in iOS Simulator.
MBP13"2016 && Mojave 10.14.6 && xcode 10.3(10G8) && swift 5
Here is the code sample. I had change a bit from "https://www.simplifiedios.net/swift-json-tutorial/"
import UIKit
class ViewController: UIViewController {
//the json file url
let URL_POSTS = "https://demo.ghost.io/ghost/api/v2/content/posts/?key=22444f78447824223cefc48062";
//A string array to save all the names
var nameArray = [String]()
//the label we create
#IBOutlet weak var labelTest: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//calling the function that will fetch the json
getJsonFromUrl();
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//this function is fetching the json from URL
func getJsonFromUrl(){
//creating a NSURL
let url = NSURL(string: URL_POSTS)
//fetching the data from the url
URLSession.shared.dataTask(with: (url as URL?)!, completionHandler: {(data, response, error) -> Void in
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
//printing the json in console
print(jsonObj.value(forKey: "posts")!)
//getting the companies tag array from json and converting it to NSArray
if let heroeArray = jsonObj.value(forKey: "posts") as? NSArray {
//looping through all the elements
for heroe in heroeArray{
//converting the element to a dictionary
if let heroeDict = heroe as? NSDictionary {
//getting the name from the dictionary
if let name = heroeDict.value(forKey: "name") {
//adding the name to the array
self.nameArray.append((name as? String)!)
}
}
}
}
OperationQueue.main.addOperation({
//calling another function after fetching the json
//it will show the names to label
self.showNames()
})
}
}).resume()
}
func showNames(){
//looing through all the elements of the array
for name in nameArray{
//appending the names to label
labelTest.text = labelTest.text! + name + "\n";
}
}
}
From the result above, it seems ok that I had found content result in console while fetching json from remote url , but nothing shows up on main interface with iOS simulator.
json result in console
main interface nothing show up in IOS Simulator
I've had some kind of this problem with JSON keys and NSDictionary usage. What worked out for me was just using Decodable protocol. I would recommend using this new API for parsing instead of JSONSerialization.
Here's a few good reads:
https://medium.com/swiftly-swift/swift-4-decodable-beyond-the-basics-990cc48b7375
https://medium.com/xcblog/painless-json-parsing-with-swift-codable-2c0beaeb21c1
I have read a similar post in Reusable cell old image showing and I am still getting the same issues. Essentially I have a TableView that downloads images from amazon s3 as you scroll down everything works good until you get to about the 12th or 13th image. What happens is that the image in the row before shows up in the new row for about 2 seconds while the new image is being downloaded . This is my code ( I'm still new at swift and learning IOS) . The stream_image_string as the full URL to download the images and PicHeight is an integer saved with the image height since every image usually has a different height .
var Stream_Cache = NSCache<AnyObject, AnyObject>()
var stream_image_string = [String]()
var PicHeight = [Int]()
This below is inside the UITableViewCell, first I check if there is a url which it will contain more than 0 characters . I then check if the image/url is saved in the Cache if not then I download it .
if stream_image_string[indexPath.row].characters.count > 0 {
if let image = Stream_Cache.object(forKey: stream_image_string[indexPath.row] as AnyObject) as? UIImage {
DispatchQueue.main.async(execute: { () -> Void in
cell.stream_image.image = image
})
} else {
if cell.stream_image != nil {
let strCellImageURL = self.stream_image_string[indexPath.row]
let imgURL: NSURL = NSURL(string: strCellImageURL)!
let request:NSURLRequest = NSURLRequest(url: imgURL as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
cell.Stream_Image_Height.constant = CGFloat(Float(cell.pic_height!))
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
DispatchQueue.main.async(execute: { () -> Void in
if data != nil {
cell.stream_image.image = UIImage(data: data!)
} else {
cell.Stream_Image_Height.constant = 0
cell.stream_image.image = nil
}
})
});
task.resume()
}
}
} else {
cell.Stream_Image_Height.constant = 0
}
In my UITableViewCell file I set the image to a default image in case it wasn't done loading the new image but it hasn't worked
class HomePageTVC: UITableViewCell {
#IBOutlet weak var stream_image: UIImageView!
var pic_height: Int?
override func awakeFromNib() {
super.awakeFromNib()
stream_image.image = #imageLiteral(resourceName: "defaultImage")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
any suggestions would be great
You're facing a pretty common cell reuse issue. When you dequeue a cell that was used before, it may already have an image installed in its image view. Set the image to nil before beginning an async download:
if let imageString = stream_image_string[indexPath.row].characters,
!imageString.isEmpty {
if let image = Stream_Cache.object(forKey: imageString) as? UIImage {
cell.stream_image.image = image
} else {
//Clear out any old image left in the recycled image view.
cell.stream_image.image = nil
//Your code to download the image goes here
}
}
Note that there's no need to wrap the cell.stream_image.image = image code in a call to DispatchQueue.main.async(). That code will be run on the main thread.
You do, however, need the second DispatchQueue.main.async() wrapper around the code inside the dataTask's completionHandler, since URLSession's completion handers are called on a background queue by default.
I am trying to append image with its data in Array after downloading and then try to save in NSUserDefaults. But getting an error. I dont know what is proper way to save and read it .
Can anyone please tell me how i can do this?
Thanks
var imgIndex = 0
var imageArray : [UIImage] = []
typealias CompletionHandler = (image: UIImage) -> Void
downloadFileFromURL(NSURL(string: self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String)!, completionHandler:{(img) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.sideImageView.image = img
imageArray.insert(img, atIndex: self.imgIndex) //.append(img)
self.imgIndex++
print("Image append with data")
self.newsDefaults.setObject(imageArray, forKey: "image")
})
})
func downloadFileFromURL(url1: NSURL?,completionHandler: CompletionHandler) {
// download code.
if let url = url1{
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let data = NSData(contentsOfURL: url)
if data != nil {
print("image downloaded")
completionHandler(image: UIImage(data: data!)!)
}
}
}
}
You can't add images to user defaults, they aren't supported. You'll need to convert the image to data and save that instead (either to user defaults or, better, onto disk in a file...
imageArray.insert(UIImageJPEGRepresentation(img, 0.75), atIndex: self.imgIndex)
I am following a tutorial about getting images from the web and storing them on the phone in Swift. For my purpose, I would like to know how I could only store them for one 'session', which means until the user stops using the app. The reason is that I want to change the image of the url every day.
Anyone any idea?
#IBOutlet var overLay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
// self.overLay.image = image
var documentsDirectory:String?
var paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if paths.count > 0 {
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/overLay.jpg"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
self.overLay.image = UIImage(named: savePath)
}
}
})
}
thank you so much!
Since you're only interested in keeping the image for the lifecycle of the app, it's perfectly viable to just hold a pointer to a UIImage object in memory, likely via some long-living object (AppDelegate would be a possible choice here).
Since you already have a UIImage from the data coming down the pipe, I'd simplify your code as such, or if you want to use some Singleton like the AppDelegate to manage the image state, see what happens when iWantToUseAppDelegate is set to true
#IBOutlet var overLay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let iWantToUseAppDelegate = false // for demonstration purposes
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
if iWantToUseAppDelegate {
let appDelegate = UIApplication.sharedApplication().delegate as! YourAppDelegateClass // YourAppDelegateClass has some property called "cachedImage"
appDelegate.cachedImage = image
self.overLay.image = appDelegate.cachedImage
} else {
self.overLay.image = image
}
}
})
}
You may need to tweak a few things but this code might work a little easier.
Used what mindfreek add to correct the code.
#IBOutlet var overLay: UIImageView!
var defaults: NSUserDefaults = NSUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
NSUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(image!), forKey: "image")
if let imagedSaved: AnyObject = defaults.valueForKey("image")
{ overLay.image = image }
else { NSKeyedUnarchiver.unarchiveObjectWithData(NSUserDefaults().dataForKey("image")!) as UIImage }
}
})