Swift keep pointer/reference to object or another a pattern? - ios

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()
}

Related

Issue with setting didSet

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)
}

How to get notified when an attribute is updated in Swift?

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?

Updating a property in a struct inside an array

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.

Variable is not nil but code thinks it's nil

I have a class that implements a protocol in order to add 3 variables. I specifically set the image variable, debugger show that the variable exist, but in code when I print it it shows nil, my if let statement also thinks the variable is nil.
#objc protocol DashboardItem {
var title: String { get }
var detail: String { get }
optional var image: UIImage { get }
}
class StaticDashboardItem: DashboardItem {
var title: String
var detail: String
var image: UIImage?
init(title: String, detail: String, image: UIImage) {
self.title = title
self.detail = detail
self.image = image
}
}
EDIT: New screenshot
Logs
nil
2
Your StaticDashboardItem does not fully implement your DashboardItem protocol. It conforms to it but does not implement the var image: UIImage { get } variable, which he has every right not to implement because this conformance is optional. So your StaticDashboardItem does not have any property/variable image that is coming from that protocol.
BUT, instead, you have added another, completely unrelated property var image: UIImage? to your StaticDashboardItem, and you unfortunately gave it the same named, hence your confusion. But that property image on your StaticDashboardItem is not the same as the one from your DashboardItem protocol and that's where your confusion comes from.
If that var image: UIImage? from your StaticDashboardItem is intended to be the implementation of that non-mandatory image property from your protocol then the types must match, so that property from your StaticDashboardItem must be of type UIImage, not UIImage?.
If that var image: UIImage? from your StaticDashboardItem is intended to be a completely different property unrelated to your protocol, then you better use a different name to avoid confusion.
[EDIT] Now that you've updated your question to show more code that confirms my assumptions. As your local dashboardItem parameter in your configure method is declared as a DashboardItem (so, the protocol), then dashboardItem.image refers to the protocol's property (as that code doesn't know about StaticDashboardItem of course, it only know the protocol), which in your case does not exist (you have no property named image of type UIImage in that dashboardItem you're introspecting) explaining that println result nil and that else branch being executed, that's all to be expected given your code.
The only thing that misleads you is the debugger tooltip that also shows you avery other properties of the object being inspected, even properties not limited to the ones from your DashboardItem protocol type, so including that image: UIImage? property and any other coming from the StaticDashboardItem real type of your object.
You are being misled by the way the debugger works. In your screen shot, we are paused before the variable whose value you are examining. Thus, your tool tip showing the value of dashboardItem is not necessarily accurate. The value of dashboardItem is valid for lines before the line where we are paused, not for lines after the line where we are paused.

How can I set the value of this variable?

I want to change the value of a UIImage view.
This is what I've got so far.
import UIKit
import Foundation
struct imageViews {
var Slope1ImageView: UIImage?
var slopeStatus = dataValues()
func setSlope1Image(slopeStatus1: Int) -> UIImage {
var image: String
switch slopeStatus1 {
case 0:
image = "NotWorkingStatus"
default:
image = "WorkingStatus"
}
var statusImage = UIImage(named: image)
return statusImage
}
}
This my setup.
I have a file which gets an object from Parse.
This will either be 0 or 1.
I then assign the 0 or the 1 to a variable in a struct.
In my code above I have created a instance of this struct.
I have then created a function which will take in a variable from my struct and check if it has the value of zero, it then sets the variable image to the respective image. If it doesn't it sets it to a different image.
How do I set the variable of Slope1ImageViews to the image selected by the function in this way.
Slope1ImageView = setSlope1Image(slopeStatus.SlopeStatus1)
each time I try I get an error along the lines of
cannot assign to "Slope1ImageView" in "self"
I'm at my wits end!
Any help would be greatly appreciated.
I think your problem is with Slope1ImageView. If the first letter is uppercased, Swift assumes you're referring to a class. Furthermore, you need to assign the local to either a variable or a constant. Structs should also have the first letter uppercased. Assuming everything else is set up correctly, you probably just need to do something like this.
struct ImageViews {
// existing code...
}
var instanceOfStruct = ImageViews()
var slope1imageView: UIImage = instanceOfStruct.setSlope1Image(slopeStatus.SlopeStatus1)
There's a lot going on here, I'll touch on some.
Declaration
You shouldn't be using a struct. Images are rather large objects in general and you don't want them to be copied across your app. They should remain reference types and be in a class. Structs/Classes should also be capitalized to adhere to common practice, while properties should be lower case. You also have your name as a plural which implies that it is an array, it shouldn't be this way. Also, don't name something imageView unless its an imageView. On that same line, don't name something image if it's a string. Also, don't prefix a function with set unless it sets something. Your function returns something, so a get prefix is probably better. I feel like you want to set the image within self, so let's do that. So first step is to change this:
struct imageViews {
var Slope1ImageView: UIImage?
var slopeStatus = dataValues()
func setSlope1Image(slopeStatus1: Int) -> UIImage {
var image: String
switch slopeStatus1 {
case 0:
image = "NotWorkingStatus"
default:
image = "WorkingStatus"
}
var statusImage = UIImage(named: image)
return statusImage
}
}
To this:
class SlopeStatus {
var slope1Image: UIImage?
var slopeStatus = dataValues()
func setSlope1Image(slopeStatus1: Int) {
var imageString: String
switch slopeStatus1 {
case 0:
image = "NotWorkingStatus"
default:
image = "WorkingStatus"
}
slope1Image = UIImage(named: image)
}
}
Assignment
You're trying to assign a UIImage to a Class with this:
Slope1ImageView = setSlope1Image(slopeStatus.SlopeStatus1)
With the changes above, it should probably be
var slopeStatusImageObject = SlopeStatus()
slopeStatusImageObject.setSlope1Image(slopeStatus.SlopeStatus1)

Resources