How do I append values to a dictionary from another viewcontroller in a macos application? - append

I am recently working on an application which contains data stored in a dictionary. This data is then stored in a table which the user can view. This is how the data is stored:
Bill(Date: "4/28/18", Item: "something", Amount: 43.67)
I want to append data from a secondary view controller and the way I am currently trying to use is this:
ViewController().bills.append(Bill(Date: valueDate, Item: valueItem, Amount: Double(valueAmount)!))
The variables are all preset and work fine when I print them out and when I run this code in the initial view controller it works perfectly, but when I run the code above in the second view controller, no errors come up but it does not seem to append the data at all.
This is on Swift 4.1 Xcode 9.4.1

All I can understand from the stated issue is you have your "bills" array in First ViewController and you are appending the Bill object in Second View Controller.
The issue in your code is that you are creating a new instance for First View controller in your second viewController, hence you can access "bills" array but issue is that the new instance and your original First View controller is different.
You can achieve this by :-
1. Using delegates (You can follow this link :- https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef).
2. Using Notifications :-
Post notification from your second View controller :-
NotificationCenter.default.post(name: NSNotification.Name(rawValue:"NotificationName"), object: nil, userInfo: ["bill":Bill(Date: valueDate, Item: valueItem, Amount: Double(valueAmount)!)])
here NotificationName could be any string, and pass your object Bill in user info
And then remove and add observers in first View controller in ViewDidLoad:-
NotificationCenter.default.removeObserver(self, name:NSNotification.Name(rawValue:"NotificationName") , object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.changeTintColor(notification:)), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)
func getData(notification: NSNotification){
let Bill = notification.userInfo!["bill"]
// do your task here
}

Related

ObservedObject being loaded multiple times prior to navigation to its view

having a bit of trouble with the following.
I have a List in a VStack as follows:
List{
ForEach(fetchRequest.wrappedValue, id: \.self) { city in
NavigationLink(destination: CityView(city: city, moc: self.moc)) {
cityRow(city: city)
}
}
}
This list is populated from a coreData fetchrequest. Each NavigationLinks navigates to CityView and passes a city Object with it.
CityView had a observable object 'notificationHandler' defined as follows:
struct CityView: View {
#ObservedObject var notificationHandler = NotificationHandler()
#ObservedObject var city: City
var body: some View {
}
}
NotificationHandler() sets up an instance of NotificationHandler and sets up a few notification observers from within init as follows:
import Foundation
import Combine
class NotificationHandler: ObservableObject {
let nc = NotificationHandler.default
#Published var networkActive: Bool = false
init() {
nc.addObserver(self, selector: #selector(networkStart), name: Notification.Name("networkingStart"), object: nil)
nc.addObserver(self, selector: #selector(networkStop), name: Notification.Name("networkingStop"), object: nil)
}
}
My issues is this - when the app boots onto its first view which contins the list above - I'm getting a number of instance of NotificationHandler starting - one for every row of the list. - This has led me to the belief that the NavigationLinks in the list are preemtivly loading the CityView's they hold. However I believe this is no longer the case and lazy load is the defualt behaviour. To add to that adding an onAppear() within CityView shows the they are not being completly loaded.
Any help would be greatly appretiated, I can't work out how this is happening.
Thanks
The Destination on the NavigationView is NOT lazy, so it initializes the Destination view as soon as it is created. An easy work around can be found here: SwiftUI - ObservableObject created multiple times. Wrap your Destination view within the LazyView.
Would like to share the latest updates for the solution of this problem.
https://stackoverflow.com/a/66520131
This is the way by which you can avoid creating multiple instances of ObservedObjects.
Sharing this because just lazy loading in NavigationLink doesn't solve the problem where refreshing the views at runtime as per user's actions creates and destroys ObservedObject multiple times.
Hence we need some solution where our Objects are created only once and also persists even after view is refreshed.

SwiftUI - KV Observe completion from Combine does not get triggered

I am trying to build a VOIP app using lib called VailerSIPLib. As the library was built using Obj-C and heavily using NotificationCenter to to publish the changes the active states all over the place.
I currently at the CallView part of the project, I can manage to start, end, reject calls. However, I need to implement connectionStatus in the view which will give information about the call like duration, "connecting..", "disconnected", "ringing" etc.
The below code is all in CallViewModel: ObservableObject;
Variables:
var activeCall: VSLCall!
#Published var connectionStatus: String = ""
Initializer:
override init(){
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
}
Methods:
func setCall(_ call: VSLCall) {
self.activeCall = call
self.activeCall.observe(\.callStateText) { (asd, change) in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
#objc func listen(_ notification: Notification) {
if let _ = self.activeCall {
print(self.activeCall.callStateText)
}
}
#objc func buildCallView(_ notification: Notification) {
print("inbound call")
self.isOnCall = true
}
Problem:
It prints out every thing except the completionBlock in setCall(_:). listen(_:) function validates that the state of the activeCall is changing and I would want to use that directly, however it does not work correct all the time. It should be triggered when the call is answered with callState value of .confirmed but sometime it does. This how I will know that it is time start the timer.
Other point is, in the example project of the VialerSIPLib they used self.activeCall.addObserver(_:) and it works fine. The problem for that is it throws a runtime error at the method something like didObservedValueChange(_:) and logs An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)' is unused
which I could not find anything related to it.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)'
This is telling you what the problem is. The observe(_:options:changeHandler:) method is only incompletely documented. It returns an object of type NSKeyValueObservation which represents your registration as a key-value observer. You need to save this object, because when the NSKeyValueObservation is destroyed, it unregisters you. So you need to add a property to CallViewModel to store it:
class CallViewModel: ObservableObject {
private var callStateTextObservation: NSKeyValueObservation?
...
And then you need to store the observation:
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
You could choose to use the Combine API for KVO instead, although it is even less documented than the Foundation API. You get a Publisher whose output is each new value of the observed property. It works like this:
class CallViewModel: ObservableObject {
private var callStateTextTicket: AnyCancellable?
...
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
.sink { print("callId: \(call.callId), callStateText: \($0)") }
}
There's no specific reason to use the Combine API in your sample code, but in general a Publisher is more flexible than an NSKeyValueObservation because Combine provides so many ways to operate on Publishers.
Your error with addObserver(_:forKeyPath:options:context:) happens because that is a much older API. It was added to NSObject long before Swift was invented. In fact, it was added before Objective-C even had blocks (closures). When you use that method, all notifications are sent to the observeValue(forKeyPath:of:change:context:) method of the observer. If you don't implement the observeValue method, the default implementation in NSObject receives the notification and raises an exception.

Returning a value from completion block/closure used in datasource method

I have a custom view which is going to be displayed in the collection view. View will be added in a stack view embedded in a view controller. A data source method is exposed to provide a view object.
Now here is the problem, before adding my custom view to stack I need to make a gateway call first to check if it is allowed to display the view or not.
As gateway call is asynchronous and returns me the response in the callback. Based on callback I need to instantiate the view and return in data source callback provided.
Here is demo code just for better understanding. Forgive me for my terrible naming conventions.
func customeViewInStack(customView: UIView) -> [UIView]? {
if viewIsAllowedToBeDisplayed {
self.downloadViewContent(onCompletionHandler: { contentDownloadSuccessfull in
if contentDownloadSuccessfull
// Return new instance of my custom view
else
// return nil
})
}
return nil
}
All the suggestions I read were about using a closure to pass the value, but due to data source method and callback in gateway call, I need to use old fashion return statement.
Edit 1
I have updated code snippet for better understanding.
I can not change function signature as it is part of a framework and a datasource method.
Also this method is shared between different features. What I mean by that is different views going to get added in stack view and they have their own conditional check wether to add them or not.
So basically what I am trying to achieve is until i do not get response from gateway operation, program execution should not proceed ahead.
I have tried using DispatchGroup class, but not able to achieve my goal.
Can someone suggest me on how should I tackle this problem
Thanks
About that, the correct solution is to not return something from the function, but instead give a completion to that function.
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]?) -> Void) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
completion(shouldDisplayView ? [...(put the stack of views to return here))] : nil)
})
}
EDIT: I read it poorly first time, so I enhanced my answer.
As you need to pass it to collection view, I suggest that you will store it afterwards, and reload collection:
private var viewsForCollection: [UIView] = []
customeViewInStack(customView: UIView(), completion: { [weak self] views in
self?.viewsForCollection = views ?? [] // I guess optionality for array is unnecessary
self?.collectionView.reloadData()
})
At first, you will have an empty collection, but as soon as your views are ready, you can reload it.
You will need to declare your own completion block and use that to get your new view:
Here is what that declaration looks like:
// Declaration
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]? -> ()) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
if shouldDisplayView
// Call the completion block with the views
completion(views)
else
// Call the completion block with nil
completion(nil)
})
}
And then in your usage of this, you would use it just like you used the checkIfViewShouldBeShown method:
customViewInStack(customView: customView, completion: { views in
guard let views = views else {
// Views are nil
}
// Do what you need to do with views
})

Using Swift 3.0, could I pass data to a view controller that is not segued?

I have 3 view controllers in my program. Can I send more than 1 value from the first view controller to the third without segue-ing?
Or do I have to send the values to the first, the second, then the third?
Thanks in advance.
You shouldn't rely on passing values directly like you are describing because you don't even know if all of the controllers have been instantiated yet. Create a model class or struct that you can store values in and reference from all 3 controllers. Basic example of model class:
import Foundation
fileprivate let sharedModelInstance = Model()
class Model {
var basicString : String = "string"
static var sharedInstance : Model {
return sharedModelInstance
}
func setBasicString(_ str: String) {
basicString = string
}
}
Then a controller:
import UIKit
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// reads 'string'
let aStr : String = Model.sharedInstance.basicString
Model.sharedInstance.setBasicString("Hello")
// Now 'Hello'
let anotherStr : String = Model.sharedInstance.basicString
}
}
This way all of your data is centralized and as long as you always refer back to your model you will never have data between controllers that is out of sync.
There are many ways to pass data from one view controller to another. What they all have in common is that the views are already created when the data is being passed. In your scenario, they have not all been created.
So if you're going from 1 to 2 to 3, when 1 is showing, 3 hasn't been instantiated yet. You would have to create 3, set the variable, then pass that newly created view controller 3 to view controller 2 so that 2 could then go ahead and display the already-created 3 on the screen. That whole process defeats the main purpose of your question and is much more work than the steps you're trying to avoid.
If you have one or two variables to pass, it's not such a hassle to pass to 2 then to 3. If you have a lot of data, you may want to consider putting it into one array to pass, saving it to Core Data, or even changing your data model completely.

Alternate way (using Selector) to set up notification observer

I have successfully setup notification observer using the code below:
func setupNotification {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action:", name: notificationString, object: nil)
}
func action(notification: NSNotification) {
// Do something when receiving notification
}
However, I am not interested in the coding style above since there might be a chance that I could type or copy/paste wrong method name action:.
So I tried to addObserver in a different way: NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector(/* What to pass in here??? */), name: notificationString, object: nil), I don't know what to pass in selector: Selector(...).
XCode hints me: Selector(action(notification: NSNotification), but this is illegal.
In Objective C, I can easily pick up a method at this phase but I don't know how in Swift.
Have you tried this syntax? Let me know.
Thanks,
The syntax for a Selector is Selector("action:")
Not the direct answer for your question but I have an idea.
In swift instance methods are curried functions that take instance as first argument. Suppose you have a class like this.
class Foo: NSObject {
func bar(notification: NSNotification) {
// do something with notification
}
}
And somewhere in code you have an instance of Foo.
let foo = Foo()
Now you can get bar method as a variable like this
let barFunc = Foo.bar(foo)
Now imagine in the future NSNotificationCenter has an api that lets you assign functions to it instead of selectors.
NSNotificationCenter.defaultCenter().addObserverFunction(barFunc, name: "notificationName", object: nil)
Though I don't like to use NSNotificationCenter but It would be nice to see this kind of api in the future.