Search code examples
swiftmacoscocoansnotificationcenternsviewcontroller

Passing Data Indirectly to an Unrelated NSViewController


  • I am new to macOS Development and I am working on a project for macOS using Xcode 10 and Swift 4.2.
  • The project contains mainly 3 view controllers.
  • Firstly, ViewController (the main welcome screen which separates the other two) has two buttons to call the other two respectively.
  • Secondly, MakeEntry View Controller creates an array of strings data variable using a form type structure comprised of text views and save button etc. which in the end just saves all input data into an array of strings data variable called carrierArray
  • Thirdly, there is a split view controller for displaying two children view controller namely EntryList and EntryDetail
  • EntryList (the left pane) contains a Table View to display titles of entries and EntryDetail (the right pane) will contain the description of the title entry (somewhat like the default notes app of macOS)

  • I want to achieve a simple functionality of being able to access or read that Array of strings variable called carrierArray which is created when the MakeEntry view controller saves it into a global variable defined within its own class file But I want to access that array of strings anywhere and anytime later.

  • I cannot use delegates and protocols, closures, segues or storyboard identifiers to carry that data because I am not navigating to the Split View Controller straightaway and also because I want to store that data to manipulate it further before displaying it in the right pane of split view controller (EntryDetail) .
  • I am unable to figure out whether how it might be possible to achieve this functionality using NSUserDefaults or CoreData.
  • Therefore I tried using the Notification Centre after storing that array of Strings in a Dictionary namely notifDictionary containing a key called carryData to be stored as the data object of notification centre And with some research and some trials and errors but without any luck all resulting in failure to get that data in the split view controller left pane class file namely (EntryDetail).
  • Code Snippets are as below, thanks a lot in advance for the kind help.

In MakeEntry View controller:

 notifDictionary = ["carryData": carrierArray]

        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "dataCarrier") , object: nil, userInfo: notifDictionary)

In EntryList View Controller: (Tried using both types of selector methods one at a time and even using them together but all without luck! Please Help!) The Variable datumDict and datumArray and nothing but copy receivers for carrierArray and notifDictionary

var datumDict: [String:[String]] = [:]
var datumArray: [String] = []


override func viewDidLoad() {
    super.viewDidLoad()



    NotificationCenter.default.addObserver(self, selector: #selector(self.getThatDict(_:)), name: NSNotification.Name(rawValue: "dataCarrier") , object: nil)

    NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "dataCarrier") , object: nil, queue: nil, using: catchNotification)

    //datumArray = datumDict["carryData"]!

}




 @objc func onNotification(notification:Notification)
    {
        print(notification.userInfo!)
    }

func catchNotification(notification: Notification) -> Void
{
    let theDict = notification.object as! NSDictionary
    datumDict = theDict as! [String: [String]]
    guard let theData = notification.userInfo!["carryData"] as? [String:[String]] else { return }
    datumDict = theData
}


@objc func getThatDict(_ notification: NSNotification)
{
    print(notification.userInfo ?? "")
    if let dict = notification.userInfo as NSDictionary?
    {
        if let thatDict = dict["carryData"] as? [String: [String]]
        {
            datumDict = thatDict
        }
    }
}

Solution

  • With the caveat that "globals and singletons should be avoided," it sounds like they are a good fit for what you're trying to do. As you get more comfortable in Cocoa you can move into more sophisticated means of accomplishing this (dependency injection). Look into this as you get more comfortable with Cocoa.

    Create a simple singleton type:

    // AppState.swift
    
    class AppState {
        // Basic singleton setup:
        static let shared = AppState()
        private init() {} // private prevents instantiating it elsewhere
    
        // Shared state:
        var carrierArray: [String] = []
    }
    

    Access it from your view controllers:

    // YourViewController.swift:
    
        @IBAction func doSomething(_ sender: Any) {
            AppState.shared.carrierArray = ...
        }
    

    If you need to update the other view controllers when this shared state changes, notifications are a good tool for that. You could do this with a didSet on carrierArray, or simply trigger the notification manually.