0

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


}
S Lareau
  • 159
  • 1
  • 5

3 Answers3

0

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)
     }
}
Kush Bhavsar
  • 909
  • 2
  • 12
  • Thanks for proposing a solution. I got your exemple working and trying to integrate the slider. . I can set the slider label and slider position with those : slider.integerValue = Int(SliderCountManager.shared.value) sliderTB.label = String(SliderCountManager.shared.value) I dont know also how to share the new slider position when the slider is used. I have tried the following. @IBAction func sliderDidChange(_ sender: Any) { //==> SliderCountManager.shared.value = UInt8(slider.integerValue) // err: Cannot assign to property: 'value' setter is inaccessible } – S Lareau Jun 29 '22 at 18:19
  • I did not find how to set the slider.maxValue from the number of client variable in the view Controller. I have tried CoCoa bineding explained in the answer above but that did not work. – S Lareau Jun 29 '22 at 18:20
  • It is because of Private(set) `Access Control`. If you want to update the value outside of the class, please update `value` property's access control to `public`. I've updated the changes to `SliderCountManager` in the answer. – Kush Bhavsar Jun 30 '22 at 16:56
0

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.

Willeke
  • 14,578
  • 4
  • 19
  • 47
  • Thanks for this exemple. Using your approach I can also set the slider maxValue. However, I want also to change the slider label accord to its position and maxValue ex "Item 102 of 226" " . I have not found how to do that. – S Lareau Jun 29 '22 at 15:44
  • @SLareau The documentation of the pattern binding is here at the bottom of the page: [NSTextField Bindings](https://developer.apple.com/library/archive/documentation/Cocoa/Reference/CocoaBindingsRef/BindingsText/NSTextField.html#//apple_ref/doc/uid/NSTextField-SW1). Don't use a number formatter. Ask a new question if you can't get it to work. – Willeke Jun 30 '22 at 08:10
0

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)

            }
}
S Lareau
  • 159
  • 1
  • 5