I have two async methods that fetch the data from an API. One updates the attribute of earthLocationData, the second one uses an attribute (imageURL) of earthLocationData to download an image that will be saved on earthLocationData.image.
Once the image is downloaded I should change the imageView with that image.
I have this code:
var earthLocationData: EarthLocationData? {
didSet {
self.imageView.image = earthLocationData!.image
}
}
but it does not work because the earthLocationData.image is downloaded in an async way.
So is there a way to use the didSet on earthLocationData.image?
Related
I have been using MVC since my first app on iOS... now i want to try MVVM.
My approach is that a Model can contain the remote URL and the ViewModel makes the request to download the image. (pushing then to binded view)... I think this is suitable so as to avoid making a network request in Views (or even worse, cells!)
class Person: NSObject {
var firstName: String?
var lastName: String?
var avatarURL: URL?
}
class PersonEntryViewModel {
var name:String?
var avatarImage:UIImage?
init(person: Person?) {
super.init()
// omitted: binding self.name based on person.firstName & person.lastName
var request: URLRequest? = nil
if let avatarURL = person?.avatarURL {
request = URLRequest(url: avatarURL)
}
fetchImageFromNetwork({ response, data in
if let data = data {
avatarImage = UIImage(data: data)
}
})
}
}
What do you think?
My doubts are about memory. I could have a big array of viewmodels filled with UIImages...
If each cell is holding its own PersonEntryViewModel and that is getting discarded in the cell's prepare for reuse, then memory isn't a problem because images will be discarded at the same time.
I think it would be best not to put the download code in the init method. Better would be to have some sort of trigger method you can call when the view model is passed to the cell.
It really depends on what the larger architecture looks like.
If you want to see some examples of handling images in an Rx-MVVM-C app there are a couple of great ones in the RxSwift slack channel:
https://rxswift.slack.com/archives/CTSAM9V27/p1583148003073800
https://rxswift.slack.com/archives/CTSAM9V27/p1583149698079500
Join the discussion by going here: https://rxslack.herokuapp.com
Hi have a project which I implemented ImageSlideshow and sdwebimage. Images are gotten from an API call but in the docs of ImageSlideshow SDwebImages are implemented as below
let sdWebImageSource = [SDWebImageSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080")!, SDWebImageSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!, SDWebImageSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!]
which works well but for my own project I have all the images in an array and I want to display this images in the ImageSlideshow. I do not know the number of images so it is difficult to hard code it so how can I pass the array of images. ProductServices.instance.selectedProduct?.productImg[0]) this gives me the image at the first index. the images could be up to the fifth index. how can I pass this?
You can try
var allImages = [SDWebImageSource]()
if let allStr = ProductServices.instance.selectedProduct?.productImg {
for str in allStr {
allImages.append(SDWebImageSource(urlString:str))
}
}
if allImages.isEmpty {
// display custom image // or add it's default str
}
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.
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 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()
}