Execute a method when a variable value changes in Swift - ios

I need to execute a function when a variable value changes.
I have a singleton class containing a shared variable called labelChange. Values of this variable are taken from another class called Model. I have two VC classes, one of them has a button and a label and the second only a button.
When the button in the first VC class is pressed I am updating the label with this func:
func updateLabel(){
self.label.text = SharingManager.sharedInstance.labelChange
}
But I want to call the same method whenever the value of the labelChange is changed. So in button click I will only update the labelChange value and when this thing happen I want to update the label with the new value of the labelChange. Also in the second VC I am able to update the labelChange value but I am not able to update the label when this value is changed.
Maybe properties are the solution but can anyone show me how to do so.
Edited second time:
Singleton Class:
class SharingManager {
func updateLabel() {
println(labelChange)
ViewController().label.text = SharingManager.sharedInstance.labelChange
}
var labelChange: String = Model().callElements() {
willSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}
First VC:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBAction func Button(sender: UIButton) {
SViewController().updateMessageAndDismiss()
}
}
Second VC:
func updateMessageAndDismiss() {
SharingManager.sharedInstance.labelChange = modelFromS.callElements()
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func b2(sender: UIButton) {
updateMessageAndDismiss()
}
I made some improvements but I need to reference a label from the first VC class in singleton. Therefore I will update that label of VC in singleton.
When I print the value of labelChange the value is being updated and everything is fine. But when I try to update that value on label from singleton I receive an error:
unexpectedly found nil while unwrapping an Optional value
and the error is pointing in 4th line of singleton class.

You can simply use a property observer for the variable, labelChange, and call the function that you want to call inside didSet (or willSet if you want to call it before it has been set):
class SharingManager {
var labelChange: String = Model().callElements() {
didSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}
This is explained in Property Observers.
I'm not sure why this didn't work when you tried it, but if you are having trouble because the function you are trying to call (updateLabel) is in a different class, you could add a variable in the SharingManager class to store the function to call when didSet has been called, which you would set to updateLabel in this case.
Edited:
So if you want to edit a label from the ViewController, you would want to have that updateLabel() function in the ViewController class to update the label, but store that function in the singleton class so it can know which function to call:
class SharingManager {
static let sharedInstance = SharingManager()
var updateLabel: (() -> Void)?
var labelChange: String = Model().callElements() {
didSet {
updateLabel?()
}
}
}
and then set it in whichever class that you have the function that you want to be called, like (assuming updateLabel is the function that you want to call):
SharingManager.sharedInstance.updateLabel = updateLabel
Of course, you will want to make sure that the view controller that is responsible for that function still exists, so the singleton class can call the function.
If you need to call different functions depending on which view controller is visible, you might want to consider Key-Value Observing to get notifications whenever the value for certain variables change.
Also, you never want to initialize a view controller like that and then immediately set the IBOutlets of the view controller, since IBOutlets don't get initialized until the its view actually get loaded. You need to use an existing view controller object in some way.
Hope this helps.

In Swift 4 you can use Key-Value Observation.
label.observe(\.text, changeHandler: { (label, change) in
// text has changed
})
This is basically it, but there is a little catch. "observe" returns an NSKeyValueObservation object that you need to hold! - when it is deallocated, you’ll receive no more notifications. To avoid that we can assign it to a property which will be retained.
var observer:NSKeyValueObservation?
// then assign the return value of "observe" to it
observer = label.observe(\.text, changeHandler: { (label, change) in
// text has changed,
})
You can also observe if the the value has changed or has been set for the first time
observer = label.observe(\.text, changeHandler: { (label, change) in
// just check for the old value in "change" is not Nil
if let oldValue = change.oldValue {
print("\(label.text) has changed from \(oldValue) to \(label.text)")
} else {
print("\(label.text) is now set")
}
})
For More Information please consult Apples documentation here

Apple provide these property declaration type :-
1. Computed Properties:-
In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.
var otherBool:Bool = false
public var enable:Bool {
get{
print("i can do editional work when setter set value ")
return self.enable
}
set(newValue){
print("i can do editional work when setter set value ")
self.otherBool = newValue
}
}
2. Read-Only Computed Properties:-
A computed property with a getter but no setter is known as a read-only computed property. A read-only computed property always returns a value, and can be accessed through dot syntax, but cannot be set to a different value.
var volume: Double {
return volume
}
3. Property Observers:-
You have the option to define either or both of these observers on a property:
willSet is called just before the value is stored.
didSet is called immediately after the new value is stored.
public var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
NOTE:- For More Information go on professional link
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

There is another way of doing so, by using RxSwift:
Add RxSwift and RxCocoa pods into your project
Modify your SharingManager:
import RxSwift
class SharingManager {
static let sharedInstance = SharingManager()
private let _labelUpdate = PublishSubject<String>()
let onUpdateLabel: Observable<String>? // any object can subscribe to text change using this observable
// call this method whenever you need to change text
func triggerLabelUpdate(newValue: String) {
_labelUpdate.onNext(newValue)
}
init() {
onUpdateLabel = _labelUpdate.shareReplay(1)
}
}
In your ViewController you can subscribe to value update in two ways:
a. subscribe to updates, and change label text manually
// add this ivar somewhere in ViewController
let disposeBag = DisposeBag()
// put this somewhere in viewDidLoad
SharingManager.sharedInstance.onUpdateLabel?
.observeOn(MainScheduler.instance) // make sure we're on main thread
.subscribeNext { [weak self] newValue in
// do whatever you need with this string here, like:
// self?.myLabel.text = newValue
}
.addDisposableTo(disposeBag) // for resource management
b. bind updates directly to UILabel
// add this ivar somewhere in ViewController
let disposeBag = DisposeBag()
// put this somewhere in viewDidLoad
SharingManager.sharedInstance.onUpdateLabel?
.distinctUntilChanged() // only if value has been changed since previous value
.observeOn(MainScheduler.instance) // do in main thread
.bindTo(myLabel.rx_text) // will setText: for that label when value changed
.addDisposableTo(disposeBag) // for resource management
And don't forget to import RxCocoa in ViewController.
For triggering event just call
SharingManager.sharedInstance.triggerLabelUpdate("whatever string here")
HERE you can find example project. Just do pod update and run workspace file.

var item = "initial value" {
didSet { //called when item changes
print("changed")
}
willSet {
print("about to change")
}
}
item = "p"

override var isHighlighted: Bool {
get { super.isHighlighted }
set {
super.isHighlighted = newValue
if newValue {
label.textColor = highlightedTextColor
contentView.backgroundColor = highlightedBackgroundColor
} else {
label.textColor = normalTextColor
contentView.backgroundColor = normalBackgroundColor
}
}
}

Related

What exactly happens when you assign self to delegate?

I'm new to Swift and I'm having a hard time understanding the purpose of assigning self to a delegate. Part of the difficulty stems from the fact that delegate seems to be used in two different ways.
First is as means to send messages from one class to another when a specific event happens, almost like state management. Second is to enable "a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type," as stated in documentation. I have a feeling that these two are fundamentally the same and I'm just not getting it.
protocol PersonProtocol {
func getName() -> String
func getAge() -> Int
}
class Person {
var delegate: PersonProtocol?
func printName() {
if let del = delegate {
print(del.getName())
} else {
print("The delegate property is not set")
}
}
func printAge() {
if let del = delegate {
print(del.getAge())
} else {
print("The delegate property is not set")
}
}
}
class ViewController: UIViewController, PersonProtocol {
var person: Person!
override func viewDidLoad() {
person.delegate = self
person.printAge()
person.printName()
}
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
What is the purpose of person.delegate = self in this case? Isn't ViewController already required to conform to PersonProtocol without it?
I have a feeling that these two are fundamentally the same
The first is a special case of the second. "send messages from one class to another" is just a specific way of "handing off some of its responsibilities". The "messages" are the "responsibilities"
What is the purpose of person.delegate = self in this case?
Here, person delegates (i.e. hands off) some of its responsibilities to another object. It does this by sending messages to another object. First, it needs to identify which objects it can delegate these responsibilities to. This is achieved by requiring that its delegate conform to PersonProtocol, as PersonProtocol defines the messages that Person is going to send.
Next, person needs to know exactly which object it should send these messages to. This is what person.delegate = self does. Remember that person doesn't know anything about your ViewController until this point. Instead of = self, you could say:
person.delegate = SomeOtherClassThatConformsToPersonProtocol()
and person will send its messages to that object instead, and the methods in your ViewController won't be called.
Isn't ViewController already required to conform to PersonProtocol without it?
Correct, but without it, person doesn't know which object it should send its messages to, and as a result, the methods in your ViewController won't be called.
Note that the delegate property should be declared as weak to avoid retain cycles. When you do person.delegate = self, you get a retain cycle: self has a strong reference to person, person also has a strong reference to self via the delegate property.
If you notice inside your Person class, delegate is nil. If you don't execute person.delegate = self, delegate will remain nil.
In other words, assigning ViewController to person.delegate allows Person to identify who the delegate is (i.e., have a reference to ViewController), and that way you can successfully execute statements like delegate?.getName() or delegate?.getAge() from the Person class.
that means Person is not able to getName() and getAge() so Person class delegate that to other DataSource.
Lets say the your view controller has a data source class PersonDataSource which deal with API to get this information So
class PersonDataSource: PersonProtocol {
func getAge() -> Int {
print("view controller")
return 99
}
func getName() -> String {
return "Some name"
}
}
so the view controller will looks like this
class ViewController: UIViewController {
var person: Person!
var personDataSource = PersonDataSource()
override func viewDidLoad() {
person.delegate = personDataSource
person.printAge()
person.printName()
}
}

How to update swift NSObject model having same property in other controllers using property observer?

I was going through Apple documentation and some tutorials where I learnt we can set observer which will be called if object if modified. But I have few doubts in my mind.
Here is a summary where a model is notified about the property changes:
Suppose there are 3 view controllers and they show listing of Foo models. Foo model has properties called id and title. My question is, is it feasible to get notified in others controllers that Foo model is modified which is having id 10 for example. Is that possible only if same instance of model is shared between 3 controllers, or we can achieve it although instances are different?
I am looking for a concrete solution, where a user like a feed (similar as Facebook) in one screen and if a feed with same id is in other controller, that controller should be notified that this feed is modified, refresh its UI. I have attached an image for clear idea.
I do not wish to go with delegate or notification pattern as it might create chaos, rather observer pattern will be more proper solution.
Here is an example of how you can achieve this.
Feed Model:
class Feed: NSObject {
var id: String
#objc dynamic var isLiked = false
init(id: String) {
self.id = id
}
}
Any property that you want to observe, mark it #objc dynamic, i.e. isLiked in Feed model.
class ListVC: UIViewController {
let feed = Feed(id: "1")
var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func openDetailVC(_ sender: UIButton) {
if let detailVC = self.storyboard?.instantiateViewController(withIdentifier: "DetailVC") as? DetailVC {
self.observer = detailVC.feed.observe(\.isLiked, options: [.new], changeHandler: { (feed, change) in
if let newValue = change.newValue {
print(newValue)
//Reload the UI here...
}
})
self.navigationController?.pushViewController(detailVC, animated: true)
}
}
}
Next there is a ListVC that has a feed property.
Add an observer to feed in viewDidLoad(). Specify the property that you want to observe in feed i.e. isLiked. The closure in the observer will be called every time there is a change in isLiked property.
Now, DetailVC will be.
class DetailVC: UIViewController {
let feed = Feed(id: "1")
#IBAction func likeButtonPressed(_ sender: UIButton) {
self.feed.isLiked = !self.feed.isLiked
}
}
In the above code, I'm changing the value of isLiked property in feed whenever likeButton is pressed.

How to pass parameters using GKStateMachine's states

While entering into state I want to pass parameters with state as follows:
playerStateMachine.enter(pauseState.self, ["score":123, "rank":1])
so I can get that value as follows in didEnter method:
class pauseState: GKState {
var userinfo:[String:Any]?
init(player: SSGameDelegate) {
super.init(player: player)
}
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
//Access input value here
print(userinfo["score"])
print(userinfo["rank"])
}
}
You don’t pass parameter this way. In you current state,there is a function.
func willExit(to nextState: GKState)
You can pass parameters over there as nextState.userinfo[score] = 111
If you keep track of the state Machine states, you are able to set a parameter before entering the state. I am not totally sure this is thread safe, I will update as I test further will usage.
let alienWaitState = AlienWaitState(game: self)
let alienFlyingState = AlienFlyingState(game: self)
aliensStateMachine = GKStateMachine(states: [
alienWaitState,
alienFlyingState,
AlienAnimateOffState(game: self),
AlienDeadRemovalState(game: self)
])
in this case alienState parameter "associatedAlienName" can be set before entry to the state
alienWaitState.associatedAlienName = newAlien.name
aliensStateMachine.enter(AlienWaitState.self)
AlienState class, alienWaitState is a subclass
class AlienState: GKState {
weak var game:GameScene?
var associatedAlienName:String?
....

set the value of struct property in different Viewcontrollers in ios swift

I have used struct to set constant.I have maxTextLength in integer form I have to set different values for different controller like one for 300 and another 1000.Here is my code
struct Validations {
static let maxAudioRecSec:Int = 150
static var maxTextLength = 300 // Default value
}
SecondVC :ViewController {
override func viewDidLoad () {
Validations.maxTextLength = 1000
}
}
So value changed inside SecondVC is retain inside that controller only that is 1000.If I access this value inside another controller should be default 300.
You must use like below, don't use static variable for maxTextLength.
struct Validations {
static let maxAudioRecSec:Int = 150
var maxTextLength = 300 // Default value
}
Now in any ViewController you must use like below.
class SecondVC :ViewController {
var validations = Validations() // create Validations struct object
override func viewDidLoad () {
validations.maxTextLength = 1000 // use like this
}
}
Any doubt plz comment.

Add property observer to global variable inside class in Swift

I have a variable globalVariable declared at global scope that may change at any time.
Different ViewControllers in my app need to react differently, when globalVariable changes.
Thus it would be desirable to add a property observer in each ViewController that execute the needed code when globalVariable changes.
I cannot seem to achieve it with override or extension. What is the way to go here?
If your goal is to simply know when your global variable changed, you could have it post a notification upon change:
extension NSNotification.Name {
static let globalVariableChanged = NSNotification.Name(Bundle.main.bundleIdentifier! + ".globalVariable")
}
var globalVariable: Int = 0 {
didSet {
NotificationCenter.default.post(name: .globalVariableChanged, object: nil)
}
}
Then any object can add an observer for that notification:
class ViewController: UIViewController {
private var observer: NSObjectProtocol!
override func viewDidLoad() {
super.viewDidLoad()
// add observer; make sure any `self` references are `weak` or `unowned`; obviously, if you don't reference `self`, that's not necessary
observer = NotificationCenter.default.addObserver(forName: .globalVariableChanged, object: nil, queue: .main) { [weak self] notification in
// do something with globalVariable here
}
}
deinit {
// remember to remove it when this object is deallocated
NotificationCenter.default.removeObserver(observer)
}
}
Note, this didSet mechanism will not detect changes if (a) the global variable is a reference type, i.e. a class; and (b) it merely mutates the object that the global variable references rather than replacing it with a new instance. To identify that scenario, you need to use KVO or other mechanism to detect mutation.
There can be only one didSet{} function for your global variable and it must belong to the variable itself. What you can do is make the variable's didSet{} function call a list of functions from other objects.
You could use notifications for this or you could build your own mechanism.
Here's an example of how you could create your own mechanism:
(note that this is pretty generic and could work for any variable types or singleton instance)
// Container for an observer's function reference
// - will be used to call the observer's code when the variable is set
// - Separates the object reference from the function reference
// to avoid strong retention cycles.
struct GlobalDidSet<T>
{
weak var observer:AnyObject?
var didSetFunction:(AnyObject)->(T)->()
init(_ observer:AnyObject, didSet function:#escaping (AnyObject)->(T)->())
{
self.observer = observer
didSetFunction = function
}
}
// Container for a list of observers to be notified
// - maintains the list of observers
// - automatically clears entries that non longer have a valid object
// - calls all observers when variable changes
// - erases type of observer to allow generic use of GlobalDidSet<>
struct GlobalDidSets<T>
{
var observers : [GlobalDidSet<T>] = []
mutating func register<O:AnyObject>(_ observer:O, didSet function:#escaping (O)->(T)->())
{
let observer = GlobalDidSet<T>(observer)
{ (object:AnyObject) in function(object as! O) }
observers.append(observer)
}
mutating func notifyDidSet(_ oldValue:T)
{
observers = observers.filter{$0.observer != nil}
observers.forEach{ $0.didSetFunction($0.observer!)(oldValue) }
}
}
...
// To use this, you will need a second variable to manage the list of observers
// and your global variable's didSet{} must use that observer list
// to perform the multiple function calls
//
var globalVariableDidSets = GlobalDidSets<String>()
var globalVariable : String = "Initial Value"
{
didSet { globalVariableDidSets.notifyDidSet(oldValue) }
}
// In your view controllers (or any other class), you need to setup the
// reaction to the global variable changes by registering to the observer list
//
class MyVC:UIViewController
{
override func viewDidLoad()
{
globalVariableDidSets.register(self){ $0.handleVariableChange }
// ...
}
func handleVariableChange(_ oldValue:String)
{
//...
}
}

Resources