What's the best way to watch the change of data in Cocoa - swift

I have a singleton to store some global data for my macOS app, one of my ViewController keeps modifying data. I want to simultaneously show the changes in a View, which is related to another ViewController. what 's the best way to do this?
Global Data:
final class AppData {
static var logs: [LogData] = []
}
ViewController 1:
class FirstViewController: NSViewController {
AppData.logs.append(newLogData)
}
ViewController 2:
class SecondViewController: NSViewController {
// what's the best way to simultaneously watch the change of AppData.logs?
}

If your App is planned to be macOS only you can use a NSObjectController. This is definitively the easiest approach and you can do most of the configuration in Interface builder. It works internally with bindings. In case of an array you want to observe, you would use a NSArrayController.

One way is to use the notificationcenter
In viewcontroller2 add:
override func viewDidLoad() {
super.viewDidLoad()
notificationCenter.default.addObserver(self,
selector: #selector(view1DidChange),
name: "view1DidChange",
object: nil
)
}
#objc private func view1DidChange(_ notification: Notification) {
// Do something
}
In viewcontroller1 add
notificationCenter.default.post(name: "view1DidChange", object: self)
This can be repeated in every class, that should listen.

Here i am sharing the Delegate & Protocol approach to achieve this functionality.
final class AppData {
static var logs: [LogData] = []
}
protocol FirstViewControllerDelegate {
func ViewControllerDelegate(appData:[LogData])
}
class FirstViewController: NSViewController {
var delegate:FirstViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
AppData.logs.append(newLogData)
self. delegate?.ViewControllerDelegate(appData: AppData.logs)
}
}
class SecondViewController: NSViewController,FirstViewControllerDelegate {
var firstViewController:FirstViewController = FirstViewController()
override func viewDidLoad() {
self.firstViewController.delegate = self
}
func ViewControllerDelegate(appData:[LogData]){
//Do Update the UI
}
}

Related

Delegate Method is not called in UIButton class

I have a UIbutton class - from which I would like to call an 'adjust constraints' method after the user makes changes on the setting screen. I have created some protocols and all seems in order but it is not calling the method from the subview UIButton after the user closes the Setting Screen.
I have tried some of the other solutions here - that hasn't worked and I think it might be because I am using a UIButton class and I can't reinstantiate it, or call the instantiation? Either way, it never calls the method from the delegate.
Is using protocols the right way to solve this problem and if so, what am I missing?
Basically I have 3 files; the MainVC which I set as my first delegate (it gets triggered from my SettingScreenVC when user is done making changes to Setting Screen):
class MainVC: UIViewController, SettingScreenDelegate {
weak var numButtonDelegate: Buttons_Numeric?
func settingSetButtonConstraints() {
numButtonDelegate?.setupButtonConstraints()
}
}
Then in my Setting Screen I call the MainVC after the user made some changes to their settings:
class MainVC: SettingScreenVC {
weak var delegate: SettingScreenDelegate?
func closeSettings() {
delegate?.settingSetButtonConstraints()
}
}
Then in my Buttons_Numeric class I declare the function and the UIButton class delegate:
protocol numButtonDelegate: class {
func setupButtonConstraints()
}
class Buttons_Numeric: UIButton, numButtonDelegate {
weak var numButtonDelegate: Buttons_Numeric?
required init(coder aDecoder:NSCoder) { super.init(coder: aDecoder)!}
override init(frame:CGRect) {super.init(frame: frame)
self.numButtonDelegate = self
setupButtonConstraints()
}
override func awakeFromNib() {
self.numButtonDelegate = self
setupButtonConstraints()
}
func setupButtonConstraints() {
//SET UP CONSTRAINTS
}
}
Ok so couple of things you need to understand about delegates:
if you create a delegate, you need to conform to the delegate somewhere.
the conforming class should be assigned to the instance of the delegate.
With that in mind lets try to fix the code:
first the settings screen:
protocol SettingScreenDelegate: class {
func settingSetButtonConstraints()
}
class SettingScreenVC {
weak var delegate: SettingScreenDelegate?
func closeSettings() {
delegate?.settingSetButtonConstraints()
}
}
So far so good now the mainScreen should conform to the SettingScreenDelegate and be assigned to its delegate:
class MainVC: UIViewController, SettingScreenDelegate {
weak var button: Buttons_Numeric!
func openSettingsScreen() {
let settingsScreen = ... // the setting screen instanciation
settingsScreen.delegate = self // the MainVC
}
func settingSetButtonConstraints() {
self.button.setupButtonConstraints()
}
}
Now for the last step, the MainVC should have an instance of the button, then the only thing we need to do is call the function 'setupButtonConstraints' from the MainVC which means we do not need the delegate at the button.
class Buttons_Numeric: UIButton {
required init(coder aDecoder:NSCoder) { super.init(coder: aDecoder)!}
override init(frame:CGRect) {super.init(frame: frame)
setupButtonConstraints()
}
override func awakeFromNib() {
setupButtonConstraints()
}
func setupButtonConstraints() {
//SET UP CONSTRAINTS
}
}
You need to assign a value to the delegate variable. I would do it like this if the answer is always self
class Example: SomeDelegate {
lazy var someDelegate: SomeDelegate? = self
}
Otherwise you'll want to do it in the class' initializer or ViewDidLoad.

Swift MVVM - use protocol to handle viewModel events

I'm trying to use MVVM with delegate protocols. When something changes in the view model I want to trigger it in the view controller.
When I want to use protocols to handle the view model's event on a view controller, I can not set the protocol to the view controller for my view model class.
It gives me the error:
Argument type (SecondViewController) -> () -> SecondViewController does not conform to expected type SecondViewModelEvents
How can I do this the right way?
Here is the code for my view model:
protocol SecondViewModelEvents {
func changeBackground()
}
class SecondViewModel:NSObject {
var events:SecondViewModelEvents?
init(del:SecondViewModelEvents) {
self.events = del
}
func loadDataFromServer() {
self.events?.changeBackground()
}
}
And for my view controller class:
class SecondViewController: UIViewController,SecondViewModelEvents {
let viewModel = SecondViewModel(del: self) //Argument type '(SecondViewController) -> () -> SecondViewController' does not conform to expected type 'SecondViewModelEvents'
#IBAction func buttonPressed(_ sender: Any) {
self.viewModel.loadDataFromServer()
}
func changeBackground() {
self.view.backgroundColor = UIColor.red
}
}
You're trying to initialize the view model variable and pass the view controller as a delegate which at this point is not fully initialized.
Try checking out the very informative and very detailed Initialization page in the official Swift language guide.
Since this is a protocol used for this specific purpose, we can safely constrain it to classes (notice the : class addition to your code.
protocol SecondViewModelEvents: class {
func changeBackground()
}
It's good practice to use more descriptive naming, and also using weak references for delegate objects in order to avoid strong reference cycles.
class SecondViewModel {
weak var delegate: SecondViewModelEvents?
init(delegate: SecondViewModelEvents) {
self.delegate = delegate
}
func loadDataFromServer() {
delegate?.changeBackground()
}
}
You can try to use an optional view model, which will get initialized in an appropriate place, like awakeFromNib():
class SecondViewController: UIViewController, SecondViewModelEvents {
var viewModel: SecondViewModel?
override func awakeFromNib() {
super.awakeFromNib()
viewModel = SecondViewModel(delegate: self)
}
#IBAction func buttonPressed(_ sender: Any) {
viewModel?.loadDataFromServer()
}
func changeBackground() {
view.backgroundColor = UIColor.red
}
}
Or an alternative approach would be to initialize a non-optional view model in the UIViewController required initializer:
// ...
var viewModel: SecondViewModel
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.viewModel = SecondViewModel(delegate: self)
}
// ...
You need to use lazy initialization as,
lazy var viewModel = SecondViewModel(del: self)
OR
lazy var viewModel = { [unowned self] in SecondViewModel(del: self) }()

ios 8 and Swift: call a function in another class from view controller

I want to touch a button in ViewController and run a simple function in a custom class. No parameters needed. Code is basically:
class MyClass: UIView {
//init function is here
//other setup stuff
...
func SetFocus() {
//I want to set the camera focus to infinity.
var error: NSErrorPointer = nil
var pos: CFloat = 1.0
captureDevice!.lockForConfiguration(error)
captureDevice!.setFocusModeLockedWithLensPosition(pos, completionHandler: nil)
captureDevice!.unlockForConfiguration()
}
}
and in ViewController
import UIKit
class ViewController: UIViewController {
#IBOutlet var myClass: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func myButton(sender: UIButton) {
myClass.SetFocus()
}
}
I've tried everything I can think of (new at Swift) and the app either crashes or the function I'm calling doesn't run. Everything else in MyClass works OK.
Try to delete the #IBOutlet from before MyClass.
var myClass : MyClass!
You can also try this:
var myClass : MyClass! = MyClass()
From the looks of it, however, everything you are doing in setFocus() in MyClass, can easily be recreated in the ViewController.
Try this
#IBAction func myButton(sender: UIButton) {
var ClassViewController = self.storyboard.instantiateViewControllerWithIdentifier("StoryboardID") as! UIViewController
ClassViewController.MyFunction()
}

NSViewController delegate?

I'm new to using delegates in Swift, and I can't seem to figure out how to communicate with my View Controller from a different class. Specifically, I call the custom class's functions from my App Delegate, and then from within that custom class, I call a function within my View Controller. My basic setup, following this question, is:
AppDelegate.swift:
var customClass = customClass()
func applicationDidFinishLaunching(aNotification: NSNotification) {
customClass.customFunction()
}
CustomClass.swift:
weak var delegate: ViewControllerDelegate?
func customFunction() {
delegate?.delegateMethod(data)
}
ViewController.swift:
protocol ViewControllerDelegate: class {
func customFunction(data: AnyObject)
}
class ViewController: NSViewController, ViewControllerDelegate
func customFunction(data: AnyObject){
println("called")
}
}
However, delegate is always nil. I am assuming this is either because the ViewControllerDelegate protocol never gets initialized or because I never set the delegate of the actual NSViewController? I know I'm missing something obvious/straightfoward, however I have yet to see what that is.
Your question is hard to answers because you have completely misunderstood the point of a protocol.
A protocol is a type which is used to define functionality. A class that conforms to this protocol provides the specified functionality, by implementing the required methods.
You can not initialize a protocol.
So if your CustomClass looks like this:
class CustomClass {
weak var delegate: ViewControllerDelegate?
func customFunction() {
delegate?.delegateMethod(data)
}
}
Why do you expect that delegate has suddenly a value?
Of course you have to set delegate to something first. The delegate must set delegate. If you want a ViewController instance to be the delegate, it must assign itself to delegate.
This for instance will work.
protocol ViewControllerDelegate {
func delegateMethod(data: AnyObject) //I renamed this because in
//CustomClass you are trying to call `delegateMethod` on the delegate
}
class CustomClass {
weak var delegate: ViewControllerDelegate?
func customFunction() {
delegate?.delegateMethod(data)
}
}
class ViewController: NSViewController, ViewControllerDelegate
var customClass = CustomClass()
func viewDidLoad(){
customClass.delegate = self
customClass.customFunction()
}
func delegateMethod(data: AnyObject){
println("called")
}
}
Read more about delegation here.

Working with delegate in swift

Im trying to figure out how to work with delegates and protocols.
I have a MessageFetcher class which fetches messages from a url.
class MessageFetcher {
func getCurrentMessagesFromString(urlStringL: String) {..}
}
I created a protocol
protocol MessageFetcherDelegate {
func currentMessagesDidUpdate()
}
I have a View controller which displays the messages and conforms to the MessageFetcherDelegate protocol:
class MessagesViewController: UIViewController, MessageFetcherDelegate {
var messageFetcher = MessageFetcher()
var delegate: MessageFetcherDelegate?
override func viewDidLoad() {
delegate = self
}
func currentMessagesDidUpdate() {
collectionView.reloadData()
}
}
I want the view controller to be notified when the fetcher has updated the messages array and reload the collection views data.
Where am I going wrong and what do I need to add? How do I notify the controller that messages have been updated?
In your class MessageFetcher you need to add delegate property not in MessagesViewController:
class MessageFetcher {
func getCurrentMessagesFromString(urlStringL: String) {..}
var delegate: MessageFetcherDelegate?
}
And your MessagesViewController should looks like below, see comments for explanation:
class MessagesViewController: UIViewController, MessageFetcherDelegate {
var messageFetcher = MessageFetcher()
override func viewDidLoad() {
// Declare MessagesViewController class as a deleagte od message Fetcher
messageFetcher.delegate = self
}
func currentMessagesDidUpdate() {
collectionView.reloadData()
}
}
Now what left to do is call delegate method inside MessageFetcher, add this code when the fetcher has updated the messages:
delegate?.currentMessagesDidUpdate()