Simple MVVM with KVO - swift

I'm trying to create a simple mvvm model using kvo
My goal is when the UITextField text changes, automatically change the UILabel text.
But for some reason the observeValue function is not called
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
var viewModel : TestViewModel?
#IBOutlet weak var LBLABEL: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
viewModel = TestViewModel()
addObserver(self, forKeyPath: #keyPath(viewModel.infoText), options: [.old, .new], context: nil)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
viewModel?.infoText = textField.text
return true
}
// MARK: - Key-Value Observing
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "info" {
// Update Time Label
LBLABEL.text = viewModel?.infoText
}
}
}
class TestViewModel : NSObject{
var model : TestModel
var infoText:String? {
didSet{
model.info = self.infoText
}
}
override init() {
model = TestModel()
}
}
class TestModel {
var info:String?
}
I already tried to change the observer's declaration or even the ViewModel gets and sets and never succeeded

UPDATE
According to Apple docs, creating an observer for the key path will be much simpler in Swift 4.
class MyObserver: NSObject {
#objc var objectToObserve: MyObjectToObserve
var observation: NSKeyValueObservation?
init(object: MyObjectToObserve) {
objectToObserve = object
super.init()
observation = observe(\.objectToObserve.myDate) { object, change in
print("Observed a change to \(object.objectToObserve).myDate, updated to: \(object.objectToObserve.myDate)")
}
}
}
let observed = MyObjectToObserve()
let observer = MyObserver(object: observed)
observed.updateDate()
You need to add dynamic to the properties of NSObject subclass you want to observe. In your case:
#objc dynamic var infoText:String? {
didSet{
model.info = self.infoText
}
}
Btw, I don't know why you want to you textField:shouldChangeCharactersIn as it gets the textfield value before updated. Also, keyPath == "info" won't never be true. Shouldn't it be something else. E.g., keyPath == "viewModel.infoText"?

Related

RxSwift - make one UI element hidden/not hidden according to other element

Im using RxSwift and RxCocoa in my project.
I have some UITextField named "lastNameTF", and there is a UILabel name "lastNameTitle".
I wanna know if there is any way to set the isHidden value of lastNameTitle always be equal to isHidden value of lastNameTF using RxSwift.
I believe you can use KVO as described here -
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#kvo
It is super easy to use KVO. Here is an example of exactly what you are trying to do, just without using RxSwift (don't know what that is...)
Here is the gist of it
class ViewController: UIViewController {
private var lastNameTextFieldHiddenContext = 0
private var lastNameObservingView:UIView? = nil
#IBOutlet weak var lastNameLabel: UILabel!
#IBOutlet weak var lastNameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// add the observer
lastNameTextField.addObserver(
self,
forKeyPath: "hidden",
options: [.new],
context: &self.lastNameTextFieldHiddenContext
)
}
/// function will be called whenever an added observer is triggered
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
// make sure it is our text field isHidden observer
if context == &self.lastNameTextFieldHiddenContext {
// get the new value that was set
if let newValue = change?[NSKeyValueChangeKey.newKey] as? Bool {
// do what needs to be done when the observer is triggered
self.lastNameLabel.isHidden = newValue
}
}
}
deinit {
// remove the observer
if let view = self.lastNameObservingView {
view.removeObserver(self, forKeyPath: "hidden")
self.lastNameObservingView = nil
}
}
#IBAction func showHideButtonAction(_ sender: Any) {
self.lastNameTextField.isHidden = !self.lastNameTextField.isHidden
}
}
If you still need a simple RxSwift approach please try this:
// Controls are visible by default (isHidden = false)
let isControlHidden = BehaviorRelay<Bool>(value: false)
override func viewDidLoad() {
super.viewDidLoad()
let isHiddenDriver = self.isControlHidden.asDriver()
isHiddenDriver
.drive(self.lastNameTitle.rx.isHidden)
.disposed(by: disposeBag)
isHiddenDriver
.drive(self.lastNameTF.rx.isHidden)
.disposed(by: disposeBag)
}
Since you need both control visibilities bound to each other, you can use a Subject or Relay to achieve that, in this case isControlHidden. So, if you want to show/hide the, you just emit a new signal:
#IBAction func hide(_ sender: Any) {
self.isControlHidden.accept(true)
}
#IBAction func show(_ sender: Any) {
self.isControlHidden.accept(false)
}

Once set up, how are NSSpeechSynthesizerDelegate protocol methods called automatically?

I'm working through a tutorial that uses NSSpeechSynthesizer and two of its NSSpeechSynthesizerDelegate protocol methods. In my ViewController, I don't explicitly call the protocol methods so I'm curious as to what do I need to research in order to understand how these methods are called during runtime? The delegate methods are working as expected but I'm wondering how are they being called which makes this possible?
import Cocoa
class MainWindowController: NSWindowController, NSSpeechSynthesizerDelegate, NSWindowDelegate {
//Now MainWindowController is more powerful by having its own KITT being able to delegate powerful functionality and do less work. The delegate will do all the heavy lifting and return the results to MainWindowController instances.
// MARK: - Properties
#IBOutlet weak var textField: NSTextField!
#IBOutlet weak var speakButton: NSButton!
#IBOutlet weak var stopButton: NSButton!
let speechSynth = NSSpeechSynthesizer.init(voice: NSSpeechSynthesizer.VoiceName.init(rawValue: "com.apple.speech.synthesis.voice.Victoria"))
var isSpeaking: Bool = false {
didSet {
updateButtons()
}
}
// MARK: - Overriden Properties
override var windowNibName: NSNib.Name? {
return NSNib.Name("MainWindowController")
}
// MARK: - Overidden Methods
override func windowDidLoad() {
super.windowDidLoad()
updateButtons()
speechSynth?.delegate = self
}
// MARK: - UI methods
#IBAction func speakIt(sender: NSButton) {
//Get tuype-in text as a string
let string = textField.stringValue
if string.isEmpty {
print("string from \(textField) is empty")
} else {
speechSynth?.startSpeaking(string)
isSpeaking = true
}
}
#IBAction func stopIt(sender: NSButton) {
speechSynth?.stopSpeaking()
}
func updateButtons(){
if isSpeaking {
speakButton.isEnabled = false
stopButton.isEnabled = true
} else {
speakButton.isEnabled = true
stopButton.isEnabled = false
}
}
// MARK: - NSSpeechSynthesizerDelegate Methods
//this functionality is considered more powerful and is made possible due to the speechSynthesizer.delegate = self
//the delegate is doing the work and reporting that completed work to the MainWindowController instance
//so kinda like the delegate is providing the signature and its up to us as the developers based on what we do with those parameters inside the function in order for us to add our own creativity.
func speechSynthesizer(_ sender: NSSpeechSynthesizer, didFinishSpeaking finishedSpeaking: Bool) {
//by setting this variable to FALSE, it will fire off the didSet computed property which this variable has both storage and behavior.
isSpeaking = false
}
// MARK: - NSWindowDelegate Methods
func windowShouldClose(_ sender: NSWindow) -> Bool {
return !isSpeaking
}
}
Your windowDidLoad method contains this line:
speechSynth?.delegate = self
This means the speech synthesizer object has a reference back to your MainWindowController, so the speech synthesizer object can send messages to your MainWindowController.
A simplified implementation inside NSSpeechSynthesizer could look something like this in Swift:
class NSSpeechSynthesizer: NSSoundDelegate {
weak var delegate: NSSpeechSynthesizerDelegate?
func startSpeaking(_ string: String) {
guard
let audioData = audioData(for: string),
let sound = NSSound(data: audioData)
else { return }
sound.delegate = self
sound.play()
}
// Part of NSSoundDelegate
func sound(_ sound: NSSound, didFinishPlaying finished: Bool) {
// The first ? means Swift only sends the message if
// delegate is not nil.
// The second ? means Swift only sends the message if delegate
// implements speechSynthesizer(_:didFinishSpeaking:).
delegate?.speechSynthesizer?(self, didFinishSpeaking: finished)
}
}
But it's actually implemented in Objective-C, where you have to be more verbose about checking whether the delegate handles the message:
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finished {
if ([delegate respondsToSelector:#selector(speechSynthesizer:didFinishSpeaking:)]) {
[delegate speechSynthesizer:self didFinishSpeaking:finished];
}
}

Swift 3 KVO to observe change in NSMutableSet (add, remove, modify item)

I'm develop an app which has an set of unique string. I have a function to add, remove, modify item in the NSMutableSet. I want to use KVO (key value observer) to observe whenever the set has change (add, remove, modify item).
Here's my code:
dynamic var barCodeSet = NSMutableSet()
in viewDidload I add observe:
override func viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: #keyPath(barCodeSet), options: [.old,.new,.initial], context: nil)
}
And this is my observe function:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(barCodeSet) {
print(barCodeSet.count)
for barcode in barCodeSet {
print(barcode)
}
}
}
I don't know why the KVO is not working. How can I modify the code so that we can get notify when set items change?
Assuming you do not need to use NSMutableSet, you could use a didSet clause in the variable declaration. try the following in a playground:
import UIKit
class myClass {
init() {
}
dynamic var barCodeSet: Set<String> = Set<String>() {
didSet {
print(barCodeSet.count)
for barcode in barCodeSet {
print(barcode)
}
}
}
}
let thisClass = myClass()
thisClass.barCodeSet = ["Apples", "Bananas", "Oranges"]
thisClass.barCodeSet.insert("Grapes")
Whenever you set the value of barCodeSet, its count and contents are printed to the console.

The class must be registered with registerSubclass before using Parse

I’m using KVO for a property in a subclassed PFObject which is already registered during initialize.
Everything is fine if I use 1 object. On the second object I get the error The class KVO_vs_PFObject.MyModel must be registered with registerSubclass before using Parse. I need multiple objects to observe properties.
I tried to use property observer(didSet) as an alternative on swift but the compiler won't let me since I'm using a managed property.
Does anyone know what's going on with this code?
Below is my code:
import UIKit
import Parse
class MyModel : PFObject, PFSubclassing {
static func parseClassName() -> String {
return "MyModel"
}
override class func initialize() {
var onceToken : dispatch_once_t = 0;
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
#NSManaged var property1 : String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var myObject = MyModel()
myObject.addObserver(self, forKeyPath: "property1", options: .New, context: nil)
myObject.property1 = "Hello"
myObject.removeObserver(self, forKeyPath: "property1")
// If I comment these 4 lines. myObject is happy observing the property.
var anotherObject = MyModel()
anotherObject.addObserver(self, forKeyPath: "property1", options: .New, context: nil)
anotherObject.property1 = "World"
anotherObject.removeObserver(self, forKeyPath: "property1")
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
var n : AnyObject? = change["new"]
switch keyPath {
case "property1" :
println("observed MyModel.property1 with value \(n)")
default :
break
}
}
}
I ended up using computed property instead of stored property as per related post. Subclassing PFObject in Swift
// #NSManaged var property1 : String?
var property1: String? {
get {
return self["property1"] as? String
}
set {
self["property1"] = newValue
println("observed MyModel.property1 with value \(newValue)")
}
}
I have this in my AppDelegate in addition to the initialize(). There were some posts here that say that initialize() needs some kick-starting before getting invoked. Worth trying the following...
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launc
//-----------------Parse customizations------------------------------
// Enable storing and querying data from Local Datastore.
// Remove this line if you don't want to use Local Datastore features or want to use cachePolicy.
Parse.enableLocalDatastore()
// ****************************************************************************
// Uncomment this line if you want to enable Crash Reporting
// ParseCrashReporting.enable()
//
// Uncomment and fill in with your Parse credentials:
//This one is for the MyAppName Parse App
MyModel.registerSubclass()
// Rest of the stuff...

Implement variable attribute property observer in Swift

I want to implement a didSet over a "sub-attribute" of a variable.
Example:
#IBOutlet weak var myLabel: UILabel!
var myLabel.hidden { didSet{ "DO SOMETHING" } }
I want to hide/show some other views when myLabel.hidden attribute change.
How can I do it?
You can make a property like this
var hideLabel: Bool = false {
didSet {
myLabel.isHidden = hideLabel
//SHOW OR HIDE OTHER VIEWS
}
}
By doing this you don't have to use KVO at the same time you can add more controls to hide to show at didSet context.
I Believe this is a simpler way to do such a thing.
The standard process is to use KVO. Add observer when the view is loaded:
override func viewDidLoad() {
super.viewDidLoad()
label.addObserver(self, forKeyPath: "hidden", options: .New | .Old, context: nil)
}
When the view controller is deallocated, make sure to remove the observer.
deinit {
label.removeObserver(self, forKeyPath: "hidden")
}
And do whatever you want inside the observeValueForKeyPath method:
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
NSLog("\(change)")
// do whatever you want here
}
Property observers can only be added to a class, not an instance. In order to do this you need to subclass UILabel and then override hidden's property observers.
EDIT: If you want to set custom observers on instances of CustomLabel, you can do this easily by adding block function variables to your CustomLabel that trigger on didSet or willSet
class CustomLabel: UILabel {
var onDidSetHidden: ((Bool) -> ())?
var onWillSetHidden: ((Bool) -> ())?
override var hidden: Bool {
didSet {
if let block = onDidSetHidden {
block(self.hidden)
}
}
willSet (willBeHidden) {
if let block = onWillSetHidden {
block(willBeHidden)
}
}
}
}
var custom = CustomLabel()
custom.onDidSetHidden = { (isHidden) in
if isHidden {
println("IS HIDDEN")
} else {
println("IS NOT HIDDEN")
}
}
custom.hidden = true //prints 'IS HIDDEN'