Swift 3 Running two bits of code at the same time

Discussion in 'Mac Programming' started by Miles.Kelly97, Aug 14, 2017.

  1. Miles.Kelly97 macrumors newbie

    Joined:
    Nov 24, 2015
    Location:
    Tunbridge Wells
    #1
    Hi,

    I am creating a status bar app that hides the desktop. I am trying to get it to run a function that checks if there is an external monitor connected and it will the update the NSOn/OffState of a menu item. I have the function working but I would like it to open the menu at the same time. At the moment i can only get either the function to run with no menu or the menu to pop up but it won't run the function.

    Code:
    func applicationDidFinishLaunching(_ aNotification: Notification) {
            showDesktopMenuItem.state = NSOnState
            statusItem.menu = JDOMenu
            if let button = statusItem.button {
                button.image = NSImage(named: "JDOIcon")
                button.action = #selector(queryState(sender:))
                button.target = self
              
            
            }
            
        }
    
    func queryState() {
    //Checks external monitor state
    }
    
    If I // out the statusItem.menu = JDO menu the function runs but when i leave it in it just opens the menu but doesn't run the function

    Can anyone help and point me in the right direction?

    Thanks
    Miles
     
  2. maculateConception macrumors 6502

    maculateConception

    Joined:
    May 28, 2017
    Location:
    Die Bundesstaat Kalifornien
    #2
    Hi, I'm confused.

    What is "the function" that needs to run ? Are you referring to "queryState()" as your "function" ? If queryState() is your function, it is not clear where you are invoking it. If you don't invoke it, how can it run ?

    Also, what do you mean by "open the menu" ? Will the menu pop out from the status bar automatically ?

    In general, when you want to run 2 pieces of code concurrently, you can submit the two pieces of code as operations or blocks to an OperationQueue.

    Here's an example:

    Code:
    var queue: OperationQueue = OperationQueue()
    
    // This is only if you want to execute your code on the main thread
    queue.underlyingQueue = DispatchQueue.main
    
    // Define how many pieces of code can execute at the same time
    queue.maxConcurrentOperationCount = (some value more than 1)
    
    // Execute your code
    
    // First piece of code to execute
    queue.addOperation({self.menu = myMenu})
    
    // Second piece of code to execute
    queue.addOperation({self.runMyFunction()})
    
    By initializing an operation queue with the capacity for multiple concurrent tasks, and submitting your pieces of concurrent code to it, you are essentially executing them "at the same time", provided number of tasks <= maxConcurrentOperationCount (assuming nothing else is currently running on the queue)

    But, that said, I don't think that what you want to do requires any concurrency at all. You should be able to execute your code serially and get it working. But, it is not very clear to me what you're trying to do. If you can clarify, I may be able to help more.

    Hope this helps.
     
  3. Miles.Kelly97, Aug 15, 2017
    Last edited: Aug 15, 2017

    Miles.Kelly97 thread starter macrumors newbie

    Joined:
    Nov 24, 2015
    Location:
    Tunbridge Wells
    #3
    When I click on the status bar button (of the app) in the status bar I would like it to both open the menu as it should when clicked on but also run the function queryState() at the same time

    When I click on the status bar button I would like it to open the menu I have created (a few menu bar items each with IB Actions connected to them). There are two menu items that I would like to update each time the menu is opened.

    So when the menu is open the state of the menu items will either switch to on or off.

    So the queryState() function gets an output for me to then set the state of the menu item/s. However I can't find a way to action the function and open the NSmenu.

    I am not sure if that makes any more sense.

    Sorry for the poor explanation
     
  4. maculateConception, Aug 15, 2017
    Last edited: Aug 15, 2017

    maculateConception macrumors 6502

    maculateConception

    Joined:
    May 28, 2017
    Location:
    Die Bundesstaat Kalifornien
    #4
    Ok, that makes a little more sense. Thanks.

    First off, I don't think you should attach an action to the button of the status menu. I think it's only purpose is to open the menu. I think that your selector action is somehow conflicting with its main purpose of opening the menu.

    But, you said that it works (runs queryState()) when the menu assignment line is commented out, so maybe the problem lies in your JDOMenu object.

    Anyway, there is one more way to do what you want to do. Your menu item is an instance of NSMenuItem. You can write a custom class that extends NSMenuItem, and then override the var "isEnabled" to be a computed property that checks the external monitor state. Something like my code below:

    Code:
    import Cocoa
    
    class MyMenuItem: NSMenuItem {
       
        override var isEnabled: Bool {
    
    // Here, do the same thing that queryState() does, and return true or false
    }
    }
    This way, you don't rely on your button to run queryState(). Any time your menu item is shown to the user, the isEnabled variable will be recomputed, and will run your queryState() logic, within your overriden var block (shown in code above).

    If you write this custom class, make sure to go to Interface Builder, and set the class of your menu items to be "MyMenuItem" or whatever you decide to call the custom class.

    Can you try this ?

    What is JDOMenu ? Could there be something wrong with it ?
     
  5. Miles.Kelly97 thread starter macrumors newbie

    Joined:
    Nov 24, 2015
    Location:
    Tunbridge Wells
    #5
    I am not sure if I know how to do that. The queryState() function runs a shell script and collects the output from the debugger window. Making the return of the function of type Void. How would i get it to be a Bool? Or convert that to a Bool.

    It collects the output from the debugger (pipe) which will either be on, off, not secondary display connected.

    This is the queryState Function

    mirrorPath is just a variable with the path to the shell script

    The parts that are commented out were the bits I was using to set the state of the menu bar originally but this didn't work because I couldn't get the menu to open

    Code:
    func queryState() {
            let task = Process()
            task.launchPath = mirrorPath
            task.arguments = ["-q"]
            let pipe = Pipe()
            task.standardOutput = pipe
            let outHandle = pipe.fileHandleForReading
            
            outHandle.readabilityHandler = { pipe in
                if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8) {
                    self.currentState = line
            }
                print(self.currentState)
                //if self.currentState == "No secondary display detected.\n" {
                    //print("Working!")
                //}
                //else if self.currentState == "on\n" {
                    //self.mirrorMenuItem.state = NSOnState
                    //self.dualDisplayMenuItem.state = NSOffState
               // }
                //else if self.currentState == "off\n" {
                    //self.dualDisplayMenuItem.state = NSOnState
                    //self.mirrorMenuItem.state = NSOffState
                //}
            }
            task.launch()
        }
    
    JDO menu is the name of the IB Outlet for the NSMenu.
     
  6. maculateConception macrumors 6502

    maculateConception

    Joined:
    May 28, 2017
    Location:
    Die Bundesstaat Kalifornien
    #6
    Ok. From looking at your code, it looks like an enum might be more appropriate than a boolean, because you have 3 possible states: 1- no secondary display, 2 - mirror, 3 - dual display. So, create an enum like the following, and return it from the queryState function. This may not fix your menuItem problem but it's a start.

    Code:
    
    enum DisplayState {
    
        case noSecondaryDisplay
        case mirror
        case dualDisplay
    }
    
    // queryState will now return this enum value
    func queryState() -> DisplayState {
    
    // Inside queryState, do the following:
    
    var displayState: DisplayState
    //if self.currentState == "No secondary display detected.\n" {
        displayState = .noSecondaryDisplay
                //}
                //else if self.currentState == "on\n" {
                    displayState = .mirror
               // }
                //else if self.currentState == "off\n" {
                    displayState = .dualDisplay
                //}
                    else {
                         // TODO: What happens in this case ? Maybe set a default value ?
                    }
     
    return displayState
    
    // Then, outside queryState(), use the enum return value as follows:
    let displayState = queryState()
    switch displayState {
        case .noSecondaryDisplay:    // do nothing
        case .mirror:    self.mirrorMenuItem.state = NSOnState
                    //self.dualDisplayMenuItem.state = NSOffState
        case .dualDisplay:    self.dualDisplayMenuItem.state = NSOnState
                    //self.mirrorMenuItem.state = NSOffState
    }
    
    To be honest, it is difficult for me to really understand what is going on without looking at all your code, so I get a proper perspective. Ideally, I'd have all the source code so I can open it and run it in XCode and debug the problem that way.

    If your source code is not open, ok, no worries. The best I can do is guess and offer suggestions. But, if you're willing to share your entire project source with me, I can do better.

    From just looking at queryState(), it seems to me like that code is not very reliable because you are deriving your display state based on a String value, which could change, or could be prone to typos in your code. In general, comparing long strings like that is error-prone. Is there a better way to determine your display state ?
     
  7. Miles.Kelly97 thread starter macrumors newbie

    Joined:
    Nov 24, 2015
    Location:
    Tunbridge Wells
    #7
    I think it would be beneficial for me to send my code to you if you are cool to help me out that would be amazing, I would rather not do it on GitHub. Is there any other way I can send it to you?

    Miles
     
  8. maculateConception macrumors 6502

    maculateConception

    Joined:
    May 28, 2017
    Location:
    Die Bundesstaat Kalifornien
    #8
    I'm busy traveling and am out of town currently, so I may not have time to look at it for a few days. But, can you send me a private message on this site, with an attachment ? Or you can upload a zip somewhere on the internet, and send me a link to it in a private message here ?
     

Share This Page