My app is a slideshow that changes once a minute with a photo corresponding to each minute in the day. I currently have it checking the time and updating the image every tenth of second, as this will be on many devices at a large public event.
The relevant bits are:
#IBOutlet weak var theBrokenWatch: UIImageView!
if theBrokenWatch.image != UIImage(named: theImageName as String) {
theBrokenWatch.image = getUncachedImage(named: theImageName)
print("changed image to", theCurrentTime as String)
}
func getUncachedImage (named name : String) -> UIImage? {
if let imgPath = NSBundle.mainBundle().pathForResource(name, ofType: nil) {
return UIImage(contentsOfFile: imgPath)
}
return nil
}
Right now the if statement doesn't work. It did work when I was setting the image using imageNamed:, but that had major memory issues with 720 photos. (The CurrentTime is calculated and the matching image path built as theImageName.)
How can I check to see if the image needs to be changed (i.e. it's not what I just calculated as the current time)?
You don't want to use an uncached image to check for equality. Each time its generated, the image object will be different (the image data will be the same, but the wrapper object will be re-generated, and therefore not equal; image equality is not based of of actual bit contents).
Instead, key off the image name. Create a new var, imageName, and set that when you set the image. Then, when your timer fires, check your cached imageName against the new one, and only change the .image var if they're different.
Related
I am getting image from a url using SDWebImage and assigning it to an array like so...
let imgUrl = arrProduct?[indexPath.section].images[indexPath.row].url
let placeholderImage = UIImage(named: "appLogo.jpg")
cell.prdImgView.sd_setImage(with:imgUrl,
placeholderImage:placeholderImage,
options: []) { (image, error, imageCacheType, imageUrl) in
arrayOfSelectedImages.append(image!)
}
Now I just don't want to add to an array like this. Instead, after adding the image to arrayOfSelectedImages I want to update this array value in didSet and empty the arrayOfSelectedImages array so that every time the array gets a new value, it updates that value in didSet and & arrayOfSelectedImages is emptied. So finally my array in didSet will have all the images I need and I can pass those images on to some other view...How can I achieve this..?
Not entirely sure if this is what you want, but didSet will fire on a property that is an array if you modify the array, not just if you assign the array. Here is an example:
struct A
{
var anArray = [1, 2, 3]
{
didSet
{
print("Hi, there!")
anArray.remove(at: 0)
}
}
}
var a = A()
a.anArray.append(4)
// Prints Hi there!
print(a.anArray)
// prints [2, 3, 4]
The task is quite straight-forward to accomplish. You need a valid criteria to compare appended objects and, what's important, criteria you apply before appending object to an array, not after that. Using didSet to verify appended object and delete it if unsuitable, is bad design.
If your UIImage objects are not encapsulated within any other object or struct to uniquely id these objects and if you don't have an option whether or not particular image should be downloaded at all (which is the best and most proper practice), you can compare two UIImage objects by comparing underlying image data. This could previously be accomplished by obtaining PNG representation of an image and comparing that data, but now there's a good simple method.
Comparing Images
The isEqual(:) method is the only reliable way to
determine whether two images contain the same image data. The image
objects you create may be different from each other, even when you
initialize them with the same cached image data. The only way to
determine their equality is to use the isEqual(:) method, which
compares the actual image data. Listing 1 illustrates the correct and
incorrect ways to compare images.
https://developer.apple.com/documentation/uikit/uiimage
Usage
if !arrayOfSelectedImages.contains(where: { $0.isEqual(image) }) {
arrayOfSelectedImages.append(image)
}
So far I have created a folder of images and I am able to assign 1 of the images to a UIImageView.
But what I want to do now is create a random generator, that will pick a random image from the folder, so if I was to create a button, I could display all of the images separately and randomly on a single UIImageView.
However, this folder is going to have more than 100 images, so I don't want to hard code each one into an array. Also, the names of the image has to be unique to the image itself.
I have been searching online but cannot find information on how to code what will suit my needs. So how would I get and display a random image(s) from a folder in swift?
You can name your images like this
image_0
image_1
image_2
Then use this code to populate your UIImageView
let numberOfImages: UInt32 = 100
let random = arc4random_uniform(numberOfImages)
let imageName = "image_\(random)"
imageView.image = UIImage(named: imageName)
Update
Since you don't want to rename your images you can create a plist file (type Array) where you put all the names of your images
And finally
func loadRandomImage() {
let path = NSBundle.mainBundle().pathForResource("Images", ofType: "plist")!
let names = NSArray(contentsOfFile: path) as! [String]
let random = Int(arc4random_uniform(UInt32(names.count)))
let imageName = names[random]
imageView.image = UIImage(named: imageName)
}
You either need to create an array of string filenames and pick a random filename from the array, or you need to use the file manager (NSFileManager) to get a list of the files in your folder and then pick from THAT array. Nether is very difficult.
In my app I download a load of JSON.
I then store that as an array of structs and use that to populate a UITableView.
One of the properties of the struct is an NSURL for an image. Another property is an optional UIImage.
The struct has a mutating function downloadImage which uses the URL to download the image and store it in its property.
Like this...
struct SearchItem {
// other properties...
let iconURL: NSURL
var icon: UIImage?
mutating func downloadImage() -> Task<UIImage> {
let tsc = TaskCompletionSource<UIImage>()
NSURLSession.sharedSession().downloadTaskWithURL(iconURL) {
(location, response, error) in
if let location = location,
data = NSData(contentsOfURL: location),
image = UIImage(data: data) {
self.icon = image
tsc.setResult(image)
return
}
tsc.setError(NSError(domain: "", code: 1, userInfo: nil))
}.resume()
return tsc.task
}
}
The problem I'm having is this. (and I have been stumped by this in the past).
I have an array [SearchItem] that I use to populate the tableview.
In cellForRow I have the code... if let searchItem = items[indexPath.row]...
It then checks if the image is nil and downloads...
if let image = searchItem.icon {
cell.imageView.image = image
} else {
searchItem.downloadImage().continueOnSuccessWith(Executor.MainThread) {
_ in
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
But this never goes through to put the image into the cell. This is because the SearchItem is struct and so pass-by-value. So the search item that I am downloading the image for is not the same SearchItem as the one stored in the array.
How can I ensure that the image that is downloaded is then stored into the SearchItem inside the actual array?
Use classes.
You're getting a copy of searchItem in your cellForRow method. Whatever you do to this, will be done only to that copy. What you actually want is for the changes you make to that copy to be applied to the version in the array.
Therefore you want reference semantics, therefore use classes.
You could dance around re-inserting the updated copy into the original array if you liked, but what does that gain you besides a line of extra code and probably some other problems.
Structs are lightweight data objects that are not passed by reference, but instead copies itself as needed when you a) pass it to a new function, b) try and access it in a block. Arrays in Swift also work slightly differently than their Obj-C counterparts. When you have an Array of class objects the array will be a reference type, and you'll be able to achieve what you're trying to achieve here. But on the other hand if the Array is of Structs the array looses its reference semantics and uses copy-by-value instead.
This difference is really powerful when used appropriately, you can greatly optimise your code, make it run faster, have less errors produced by mutable object references having changes happen in multiple parts of your code, etc. But it's up to you as a developer to see where the gains of these optimisations are useful or where it makes sense to use objects instead.
I have an enum with get-only property image. It seems like every time I read it, it keeps this image in memory and deletes it only after memory warning (since I use multiple enum instances and display their images in a sequence) even after I set another image to the UIImageView it was displayed on.
How do I force them to be removed from memory?
Well, the best way to not overload the memory is not to cache UIImages in the first place, which can be done with:
extension UIImage{
static func getUcachedImage(named name: String) -> UIImage?{
let path = NSBundle.mainBundle().pathForResource(name, ofType: nil)!
let image = UIImage(contentsOfFile: path)
return image
}
}
This is how I solved my problem.
I am converting a project from another tool and language:
Suppose I have a
singleimagecache: UIImage;
I now pass this to a structure which does
var myimage: UIImage = singleimagecache;
Now, this struct is passed to a function that does some work.
This function determines another image should be cached. In my original code, it would simply use myimage and assign its content some other image-bitmap data. Since the object-reference itself was not changed (only its content) singleimagecache would still point to valid fresh new cache data.
However, this is not possible in Swift since UIImage requires to be reconstructed like this:
myimage = UIImage(...)
But doing that would leave singleimagecache with wrong data
So that leaves me the following options:
Any support in Swift for keeping references in sync?
Any support in Swift for keeping pointers to objects (that themselves possibly can be nillable)
Wrap UIImage inside another object that is persistant and use that.
There is no built-in Swift support for what you wish to do. I would just make a wrapper class with 2 UIImage properties. One would be myimage and the other would be singleimagecache. You could then use the didSet property observer on myimage to achieve the desired synchronization so that singleimagecache will always be up to date with myimage. didSet will be called everytime a new value is stored/set in the property.
class imageCache
{
var myimage:
= UIImage() {
didSet {
singleimagecache = myimage
}
}
var singleimagecache = UIImage()
}