I have an array which stores objects of a class:
class Apple {
var color = "Red"
}
let myApple = Apple()
var apples = [Apple]()
apples.append(myApple)
// Func should be called here
myApple.color = "Blue"
let otherApple = Apple()
// Func should not be called here,
// because otherApple is not a member of apples array
otherApple.color = "Green"
I want to run a function when any property of any member of "apples" array has changed. When calling this function, I need to pass the item of the array whose property is changed as a parameter.
I thought of using didSet on color property, but in that case function is called when otherApple's property is changed too. This is not what I want. I only want to run the function when a property of a member of the array has changed. If it is not a member, the function should not run.
Using didSet, running the function in any case, and checking membership in the beginning of the function might be an idea but I feel this is not a good way.
How can I properly achieve this with Swift?
Edit: Apple's guide for Using Key-Value Observing in Swift
You need to add the observer to all the Apple objects that you're adding in apples array.
First of all create a property named observers of type [NSKeyValueObservation] at class level, i.e.
var observers = [NSKeyValueObservation]()
Now, create a method that will append new Apple instances in apples array and add observer to it,
func addNewApple(_ apple: Apple) {
observers.append(apple.observe(\.color, options: [.new], changeHandler: { (apple, changes) in
if let newValue = changes.newValue {
print(newValue)
}
}))
apples.append(apple)
}
To observe a property of an object, that must be marked it #objc dynamic. So, the Apple definition goes like,
class Apple: NSObject {
#objc dynamic var color = "Red"
}
Now, you can use it as described below,
let myApple = Apple()
self.addNewApple(myApple)
myApple.color = "Blue"
Combining all the bits and pieces, the whole code can be written like,
class VC: UIViewController {
var apples = [Apple]()
var observers = [NSKeyValueObservation]()
override func viewDidLoad() {
super.viewDidLoad()
let myApple = Apple()
self.addNewApple(myApple)
myApple.color = "Blue"
let otherApple = Apple()
otherApple.color = "Green"
}
func addNewApple(_ apple: Apple) {
observers.append(apple.observe(\.color, options: [.new], changeHandler: { (apple, changes) in
if let newValue = changes.newValue {
print(newValue)
}
}))
apples.append(apple)
}
}
Related
I have a tableview and each cells are meant to be linked to an array inside a dictionary.
var buttonAction : [String: [ButtonAction]] = [:]
below is the struct of the buttonAction
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
It gets a bit complicated to explain the whole code but when I connect two buttons together, I can get the data for the variable "singleAction" which then can be added to the button action dictionary "array_linked_of_buttons".
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
let mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER Exists", addAction)
} else {
actionSub.append(singleAction)
print("UPDATED MARKER", actionSub)
}
let action = ButtonAction(action: actionArray[indexRowTag].action, array_linked_of_buttons: actionSub)
//ISSUE--?? mainMarker.buttonAction.updateValue(VALUE forKey: "button actions array")
saveData()
I can workout which item of the dictionary needs to be edited but how do I update that specific value? I am really confused has it just creates a new item but I want to update a previous one.
So for example, I want to append the "array_linked_buttons" of the item 0 and have 4 items instead of 3.
This is how my dictionary looks like if that helps too
I have searched other questioned but I still work it out.
Any help pointing me in the right direction is much appreciated!
Copying instead of changing value happened because you're using struct for ButtonAction which has value semantics and swift create copy on assigning to any variable.
You need to use class instead. Classes have reference semantics, so it won't create new instance on assigning to variable so you'll be able to update property of needed instance.
I worked out i was updating the main dictionary array and not the nested array.
The code below is what I used in the end.
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
var mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER HERE", addAction)
} else {
actionArray[indexRowTag].array_linked_of_buttons.append(singleAction)
}
mainMarkerAction[indexRowTag] = actionArray[indexRowTag]
I'm writing a card game with a number of cards collected together in one array.
The data structure of the cards is the same, but the data is different.
Note: My shuffle does work.
I'm wanting to filter this array, and only shuffle the cards I have filtered.
However, whilst I can shuffle the cards, I've noticed that my originating array of cards does not change at all.
I believe that this issue is caused because my card model is a struct and not a class.
More background info.
Whilst the data for each card is different, the structure is exactly the same; both types have a name, and a numeric value.
This is modelled thusly;
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
I create my cards in a static function like this:
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx, owner: nil)
properties.append(property)
}
return properties
}
}
Okay, so now I only want to shuffle my property cards within this cards array.
So in my XCTest file first filter all cards that are of type: property
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
This calls:
// Inside my FSCardAPI
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
The issue:
When I run my XCTest, I notice that whilst the propertyCards is shuffled, my gameModel.cards are not shuffled;
Test Case 'testShuffleProperties' started.
// contents of `propertyCards`
[FSCard(type: FSCardType.property, name: "Property #4", value: 4, owner: nil),
FSCard(type: FSCardType.property, name: "Property #16", value: 16, owner: nil),
// .. etc
// contents of `gameModel.cards`
[FSCard(type: FSCardType.property, name: "Property #1", value: 1, owner: nil),
FSCard(type: FSCardType.property, name: "Property #2", value: 2, owner: nil),
// .. etc
Summary
I have an array of cards (ie: 30 cards)
Cards are separated by types (ie: property)
I want to filter the property cards and shuffle those cards only
I want the original array to reflect these changes
In my test, the array gets shuffled; but the original array remains the same.
One way I can think of is that I do all the shuffling, and then sort the array by card types and then update the gameModel.cards, but that seems a bit over the top.
Another obvious way I can think about solving this is to change my struct to a class or; perhaps I need another layer in between the struct?
So my query is:
How do I filter an array of structs, only shuffle those results and change the state of the originating array?
Many thanks
Edit:
The properties array is the only item to shuffle.
If I add another array to the list it should not shuffle the entire contents.
IE; if I do this:
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
let cheqs:[FSCard] = FSCardAPI.createCheques()
cards.append(contentsOf: properties)
cards.append(contentsOf: cheqs)
return cards
}
I should only shuffle the property cards within themselves without impacting the cheqs.
I guess I could make it easier and just have 2 arrays, but at the same time I think there is no reason to do this because the data structure is the same.
You are not assigning to gameModel.cards and not actually changing the array. so I would not expect gameModel.cards to be shuffled.
You should either just assign the shuffled array back to gameModel.cards like so:
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
gameModel.cards = propertyCards
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
Or if you want to mutate the array directly you should look at passing the cards by reference, or in Swift.. using an inout parameter. An inout parameter passes the value to the function by reference, or passed the memory addres of the value, so that it can be modified directly. Check the shuffle cards function below (how it is defined and used)
(I replaced the shuffle function with a swift extension for ease of use in a playground)
extension MutableCollection where Indices.Iterator.Element == Index {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
guard d != 0 else { continue }
let i = index(firstUnshuffled, offsetBy: d)
swap(&self[firstUnshuffled], &self[i])
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Iterator.Element] {
var result = Array(self)
result.shuffle()
return result
}
}
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:inout [FSCard]) {
cards = cards.shuffled()
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx)
properties.append(property)
}
return properties
}
}
var cards = FSCardAPI.createCards()
FSCardAPI.shuffleCards(cards: &cards)
Output:
Read up on inout parameters here in the In-Out parameters section.
Exert from the documentation:
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value. For a detailed discussion of the behavior of in-out parameters and associated compiler optimizations, see In-Out Parameters.
You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
EDIT: Try this updated shuffle function, pass in the whole array and see if does what you need.
public static func shuffleCards(cards:inout [FSCard]) {
let propertyCards = cards.filter({ $0.type == .property })
let indexes = propertyCards.map { Int(cards.index(of: $0)!) }
let shuffledCards = propertyCards.shuffled()
for (idx, val) in indexes.enumerated() {
cards[val] = shuffledCards[idx]
}
}
The problem is that after you shuffle your property cards, you are not doing anything with them. You should replace property cards in your original card list with the ones in your shuffled property card list.
var propertyCardsShuffledOriginalArray = originalArray.map {
var card = $0
if $0.type == .property {
card = shuffledPropertyCards.first as! FSCard
shuffledPropertyCards.removeFirst()
}
return card
}
propertyCardsShuffledOriginalArray is what you need
So, I have a generic (with restrictions) class and a number of subclasses of it that concrete the generic type when they subclass.
I want to store instances of these subclasses in an array so then they can be iterated and treated all of them in the same way, but apparently, there is no way to convert from the subclass to the generic superclass.
Here is some code that illustrates the problem (you can copy-paste it in a playground to see the outcome):
// Lets create regular classes
class Fruit {
var text: String { return "I am some Fruit" }
}
class Apple: Fruit {
override var text: String { return "I am an Apple" }
}
class Orange: Fruit {
override var text: String { return "I am an Orange" }
}
// This obviously works:
let test1: Fruit = Apple()
let test2: Fruit = Orange()
// Let's create some generic class
class Tree<T: Fruit> {
let fruit: T
init(fruit: T) {
self.fruit = fruit
}
}
// Subclasses from the generic class (these work)
class AppleTree: Tree<Apple> {
convenience init() {
self.init(fruit: Apple())
}
}
class OrangeTree: Tree<Orange> {
convenience init() {
self.init(fruit: Orange())
}
}
// This works:
let tree: Tree<Fruit> = Tree(fruit: Apple())
tree.fruit.text // "I am an Apple"
// This works:
let appleTree1: Tree<Apple> = AppleTree()
appleTree1.fruit.text // "I am an Apple"
// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let appleTree2: Tree<Fruit> = AppleTree()
// This works:
let fruitArray: [Fruit] = [Apple(), Orange()]
// THIS IS MY GOAL:
// This fails: "Cannot convert value of type 'AppleTree' to specified type 'Tree<Fruit>'
let treeArray: [Tree<Fruit>] = [AppleTree(), OrangeTree()]
// Let's try with a generic subclass
class FruitTree<T: Fruit>: Tree<T>{}
// This works:
let genericTree: Tree<Fruit> = FruitTree(fruit: Apple())
// Let's try with a generic but more concrete subclass
class GenericOrangeTree<T: Orange>: Tree<T>{
convenience init() {
self.init(fruit: Orange() as! T)
}
}
// This works:
let genericOrangeTree1 = GenericOrangeTree(fruit: Orange())
let genericOrangeTree2 = GenericOrangeTree()
// This fails: Cannot invoke initializer for type 'GenericOrangeTree<Orange>' with an argument list of type '(fruit: Orange)'
let genericTree2: Tree<Fruit> = GenericOrangeTree(fruit: Orange())
// Again, this fails: "Cannot convert value of type 'GenericOrangeTree<Orange>' to specified type 'Tree<Fruit>'
let genericTreeArray: [Tree<Fruit>] = [GenericOrangeTree()]
What I am trying to do is illustrated in the sample code by the treeArray variable.
I don't understand why the code fails when it fails. My intuition says that this should work and I cannot find a work around this problem.
TL;DR: I have a Generic class with some subclasses, I want to have an array of the Generic class filled with the subclasses, but the compiler complains.
You are mixing up the type hierarchies before and after Generics have been materialized. With a generic class/func you essentially setup a template (a compiler macro) which is resolved at compile-time.
If you say
Generic<SubClass1>
Generic<SuperClass>
those are resolved by the compiler to types like:
class Generic_SubClass1 {
let property : SubClass1
}
class Generic_SuperClass {
let property : SuperClass
}
After the generics are resolved those two types share no common base type and hence can't be cast to each other. They are completely separate.
Not sure and didn't try, but maybe this is what you want:
class GenericBase {
let property : SuperClass
}
class Generic<T: SuperClass> : GenericBase {
final var typedProperty : T {
get { return property as T }
set { property = T }
}
}
You can then use GenericBase as the common ancestor and use dynamic type to check for subclasses.
P.S.: Your code is a little hard to follow, maybe use something like 'Fruit', 'Apple' and 'Orange' - much easier to read than 'Superclass', 'Subclass1', 'Subclass2' ;-)
I think what you are looking for is type erasure.
For example, imaging you have the following:
protocol ClassWithView {
associatedType: View: UIView
var view: View { get set }
}
class ConcreteWithView {
associatedType View = SubclassOfUIView
var view: SubclassOfUIView
}
// Somewhere this will fail because there is missing associated type information
var array: [ConcreteWithView]
With type erasure you can enforce that any access to the array will only let you access the common UIView stuff. Modifying the above like:
protocol AnyClassWithView {
var baseView: UIView
}
protocol ClassWithView: AnyClassWithView {
associatedType: View: UIView
var view: View { get set }
}
// Default implementation
extension AnyClassWithView {
var baseView: UIView {
return view as UIView
}
}
Now, elsewhere you can define the array like:
var array: [AnyClassWithView]
Which will succeed, and you can access only the UIView type with .baseView. You can add stuff to the AnyClassWithView definition and do stuff with the shared UIView.
If you want access to the individual subtypes, create a function on this that accepts a generic parameter and you should be able to cast to access the subtype information.
Perhaps you could define a protocol named FruitTree that can get a Fruit and return some kind of Fruit:
protocol FruitTree {
associatedType FruitKind
func getFruit() -> Fruit
func setFruit(FruitKind)
}
Then, define classes something like:
class AppleTree: FruitTree {
var apple Apple
typeAlias FruitKind = Apple
func getFruit() -> Apple {return apple}
func setFruit(Apple a} {apple = a}
}
class OrangeTree: FruitTree {
var orange Orange
typeAlias FruitKind = Orange
func getFruit() -> Orange { return orange}
func setFruit(Orange o) {orange= o}
}
let treeArray: [FruitTree] = [AppleTree(), OrangeTree()]
I want to check if elements have been added to an array in swift using KVO, and I essentially copied the example from Apple's documentation, but when the code runs, it does not catch when the size of the array updates. Here is what I have now:
class ShowDirectory: NSObject {
var shows = [Show]()
dynamic var showCount = Int()
func updateDate(x: Int) {
showCount = x
}
}
class MyObserver: NSObject {
var objectToObserve = ShowDirectory()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "showCount", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("\(newValue) shows were added")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
After I add the shows to the array, I set showCount equal to the number of elements in the array, however, it does not print "X shows were added" to console. My viewDidLoad() function simply calls the function that adds elements to the array, and nothing else at the moment.
You unfortunately cannot add as an observer to an Int, as it does not subclass NSObject
See the Apple Docs and search for "Key-Value Observing"
You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class.
Otherwise, your KVO boiler-plate code looks good to me.
If you want to be notified when your array's contents change, you could try what #Paul Patterson recommends and use a proxy object
I am new in swift language and my problem is about how to use observable/observer pattern in swift.
I want to make my array to be observable in my SocketManager class so it can be observed by my UIViewController class. I have used the Observable class written by Andrew J Wagner which I got from this link:
http://www.drewag.me/posts/swift-kvo-substitute-observable-variables
I have the array:
var marketIndexList: Array< MarketIndex > = []
which will get its data from a server. This list will be updated every time a new data received from server. After I got the values of my Array from server I want to make it of type Observable class which is implemented by the above link:
marketIndexList = Observable(marketIndexList)
But I got this error:
'MarketIndex' is not identical to 'AnyObject'
MarketIndex is a class of type NSObject which has some properties of type String.
This is the Observable class that I have used:
import Foundation
class Observable {
typealias DidChangeHandler = (oldValue: Array<MarketIndex>?, newValue: Array<MarketIndex>) -> ()
var value : Array<MarketIndex> = [] {
didSet {
for (owner, handlers) in self.observers {
for handler in handlers {
handler(oldValue: oldValue, newValue: value)
}
}
}
}
init(_ value: Array<MarketIndex>) {
self.value = value
}
func addObserverForOwner(owner: IndexViewController, triggerImmediately: Bool, handler: DidChangeHandler) {
if let index = self.indexOfOwner(owner) {
// since the owner exists, add the handler to the existing array
self.observers[index].handlers.append(handler)
} else {
// since the owner does not already exist, add a new tuple with the
// owner and an array with the handler
self.observers.append(owner: owner, handlers: [handler])
}
if (triggerImmediately) {
// Trigger the handler immediately since it was requested
handler(oldValue: nil, newValue: self.value)
}
}
func removeObserversForOwner(owner: AnyObject) {
if let index = self.indexOfOwner(owner) {
self.observers.removeAtIndex(index)
}
}
// #pragma mark - Private Properties
var observers: [(owner: IndexViewController, handlers: [DidChangeHandler])] = []
// #pragma mark - Private Methods
func indexOfOwner(owner: AnyObject) -> Int? {
var index : Int = 0
for (possibleOwner, handlers) in self.observers {
if possibleOwner === owner {
return index
}
index++
}
return nil
}
}
Can anyone tell me what the problem is?
Also does anyone know a way to observe an array of objects in swift?
I would appreciate any help.
Thanks in advance.
The error is because marketIndexList is defined as Array<MarketIndex> but you assigned Observable instance. Perhaps you wanted to do something like this:
var observableList: Observable = Observable([])
var marketIndexList: Array<MarketIndex> = [MarketIndex(), MarketIndex()]
observableList.value = marketIndexList
// Or maybe
observableList = Observable(marketIndexList)
By the way, you can also use Objective-C KVO from Swift. Just mark the property as dynamic and make sure the class inherits NSObject to make the property observable. For example:
class ObservableClass: NSObject {
dynamic var value = [Int]()
}
This post is good to read for KVO in Swift in addition to what you referred to.
https://medium.com/proto-venture-technology/the-state-of-kvo-in-swift-aa5cb1e05cba