How to confirm ObservableObject for an AppDelegate? - macos

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

Related

No ObservableObject of type User found

I am trying to understand #EnvironmentObject better so I wrote sample code below to replicate the issue i am facing
This is the class where i declare the array which needs to be accessed in multiple locations and be displayed and updated in ContentView
class User: ObservableObject {
#Published var array = [String]()
func diplayName(name: String){
self.array.append(name)
}
}
I want to be able to append my array in another class. Something like the below code
class myTests: ObservableObject {
#EnvironmentObject var user:User
func diplayMyName(name: String){
self.user.array.append(name)
}
}
When I call displayMyName function in myTests class i get an Error message as below
Fatal error: No ObservableObject of type User found.
A View.environmentObject(_:) for User may be missing as an ancestor of this view.
This is how my contentView looks like
struct ContentView: View {
#EnvironmentObject var user:User
var testing = myTests()
var body: some View {
VStack {
List(user.array, id: \.self){ x in
Text(x)
}
Button(action: {
self.user.diplayName(name: "Name1")
// self.testing.diplayMyName(name: "Name2")
}){
Text("Call Function")
}
}
}
}
This is how i declare my environment object in scene delegate
let contentView = ContentView().environmentObject(User())
I would really appreciate if someone can help me understand why am i getting the error when i append the published array from myTests class. Thank you.
UPDATE
To work around my issue i did the following adjustments
I returned an array in myTests class
class myTests {
var ar = [String]()
func displayMyName() -> [String] {
ar.removeAll()
ar.append(contentsOf: ["Name2", "Name3"])
return ar
}
}
And added it to the array in ContentView
struct ContentView: View {
#EnvironmentObject var user : User
var testing = myTests()
var body: some View {
VStack {
List(user.array, id: \.self){ x in
Text(x)
}
Button(action: {
self.user.array.append(contentsOf: self.testing.displayMyName())
}){
Text("Call Function")
}
}
}
}

how to Load initial window controller from storyboard?

I have gone through many questoions but none of them snaswers my query.
I am trying to load initial window programmatically
Here is what I have done.
I have added main.swift as-
import Cocoa
private func runApplication(
application: NSApplication = NSApplication.sharedApplication(),
delegate: NSApplicationDelegate? = AppDelegate(),
bundle: NSBundle = NSBundle.mainBundle(),
nibName: String = "MainMenu",
var topLevelObjects: NSArray? = nil) {
setApplicationDelegate(application, delegate)
}
private func setApplicationDelegate(application: NSApplication, delegate: NSApplicationDelegate?) -> NSApplication {
if let delegate = delegate {
application.delegate = delegate
}
return application
}
runApplication()
Appdelegate.swift is-
import Cocoa
//#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var appControl:AppFlow?
func applicationDidFinishLaunching(aNotification: NSNotification) {
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
override init() {
//
self.appControl = AppFlow()
super.init()
}
}
And in AppFlow I am trying to load window controller from storyboard.-
import Cocoa
class AppFlow{
let initialStoryBoard:NSStoryboard?
override init() {
self.initialStoryBoard = NSStoryboard(name: "Main" , bundle : nil)
super.init()
var windowController = (self.initialStoryBoard?.instantiateControllerWithIdentifier("mainWindow")) as! NSWindowController
windowController.window?.makeKeyAndOrderFront(nil)
}
}
But I am not able to launch initial window controller and view controller. App starts and terminates automatically, no window is presented to user.
What I am doing wrong? Thanks for your help.
Here is what I did in order to load initial window from storyboard (as well as MainMenu) programmatically without attribute #NSApplicationMain and function NSApplicationMain(_, _)
File: AppConfig.swift (Swift 4)
struct AppConfig {
static var applicationClass: NSApplication.Type {
guard let principalClassName = Bundle.main.infoDictionary?["NSPrincipalClass"] as? String else {
fatalError("Seems like `NSPrincipalClass` is missed in `Info.plist` file.")
}
guard let principalClass = NSClassFromString(principalClassName) as? NSApplication.Type else {
fatalError("Unable to create `NSApplication` class for `\(principalClassName)`")
}
return principalClass
}
static var mainStoryboard: NSStoryboard {
guard let mainStoryboardName = Bundle.main.infoDictionary?["NSMainStoryboardFile"] as? String else {
fatalError("Seems like `NSMainStoryboardFile` is missed in `Info.plist` file.")
}
let storyboard = NSStoryboard(name: NSStoryboard.Name(mainStoryboardName), bundle: Bundle.main)
return storyboard
}
static var mainMenu: NSNib {
guard let nib = NSNib(nibNamed: NSNib.Name("MainMenu"), bundle: Bundle.main) else {
fatalError("Resource `MainMenu.xib` is not found in the bundle `\(Bundle.main.bundlePath)`")
}
return nib
}
static var mainWindowController: NSWindowController {
guard let wc = mainStoryboard.instantiateInitialController() as? NSWindowController else {
fatalError("Initial controller is not `NSWindowController` in storyboard `\(mainStoryboard)`")
}
return wc
}
}
File main.swift (Swift 4)
// Making NSApplication instance from `NSPrincipalClass` defined in `Info.plist`
let app = AppConfig.applicationClass.shared
// Configuring application as a regular (appearing in Dock and possibly having UI)
app.setActivationPolicy(.regular)
// Loading application menu from `MainMenu.xib` file.
// This will also assign property `NSApplication.mainMenu`.
AppConfig.mainMenu.instantiate(withOwner: app, topLevelObjects: nil)
// Loading initial window controller from `NSMainStoryboardFile` defined in `Info.plist`.
// Initial window accessible via property NSWindowController.window
let windowController = AppConfig.mainWindowController
windowController.window?.makeKeyAndOrderFront(nil)
app.activate(ignoringOtherApps: true)
app.run()
Note regarding MainMenu.xib file:
Xcode application template creates storyboard with Application Scene which contains Main Menu. At the moment seems there is no way programmatically load Main Menu from Application Scene. But there is Xcode file template Main Menu, which creates MainMenu.xib file, which we can load programmatically.
This is not how you start (and maintain) an application's main run loop. See #NSApplicationMain. This causes the main run loop to be set up and run until it's terminated. There's no need for a main.swift file any longer, as you can just put this into your app delegate's file directly.
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
}
Xcode's new application project template does this for you.

Make a public var without making its class and related functions public?

I'm starting to learn Swift.
I've a viewController that has a var which needs updating from an outside viewController. So I added public to its declaration but my code won't compile because my class is internal (by default). So i make my class public but then it forces me to make all functions inside my class public including viewDidLoad, the tableView dataSource and delegate methods. What am i doing wrong? I don't want anyone else to call my controller's viewDidLoad.
All I wanted to viewControllerA to access a var inside viewControllerB without exposing every function inside viewControllerB to the outside world.
In ObjC, this can be achieved very easily by marking the property readonly in the headerfile and readwrite in the implementation. In this case, I would've the property in the header file so it's read-writable from outside.
here's some pseudo code
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var myTitle: NSString?
override func viewDidLoad() {
super.viewDidLoad()
}
}
// objC part
MyViewController *myViewController = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
myViewController.myTitle = #""; // not available
Now if i make myTitle a public var, i get this error
Declaring a public var for an internal class
So I make MyViewController a public class.
Now i get bunch of errors
Method 'tableView(_:numberOfRowsInSection:)' must be declared public
because it matches a requirement in public protocol
'UITableViewDataSource'
You could make a protocol to save and access data across view controllers. Here's one way to do it.
// Make a custom protocol delegate with a method to store the variable. In this case I'll store a boolean.
protocol storeViewControllerBVariableDelegate {
func storeVariable(data: Bool?)
}
// In your view controller A, assign your custom protocol delegate to it and add the new delegate method.
class viewControllerA: UIViewController, storeViewControllerBVariableDelegate {
func storeVariable(data: Bool?) {
self.variableName = data
}
}
// In your view controller A's prepare for segue, assign the stored variable to view controller B if you wanted to pass it forward and backward between view controllers.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewControllerB = segue.destinationViewController as! viewControllerB
viewControllerB.variableName = variableName
}
// In your view controller B, initialize a variable and assign it to the delegate.
class viewControllerB: UIViewController {
var variableName: Bool!
var delegate: storeViewControllerBVariableDelegate?
// However you want to save the variable in view controller B, you can do so in an IBAction, viewDidLoad, etc.
#IBAction func saveVariable(sender: UIButton) {
delegate?.storeVariable(self.variableName)
}
}
Here are two solutions that I can think of for passing variables between view controllers
Global Option
ViewController2.swift
import UIKit
var globalVariable = String()
class ViewController1: UIViewConroller {
}
ViewController2.swift
class ViewController2: UIViewController {
overload func viewDidLoad() {
globalVariable = "some string data"
}
}
you can now access that variable globally.
Segue Option
I think a better way to handle sending data back and forth between View Controllers is by using delegates and the prepareForSegue function which is covered in depth here.
You declare your prepareForSegue function like so:
View Controller 1
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "yourIdentifierInStoryboard") {
var yourNextViewController = (segue.destinationViewController as yourNextViewControllerClass)
yourNextViewController.value = yourValue
ViewController 2
class yourNextViewControllerClass {
var value:Int! // or whatever
The you can call it programmatically
self.performSegueWithIdentifier("yourIdentifierInStoryboard", sender: self)
If you want to set values back from your second View Controller, you can use a delegate Method, to respect the original author of this content I'll redirect you to his post:
Read the rest from the original post.

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.

Changing a JSContext-passed Swift object with JavaScriptCore

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

Resources