0

I have NSSlider in ViewController that must pass integer value to SecondViewController via segue of type Show and update a View every time I move it. So, moving slider I interactively shuffle images in SecondViewController's window.

I spent a week trying to implement such a behaviour of NSSlider but I failed. Maybe I made wrong connections in Interface Builder. I don't know. If you know any other method to make NSSlider work for shuffling images, tell me, please.

See updated answer for detailed information.

Really appreciate any help!!!

ViewController

import Cocoa

class ViewController: NSViewController {

    @IBOutlet weak var slider: NSSlider!

    @IBAction func passData(_ sender: NSSlider) {        
        func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {
            if segue.identifier!.rawValue == "SegueIdentifierForSecondVC" {
                if let secondViewController = segue.destinationController as? SecondViewController {
                    secondViewController.imagesQuantity = slider.intValue 
                }
            }
        }
    }
}

SecondViewController

import Cocoa

class SecondViewController: NSViewController {

    var imagesQuantity: Int = 1

    override func viewWillAppear() {
        super.viewWillAppear()

        print(imagesQuantity)
    }
}

But it doesn't work. What's wrong in my code?

Any help appreciated.

UPDATED ANSWER

VIEW CONTROLLER

import Cocoa

extension NSStoryboardSegue.Identifier {
    static let secondVC = NSStoryboardSegue.Identifier("SegueIdentifierForSecondVC")
}

class ViewController: NSViewController {

    @IBOutlet weak var slider: NSSlider!

    @IBAction func segueData(_ sender: NSSlider) {
        self.performSegue(withIdentifier: .secondVC, sender: slider)
    }
    override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
        if segue.identifier! == .secondVC {
            if let secondViewController =
                segue.destinationController as? SecondViewController {
                secondViewController?.imagesQty = slider.integerValue 
            }
        }
    }
}

SECOND VIEW CONTROLLER

import Cocoa

class SecondViewController: NSViewController {

    var imagesQty = 30  

    override func viewWillAppear() {
        super.viewWillAppear()
        //let arrayOfViews: [NSImageView] = [view01...view12]

        let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Desktop/ArrayOfElements")

        do {
            let fileURLs = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).reversed()
            let photos = fileURLs.filter { $0.pathExtension == "jpg" }

            for view in arrayOfViews {
                //"imagesQty" is here
                let i = Int(arc4random_uniform(UInt32(imagesQty-1)))
                let image = NSImage(data: try Data(contentsOf: photos[i]))
                view.image = image
            }
        } catch {
            print(error)
        }
    }
}

enter image description here

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • Why is `func prepare(for segue: NSStoryboardSegue, sender: NSSlider?)` inside the `IBAction` method? Put it outside, and instead in it write `self.perform(segue: SegueIdentifierForSecondVC sender:slider)` – Larme Mar 25 '18 at 17:18
  • 1
    You seem to have some knowledge in coding. Don't you see that you have a method inside another? `@IBAction func passData(_ sender: NSSlider){}` and `func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {}` being `@IBAction func passData(_ sender: NSSlider){unc prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {}}` instead of being one after the other? So in `passData()` call `performSegue()` and it should trigger `prepareForSegue()` (if it has the correct declaration according to the one in the doc). – Larme Mar 25 '18 at 17:22

2 Answers2

2

The mistake is not in the code.

You have to connect the segue to the ViewController, not to the WindowController.

Connect the slider action to the IBAction (not to the segue) and the segue from ViewController to SecondViewController (not from the slider).

And if the class of the second view controller is SecondViewController it should be indicated in the window controller. Where does SecondVC come from?

enter image description here

And once again the suggestion to create an extension of NSStoryboardSegue.Identifier

extension NSStoryboardSegue.Identifier {
    static let secondVC = NSStoryboardSegue.Identifier("SegueIdentifierForSecondVC")
}

and to use it in these two methods. And force cast the segue.destinationController. It must not crash if everything is hooked up properly.

@IBAction func segueData(_ sender: NSSlider) {
    self.performSegue(withIdentifier: .secondVC, sender: nil)
}

override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
    if let identifier = segue.identifier, identifier == .secondVC {
        let secondViewController = segue.destinationController as! SecondViewController
        secondViewController.imagesQty = slider.integerValue
    }
}  

Finally NSImage got its own initializer taking an URL

let image = NSImage(contentsOf: photos[i])
vadian
  • 274,689
  • 30
  • 353
  • 361
  • But photos update only after closing/opening a Second Window. How to update photos without closing a window? – Andy Jazz Mar 27 '18 at 22:51
  • Also there's yellow triangle: file:///Users/me/Desktop/Pixie/Pixie/Base.lproj/Main.storyboard: warning: Unsupported Configuration: “Aux Window Controller“ is unreachable because it has no entry points, and no identifier for runtime access via -[NSStoryboard instantiateControllerWithIdentifier:] – Andy Jazz Mar 27 '18 at 22:59
  • 1
    Basically you can delete the second window controller. To show the second view controller in the same window please look at https://stackoverflow.com/questions/28139294/how-does-one-display-a-new-view-controller-in-the-same-mac-window or http://theiconmaster.com/2015/03/transitioning-between-view-controllers-in-the-same-window-with-swift-mac/ – vadian Mar 28 '18 at 05:08
  • I deleted second window controller. But how to update views moving the slider in real-time? – Andy Jazz Mar 28 '18 at 06:48
  • That's a new question – vadian Mar 28 '18 at 06:50
  • https://stackoverflow.com/questions/49524384/how-to-update-nsimageview-using-nsslider – Andy Jazz Mar 28 '18 at 07:36
  • I can award a bounty to you in 12 hours)) – Andy Jazz Mar 28 '18 at 07:38
  • App works fine. Thank you one more time for your help! I have just one question: If I move a slider to the highest value (in my case 200) it shuffles my photos. But if I move it to 1 it fills all the views with photo number 256. I understand that it's `Int8` type. How can I get rid of it? I need a range much more than 256? I tried to assign type `Int16` but I couldn't. – Andy Jazz Mar 28 '18 at 18:07
  • Where does this `Int8` come from? – vadian Mar 28 '18 at 18:10
  • I suppose from slider. – Andy Jazz Mar 28 '18 at 18:11
  • I printed `photos[i]` in the loop and got this: file:///Users/me/Desktop/ArrayOfElements/0256.jpg – Andy Jazz Mar 28 '18 at 18:14
  • No, `intValue` is `Int32` and `integerValue` is `Int64`. You need to set the `maxValue` property of the slider (which is a `Double`) to the number of photos - 1. – vadian Mar 28 '18 at 18:14
  • Can a number of photos be more than slider's range? I use `.reversed` method to show just 1 last photo on all 12 views. (for instance my photo library contains 1000 photos, but slider's range is 200). So I can show only 200 out of 1000 photos in views. – Andy Jazz Mar 28 '18 at 18:23
  • It can be more but it shouldn't be less (to avoid an out-of-range exception) – vadian Mar 28 '18 at 18:28
  • Ok, I understand it. So, at the moment I have 205 photos in macOS folder and their names are `0201.jpg` to `0405.jpg` (I used frame-padding `%04d`). As I said I'm using `.reversed` method. In this case app shows me photo `0256.jpg` when I move a slider to `1` (instead of `0405.jpg`). This is quite strange! – Andy Jazz Mar 28 '18 at 18:33
  • I forgot to say: I'm making this app for my own work. I'm compositing artist working in Nuke, Fusion and Shake. So I need this app to watch compositing sequences of files.)) Maybe you think: this guy is strange, he needs strange app))) – Andy Jazz Mar 28 '18 at 18:42
  • 1
    Well, you can see a lot of real strange stuff here on SO – vadian Mar 28 '18 at 18:54
  • The issue on `0256.jpg` is not correct. Or it's an Xcode's cache. Or it is side-effect of `arc4random_uniform` method. I tried to work with images `0001.jpg`...`0200.jpg`. For slider `value = 1` application chose `0090.jpg` (not `0200.jpg`) – Andy Jazz Mar 29 '18 at 03:22
1
@IBAction func passData(_ sender: NSSlider) {        
    func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {
        if segue.identifier!.rawValue == "SegueIdentifierForSecondVC" {
            if let secondViewController = segue.destinationController as? SecondViewController {
                secondViewController.imagesQuantity = slider.intValue 
            }
        }
    }
}

Should be

@IBAction func passData(_ sender: NSSlider) {        

}

func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {
    if segue.identifier!.rawValue == "SegueIdentifierForSecondVC" {
        if let secondViewController = segue.destinationController as? SecondViewController {
            secondViewController.imagesQuantity = slider.intValue 
        }
    }
}

Because you have a method inside another while they should be one after the other.

Now, you need to modify passData(_sender:). You need to call inside performSegue(withIdentifier:sender) inside it.
If will trigger at some point prepare(for:sender:). I'm saying at "some point" because there is at least a shouldPerformSegue(withIdentifier:sender:) call before the prepare(for:sender:) is called.

So now do:

@IBAction func passData(_ sender: NSSlider) {        
    self.performSegue(withIdentifier: "SegueIdentifierForSecondVC"  sender:nil)
}
Larme
  • 24,190
  • 6
  • 51
  • 81
  • Is `prepare(for segue:sender:)` called? For the identifier, it seems correct, it's just that I'm more used to iOS than macOS. What window does it open? secondVC? New one? Is secondVC alreay on screen when you want to update the value? – Larme Mar 25 '18 at 17:46
  • Slider opens the window of SecondVC and doesn't update the value((( – Andy Jazz Mar 25 '18 at 17:50
  • Again: Is `prepare(for segue:sender:)` called? Could you add a `print()` inside it? Also, if it's called, is it printed before or after `print(imagesQuantity)`? – Larme Mar 26 '18 at 08:14
  • I'm wondering if `func prepare(for segue: NSStoryboardSegue, sender: NSSlider?) {` maybe changed with the correct one from the doc. – Larme Mar 27 '18 at 13:04