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];
}
}
Related
So I'm setting up a simple VIPER architecture in Swift.
The Interactor gets some data from an API, and passes the data to the presenter that then passes formatted data to the view.
The presenter will process the data, and just count the number of objects that are downloaded. To do so I have stored a var in the presenter. The question is should I store data in the presenter?
Interactor:
class Interactor {
weak var presenter: Presenter?
func getData() {
ClosureDataManager.shared.fetchBreaches(withURLString: baseUrl + breachesExtensionURL, completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
print(error)
case .success(let breaches):
self.presenter?.dataDidFetch(breaches: breaches)
self.presenter?.dataNumberDidFetch(number: breaches.count)
}
})
}
}
Presenter:
class Presenter {
var wireframe: Wireframe?
var view: ViewController?
var interactor: Interactor?
var dataDownloaded = 0
func viewDidLoad() {
print ("presenter vdl")
}
func loadData() {
interactor?.getData()
}
func dataDidFetch(breaches: [BreachModel]) {
view?.dataReady()
}
func showDetail(with text: String, from view: UIViewController) {
wireframe?.pushToDetail(with: text, from: view)
}
func dataNumberDidFetch(number: Int) {
dataDownloaded += number
view?.showData(number: String(dataDownloaded) )
}
}
View (ViewController)
protocol dataViewProtocol {
func showData(number: String)
}
class ViewController: UIViewController, dataViewProtocol {
#IBOutlet weak var showDetailButton: UIButton!
#IBOutlet weak var dataLabel: UILabel!
// weak here means it won't work
var presenter: Presenter?
#IBAction func buttonPressAction(_ sender: UIButton) {
presenter?.loadData()
}
#IBAction func buttonShowDetailAction(_ sender: UIButton) {
presenter?.showDetail(with: "AAA", from: self)
}
func dataReady() {
showDetailButton.isEnabled = true
}
func showData(number: String) {
dataLabel.text = number
}
override func viewDidLoad() {
super.viewDidLoad()
Wireframe.createViewModule(view: self)
presenter?.viewDidLoad()
}
}
Router (Wireframe)
class Wireframe {
static func createViewModule (view: ViewController) {
let presenterInst = Presenter()
view.presenter = presenterInst
view.presenter?.wireframe = Wireframe()
view.presenter?.view = view
view.presenter?.interactor = Interactor()
view.presenter?.interactor?.presenter = presenterInst
}
}
So should the presenter be used to store the number of objects downloaded?
What have you tried I've implemented the var, as shown above. This is a minimum example of the problem.
What resources have you used I've looked on StackOverflow, and Googled the issue. I can't find an answer, but know I could store the data in the view but I think this is incorrect. I could store the number of data in the Interactor, but this also doesn't seem right. It all seems...to violate separation of concerns...
I won't do your homework / use a different architecture / You should use protocols / Why is there a single protocol in your implementation This isn't homework, it is for my own self - study. There may be other architectures that can be used to do this (and coding to protocols is good practice) but this is about storing a variable in the presenter. I want to know if I should store the variable in the presenter, using VIPER and using Swift. Comments about trivia around the question are seldom helpful if they are about variable names, or the like.
What is the question? I want to know if I can store the number of downloaded data items in the presenter.
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)
}
I have 3 UIViewControllers. ContainerVC which contains 2 ContainerViews. First Container View is DashboardVC and second one is SidebarVC. The DashboardVC covers the entire screen, while the SidebarVC is outside.
I have a leading constraint for the SidebarVC that should be animated and the SidebarVC should slide in (from the left side). On the DashboardVC I have a UIBarButtonItem and when it's pressed it should perform the animation. The problem is that I'm doing something wrong with the delegate and when the ContainerVC conforms to the protocol, nothing happens.
PS: I have very hard time understanding protocols/delegates despite having watch a bunch of different videos on this concept. Here's the code:
DashboardVC
protocol SideBarDelegate {
func showMenu()
func hideMenu()
}
class DashboardVC: UIViewController {
var delegate: SideBarDelegate?
var isSideMenuOpen = true
#IBAction func menuButtonPressed(_ sender: UIBarButtonItem) {
if isSideMenuOpen {
delegate?.showMenu()
isSideMenuOpen = false
}
else {
delegate?.hideMenu()
isSideMenuOpen = true
}
}
}
ContainerVC
class ContainerVC: UIViewController {
#IBOutlet weak var sideBarMenuLeadingConstraint: NSLayoutConstraint!
}
extension ContainerVC : SideBarDelegate {
func showMenu() {
sideBarMenuLeadingConstraint.constant = -290
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
func hideMenu() {
sideBarMenuLeadingConstraint.constant = 0
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
You use the delegate only on classes. To prevent memory leaks, do those two things:
Change:
protocol SideBarDelegate {
func showMenu()
func hideMenu()
}
to:
protocol SideBarDelegate: class {
func showMenu()
func hideMenu()
}
Now, rename delegate property to:
weak var delegate: SideBarDelegate?
Weak does not increase the reference counting. This is important to prevent memory leaks.
Your instance of ContainerVC must have some sort of reference to an instance of DashboardVC (or make the delegate static but I have never seen something like that). Then, in your viewDidLoad method of ContainterVC, set this:
myInstanceReferenceToDashboardVC.delegate = self
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"?
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'