UndoManager in NSResponder subclass (macOS) - swift

I am trying to implement undo/redo in my model. So I made my model class a subclass of NSResponder, and then implemented the following:
note: this code is edited based on more research after comments
func setAnnotations(_ newAnnotations: [Annotation]) {
let currentAnnotations = self.annotations
self.undoManager.registerUndo(withTarget: self, handler: { (selfTarget) in
selfTarget.setAnnotations(currentAnnotations)
})
self.annotations = newAnnotations
}
Annotation is a struct.
The code inside the closure never gets executed. Initially I noticed that undoManager is nil, but then I found this snippet:
private let _undoManager = UndoManager()
override var undoManager: UndoManager {
return _undoManager
}
Now undoManager is no longer nil, but the code inside the closure still doesn't get executed.
What am I missing here?

I can't reproduce any issue now that you've made your undo register code make sense. Here is the entire code of a test app (I don't know what an Annotation is so I just used String):
import Cocoa
class MyResponder : NSResponder {
private let _undoManager = UndoManager()
override var undoManager: UndoManager {
return _undoManager
}
typealias Annotation = String
var annotations = ["hello"]
func setAnnotations(_ newAnnotations: [Annotation]) {
let currentAnnotations = self.annotations
self.undoManager.registerUndo(withTarget: self, handler: { (selfTarget) in
selfTarget.setAnnotations(currentAnnotations)
})
self.annotations = newAnnotations
}
}
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let myResponder = MyResponder()
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
print(self.myResponder.annotations)
self.myResponder.setAnnotations(["howdy"])
print(self.myResponder.annotations)
self.myResponder.undoManager.undo()
print(self.myResponder.annotations)
}
}
The output is:
["hello"]
["howdy"]
["hello"]
So Undo is working perfectly. If that's not happening for you, perhaps you are mismanaging your "model class" in some way.
By the way, a more correct to write your registration closure is this:
self.undoManager.registerUndo(withTarget: self, handler: {
[currentAnnotations = self.annotations] (selfTarget) in
selfTarget.setAnnotations(currentAnnotations)
})
This ensures that self.annotations is not captured prematurely.

Related

VIPER architecture using Swift to store data in presenter

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.

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];
}
}

How to pass value from NSViewController to custom NSView of NSPopover?

By using the delegation protocol I have tried to pass a string (inputFromUser.string) from NSViewController - mainController to custom subclass of NSView of NSPopover - PlasmidMapView, to drawRect function, see code below. But, it didn’t work. I don’t know where a mistake is. Maybe there is another way to pass this string.
Update
File 1.
protocol PlasmidMapDelegate {
func giveDataForPLasmidMap(dna: String)
}
class MainController: NSViewController {
#IBOutlet var inputFromUser: NSTextView!
var delegate: plasmidMapDelegate?
#IBAction func actionPopoverPlasmidMap(sender: AnyObject) {
popoverPlasmidMap.showRelativeToRect(sender.bounds,
ofView: sender as! NSView, preferredEdge: NSRectEdge.MinY)
let dna = inputDnaFromUser.string
delegate?.giveDataForPLasmidMap(dna!)
}
}
File 2
class PlasmidMapView: NSView, PlasmidMapDelegate {
var dnaForMap = String()
func giveDataForPLasmidMap(dna: String) {
dnaForMap = dna
}
override func drawRect(dirtyRect: NSRect) {
let objectOfMainController = MainController()
objectOfMainController.delegate = self
//here I have checked if the string dnaForMap is passed
let lengthOfString = CGFloat(dnaForMap.characters.count / 10)
let pathRect = NSInsetRect(self.bounds, 10, 45)
let path = NSBezierPath(roundedRect: pathRect,
xRadius: 5, yRadius: 5)
path.lineWidth = lengthOfString //the thickness of the line should vary in dependence on the number of typed letter in the NSTextView window - inputDnaFromUser
NSColor.lightGrayColor().setStroke()
path.stroke()
}
}
Ok, there's some architecture mistakes. You don't need delegate method and protocol at all. All you just need is well defined setter method:
I. Place your PlasmidMapView into NSViewController-subclass. This view controller must be set as contentViewController-property of your NSPopover-control. Don't forget to set it the way you need in viewDidLoad-method or another.
class PlasmidMapController : NSViewController {
weak var mapView: PlacmidMapView!
}
II. In your PlacmidMapView don't forget to call needsDisplay-method on dna did set:
class PlasmidMapView: NSView {
//...
var dnaForMap = String() {
didSet {
needsDisplay()
}
//...
}
III. Set dna-string whenever you need from your MainController-class.
#IBAction func actionPopoverPlasmidMap(sender: AnyObject) {
popoverPlasmidMap.showRelativeToRect(sender.bounds,
ofView: sender as! NSView, preferredEdge: NSRectEdge.MinY)
let dna = inputDnaFromUser.string
if let controller = popoverPlasmidMap.contentViewController as? PlasmidMapController {
controller.mapView.dna = dna
} else {
fatalError("Invalid popover content view controller")
}
}
In order to use delegation your class PlasmidMapView needs to have an instance of the MainController (btw name convention is Class, not class) and conform to the PlasmidMapDelegate (once again name convention dictates that it should be PlasmidMapDelegate). With that instance you then can:
mainController.delegate = self
So, after several days I have found a solution without any protocols and delegation as Astoria has mentioned. All what I needed to do was to make #IBOutlet var plasmidMapIBOutlet: PlasmidMapView!for my custom NSView in MainController class and then to use it to set the value for the dnaForMap in #IBAction func actionPopoverPlasmidMap(sender: AnyObject).
class PlasmidMapView: NSView
{
var dnaForMap = String()
}
class MainController: NSViewController
{
#IBOutlet var inputFromUser: NSTextView!
#IBOutlet var plasmidMapIBOutlet: PlasmidMapView!
#IBAction func actionPopoverPlasmidMap(sender: AnyObject)
{
plasmidMapIBOutlet.dnaForMap = inputDnaFromUser.string!
popoverPlasmidMap.showRelativeToRect(sender.bounds,
ofView: sender as! NSView, preferredEdge: NSRectEdge.MinY)
}
}

How to call a func without return in Swift? [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I have this function in my class "RunnerWindowController":
func didChangeScreenParameters(){
runnerLayer.removeAllAnimations()
animateRunner()
}
I try to call the function out of another class using:
var RunnerWindowC: RunnerWindowController!
#IBAction func btnSenden(sender: AnyObject) {
RunnerWindowC.didChangeScreenParameters()
}
I get this fatal error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I won't need any return, only the execute of the function.
EDIT: 05/13/2016
None of the answers help because i have a INIT in die RunnerWindowController.
SOLVED PROBLEM:
I solved the problem with a NSTimer.
RunnerWindowController:
var SetDidChange : Bool = false
class RunnerWindowController{
var startvalue : CGFloat = 0
var timer : NSTimer?
func initTimer() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target:
self, selector: #selector(refresh), userInfo: nil, repeats: true)
}
#objc func refresh() {
if (SetDidChange == true) {
print("Refresh done")
runnerLayer.removeAllAnimations()
animateRunner()
SetDidChange = false
}
}
....
}
RunnerPrefController:
override func viewDidAppear() {
SetDidChange = false
}
#IBAction func btnSenden(sender: AnyObject) {
SetDidChange = true
}
try this:
class RunnerWindowController: UIViewController {
class func didChangeScreenParameters(){
runnerLayer.removeAllAnimations()
animateRunner()
}
}
class yourClassName: UIViewController {
#IBAction func btnSenden(sender: AnyObject) {
RunnerWindowController().didChangeScreenParameters()
}
}
One set of parenthesis is enough - ie. RunnerWindowC.didChangeScreenParameters(). However, the error you're getting probably has to do with RunnerWindowC being nil - make sure you assign RunnerWindowC (eg. if you're segueing from it, pass it along).
try to use with segue as
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let vc = segue.destinationViewController as? RunnerWindowController
vc?.didChangeScreenParameters()
}
The problem is likely that you are trying to call a method on a variable that has not been initialized. The code you've shown will only define the class variable, not initialize it. At some point before calling that method you'll have to initialize it like this:
self.RunnerWindowC = RunnerWindowController()
If that controller is coupled to a storyboard you can initialize it like this:
let storyboard = UIStoryboard(name: "NameOfStoryboardFile", bundle: nil)
self.RunnerWindowC = storyboard.instantiateViewControllerWithIdentifier("RunnerWindowController") as! RunnerWindowController
EDIT 5-12-16:
To initialize a variable at the class level in swift, there are two main approaches:
Approach A:
Please note that the initializer you implement will depend on
which class you inherit from. If you don't inherit from any class or you
inherit from NSObject (like I do below) then you'll override init()
If you inherit from a different class, you may have to override a different
initializer. For example, inheriting from UIViewController means you should
override the init(coder:) method, not init().
class MyParentClass: NSObject {
var RunnerWindowC: RunnerWindowController!
init() {
RunnerWindowC = RunnerWindowController()
super.init()
// ^^^ All class variables need to be initialized
// before the call to super.init() in swift!
}
// If you were doing this in a class that inherits UIViewController
// you would implement the initializer like this instead:
/****
required init?(coder aDecoder: NSCoder) {
RunnerWindowC = RunnerWindowController()
super.init(coder: aDecoder)
}
****/
#IBAction func btnSenden(sender: AnyObject) {
self.RunnerWindowC.didChangeScreenParameters()
}
}
Approach B:
Swift also allows you to initialize the variables when they're defined at the class level, so you can do this as well:
class MyParentClass: NSObject {
var RunnerWindowC = RunnerWindowController()
#IBAction func btnSenden(sender: AnyObject) {
self.RunnerWindowC.didChangeScreenParameters()
}
}
I honestly don't know if there's any technical difference between the two other than just personal preference. I've used both and never had a problem.

private func: Missing argument for parameter #1 in call

Well, here's another 'Missing argument for parameter #1 in call' issue. (Seems Apple could do a better job naming its errors :-p )
In my class, I'm calling a private function libraryVisibility(), and on that line I get a Missing argument for parameter #1 in call error on compilation. Don't really understand why.
Anyhow, here's the code:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var libraryState: Bool = libraryVisibility()
func applicationDidFinishLaunching(aNotification: NSNotification) {
…
}
private func libraryVisibility() -> Bool {
…
// dostuff and return boolean
return true
}
}
You can't call instance functions in the default initialiser of a property
You can either make your libraryVisibility() function a class function:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var libraryState : Bool = AppDelegate.libraryVisibility()
private class func libraryVisibility() -> Bool {
let homeUrl = NSURL(string: NSHomeDirectory())
let libraryUrl = NSURL(string: "Library", relativeToURL: homeUrl)
return libraryUrl!.hidden
}
}
Or you could make your libraryState property a lazy property:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
lazy var libraryState : Bool = self.libraryVisibility()
private func libraryVisibility() -> Bool {
let homeUrl = NSURL(string: NSHomeDirectory())
let libraryUrl = NSURL(string: "Library", relativeToURL: homeUrl)
return libraryUrl!.hidden
}
}
The property will then get initialised the first time you use it.
Mike Buss has a nice usage guide on how to use lazy variables.
Ok, I have solved it, but not entirely sure why this is working:
Giving the libraryState variable a default value and setting it inside applicationDidFinishLaunching seems to do the trick:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var libraryState: Bool = false
func applicationDidFinishLaunching(aNotification: NSNotification) {
libraryState = libraryVisibility()
}
}