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 :)
Related
I have two instances from the same struct in Swift. I need to find out key-values that have the same keys but different values.
For example:
struct StructDemo {
let shopId: Int
let template: String?
}
let a = StructDemo(shopId: 3, template: "a")
let a = StructDemo(shopId: 3, template: "different a")
// My expectation is to return the change pairs
let result = [template: "different a"]
My approach is as show below but comes errors.
static func difference(left: StructDemo, right: StructDemo) -> [String: Any]{
var result:[String: Any] = [:]
for leftItem in Mirror(reflecting: left).children {
guard let key = leftItem.label else { continue }
let value = leftItem.value
if value != right[key] { // This is the problem. Errror message: Protocol 'Any' as a type cannot conform to 'RawRepresentable'
result[key] = right[key]
}
}
}
Appreciate for any suggestion and solutions.
Thank you
The problem that you are seeing is that you referred to
right[key]
but right is a StructDemo and is not subscriptable. You can't look up fields given a runtime name. Well, you can with Mirror which you correctly used for left, but you did not mirror right.
Using Mirror will lead to other issues, as you will have expressions with static type Any where Equatable will be required in order to compare values.
IMHO, your best bet is to avoid a generic, reflective approach, and just embrace static typing and write a custom difference functions that iterates all the known fields of your type. Hard coding is not so bad here, if there is only one struct type that you are trying to diff.
If you have a handful of struct types each needing diffs then that might be a different story, but I don't know a good way to get around the need for Equatable. But if you have a ton of diffable types, maybe you want dictionaries to begin with?
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])
}
}
So i have gotten the task of converting a Microsoft App to IOS and I've stumbled upon a little issue that works in C# but not in swift 2.
What i want to do, is what the title says and heres the code.
private let secondsCircle = CAShapeLayer()
private let minutesCircle = CAShapeLayer()
private let hoursCircle = CAShapeLayer()
private var shapeLayers: [CAShapeLayer] = []
private let secondsGradientMask = CAGradientLayer()
private let minutesGradientMask = CAGradientLayer()
private let hoursGradientMask = CAGradientLayer()
private var gradientLayers: [CAGradientLayer] = []
The 2 different types i want to return, according to the right TYPE of array, is CAShapeLayer and CAGradientLayer. My Method that checks and "sorts" it out is here:
private func iterateThroughCollection(collection: Array<AnyObject>) -> Array<AnyObject> {
let newCollection: Array<AnyObject> = collection
if var getLayer = newCollection as? Array<CAShapeLayer> where getLayer.count == 0 {
getLayer.append(self.secondsCircle)
getLayer.append(self.minutesCircle)
getLayer.append(self.hoursCircle)
return getLayer
} else if var getGradients = newCollection as? Array<CAGradientLayer> where getGradients.count == 0 {
getGradients.append(self.secondsGradientMask)
getGradients.append(self.minutesGradientMask)
getGradients.append(self.hoursGradientMask)
return getGradients
}
return newCollection
}
The issue i have is when it returns the new Array it doesn't let me iterate through the different Elements:
My Error
And i really don't know why? I have the exact same thing working in C#. And i can't see why this shouldn't work
(Note: I have tried with [] instead of Array<>.
tried to return only 1 specific type with success but not with the second option)
“Arrays
An array stores values of the same type in an ordered list. The same value can appear in an array multiple times at different positions.”
Excerpt From: Apple Inc. “The Swift Programming Language (Swift 2.1 Prerelease).” iBooks. https://itun.es/us/k5SW7.l
In Swift the elements of an array must be of the same type. However if you use an NSArray or NSMutableArray from the Apple API, then you can store different types within the same collection.
re: NSArray documentation:
"The array representation in the file identified by aPath must contain only property list objects (NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary objects). For more details, see Property List Programming Guide. The objects contained by this array are immutable, even if the array is mutable."
After loading the array correctly you can loop through it in Swift using loops or functions like map, etc.
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
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)