Swift-passing global variables to the #IBAction function? - swift

I am trying to allow the user to select a .png file to open by clicking file on the menu bar of the application, and then open a Microsoft Word file in the same way.
The problem is it appears that #IBAction func SelectFileToOpen(sender: NSMenuItem) {} cannot access global variables, or set them, and seems completely independent from the rest of the code
here is my code designed to demonstrate how the method can't read global variables:
//
// AppDelegate.swift
// Swift class based
//
// Created by ethan sanford on 2015-01-16.
// Copyright (c) 2015 ethan D sanford. All rights reserved.
//
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
var myURL=NSURL(fileURLWithPath: "")
#IBAction func btnConcat(sender: NSButton) {
myURL = NSURL(fileURLWithPath: "///Users/ethansanford/Desktop/BigWriting.png")
var say_something = "set URL button clicked"
print(say_something);
print(myURL)
}
#IBAction func SelectFileToOpen(sender: NSMenuItem) {
var say_something = "Menu bar, file-open clicked:"
print(say_something);
print(myURL);
}
#IBAction func communicate(sender: AnyObject) {
var say_something = "communicate button clicked:"
print(say_something);
print(myURL);
}
}
Here is the NSlog produced from this code. Notice that the URL button and the commincate button methods can share the myURL variable, but the file open button seems unable to:
URL button clickedOptional(file://///Users/ethansanford/Desktop/BigWriting.png)
communicate button clicked:Optional(file://///Users/ethansanford/Desktop/BigWriting.png)Menu bar
file-open clicked:nil
communicate button clicked:Optional(file://///Users/ethansanford/Desktop/BigWriting.png)
I need the myURL variable to be able to be used in all three methods. This is necessary for later when I need these methods to communicates so I can take the users selection and display it in an image well. Thanks for any help you can provide. I believe the problem is something specific to the file button in the menu bar.
Can anyone explain to me how to get around this problem?

In your code myURL is an instance variable that will be created within the app delegate. I wonder if you have oversimplified your code sample.
Having said that it should be accessible from the instance methods of the app delegate but having IBAction methods in the AppDelegate rather than in UI code seems like an odd choice (I've never tried it).

Related

How to create separate data mapping file with function names in Swift

I have a macOS app that I'm creating in Swift and I have integrated an external HID device that has a number of controls on it.
The HID part is done where I am receiving all of the hid commands from the device and I am trying to create a mapping file where I can maintain the HID key mappings in a separate swift file.
All I want in that file is the data and what I want to do is this;
raw hid data is received from HID device (In ViewController)
Lookup the function name assigned to this hid data (In separate file)
Run the function that is mapped to that key. (Function located in the main ViewController)
So far I have the external swift file setup with all of the mapping and that all works fine but my issue is when I try to call the looked up function in the ViewController, it says the function can't be found in the scope.
Initially I thought I would use a delegate but the external file isn't a viewcontroller, just a separate swift file so I don't know if I can do that?.
I've tried searching but everything I've found is calling a function from another ViewController which I'm not. It's very possible I'm not using the best approach and my goal is to just keep all of the mapping in a separate file as there is a lot and it woudl be easier to maintain.
Any suggestions are appreciated.
This is one way to achieve this. It can get tedious. You can totally skip writing out a separate protocol for the delegate, but this is cleaner design.
protocol HIDMessageDelegate: AnyObject {
// example messages
func message1()
func message2()
func message3()
}
class HIDMessageParser {
static weak var delegate: HIDMessageDelegate?
static func parseHIDMessage() {
var condition = 0
// this is where your switch statement will go and you'll parse things and call the relevant delegate method
switch (condition) {
default:
delegate?.message1()
}
}
}
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
HIDMessageParser.delegate = self
}
}
extension MyViewController: HIDMessageDelegate {
func message1() {
}
func message2() {
}
func message3() {
}
}
You can simply create a UIViewController as the external file and add it as a property to the main ViewController.
In the external file add this.
#IBOutlet var uiViewController: UIViewController!
In the ViewController add this.
var externalFileViewController: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
externalFileViewController = externalFileViewController?.loadView()
// If we have an object then load it
if let viewController = externalFileViewController {
viewController.view.frame = view.frame
viewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(viewController.view)
uiViewController = viewController
}
}
Now in the viewController look up the functions to be called from the external file and call them using the function name.
The functions are defined in the external file using #IBAction.
Let me know if you have any questions.

#IBAction fails to work in Swift, though I can connect it in Interface Builder, and it works in Objective-C

In all cases, I can wire user-interface buttons to actions with Interface Builder. But the buttons work for Objective-C but not for Swift.
Objective-C example (it works):
- (IBAction)TogglePlaying:(id)sender {
(details snipped for brevity)
}
Swift example (it doesn't work, though it's wired to its button):
#IBAction func Go(_ sender: Any) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
I have no idea of what the difference might be, because everything I've found on using IBAction in Swift indicates that I've written it correctly. Also, in Interface Builder, I've set File's Owner's Custom Class correctly.
Update:
Using
ios - Find what Action is called by a Button in interface builder in debug mode - Stack Overflow
Find what Action is called by a Button in interface builder in debug mode
I used "Debug View Hierarchy", right-clicked on "NSButton - Go!" in the widget-hierarchy view, and selected "Print Description of NSButton - Go!"
I got
Printing description of $13:
<NSButton: 0x7fac1b116250>
I then did:
po [0x7fac1b116250 allTargets]
error: Execution was interrupted, reason: Attempted to dereference an invalid ObjC Object or send it an unrecognized selector.
The process has been returned to the state before expression evaluation.
Update:
I tried
po [0x7faf38011790 target]
(new address of that button) and I got
nil
Update:
The complete code of TLWindow, in Swift:
import Cocoa
class TLWindow: NSWindowController {
#IBOutlet weak var InputText: NSTextField!
#IBOutlet weak var OutputText: NSTextField!
override var windowNibName: NSNib.Name? {
return NSNib.Name("TLWindow")
}
#IBAction func Go(_ sender: Any?) {
print("Going")
OutputText!.stringValue = InputText!.stringValue
}
}
I don't know how to show that the xib is wired up correctly without doing a lot of screenshots. But it is, with the "Go!" button connected to "Go:" in "File's Owner". Also, "File's Owner" is set to "TLWindow", this class.
You are creating an instance of TLWindow in newDocument(), but then you're letting it go out-of-scope...
Try this:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// add a property to "hang onto" the instance
var myTLWindow: TLWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
newDocument(self)
}
// Create an app window
#IBAction func newDocument(_ sender: Any?) {
let wc = TLWindow()
// add this line
myTLWindow = wc
wc.showWindow(self)
}
}

Swift: Perform Segue in AppDelegate.swift

I've successfully integrated Google Sign-In into my iOS app in my AppDelegate.swift file, and can successfully detect a successful sign-in (by printing "success" out onto the console). The issue is that after a successful sign-in, I'm back at the Google Sign-In page when I want to be in the next screen of the app.
The AppDelegate.swift file did not recognize the performSegue function, as it is a function of the UIViewController class (please correct me if I'm wrong). To get around this, I created a global variable in the ViewController file such that the segue would be performed whenever this value would be changed:
AppDelegate.swift:
var userSignedInGlobal = "n/a"
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
// A bunch of code that implements the Google Sign in ...
print("Successfully logged into Google.", user)
userSignedInGlobal = "success"
}
And then my InitialViewController.swift file:
class InitialViewController: UIViewController, GIDSignInUIDelegate {
var userSignedIn = userSignedInGlobal {
didSet {
performSegue(withIdentifier: "segueOne", sender: self)
}
}
// A bunch of irrelevant code.
}
This did not work, and the reason I think this didn't work is that userSignedInGlobal in the InitialViewController.swift file is being passed by reference - so even when its value changes, the value of userSignedIn does not change (again, please do correct me if I'm wrong).
To get around this, I changed my InitialViewController.swift file as follows:
var userSignedIn = userSignedInGlobal {
didSet {
doSegue()
}
}
class InitialViewController: UIViewController, GIDSignInUIDelegate {
func doSegue() {
performSegue(withIdentifier: "segueOne", sender: self)
}
// A bunch of irrelevant code.
}
This gave me an error in the third line: "Use of unresolved identifier 'doSegue()' "
I do not know how to go about performing the segue when the sign in is successful. Any help will be greatly appreciated, thanks in advance.
There are a few problems here:
Global Variables in App Delegate
There are numerous answers demonstrating how to store a global variable in your AppDelegate and then subsequently reference that variable in your view controllers, e.g. getting a reference to app delegate
Perform Segue only from Storyboard based controllers
You won't be able to use performSegue unless your current View Controller was loaded from a StoryBoard (see performSegue). This probably is not the case with whatever Google view you are loading from your AppDelegate.
Are you doing the login in the right place?
You might want to reconsider the division of duties between your AppDelegate and your initial view controller. Perhaps you are trying to do too much in the AppDelegate. Perhaps you should move some of this code (showing the Google form) to your initial VC and then either segue or reset your Navigation Controller based on the result.
Hope this helps.

Xcode: how to create instances of views and pass info to them?

I'm trying to create a MacOS app that plays audio or video files. I've followed the simple instructions on Apple's website here
But I want to use the File > Open menu items to bring up an NSOpenPanel, and pass that to the View Controller.
So presumably, the Open action should be in the AppDelegate, as the ViewController window might not be open.
And then pass the filename to a new instance of the ViewController window.
Is that right? If so, how do I "call" the View from AppDelegate?
Here's the AppDelegate:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBAction func browseFile(sender: AnyObject) {
let dialog = NSOpenPanel();
if (dialog.runModal() == NSModalResponseOK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
// Pass the filepath to the window view thing.
} else {
// User clicked on "Cancel"
return
}
}
}
and here's the ViewController:
class ViewController: NSViewController {
#IBOutlet weak var playerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the URL somehow
let player = AVPlayer(url: url)
playerView.player = player
}
There are some details not disclosed in your question, but I believe I can provide the proper answer still.
You can call NSOpenPanel from AppDelegate, nothing wrong with that. Just note that user may cancel the dialog and how to handle that situation.
Considering the view the best thing is to create WindowController that is connected to the ViewController (it is like that by default) in the Storyboard, then access it from the code using NSStoryBoard.instantiateController(withIdentifier:), and then use its window property with something like window.makeKeyAndOrderFront(self) . If you have NSWindow or NSWindowController class in your code then you should initialize the class in the code and again make window key and front.

ViewController + Storyboard setting up validation with controlTextDidChange

Trying to setup validation for a few text fields in a new (and very small) Swift Mac app. Following various other topics here on SO and a few other examples, I can still not get controlTextDidChange to propagate (to my ViewController).
E.g: How to live check a NSTextField - Swift OS X
I have read at least a dozen variations of basically that same concept. Since none of the accepted answers seem to work I am just getting more and more confused by something which is generally a fairly simple task on most platforms.
I have controlTextDidChange implemented to just call NSLog to let me know if I get anything.
AppDelegate should be part of the responder chain and should eventually handle controlTextDidChange but I see nothing there either.
Using the current Xcode I start a new project. Cocoa app, Swift, Storyboard and nothing else.
From what I can gather the below isolated example should work. In my actual app I have tried some ways of inserting the ViewController into the responder chain. Some answers I found suggested it was not always there. I also tried manually adding the ViewController as the delegate in code theTextField.delegate = self
Nothing I have done seems to get text changed to trigger any events.
Any ideas why I have so much trouble setting up this delegation?
My single textfield example app
Storyboard is about as simple as it gets:
AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSTextFieldDelegate, NSTextDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("AppDelegate::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
ViewController
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate, NSTextDelegate {
#IBOutlet var theTextField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func controlTextDidChange(notification: NSNotification) {
let object = notification.object as! NSTextField
NSLog("ViewController::controlTextDidChange")
NSLog("field contains: \(object.stringValue)")
}
}
I think the samples you're following are a bit out-of-date.
Try...
override func controlTextDidChange(_ notification: Notification) {
...as the function definition for your method in your NSTextFieldDelegate.