In this function, I need to change the image of the image view, moving it onto the next image in the array.
private func updateImage(index: Int) {
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.iv.image = self.imagesArray.object(at: 0) as! UIImage
}
}else{
print("ERROR - \(error!)")
}
})
}
}
However, currently, the image being used is set as the image at index 0 in my array. How would I move onto the next image if this function is called multiple times?
This is the line that I mainly need help with:
self.iv.image = self.imagesArray.object(at: 0) as! UIImage
UPDATE: I've tried to add the code Charly Pico suggested to my functions but it doesn't seem to be moving onto the next image in the array, I think it's to do with how it's being called. I'm going to go into detail in how my file is constructed and works to hopefully clarify how I want to use the change image code.
var urlArray:NSMutableArray! = NSMutableArray()
var imagesArray:NSMutableArray! = NSMutableArray()
These arrays which hold the URL's and Images are outside of any function.
func GetTheStories() {
let ref = FIRDatabase.database().reference().child("Content")
ref.observe(FIRDataEventType.value, with: {(snapshot) in
self.fileArray.removeAll()
if snapshot.childrenCount>0{
for content in snapshot.children.allObjects as![FIRDataSnapshot]{
let contentInfo = content.value as? [String: String]
let contentType = contentInfo?["contentType"]
let storyLink = contentInfo?["pathToImage"]
let content = storyLink
self.urlArray = NSMutableArray.init(array:[storyLink])
}
}
})
}
This is the function I call to append the URL's from my Firebase server to the URLSARRAY.
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.iv.image = self.imagesArray.object(at: 0) as! UIImage
}
}else{
print("ERROR - \(error!)")
}
})
}
}
This is the function Charly created and explained which allows me to set the initial image on the ImageView when the screen loads
private func updateImage(index: Int) {
var imageToPresent = 0
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.removeAllObjects()
self.imagesArray.add(UIImage(data: imageDta!)!)
if self.imagesArray.count > 0
{
for x in 0..<self.imagesArray.count
{
if self.iv.image == self.imagesArray.object(at: x) as! UIImage
{
imageToPresent = x + 1
if imageToPresent >= self.imagesArray.count
{
imageToPresent = 0
}
}
}
self.iv.image = self.imagesArray.object(at: imageToPresent) as! UIImage
}
}else{
print("ERROR - \(error!)")
}
})
}
}
This is the function being called to update the image to the next one in the array. However, I believe it's not working either because my attempt to apply Charly's code below is not correct. This function is called in the ViewDidLoad like so
updateImage(index: 0)
And it is called to update the image here. This function is called when the Segmented progress bar changes to the next one.
func segmentedProgressBarChangedIndex(index: Int) {
print("Now showing index: \(index)")
updateImage(index: index)
}
I know it's a bit confusing as to why I'm not using buttons and actions, this is mainly because I'm trying to create an Instagram-style story, with segments that when finished, or the screen is tapped, change the image to the next in the array. I know there's a lot to look over but I think this will explain how I want to use the images array in greater detail.
so, lets say that your imagesArray contains 2 images, what I recommend doing is the following:
//Create a #IBAction and attach it to a UIButton in your Storyboard as a touchUpInside action.
#IBAction func changeImage()
{
//Set a variable with Int as 0.
var imageToPresent = 0
//Create a for to check image by image inside the array.
for x in 0..<imagesArray.count
{
//Compare the image at self.iv with the image in imagesArray at index x. And if the images are the same add a + 1 to imageToPresent.
if self.iv.image == imagesArray.object(at: x) as! UIImage
{
imageToPresent = x + 1
//Check if imageToPresent isn't bigger or equal than imagesArray, otherwise we will get an error and the App will crash.
if imageToPresent >= imagesArray.count
{
//If imageToPreset if bigger or equal to imagesArray.count, it means that there are no more images at a higher index, so we return imageToPresent to 0.
imageToPresent = 0
}
}
}
//Set the new image of imagesArray at index imageToPresent as UIImage to self.iv.
self.iv.image = imagesArray.object(at: imageToPresent) as! UIImage
}
Every time you want to change self.iv image you need to touch the button for the action to be triggered.
UPDATE
So I added a NSTimer to simulate that in 3 seconds your Progress View finishes animating, after the 3 seconds it rotates the image to another one, you can add this code after the for that loads all the images from the urls:
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { (timer) in
self.changeImage()
}
And first of all update your urlArray to this:
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","https://www.videomaker.com/sites/videomaker.com/files/styles/magazine_article_primary/public/articles/18984/363%20C03%20Lighting%20primary.png"])
So you have at least 2 images to make the image rotation.
change the line to use the index being passed into your function like this
self.iv.image = self.imagesArray.object(at: index) as! UIImage
This function is trying to do a lot of things at once, so it's hard to tell how to make it do what you want, but, you do have an index parameter. Using that instead of the 0 here:
if self.imagesArray.count > index
{
self.iv.image = self.imagesArray.object(at: index) as! UIImage
}
Would wait for that image to load and then set that to the view.
NOTE: you need to check that the index is within bounds
Related
I'm trying to retrieve images from array of url..
I have this function that do the same as I won't but it doesn't work so I tried to use URLSession but didn't know how exactly to make it >>
func downloadImages(imageUrls: [String], completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
for link in imageUrls {
let url = NSURL(string: link)
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
downloadQueue.sync {
downloadCounter += 1
let data = NSData(contentsOf: url! as URL)
if data != nil {
//image data ready and need to be converted to UIImage
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
DispatchQueue.main.async {
completion(imageArray)
}
}
} else {
print("couldnt download image")
completion(imageArray)
}
}
}
}
The function I work on :
public func imagesFromURL(urlString: [String],completion: #escaping (_ images: [UIImage?]) -> Void) {
var imageArray: [UIImage] = []
var downloadCounter = 0
let downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
for link in urlString {
downloadQueue.sync {
downloadCounter += 1
let dataTask = URLSession.shared.dataTask(with: NSURL(string: link)! as URL, completionHandler: { (data, response, error ) in
if error != nil {
print(error ?? "No Error")
return
}
if data != nil {
imageArray.append(UIImage(data: data! as Data)!)
if downloadCounter == imageArray.count {
completion(imageArray)
}
} else {
print("couldnt download image")
completion(imageArray)
}
} dataTask.resume()
}
}
}
i want to call the function in the collection cell and get the display the first image only from each artwork array..
//download the first image only to display it:
if artwork.ImgLink != nil && artwork.ImgLink.count > 0 {
downloadImages(imageUrls: [artwork.ImgLink.first!]) { (images) in
self.artworkImage.image = images.first as? UIImage
}
}
If you intend to use only the first available UIImage from an array of urls, you do not design a function trying to download all of them. Instead, try to download from the first url, return the downloaded UIImage if it succeeds, or continue with the second url if it fails, repeat until you get an UIImage.
Creating a DispatchQueue in a local function looks dangerous to me. A more common practice is to maintain a queue somewhere else and pass it to the function as a parameter, or reuse one of the predefined global queues using DispatchQueue.global(qos:) if you don't have a specific reason.
Be careful with sync. sync blocks the calling thread until your block finishes in the queue. Generally you use async.
Use a Int counter to control when to finish multiple async tasks (when to call the completion block) works but can be improved by using DispatchGroup, which handles multiple async tasks in a simple and clear way.
Here's two functions. Both work. firstImage(inURLs:completion:) only return the first UIImage that it downloads successfully. images(forURLs:completion:) tries to download and return them all.
func firstImage(inURLs urls: [String], completion: #escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
for urlString in urls {
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
return
}
}
}
DispatchQueue.main.async {
completion(nil)
}
}
}
// Use it.
firstImage(inURLs: artwork.ImgLink) { image in
self.artworkImage.image = image
}
func images(forURLs urls: [String], completion: #escaping ([UIImage?]) -> Void) {
let group = DispatchGroup()
var images: [UIImage?] = .init(repeating: nil, count: urls.count)
for (index, urlString) in urls.enumerated() {
group.enter()
DispatchQueue.global().async {
var image: UIImage?
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
image = UIImage(data: data)
}
}
images[index] = image
group.leave()
}
}
group.notify(queue: .main) {
completion(images)
}
}
// Use it.
images(forURLs: artwork.ImgLink) { images in
self.artworkImage.image = images.first(where: { $0 != nil }) ?? nil
}
I try to create an Array of UIImages from a URL Array received from a JSON Request to show them afterwards in a UITableView.
But somehow my UIImage Array stays Empty and is not receiving any Data.
The other Arrays for example memeURL are receiving all Data correct but memePics.count stays on 0.
Would be great if someone could show me what i am doing wrong.
Also if for this task there is a better way on how to do it - it would be also appreciated!
Var:
var memePics: [UIImage] = []
Loop to add Images to Array:
while(i < memeURL.count) {
MemeAPI.requestAPIImageFile(url: memeURL[i]) { (image, error) in
guard let image = image else {
print("PIC IS NIL")
return
}
self.memePics.append(image)
i+=1
}
}
RequestAPIImageFile Function:
class func requestAPIImageFile(url: URL, completionHandler: #escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completionHandler(nil, error)
return}
let downloadedImage = UIImage(data: data)
completionHandler(downloadedImage, nil)
}
task.resume()
}
Add plus line out of callback
self.memePics.append(image)
}
i+=1
and use DispatchGroup to be notified on finish like
let g = DispatchGroup()
memeURL.forEach {
g.enter()
MemeAPI.requestAPIImageFile(url:$0) { (image, error) in
guard let image = image else {
print("PIC IS NIL")
return
}
self.memePics.append(image)
g.leave()
}
}
g.notify(queue:.main) {
print("All done")
}
I am trying to retrieve pictures saved from the database (in this case I hard coded the exact picture I want from the database). I have two UIImage variables initiated: imageView and rawImage. imageView is using SDWebImage from FirebaseUI to load the image, but I don't know how to save it into a UIImage array.
rawImage is grabbing the data from the path of the exact image from Firebase. I am converting it to a UIImage but when I want to append it to my UIImage array, the application crashes.
class GalleryViewController:UIViewController{
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var rawImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
view.addVerticalGradientLayer(topColor: primaryColor, bottomColor: secondaryColor)
self.tabBarController?.tabBar.isHidden = true
retrievePicture()
}
func retrievePicture()
{
guard let user_email = Auth.auth().currentUser?.email else { return }
let storageRef = Storage.storage().reference()
// Reference to an image file in Firebase Storage
let reference = storageRef.child("\(user_email)").child("/top").child("1.jpg")
// UIImageView in your ViewController
let imageView: UIImageView = self.imageView
// Placeholder image
let placeholderImage = UIImage(named: "placeholder.jpg")
//UploadViewController.Clothing.top_images.append(<#UIImage#>)
// Load the image using SDWebImage
imageView.sd_setImage(with: reference, placeholderImage: placeholderImage)
// this is the code for the rawImage portion I described above
reference.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print("Error \(error)")
} else {
let picture = UIImage(data: data!)
UploadViewController.Clothing.top_images.append(picture!)
}
}
// loading the image to the rawImage imageview
rawImage.image = UploadViewController.Clothing.top_images[0]
}
}
If you already load the images using SDWebImage then you can also append the image in your UIImage array with that.
Just use SDWebImage with a completed block like this
var arr_image = [UIImage]()
imageView.sd_setImage(with: reference_url, placeholderImage: UIImage(named: "placeholder.jpg"), options: options: [.highPriority]) { (img, error, cachtype, url) in
if img != nil{
arr_image.append(img) // append UIimage in array
}
}
You have overlooked force unwrapping optionals, and I propose to you to remove ! usage to avoid crashes, use following instead
reference.getData(maxSize: 1 * 1024 * 1024) { data, error in
guard let data = data else {
print(error?.localizedDescription ?? "Check remote url or service")
return
}
if let picture = UIImage(data: data) {
UploadViewController.Clothing.top_images.append(picture)
} else {
print("Invalid image data\n\(String(data: data, encoding: .utf8))")
}
}
or following if statement is safe
if error == nil && data != nil, let picture = UIImage(data: data!) {
UploadViewController.Clothing.top_images.append(picture)
}
You can also try path without leading slash, like so:
let reference = storageRef.child("\(user_email)/top/1.jpg")
I have the following imagePickerController delegate function, every time I select an image it replaces the previous urlString of the uploaded image to Firebase Storage, instead of inserting at the second to last array.count - 1 position (by design as I use the last entry to dequeue a different cell in the collectionview) of the images array. Resulting in the last image urlString always being replaced by the latest one.
I must add, if I upload one image at a time by navigating away from the current VC then returning and uploading another image again then it works as expected.
The result of the print statements in the code are as follows:
Upload image 1:
0. ADDING images.count: 3
1. ADDING images.count: 4
2. ADDING images.count: 4
images.last item: Optional("dummy string")
Upload image 2 (image count has not increased):
0. ADDING images.count: 4
1. ADDING images.count: 4
2. ADDING images.count: 4
images.last item: Optional("dummy string")
Function:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
self.dismiss(animated: true, completion: nil)
Utilities.run.showSVHUD(uiView: self.view, status: "Saving")
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
guard let uid = Auth.auth().currentUser?.uid else { return }
print("0. ADDING images.count: \(self.images.count)")
DataService.run.uploadProfileImageToFirebaseStorage(image: selectedImage) { (success, urlString) in
self.images.insert(urlString, at: self.images.count - 1)
print("1. ADDING images.count: \(self.images.count)")
let slice = self.images.dropLast()
let urlArray = Array(slice) //Converting Slice to Array
let dictionary = urlArray.indexedDictionary //converting to Dictionary
DataService.run.updateProfileImagesValueForUser(uid: uid, imagesUrls: dictionary, handler: { (success) in
if success {
Utilities.run.dismissSVHUD(delay: 0.5)
print("2. ADDING images.count: \(self.images.count)")
print("images.last item: \(String(describing: self.images.last))")
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
}//end if success
})// end updateProfileImagesValueForUser
}//end uploadProfileImageToFirebaseStorage
}//end if let selectedImage = selectedImageFromPicker
}//end func
images array is initiated from the User object passed which contains an array of String objects.
func initData(forUser user:User) {
self.user = user
}//end func
setupviews() is called in viewWillAppear() which initialises images array
func setupViews(){
images = user!.profileImages
images.append("dummy string")
let imageString = user!.profilePictureURL
let url = URL(string: imageString)//create URL from string
profileImg.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground])
}//end func
I am working with Parse and would like to download images for offline use. I understand that this is not possible with Local Datastore so I have decided to add them to Core Data.
I have successfully downloaded the PFFiles and put them in to an Array. I am then trying to create an Array for the NSData, but the Array count is always 0 when I use the code below
class DealsDownloadViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var trailID = [Int]()
var trailStep = [Int]()
var dealNumber = [Int]()
var imageFile = [PFFile]()
var imagesArray = [UIImage]()
var imageDataArray = [NSData]()
var number = 0
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let dealsQuery = PFQuery(className: ("Deals"))
dealsQuery.orderByAscending("TrailId")
dealsQuery.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.trailID.append(object["TrailID"] as! Int)
self.trailStep.append(object["TrailStep"] as! Int)
self.dealNumber.append(object["dealNumber"] as! Int)
self.imageFile.append(object["dealImage"] as! PFFile!)
}
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
self.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
} else {print("error here")}
})
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
}
} else {print("problem making arrays")}
}
}
If I move the Print statement up, I just get it printing every iteration of the loop.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
self.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
} else {print("error here")}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
})
}
} else {print("problem making arrays")}
}
}
In this case I can see that the data is added to both the imagesArray and imageDataArray.
This seems like such a simple issue but I am going crazy over it. What am I doing wrong, and is this the most efficient way of adding this data to Core Data? Am I overlooking something obvious?
I am new to programming so please do point out any mistakes I have made, and I am especially new as a questioner to stackoverflow (you have been indispensable while learning) so please let me know if you need any information that I have missed.
Thanks for your help.
Update 1
I have tried editing the code as explained in the comments and I am still getting the same result. I have moved the Print statement around on this code and it is still giving me the same results as above.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
weak var aBlockSelf = self
let image = UIImage(data: imageData!)
aBlockSelf!.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
})
}
} else {print("problem making arrays")}
}
}
Am I missing something very simple? Thanks again for your help.
Update 2
This is the same code with (I think) the print statements moved outside of the For Loop. This is giving me counts of 9,9,9,0,0 from the print statements, whereas I think I should be expecting 9,9,9,9,9.
for file in self.imageFile {
let dealImage = file
dealImage.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
weak var aBlockSelf = self
let image = UIImage(data: imageData!)
aBlockSelf!.imageDataArray.append(imageData!)
self.imagesArray.append(image!)
}
})
}
print(self.trailID.count)
print(self.trailStep.count)
print(self.dealNumber.count)
print(self.imageDataArray.count)
print(self.imagesArray.count)
} else {print("problem making arrays")}
}
}
There is no issue here!
You are being deceived by the way asynchronous block works. Asynchronous block gets queued up to get executed some point later in next run loop.
Your first print statement is just after you pass the code to block which is yet to be executed. Which is why you do see your image array empty.