Changing a JSContext-passed Swift object with JavaScriptCore - macos

I have a problem changing an object passed into JavaScriptCore.
Here is my custom object, defining a single String property called testProperty:
import Foundation
import JavaScriptCore
protocol JSCustomObjectExport: JSExport {
var testProperty: String { get set }
}
class JSCustomObject: NSObject, JSCustomObjectExport {
var testProperty: String = "ABC"
}
Here is the AppDelegate in which I create a JSContext, pass my custom object to it and run a JS script to change its testProperty from "ABC" to "XYZ". However the testProperty is never changed.
import Cocoa
import JavaScriptCore
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow!
lazy var context = JSContext()
func applicationDidFinishLaunching(aNotification: NSNotification?) {
println("Started")
var co = JSCustomObject()
context.globalObject.setValue(co, forProperty: "customObject")
context.evaluateScript("customObject.testProperty = 'XYZ'")
println(co.testProperty) // ERROR(?): This prints "ABC" instead of "XYZ"
}
}
Am I doing something wrong? Shouldn't co.testProperty change?
Btw, this is an OS X app, compiled in XCode 6.1.1 on OSX 10.10.1.

It seems, it requires that the protocol is marked as #objc, and the class has explicit #objc export name.
I tried the following script in Playground, and it works
import Foundation
import JavaScriptCore
#objc // <- HERE
protocol JSCustomObjectExport: JSExport {
var testProperty: String { get set }
}
#objc(JSCustomObject) // <- HERE
class JSCustomObject: NSObject, JSCustomObjectExport {
var testProperty: String = "ABC"
}
var context = JSContext()
var co = JSCustomObject()
context.globalObject.setValue(co, forProperty: "customObject")
context.evaluateScript("customObject.testProperty = 'XYZ'")
println(co.testProperty) // -> XYZ

Related

How to confirm ObservableObject for an AppDelegate?

I'm trying to observe a value in macOS AppDelegate but I got an Error
ContentView.swift:14:6: Generic struct 'ObservedObject' requires that 'NSApplicationDelegate?' conform to 'ObservableObject'
when I try to cast the object into ObservedObject with as! ObservedObject I have another Error
ContentView.swift:14:6: Generic struct 'ObservedObject' requires that 'ObservedObject' conform to 'ObservableObject'
Inside AppDelegate.swift file
import Cocoa
import SwiftUI
import Combine
#NSApplicationMain
class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate {
var isFocused = true
// Other code app life-cycle functions
}
Inside the ContentView.swift file
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var appDelegate = NSApplication.shared.delegate
// Other UI code
}
This looks like mix of concepts.. I'd recommend to avoid such... instead created explicit observable class.
Like below (sketch)
class AppState: ObservableObject {
#Published var isFocused = true
}
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var appState = AppState()
// Other code app life-cycle functions
// in place where ContentView is created
...
ContentView().environmentObject(self.appState)
...
}
and in ContentView just use it
struct ContentView: View {
#EnvironmentObject var appState: AppState
// Other UI code
var body: some View {
// .. use self.appState.isFocused where needed
}
}

Accessing PFObject Subclassed Properties from a Query

I am using Parse and have created a subclass of PFObject. When creating objects it makes things much easier. Once objects are created, I am experimenting with querying the database and accessing the custom properties I created. What I am finding is that I cannot use dot notation to access the properties when I am working the the PFObjects returned from the query. Is this normal?
Here is subclass I created.
import Foundation
import UIKit
import Parse
class MessagePFObject: PFObject
{
#NSManaged var messageSender : String
#NSManaged var messageReceiver : String
#NSManaged var messageMessage : String
#NSManaged var messageSeen : Bool
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Custom Query method.
override class func query() -> PFQuery?
{
let query = PFQuery(className: MessagePFObject.parseClassName())
query.includeKey("user")
query.orderByDescending("createdAt")
return query
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
init(messageSenderInput: String?, messageReceiverInput: String?, messageMessageInput: String?)
{
super.init()
self.messageSender = messageSenderInput!
self.messageReceiver = messageReceiverInput!
self.messageMessage = messageMessageInput!
self.messageSeen = false
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override init()
{
super.init()
}
}
//++++++++++++++++++++++++++++++++++++ EXTENSION +++++++++++++++++++++++++++++++++++++++++++++++++++
extension MessagePFObject : PFSubclassing
{
class func parseClassName() -> String
{
return "MessagePFObject"
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override class func initialize()
{
var onceToken: dispatch_once_t = 0
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
}
Here is my query and what I am required to do to access the properties. createdAt, updatedAt, etc are all available with dot notation but none of my custom properties are. You can see I access messageSeen with element.objectForKey("messageSeen").
let predicate = NSPredicate(format: "messageSender == %# OR messageReceiver == %#", self.currentUser!.username!, self.currentUser!.username!)
let query = messagePFObject.queryWithPredicate(predicate)
query!.findObjectsInBackgroundWithBlock ({ (objects, error) -> Void in
if error == nil && objects!.count > 0
{
for element in objects!
{
print(element)
print(element.parseClassName)
print(element.objectId)
print(element.createdAt)
print(element.updatedAt)
print(element.objectForKey("messageSeen"))
}
}
else if error != nil
{
print(error)
}
})
If this is normal then that is fine. I just want to make sure I am not missing something.
Take care,
Jon
Your object subclass has to implement the PFSubclassing protocol and you need to call MessagePFObject.registerSubclass() in your app delegate.
The parse documentation is very good : https://parse.com/docs/ios/guide#objects-subclasses

Swift - Pass a reference to a class

I have a class which will perform a search. Once complete I want the search class to pass back the results to which ever instance (of another class) started the search. My thinking was to pass a reference to the class which instantiates the search class and use that reference to call a function. Here's a basic example of what I'm trying to do. How can I get this to work, or is there another/better way?
Search Class (I've tried AnyObject and UITableViewContoller):
class SearchClass : NSObject, NSURLConnectionDelegate, NSURLConnectionDataDelegate {
var callingClass : AnyObject? = nil //To store reference to the "other" class
var searchResults : [[String : AnyObject]]? = nil
init(callingClass: AnyObject) { //I don't know the name of the ViewController class which will instantiates this as it will be called by several different classes
self.callingClass = callingClass
}
func startSearch(searchString: String) {
//NSURLConnection etc
}
func connectionDidFinishLoading(connection: NSURLConnection) {
//more code
searchResults = ...
callingClass!.searchCompleted(searchResults) //Pass the search results back to the class instance which started the search
}
}
Other Classes:
class RandomViewController : UITableViewController {
//The casting on the next line fails
let Searcher = SearchClass(callingClass: self as! UITableViewController) //OR AnyObject
func randomFunction() {
searcher.startSearch("search query")
}
func searchComplete(searchResults: [[String : AnyObject]]) {
self.searchResults = searchResults
tableView.reloadData()
}
}
You can add a closure parameter to your startSearch function in your search class:
typealias SearchResultHandler = (result: [String : AnyObject]) -> ()
func startSearch(query: String, resultHandler: SearchResultHandler) {
// send the search request and in the completion handler of the request call your result handler:
resultHandler(result: searchResult)
}
Which you would then call from any class:
let searcher = SearchClass()
searcher.startSearch("query") { (result) -> () in
self.searchResults = results
tableView.reloadData()
}
You can use generics when you don't know what the class is going to be:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html
However....
This seems like something that you should use protocol / delegates for.
// NOTE: weak references must be class type
protocol SearchClassDelegate: class {
func searchComplete(results: [[String: AnyObject]?])
}
// This is where you define T as the generic class
class SearchClass {
weak var delegate: SearchClassDelegate? // This is your "callingClass"
// NOTE: make sure it's weak
// ...
func connectionDidFinishLoading() {
// ...
self.delegate?.searchComplete(results)
}
}
// Set to the delegate
class ViewController: UIViewController, SearchClassDelegate {
// ...
// Make sure you set the delegate
// Here is where you implement this function
func searchComplete(results: [[String : AnyObject]?]) {
// Do whatever
// ...reloadData() etc.
}
}

error message in Xcode v 6.3.2

I'm getting this error message in relation to the exclamation mark at the end of this line:
sound1.URL = soundURL!
"Cannot assign a value of type ‘NSURL’ to a value of type ‘NSURL’
Type"
I've googled this issue and looked at a couple of explanations for this error code but I still can't work out what's wrong. Can anyone help?
import UIKit
import AVFoundation
class SoundListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var audioPlayer = AVAudioPlayer()
var sounds: [Sound] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.dataSource = self
self.tableView.delegate = self
var soundPath = NSBundle.mainBundle().pathForResource("mhsTesting", ofType: "m4a")
var soundURL = NSURL.fileURLWithPath(soundPath!)
var sound1 = Sound()
sound1.name = "Michael"
sound1.URL = soundURL!
self.sounds.append(sound1)
}
}
This is your Sound class?
class Sound{
var name: String?
var URL : NSURL.Type?
}
Try with code below:
class Sound{
var name: String?
var URL : NSURL?
}

Label is nil in custom controller class

// This is in ViewController.swift
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var oneLabel: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
oneLabel.stringValue = "All is well" // Here it works
//...
}
}
// Separate Swift code file
import Cocoa
import Foundation
var si = Simulate()
class Simulate: NSViewController {
#IBOutlet weak var aLabel: NSTextField!
func simulationManager() -> Bool {
var ni: Int
var breakPoint = false
rd.simStatus = .Running
do {
if rd.rchIndex >= ld.NodeCount(.Reach) {
if InterStepConvergence() {
NextTimeStep()
if aLabel != nil { // This is always false
aLabel.stringValue = String(rd.elapsedSec)
}
else {
println("Label is nil")
}
//...
}
}
}
}
}
I am trying to set up a custom controller to update the interface while a
simulation is running. I need to show the status of the simulation. The simulation runs in a separate thread, but even if I do it in the main thread, same problem as described below.
The label text can be changed if I do it in the ViewController class as above.
But if I try to modify the text on the label in the Simulate class the label
is always nil and so it doesn't work. But the code compiles OK. What am I missing here such that the label is always nil in the Simulate class? Thanks much, in advance.

Resources