How can I set the value of this variable? - ios

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)

Related

How can I make an UIImageView change depending on the chosen number?

I'm creating an app which chooses two random cards out of a 52-card deck. Then, if one of the card is strong (in my case, strong cards are "10" or stronger) I want it to show an image "yes" (tick). If both of the cards are weak, then I want it to show an image "no" (cross). I've been trying to find a problem, but every time I change something, a new type of error occurs.
I tried to set an unknown output type in func resultOfShuffle, I tried creating and naming an outlet for my UIImageView couple of times.
let cardArray = ["1.png", (...), "52.png"]
// All of the cards, from 2 to Ace (with every color). Number "33" is a card 10.
...
let cardResults = ["yes.png"]
...
#IBOutlet weak var theResult: UIImageView!
...
func randomizeCards() {
chooseCardOne = Int.random(in: 0 ... 51)
chooseCardTwo = Int.random(in: 0 ... 51)
...
func resultOfShuffle(firstCard : Int, secondCard : Int) -> UIImageView {
if firstCard > 33 {
return theResult.image = UIImage(named: cardResults)
}
}
And now, the return of the last func resultOfShuffle is wrong - telling me: Use of unresolved identifier 'theResult'. I also tried to find the solution to this problem, but it is kinda tricky and I don't get it.
That's how my app looks like:
https://imgur.com/a/kjdcqcO
Is every statement executed in the same ViewController?
It should recognize theResult as a UIImageView, then.
Update
The problem is, as solved in the comments, based on the scope of the function declaration. Since it is declared outside of the class where theResult is defined, it can't get access to it.
The solutions could be passing the variable as an argument to the function, or declaring the function in the same scope of the variable - inside the ViewController.
Other notes
You are, anyway, trying to return something with this line:
theResult.image = UIImage(named: cardResults)
which doesn't evaluate to a type, but simply sets the image of the view to what's in cardResults. Therefore, you shouldn't return anything, but merely using this function in order to update the content of the view - this means you should use a Void return type.
Furthermore, you are passing to the image initializer a [String] type, while you should just pass a String.
Try something like this:
func resultOfShuffle(firstCard : Int, secondCard : Int) {
if firstCard > 33 {
theResult.image = UIImage(named: cardResults[0])
}
}

iOS UI Test: Get image filename (Swift)

My question is about getting the filename of image of the UIImage that is used in a UIImageView.
Here is my sample code:
// Using image from UIImageObject
imageView1.image = myUIImage
// Using image from XAssets
imageView2.image = UIImage(named: "myImageName")
In UI Tests, how can I get the name of the image file?
The expected resoult would be:
"myUImageObject" // For imageView1
"myImageName" // For imageView2
Is there any way to get this value?
Thanks everyone!
Unfortunately you can't do that, however there's an easy workaround, try something like this:
let myImageView = UIImageView()
myImageView.image = UIImage(named: "anyImage")
myImageView.restorationIdentifier = "anyImage" // Same name as image's name!
// Later, in UI Tests:
print(myImageView.restorationIdentifier!) // Prints "anyImage"
Basically in this solution you're using the restoration identifier to hold the image's name, so you can use it later anywhere. If you update the image, you must also update the restoration identifier, like this:
myImageView.restorationIdentifier = "newImageName"
I hope that helps you, good luck!
Swift 4
Declare Outlet
#IBOutlet weak var m_SetName_Img: UIImageView! //Image is already set image from Assets File
Declare Function
extension UIImageView {
func getImageName() -> String {
if let image = self.image, let imageName = image.accessibilityIdentifier {
return imageName
} else {
return "nil"
}
}
}
Use
print(self.m_SetName_Img.getImageName())
You can use:
imageView2.image?.imageAsset?.value(forKey: "assetName")
assetName is private API, so officially out of bounds for the App Store, but for testing it should be just fine.

What is payload data in iOS?

I'm debugging my program to check if the value of a property is correctly set. I put a breakpoint in this function:
func showContent(data: Any) -> UIView {
// breakpoint here
var contentView = UIView()
if let image = data as? UIImage {
let imageView = UIImageView()
imageView.image = image
contentView = imageView
}
if let text = data as? String {
let label = UILabel()
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.text = text
contentView = label
}
return contentView
}
The value passed to this function is from a view controller:
override func viewDidLoad() {
calcGroupFamiliarity()
flashCardView.linkedMemory = Memory(masteryLevel: 1, algorithm: Algorithm.algorithm1.chooseAlgorithm(), forgetRatio: 0, lastStudyTime: Date(), front: #imageLiteral(resourceName: "Ideas-Blue"), back: #imageLiteral(resourceName: "Ideas-Yellow"))
}
As you can see, both the front and the back are images, however, in the debugger, they both appeared as some payload_data, while the data type of other values such as masteryLevel and algorithm are correct:
Can somebody explain what that means? And what should I do to pass the normal image data instead?
Update:
This is Memory class:
class Memory: NSObject, NSCoding {
var masteryLevel: Int
var algorithm: [Int: Double]
var forgetRatio: Int
var lastStudyTime: Date
var strength: Double = 0
var front: Any
var back: Any
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask)[0]
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Memory")
init(masteryLevel: Int, algorithm: [Int: Double], forgetRatio: Int, lastStudyTime: Date, front: Any, back: Any){
self.masteryLevel = masteryLevel
self.algorithm = algorithm
self.forgetRatio = forgetRatio
self.lastStudyTime = lastStudyTime
self.front = front
self.back = back
}
...
}
This is a detail of how Swift implements the Any type. Given that Swift can't know beforehand what you are putting into e.g. front, it needs to store a pointer to that object (in payload_data_0) as well as a pointer to metadata about the stored object's type (in instance_type). As far as I know, payload_data_1 and payload_data_2 are there as an optimization so that Swift can store up to 24-byte large structs in place rather than having to put them in a wrapper object that needs to be stored in a separate heap location.
So, to answer your question: This is neither a bug in Swift, nor an error on your side. Your data gets stored and accessed in the right way. If you would prefer to inspect front more easily while debugging, try
(lldb) po front
in the debugger. Alternatively, change front's type to e.g. UIImage?. If that is not possible, you could declare a protocol
protocol MemoryStoreable: class { }
and extend every type that needs to be stored in those fields like so:
extension UIImage: MemoryStoreable { }
and declare the variable as
var front: MemoryStoreable?
Note that MemoryStoreable is restricted to classes, as otherwise a protocol witness would need to be stored (which is again implemented similarly to the Any field you observed).
Another alternative would be, if you know you'll store e.g. only images and strings in front, to use an enum:
enum MemoryStoreable {
case `string`(String)
case image(UIImage)
}
var front: MemoryStoreable?
That will still require at least 25 bytes of storage in your object (possibly 32 due to memory alignment constraints), though, as that's the size of the String struct.
For more details, see e.g. https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html.
Base on #MrMage's awesome answer.
#MrMage gave 2 suggestions on approaching this problem, it took some
effort for me to grasp them, for what I wanted to achieve in the
program, it's the perfect scenario for me to go
protocol-oriented-programming.
Here's a really easy to understand tutorial that help me a lot:
Introduction to Protocol Oriented Programming in Swift
Hopefully this can be helpful to you :)

What if I want to assign a property to itself?

If I attempt to run the following code:
photographer = photographer
I get the error:
Assigning a property to itself.
I want to assign the property to itself to force the photographer didSet block to run.
Here's a real-life example: In the "16. Segues and Text Fields" lecture of the Winter 2013 Stanford iOS course (13:20), the professor recommends writing code similar to the following:
#IBOutlet weak var photographerLabel: UILabel!
var photographer: Photographer? {
didSet {
self.title = photographer.name
if isViewLoaded() { reload() }
}
}
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
func reload() {
photographerLabel.text = photographer.name
}
Note: I made the following changes: (1) the code was switched from Objective-C to Swift; (2) because it's in Swift, I use the didSet block of the property instead of the setPhotographer: method; (3) instead of self.view.window I am using isViewLoaded because the former erroneously forces the view to load upon access of the view property; (4) the reload() method (only) updates a label for simplicity purposes, and because it resembles my code more closely; (5) the photographer IBOutlet label was added to support this simpler code; (6) since I'm using Swift, the isViewLoaded() check no longer exists simply for performance reasons, it is now required to prevent a crash, since the IBOutlet is defined as UILabel! and not UILabel? so attempting to access it before the view is loaded will crash the application; this wasn't mandatory in Objective-C since it uses the null object pattern.
The reason we call reload twice is because we don't know if the property will be set before or after the view is created. For example, the user might first set the property, then present the view controller, or they might present the view controller, and then update the property.
I like how this property is agnostic as to when the view is loaded (it's best not to make any assumptions about view loading time), so I want to use this same pattern (only slightly modified) in my own code:
#IBOutlet weak var photographerLabel: UILabel?
var photographer: Photographer? {
didSet {
photographerLabel?.text = photographer.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
photographer = photographer
}
Here instead of creating a new method to be called from two places, I just want the code in the didSet block. I want viewDidLoad to force the didSet to be called, so I assign the property to itself. Swift doesn't allow me to do that, though. How can I force the didSet to be called?
Prior to Swift 3.1 you could assign the property name to itself with:
name = (name)
but this now gives the same error: "assigning a property to itself".
There are many other ways to work around this including introducing a temporary variable:
let temp = name
name = temp
This is just too fun not to be shared. I'm sure the community can come up with many more ways to do this, the crazier the better
class Test: NSObject {
var name: String? {
didSet {
print("It was set")
}
}
func testit() {
// name = (name) // No longer works with Swift 3.1 (bug SR-4464)
// (name) = name // No longer works with Swift 3.1
// (name) = (name) // No longer works with Swift 3.1
(name = name)
name = [name][0]
name = [name].last!
name = [name].first!
name = [1:name][1]!
name = name ?? nil
name = nil ?? name
name = name ?? name
name = {name}()
name = Optional(name)!
name = ImplicitlyUnwrappedOptional(name)
name = true ? name : name
name = false ? name : name
let temp = name; name = temp
name = name as Any as? String
name = (name,0).0
name = (0,name).1
setValue(name, forKey: "name") // requires class derive from NSObject
name = Unmanaged.passUnretained(self).takeUnretainedValue().name
name = unsafeBitCast(name, to: type(of: name))
name = unsafeDowncast(self, to: type(of: self)).name
perform(#selector(setter:name), with: name) // requires class derive from NSObject
name = (self as Test).name
unsafeBitCast(dlsym(dlopen("/usr/lib/libobjc.A.dylib",RTLD_NOW),"objc_msgSend"),to:(#convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
unsafeBitCast(class_getMethodImplementation(type(of: self), #selector(setter:name)), to:(#convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
unsafeBitCast(method(for: #selector(setter:name)),to:(#convention(c)(Any?,Selector,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
_ = UnsafeMutablePointer(&name)
_ = UnsafeMutableRawPointer(&name)
_ = UnsafeMutableBufferPointer(start: &name, count: 1)
withUnsafePointer(to: &name) { name = $0.pointee }
//Using NSInvocation, requires class derive from NSObject
let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(#convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(#convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(#convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
var localVarName = name
withUnsafePointer(to: &localVarName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(#convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
let test = Test()
test.testit()
There are some good workarounds but there is little point in doing that.
If a programmer (future maintainer of the code) sees code like this:
a = a
They will remove it.
Such a statement (or a workaround) should never appear in your code.
If your property looks like this:
var a: Int {
didSet {
// code
}
}
then it's a not a good idea to invoke the didSet handler by assignment a = a.
What if a future maintainer adds a performance improvement to the didSet like this?
var a: Int {
didSet {
guard a != oldValue else {
return
}
// code
}
}
The real solution is to refactor:
var a: Int {
didSet {
self.updateA()
}
}
fileprivate func updateA() {
// the original code
}
And instead of a = a directly call updateA().
If we are speaking about outlets, a suitable solution is to force the loading of views before assigning for the first time:
#IBOutlet weak var photographerLabel: UILabel?
var photographer: Photographer? {
didSet {
_ = self.view // or self.loadViewIfNeeded() on iOS >= 9
photographerLabel?.text = photographer.name // we can use ! here, it makes no difference
}
}
That will make the code in viewDidLoad unnecessary.
Now you might be asking "why should I load the view if I don't need it yet? I want only to store my variables here for future use". If that's what you are asking, it means you are using a view controller as your model class, just to store data. That's an architecture problem by itself. If you don't want to use a controller, don't even instantiate it. Use a model class to store your data.
I hope one day #Swift developers will fix this miscuzzi :)
Simple crutch:
func itself<T>(_ value: T) -> T {
return value
}
Use:
// refresh
style = itself(style)
image = itself(image)
text = itself(text)
(optionals including)
Make a function that the didSet calls then call that function when you want to update something? Seems like this would guard against developers going WTF? in future
#vacawama did a great job with all those options. However in iOS 10.3, Apple banned some of these ways and most likely will be doing it in the future again.
Note: To avoid the risk and future errors, I will use a temporary variable.
We can create a simple function for that:
func callSet<T>(_ object: inout T) {
let temporaryObject = object
object = temporaryObject
}
Would be used like: callSet(&foo)
Or even a unary operator, if there is a fitting one ...
prefix operator +=
prefix func +=<T>(_ object: inout T) {
let temporaryObject = object
object = temporaryObject
}
Would be used like: +=foo

How to find value of UIImage called in ViewDidLoad, pass to another function in Swift

I am using a simple function to call a random image when a view controller is loaded.
override func viewDidLoad() {
super.viewDidLoad()
var randomtest = ReturnRandom()
mainImage.image = UIImage(named: randomtest)
}
I want a button's action to be based on what image was displayed. For example
#IBAction func Button1(sender: AnyObject) {
switch randomtest {
case 1: //Do something here
case 2: //Do something different here
default:
}
However I can not figure out how to get the value of "randomtest" out of ViewDidLoad. I also can not seem to create the random number outside the ViewDidLoad then pass the variable in. Sorry I'm new to all this, iOS development is a long way away from php...
The reason I was having trouble declaring the result of my function as a instance variable is that my function is also an instance function. I can not call it, there has been no instance. So this
class ViewController : UIViewController {
var randomtest: Int = ReturnRandom();
Will return a "Missing argument for parameter #1 in call"
For more details please check out this very helpful thread on setting initial values. Since my function was so simple I just computed the property while setting the initial value, no need for an additional class level function.
dynamic var randomtest:String {
let imageNumber = arc4random_uniform(3)
var imageString = String(imageNumber)
return (imageString)}
Hope this helps someone.

Resources