-2

I would like to write a simple swift code for a MacOS app which add a new label wherever the user perform a mouse click in a window.

This code compiles but make the app crash:

@IBOutlet var here2 = [NSTextField]()
var count: Int = 0
func getCoordinates(){
        NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown]) {
        if self.location.x < 700 && self.location.y<750 {
            self.here2.append(NSTextField.init())
            self.here2[self.count].frame.origin = CGPoint(x: self.location.x, y: self.location.y)
            self.here2[self.count].stringValue = String(self.count)
            print("count is: " + String(self.here2.count))
            self.count+=1
        }
        return $0
    }

Asperi
  • 228,894
  • 20
  • 464
  • 690
marco
  • 3
  • 1
  • "but make the app crash", alright, so what is the error message? – Roope May 30 '20 at 15:49
  • Failed to set (contentViewController) user defined inspected property on (NSWindow): -[NSTextField copyWithZone:]: unrecognized selector sent to instance 0x100724e30 – marco May 30 '20 at 15:53
  • 1
    Off-topic, it will only complicate things to have a class property `count` to maintain, instead when you need it just use the `.count` property on your array – Joakim Danielson May 30 '20 at 15:54
  • When does the app crash? From where is `getCoordinates` called? Is `here2` connected in the storyboard? – Willeke May 30 '20 at 21:33
  • getCoordinates() is called inside viewDidLoad() of the third ViewController of the app and here2 is connected in the storyboard to a label object. The app crash when the third ViewController is called – marco May 30 '20 at 22:00
  • 1
    i'm new to swift so maybe it is a stupid observation, but i think that the problem begins when i connect the array [NSTextField] to the "simple" NSTextField object in the storyboard. For my purpose I need to use arrays because I don't know how many NSTextField should be added in the ViewController... this is the whole problem of my question – marco May 30 '20 at 22:08

4 Answers4

0

You cannot call copy on NSTextView, that causes the error message.

You implement all the required methods by subclassing the view, but I'd suggest you allocate new text views programmatically and set their style in code instead of Interface builder.

Here's another thread dealing with copying objects: "[something copyWithZone:]: unrecognized selector sent to instance" when using Bindings / Core Data

Tritonal
  • 607
  • 4
  • 16
0

Here is a programmatic approach which may be run in Terminal. Note that window.acceptsMouseMovedEvents is set to true.

//May be run in Terminal with:
//swiftc label.swift -framework Cocoa -o label && ./label

import Cocoa

class AppDelegate: NSObject, NSApplicationDelegate {
var window : NSWindow!
var here2 = [NSTextField]()
var count: Int = 0
let _wndW : CGFloat = 700
let _wndH : CGFloat = 750
let _labelW : CGFloat = 24
let _labelH : CGFloat = 24

@objc func getCoordinates(_ sender:AnyObject ) {
 NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown]) {
var pt: NSPoint? { self.window.mouseLocationOutsideOfEventStream }
if let location = pt {
 let msPt:NSPoint = self.window.contentView!.convert(location, from: nil)
 print("x = \(msPt.x) : y = \(msPt.y)")
 if msPt.x < (self._wndW - self._labelW) && msPt.y < (self._wndH - self._labelH ) {
 self.here2.append(NSTextField.init())
 self.here2[self.count].frame = NSMakeRect(msPt.x, msPt.y, self._labelW, self._labelH) 
 print(self.here2[self.count].frame)
 self.window.contentView!.addSubview (self.here2[self.count])
 self.here2[self.count].backgroundColor = NSColor.white
 self.here2[self.count].isSelectable = false
 self.here2[self.count].stringValue = String(self.count)
 print("count is: " + String(self.here2.count))
 self.count+=1
}
}
  return $0
 }
}

func buildMenu() {
 let mainMenu = NSMenu()
 NSApp.mainMenu = mainMenu
 // **** App menu **** //
 let appMenuItem = NSMenuItem()
 mainMenu.addItem(appMenuItem)
 let appMenu = NSMenu()
 appMenuItem.submenu = appMenu
 appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
}

func buildWnd() {
 window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable, .resizable], backing:.buffered, defer:false)
 window.center()
 window.title = "Swift Test Window"
 window.makeKeyAndOrderFront(window)
 window.acceptsMouseMovedEvents = true

// **** Button **** //
let myBtn = NSButton (frame:NSMakeRect( _wndW - 180, 15, 135, 24 ))
 myBtn.bezelStyle = .rounded
 myBtn.autoresizingMask = [.maxXMargin,.minYMargin]
 myBtn.title = "Start Label Maker"
 myBtn.action = #selector(self.getCoordinates(_:))
 window.contentView!.addSubview (myBtn)

// **** Quit btn **** //
let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
 quitBtn.bezelStyle = .circular
 quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
 quitBtn.title = "Q"
 quitBtn.action = #selector(NSApplication.terminate)
 window.contentView!.addSubview(quitBtn)
}

func applicationDidFinishLaunching(_ notification: Notification) {
 buildMenu()
 buildWnd()
}

func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
 return true
}

}
let appDelegate = AppDelegate()

// **** main.swift **** //
let app = NSApplication.shared
app.delegate = appDelegate
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps:true)
app.run()


apodidae
  • 1,988
  • 2
  • 5
  • 9
  • Thanks @apodidae! the code was really useful and it works very well! Now I should integrate this code inside my app that was done with XCode as a single view application and storyboard... How can I do that? Maybe I can completely rewrite the app in order to be run with terminal... Do you know where can I learn this different approach (books? other resources?)? – marco May 31 '20 at 11:35
0

You can't connect and array of NSTextField in a storyboard. In my opinion it's a bug in Xcode, IB shouldn't make the connection. It's also a bug in AppKit, I tried this and got this weird error:

Failed to set (contentViewController) user defined inspected property on (NSWindow): [<__NSTimeZone 0x6040000a2280> valueForUndefinedKey:]: this class is not key value coding-compliant for the key identifier.

Solution: remove the connection in IB and change

@IBOutlet var here2 = [NSTextField]()

to

var here2 = [NSTextField]()
@IBOutlet weak var here2TextField: NSTextField!

Connect here2TextField in IB and append here2TextField to here2 in viewDidLoad().

Willeke
  • 14,578
  • 4
  • 19
  • 47
0

@Marco: The code which I posted is a template (aka boilerplate) example of the programmatic approach to creating applications. All that I did was copy/paste your function into the AppDelegate class of the template and connect it to a button. From there I revised your code until the desired result was obtained. Instead of using a XIB or storyboard the technique requires that source code be written by the author (or use templates). The good news is that you have control of your application and nothing is done behind your back; what you see is what you get. The downside is that the technique is cumbersome for really large projects; for this I rely on Xcode with XIBs. The autocompletion feature of Xcode is also very helpful, while with the programmatic approach you have to rely on Xcode/Help/DeveloperDocumentation as well as internet searches, especially StackOverflow (usually someone else has had the same problem that you are experiencing). In lieu of using the CommandLine from Terminal for every compile I automated the process with a very simple editor which was written in Swift using Xcode (with XIB). However, the posted code may also be run in Xcode by doing the following:

1) Create a new macOS App using Swift (User Interface is XIB but it won’t be needed for the demo) 2) Use File/New/File to add a Swift file and name it ‘main.swift’ 3) In the new main.swift file change ‘import Foundation’ to ‘import Cocoa’ 4) Copy/paste the last five lines of the demo (labelled main.swift) into this file 5) Go into the AppDelegate file that Apple provided and delete everything there except for ‘import Cocoa’ 6) Copy/paste the entire AppDelegate class of the posted code (down to the main.swift section which you have already used) 7) Hit the ‘Run’ button, and it should compile without error.

Hope it helps and good luck with your project. Thanks for posting.

apodidae
  • 1,988
  • 2
  • 5
  • 9