I have a class in Swift called Button:
enum ButtonBackgroundColor {
case None
case Yellow
}
class Button: UIButton {
var backgroundColorType: ButtonBackgroundColor?
}
And then I'm setting a runtime attribute:
However, I get a non-descriptive crash. The stack trace mentions something about key value coding.
Runtime attributes do work however for non enum types, like Int or Float:
// runtime attributes work for this
var backgroundColorType: Int = 0
Is there something wrong with the way I'm declaring my enum?
Given that your enum ButtonBackgroundColor includes a .None case, why not avoid declaring backgroundColorType in Button as an optional? Doing so needlessly complicates the declaration. Prefer
class Button: UIButton {
var backgroundColorType = ButtonBackgroundColor.None
}
Besides being more idiomatically correct, the above might, just might, fix your 'user defined attribute' issue now that the variable is not an optional.
You also might need to provide raw values in your enum. Like:
enum ButtonBackgroundColor : Int {
case None = 0
case Yellow = 1
}
if you expect the runtime attribute value of '0' to perhaps map to .None
#GoZoner's answer didn't work for me. Tried changing the raw type to String and that didn't work as well. My solution is to define another variable (of whichever type supported by the runtime attributes) and then override the didSet of that variable to set the enum.
So in code:
var IBButtonBackgroundColor = "None"
{
didSet
{
switch self.IBButtonBackgroundColor
{
case "None":
self.backgroundColorType = .None
break
case "Yellow":
self.backgroundColorType = .Yellow
break
default:
self.backgroundColorType = .None
break
}
}
}
var backgroundColorType:ButtonBackgroundColor = .None
I don't like having to repeat things in the switch/case, but I can't think of any better solution for now.
Related
This is an example of our use case:
We have a selectedIndex and a list of items.
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let items: [Foo] = ... // assume that this is initialized properly
}
In reality, we often care about which item is selected instead of the index of the selected item. So we'll have code like this:
selectedIndex.asObservable().subscribe(onNext: { [weak self] index in
guard let self = self else { return }
let selectedItem = items[index]
// Do sth with `selectedItem` here
}
Notice that the value of selectedItem is always driven by selectedIndex. Therefore, we change the code to the following:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem = Variable<Int>(items[0])
let items: [Foo] = ... // assume that this is initialized properly
init() {
selectedIndex.asObservable().subscribe(onNext: { [weak self] index in
guard let self = self else { return }
self.selectedItem = items[index]
}
}
}
This seems to be a common enough use case. Do we have an existing operator in Rx that can map a Variable to another? Is there sth like this:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem = selectedIndex.map{ items[$0] }
let items: [Foo] = ... // assume that this is initialized properly
}
What you have done is created two bits of state that are dependent on each other. It would be better to just have one source of truth and a derivative which means that one should be implemented differently than the other. Assuming that selectedIndex is the source of truth, then I would expect to see:
class FoosViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem: Observable<Foo?>
let items: [Foo]
init(items: [Foo]) {
selectedItem = selectedIndex.asObservable().map { index in
index < items.count ? items[$0] : nil
}
self.items = items
}
}
Unlike in your attempt, there is no temptation for a user of this class to try to assign a new value to selectedItem (in fact, the code won't even compile if you try.) As a side benefit, there is no need to do the "weak self dance" either since the map doesn't refer to self at all. All of this works because you made items a let rather than a var (good for you!)
If you wanted to be able to add/remove items then things get a bit more complex...
class MutableFooViewModel {
let selectedIndex = Variable<Int>(0)
let selectedItem: Observable<Foo?>
let items = Variable<[Foo]>([])
init(items: [Foo]) {
items.value = items
let _items = self.items // this is done to avoid reference to `self` in the below.
selectedItem = Observable.combineLatest(
_items.asObservable(),
selectedIndex.asObservable()
) { items, index in
index < items.count ? items[index] : nil
}
}
}
The idea here is that Subjects (Variable is a kind of subject) should not be the first thing you think of when making an Observable that depends on some other observable. In this respect, they are only good for creating the initial observable. (RxCocoa is full of them.)
Oh and by the way, Variable had been deprecated.
The problem I'm having is that my property's willSet and didSet are being called even though I'm only reading the property, and this breaks my app.
I condensed my problem into a playground. Uncomment #1 to see the problem, or #2 to see the expected behavior.
What's going on here?
protocol Departure
{
var line: String? { get }
}
class MyDeparture : Departure
{
var line: String? = "SomeString"
}
#if true
// #1: this causes a write to tableContet later (!)
typealias TableSection = (title: String, rows: [Departure])
#else
// #2: this doesn't cause a write to tableContent later
typealias TableSection = (title: String, rows: [MyDeparture])
#endif
var tableContent: [TableSection] = [ TableSection(title: "MySectionTitle", rows: [ MyDeparture() ]) ]
{
willSet { print("willSet tableContent") }
didSet { print("didSet tableContent") }
}
func getDepartureDescription() -> String? {
print("start getDepartureDescription")
defer { print("end getDepartureDescription") }
#if true
// writes to tableContent in case #1
let lineNumber = tableContent[0].rows[0].line
#else
// never writes to table content
let row = tableContent[0].rows[0]
let lineNumber = row.line
#endif
return "Your line is \(lineNumber)"
}
getDepartureDescription()
This prints
start getDepartureDescription
willSet tableContent
didSet tableContent
end getDepartureDescription
I'm using Xcode 7 (7A218) GM seed. Everything worked as expected in Xcode 6.4 and Swift 1.2.
Side note:
At first I thought that the runtime was--on reading TableSection.rows--creating a new [Departure] array from the [MyDeparture] array that was assigned to it. But even correcting for that in the most explicit way I could think of didn't get rid of the problem:
// More correct types makes no difference:
var departures: [Departure] {
var result = Array<Departure>()
result.append(MyDeparture())
return result
}
var tableContent: [TableSection] = [ TableSection(title: "MyTitle", rows: departures ) ]
There's some sort of lazy initialisation going on. It's only when it gets to the line
let lineNumber = tableContent[0].rows[0].line
that the run time seems to be filling in the contents of the array.
If you declared the array as containing elements that conform to the Departure protocol, the runtime does not know how big the elements of tableContent actually are because both classes and structs can conform to Departure. Hence, I think it is recreating the array and triggering didSet and willSet erroneously.
I tried limiting your protocol to classes like so:
protocol Departure: class
{
var line: String? { get }
}
and the problem goes away because now the runtime knows the array can only contain class references.
I think this is a bug and you should raise it with Apple.
Currently I'm working on my new App written with Swift 2.0. Today I faced two strange errors in Xcode beta 5. I'd love if someone with a previous beta version of Xcode can confirm if I'm right or not. I also could misunderstand something, so I'll appreciate any feedback.
Here is some example code that made me struggle a while:
// Frist bug
protocol SomeProtocol {
var someArray: [String] { get set } // #1 bug
}
extension SomeProtocol {
func someFunction(someString: String) {
self.someArray.append(someString) // #1: Immutable value of type '[String]' only has mutating members named 'append'
}
}
// Second bug
protocol SomeInterfaceProtocol {
var someBool: Bool { get set } // #2 bug
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeInterfaceProtocol {
return self
}
}
let someInstance = SomeClass()
// can't set the value
someInstance.returnInterface().someBool = true // #2: Cannot assign to property: function call returns immutable value
The first error can be solved if you add the modifier mutating before the extension func declaration like this:
mutating func someFunction(someString: String) {
I suspect that's a change in the language.
The other one puzzles me as well. At least, here's a work-around:
var c = someInstance.returnInterface()
c.someBool = true
I think the second one isn't a bug as well for the same reason that you can't modify an item in a dictionary directly, or that you can't change elem in for elem in array { ... }.
Something has to be saved to be able to change it. Because you're returning the protocol type, the compiler can't know whether it's a struct or a class, whereas if it's a struct the operation of changing it would have no effect because it's not persisted in any way and structs aren't passed by reference. That's why Thomas' workaround works. Maybe it'll work too if returnInterface returned a class instance, instead of the protocol type.
EDIT: Just tried it out: Indeed it works either if you return SomeClass instead of SomeInterfaceProtocol or if you change the protocol to a class protocol, as it can't be a struct
protocol SomeInterfaceProtocol : class {
var someBool: Bool { get set }
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeInterfaceProtocol {
return self
}
}
or
protocol SomeInterfaceProtocol {
var someBool: Bool { get set }
}
class SomeClass: SomeInterfaceProtocol {
var someBool: Bool = false
func returnInterface() -> SomeClass {
return self
}
}
both work
I have this for-in loop:
for button in view.subviews {
}
Now I want button to be cast into a custom class so I can use its properties.
I tried this: for button in view.subviews as AClass
But it doesnt work and gives me an error:'AClass' does not conform to protocol 'SequenceType'
And I tried this: for button:AClass in view.subviews
But neither does that work.
For Swift 2 and later:
Swift 2 adds case patterns to for loops, which makes it even easier and safer to type cast in a for loop:
for case let button as AClass in view.subviews {
// do something with button
}
Why is this better than what you could do in Swift 1.2 and earlier? Because case patterns allow you to pick your specific type out of the collection. It only matches the type you are looking for, so if your array contains a mixture, you can operate on only a specific type.
For example:
let array: [Any] = [1, 1.2, "Hello", true, [1, 2, 3], "World!"]
for case let str as String in array {
print(str)
}
Output:
Hello
World!
For Swift 1.2:
In this case, you are casting view.subviews and not button, so you need to downcast it to the array of the type you want:
for button in view.subviews as! [AClass] {
// do something with button
}
Note: If the underlying array type is not [AClass], this will crash. That is what the ! on as! is telling you. If you're not sure about the type you can use a conditional cast as? along with optional binding if let:
if let subviews = view.subviews as? [AClass] {
// If we get here, then subviews is of type [AClass]
for button in subviews {
// do something with button
}
}
For Swift 1.1 and earlier:
for button in view.subviews as [AClass] {
// do something with button
}
Note: This also will crash if the subviews aren't all of type AClass. The safe method listed above also works with earlier versions of Swift.
This option is more secure:
for case let button as AClass in view.subviews {
}
or swifty way:
view.subviews
.compactMap { $0 as AClass }
.forEach { .... }
The answers provided are correct, I just wanted to add this as an addition.
When using a for loop with force casting, the code will crash (as already mentioned by others).
for button in view.subviews as! [AClass] {
// do something with button
}
But instead of using an if-clause,
if let subviews = view.subviews as? [AClass] {
// If we get here, then subviews is of type [AClass]
...
}
another way is to use a while-loop:
/* If you need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let (index, subview) = iterator.next() as? (Int, AClass) {
// Use the subview
// ...
}
/* If you don't need the index: */
var iterator = view.subviews.enumerated().makeIterator()
while let subview = iterator.next().element as? AClass {
// Use the subview
// ...
}
Which seems to be more convenient if some elements (but not all) of the array might be of type AClass.
Although for now (as of Swift 5), I'd go for the for-case loop:
for case let (index, subview as AClass) in view.subviews.enumerated() {
// ...
}
for case let subview as AClass in view.subviews {
// ...
}
You can also use a where clause
for button in view.subviews where button is UIButton {
...
}
The answer provided by vacawama was correct in Swift 1.0. And no longer works with Swift 2.0.
If you try, you will get an error similar to:
'[AnyObject]' is not convertible to '[AClass]';
In Swift 2.0 you need to write like:
for button in view.subviews as! [AClass]
{
}
You can perform the casting and be safe at the same time with this:
for button in view.subviews.compactMap({ $0 as? AClass }) {
}
If you want to have an index in your loop:
for case let (index, button as AClass) in view.subviews.enumerated() {
}
I am trying to set a global variable. In my case, just a boolean flag that indicates if a view is being presented for the first time:
var initialLoadFlag: Bool = true
After the view is presented, I want to set this flag to false:
var initialLoadFlag: Bool = false
And then check for it thenceforth:
if initialLoadFlag {
showWelcomeMessage()
}
So, I would like to create initialLoadFlag as a global variable. Where and how? I've tried:
In the viewDidLoad area of my view controller
In the application() method in my AppDelegate.swift file
In the AppDelegate class
No luck. I'm getting a Use of unresolved identifier 'initialLoadFlag' error message
(Note: I realize that in this question I betray my ignorance of how scope is handled in Swift. Please forgive me... I'm on a deadline, and still new to the language.)
Thanks for your help.
You can define a struct with static field:
struct MyViewState {
static var initialLoadFlag = false
}
Usage:
// set
MyViewState.initialLoadFlag = true
// get
let state = MyViewState.initialLoadFlag
println("My view state:\(state)")
Remarks:
Such hacks as singletons and global vars are usually needed in case of bad design. Maybe you can store your state in NSUserDefaults? Or store it in some session object that can be injected in any ViewController that needs to be aware about context.
You could store a flag in the master controller and set it to true when you perform the segue to the details controller. E.g.
class MasterViewController: UIViewController {
var firstTimePresenting = true
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if firstTimePresenting {
println("First time!")
firstTimePresenting = false
}
}
}
}