How to set NSSlider value in a NSToolbar - Swift Cocoa + Storyboard

180 Views Asked by At

I am quite new to Swift programming and I am trying to set a slider min, max and value in a NSToolbar. As an hypothetical exemple, I have a list of client and I want to use the slider in the toolbar to select a client data page. I will firt to load the client database in the NSViewController and count the number of client. Than I would like to set the slider in the toolbar minvalue to 1 and maxvalue to the number of client. I understand how to send slider values from the Windowcontroller to the ViewController but I did not found how to do the inverse , how to send data from the Viewcontroller to the Window controller in order to set the slider values. I have attach an simple code based on this exemple https://github.com/gbdavid2/DavidCodes_macOS/tree/master/NSToolbar%20with%20Storyboards/NSToolbar%20with%20Storyboards

In this exemple, the Toolbar shows a Previous and an Next button that , when clicked, they change a counter value (count). I would like to send back that value from the ViewCoOntroller to the WindowController in order to display it in label and eventually, the slider value in the toolbar. Thanks for your help.

//  WindowController.swift


import Cocoa

class WindowController: NSWindowController {

    @IBOutlet weak var myBoutton: NSToolbarItem!
    
    var viewController: ViewController {
        get {
            return self.window!.contentViewController! as! ViewController
        }
    }
    
    override func windowDidLoad() {
        super.windowDidLoad()
    
        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
        //viewController.myLabel.stringValue = "boo"
    }
    
    @IBAction func previous(_ sender: Any) {
        viewController.updateMyLabelText(newText: "Prev Button clicked! ")
        }
    
    @IBAction func next(_ sender: Any) {
        viewController.updateMyLabelText(newText: "Next Button clicked! ")
    }

}

import Cocoa

 class ViewController: NSViewController {
    
    var count  : Int = 0
    
    
    @IBOutlet weak var myLabel: 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 updateMyLabelText(newText: String){
        if newText.contains("Prev") {count -= 1}
        else if newText.contains("Next") {count += 1}
        myLabel.stringValue = newText + String(count)
    }


}
3

There are 3 best solutions below

0
S Lareau On BEST ANSWER

Thanks for the proposed solutions. It certainly put me in the wrigth direction.

Here is what I did. In the WindowController , I set a toolbar with 1) button «previous», 2) button «next» and 3) a slider «slider».

Those are linked to the proper IBOutler and IBaction in the WindowController. The viewController have a textLabel «myLabel»

The 2 buttons and the slider change the slider_ptr value in the ViewControler and is sent to myLabel. Also, the slider.label change according to the slider_pointer and the slider_max values. Here is the code for the windowController:


import Cocoa

class WindowController: NSWindowController {

 
    @IBOutlet weak var slider: NSSlider!
    @IBOutlet weak var sliderTB: NSToolbarItem!
    
   var viewController: ViewController {
        get {
            return self.window!.contentViewController! as! ViewController
        }
    }
    
    override func windowDidLoad() {
        super.windowDidLoad()

        setSlider()   //  set initial value based on ViewController
  
    }
    
 
    
    
    @IBAction func previous(_ sender: Any) {
        
        viewController.previous (WindowController())
        setSlider()
        
        }
    
    @IBAction func next(_ sender: Any) {
        //viewController.updateMyLabelText(newText: "Prev Button clicked! ")
        viewController.next (WindowController())   //send to VC function previous
      // let pt = viewController.slider_ptr + 1
       //let sMax = viewController.slider_max
        setSlider()
        //sliderTB.label = String(pt) + " de " + String(sMax)
    }
    
    
    @IBAction func sliderDidChange(_ sender: Any) {
        
        
        
        viewController.sliderDidSlide (WindowController(), pointer: Int(slider.doubleValue))
        setSlider()
        
    //    viewController.sliderDidSlide(PosiWC(), sValue: myslider.doubleValue)
           
    }
    

    func setSlider() {
        
       /* myslider.minValue = 1
        myslider.maxValue = Double(max)
        myslider.integerValue = pointer*/
        
        //print ("WCP58:" , myslider.integerValue )
        
        let pt = viewController.slider_ptr
        let sMax = viewController.slider_max
        //slider (max : pt, pointer: sMax)
        sliderTB.label = String(pt) + " de " + String(sMax)
        
        slider.minValue = 1
        slider.maxValue = Double(sMax)
        slider.integerValue = pt

    }
}

and for the Viewcontroller :

 class ViewController: NSViewController {
    
    
    var slider_ptr = 1  // slider position
    var slider_max: Int = 0  //

    @IBOutlet weak var myLabel: NSTextField!
 
    override  func viewDidLoad() {
        super.viewDidLoad()
        
       slider_max = 250
       myLabel.stringValue = String(slider_ptr)
        
        
    }

    override  var representedObject: Any? {
        didSet {
        }
    }
  
      func previous(_ sender: Any) {          
             if slider_ptr > 1  {
                  slider_ptr -= 1
                  }
              else  { NSSound.beep()}
        
        myLabel.stringValue = String(slider_ptr)
        
          }
          
          
     func next(_ sender: Any) {

              if  slider_ptr < slider_max {
                  slider_ptr += 1

              }
              else  { NSSound.beep()}
            myLabel.stringValue = String(slider_ptr)
          
          }
    
    func sliderDidSlide(_ sender: Any, pointer : Int) {
                print (pointer)
                slider_ptr = pointer
                myLabel.stringValue = String(slider_ptr)

            }
}
3
Kush Bhavsar On

There are multiple ways to achieve this.

One of the way is by creating a class [e.g: SliderManager] which keep tracks of current value and handles increment/decrement. You can get the current value of Slider with the help of Singleton in any Controller.

Here is an example implementation:

protocol SliderCountDelegate: NSObject {
    func counterDidUpdate()
}

final class SliderCountManager {
    
    static let shared = SliderCountManager()
    
    var value: UInt8 = 0 // <-- Unsigned Integers: Only Positive numbers
    
    weak var delegate: SliderCountDelegate?
    
    public func increaseCounter() {
        value += 1
        delegate?.counterDidUpdate()
    }
    
    public func decreaseCounter() {
        value -= 1
        delegate?.counterDidUpdate()
    }
}

Here is how you should use this in your code:

//  WindowController.swift


import Cocoa

class WindowController: NSWindowController {

    @IBOutlet weak var myBoutton: NSToolbarItem!
        
    override func windowDidLoad() {
        super.windowDidLoad()
    }
    
    @IBAction func previous(_ sender: Any) {
        SliderCountManager.shared.increaseCounter()
        print(SliderCountManager.shared.value) // <- Accessing Current value here
    }
    
    @IBAction func next(_ sender: Any) {
        SliderCountManager.shared.decreaseCounter()
        print(SliderCountManager.shared.value)
    }
}

import Cocoa

 class ViewController: NSViewController, SliderCountDelegate {
        
    @IBOutlet weak var myLabel: NSTextField!
 
    override  func viewDidLoad() {
        super.viewDidLoad()
        SliderCountManager.shared.delegate = self  // Set Delegate to `self`
    }

    override  var representedObject: Any? {
        didSet {
        }
    }
    
     // Protocol conformance
     func counterDidUpdate() {
         myLabel.stringValue = String(SliderCountManager.shared.value)
     }
}
2
Willeke On

Another way to to achieve this is with Cocoa Bindings. Example:

In the toolbar are a Previous button, a Next button and a slider. The actions of the buttons are connected to the First Responder. The action methods are implemented in ViewController. The count property of ViewController has attributes @objc dynamic so it can be used with Cocoa Bindings.

class ViewController: NSViewController {

    @objc dynamic var count: Int = 0
    
    @IBAction func previous(_ sender: Any) {
        count -= 1
    }
    
    @IBAction func next(_ sender: Any) {
        count += 1
    }
    
}

The slider in the toolbar is bound to the Window Controller, key path window.contentViewController.count.

Slider value binding

In the view is a label with a number formatter. The value of the label is bound to the View Controller, key path count.

Label value binding

The window controller isn't subclassed.