I have a class that is a GCDAsyncUdpSocketDelegate. Now I'm getting some data thru UDP (in a background thread) and need to display that data with an IBOutlet in my ViewController. But ViewController is just a class. Where is its object instance defined? What is its name? What do I do to access it, or one of its properties, methods? I've seen this UIApplication.sharedApplication().keyWindow?.rootViewController method but this doesn't guarantee the exact ViewController I want to access, I guess.
I don't know the exact structure of your app, but you could try something like this:
let rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
if let customViewController = rootViewController as? CustomViewController {
customViewController.label.text = theData
}
else{
let customViewController = storyoboard!.instantiateViewControllerWithIdentifier("CustomViewControllerID")
// (This assumes CustomViewController is defined in the same
// storyboard as the view controller running this code. Otherwise,
// you need to get a reference to the storyboard first).
rootViewController.presentViewController(customViewController, animated:true)
customViewController.label.text = theData
}
EDIT: If the object that receives your asynchronous server data is dettached from your navigation (good call, by the way), you can have it:
Store the data somewhere it will persist even if said object is deallocated
(or make said object a singleton, if applicable).
Post a system notification (using NSNotificationCenter) once the data is available, and have your main ViewController "listen to it" (-addObserver:selector:name:object). When the notification is posted and the notification handler is called, your view controller can retrieve the data from its (persistent) location (file, or property of the singleton mentioned above if you chose that route).
Finally, for the case when the data is already available by the time your ViewController is instantiated, check for data availability and retrieve it if present in e.g. your main view controller's viewDidLoad().
There are many ways you can achieve this,
1. Use Protocols or Delegates
You said "I have a class that is a GCDAsyncUdpSocketDelegate." Write a protocol implementation in that class and make your view controller subscribe to that delegate, when you get delegate call back for your GCDAsyncUdpSocketDelegate do neccessary check and fire your custom delegate to the view controller, pass the data as an argument to the delegate method. In the view controller get the data and update the IBOutlet.
2. Use NSNotificationCenter (dirty way of doing things, gotta be careful though)
In your view controller class add an observer for the notification you are going to post like,
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI:", name: "didRecieveDataFromUDP", object: nil)
Add a method in your VC
func updateUI(notifObject:NSNotification){
let responseString = notifObject.object
self.yourOutlet.string = responseString
}
and also remove the notification observer when you are done with the VC.
In the GCDAsyncUdpSocketDelegate delegate class when you get the call back with results fire this notification,
NSNotificationCenter.defaultCenter().postNotificationName("didRecieveDataFromUDP"", object: theResposenObjectYouNeedToSend)
3. Use sharedInstances or mis-use the AppDelegate (not recommended)
Create shared instances or singleton classes that will live through the app lifecycle and use them to store/retrieve data from any class or any thread.
Hope this helps, I wish you choose the first or second way.
Related
I have two questions.
I want to pass data. I have two ViewControllers.
Controller A has Data and I want to pass it from A to B controller.
First - tab bar controller has a relationship segue. Can I use this? E.g.: perform segue or prepare?
Second - I want to pass data between ViewControllers in UITabBarController
I can't do this. I searched this I can't find this...
I don't know if the search word is wrong. Can you tell me how to do it or how to search for it?
No. A relationship is not really a segue. No segue is triggered merely by switching tabs.
You just have to put the data where the various view controllers can find it. If you want to notify other view controllers that the data has changed, use a notification or take advantage of the fact that the tab bar controller delegate knows when the user switches tab bar items.
No, You can not pass the data between two ViewController directly, if they are connected to TabbArController.
If you want you can manage through NotificationCenter (So when a new change occur and you post the notification, it will automatically refresh the data)
NotificationCenter.default.addObserver(
self,
selector: #selector(self.getData),
name: .refreshAttendanceForSelectedDate,
object: nil)
#objc func getData() {
/// perform task in view controller
}
// post the changes when you get response ....
NotificationCenter.default.post(name: Constants.Notification.refreshAttendanceForSelectedDate, object: nil)
Or
You can pass the updated data by DataStorgae in a variable of that respective model type (LocalCache) by creating a singleton object. This variable will reside in memory till the app will reside in memory. After forcing kill the app or flush out the app from memory, this variable will automatically be removed.
Note: If needed, please don't forget to set value nil. (Eg. on logout action)
class DataStorage {
/// Created the singleton objectbas
static let instance = DataStorage()
/// Created the properties
var notificationbadgeCount: Int?
var configData: ConfigurationModel?
}
Use by set
DataStorage.instance.selectedStaffId = model.id
get the value
if let id = DataStorage.instance.selectedStaffId {
/// Perform respective task ....
}
My first view controller has a snapshotlistener on a document then passes this doc data onto my second view controller- by let vc = vc2(data:data), the user than navigates to my second vcontroller and has the option to write data to the document.
When the user writes to the document my snapshot listener fires, and the data in vc1 is updated, but obviously the data in vc2 wont be updated.
I could add a function in vc2 to read data from firebase as soon as i call the function to write, but I'm curious if there is a better method of passing the data from vc1 to vc2 so that the data in vc2 is updated when the snapshot listener in vc1 fires?
This decision depends on (1) how many view controllers deep this will go and (2) how you handle these types of situations elsewhere in your app.
To the first point, if there are no other view controllers that need to know this data beyond the second one, then just have the first view controller call a method on the second view controller when data updates. But if there are more view controllers down this navigation stack that need to know about this data update, then I'd consider something else--perhaps a shared resource like a data manager or a view model that all view controllers observe or posting a local notification.
To the second point, just be consistent. However you normally handle these realtime-data situations, try to keep doing that. For instance, if you normally use protocols, use them here.
But for pure simplicity in a scenario where the second view controller is the only other object interested in the data updates in the first view controller, just have the first notify the second:
class VC1: UIViewController {
private var vc2: VC2? // Make it an instance property that the navigation controller pushes to.
Firestore.firestore().document(docPath).addSnapshotListener({ (snapshot, error) in
vc2?.dataDidUpdate() // If the second view controller was never instantiated, the optional chaining of vc2 will fail silently.
})
}
class VC2: UIViewController {
func dataDidUpdate() {
// update UI
}
}
Evening, my question is full about theory.
I understood reading from Apple developer documentation that is better to use the Delegates Pattern to keep track of some object attributes. In this way we can access the delegate without access to the object. (I really didn't get the reason of this choice)
I also understood that is better to define: protocolDelegate: class
and when we are declaring the delegate inside the class it's better to use the weak word to prevent some "kind of problem cycle". (??)
So, while I was playing a bit with code, I've discovered that you can't pass a weak delegate between two view controllers, because of course, when you change the controller, the weak delegate is going to be deleted because is a weak thing (or at least this is what I understood).
So, I have to choose between 2 options:
make the delegate "strong" deleting the weak key.
or pass the object in the segue and keep the delegate as weak.
I have a lot of confusion, can you clear my mind? :D
The cycle you're referring to is called a retain cycle.
Let's use a concrete example to clear this up: say you've got a UIViewController which has a UITableView. The view controller has a strong reference to the table view. The view controller now wants to act as the delegate to the table view.
Now, if the table view would have a strong reference to its delegate, we would have the following situation: the view controller has a strong reference to the table view, and the table view in turn would have a strong reference back to the view controller. Thus neither can ever get deallocated.
To break this cycle, references to delegates are usually weak. This allows the retain count of the view controller to drop to 0 eventually, which can in turn release the table view.
Your classes that want to use delegates should also follow this pattern and use weak references to their delegates. You should thus pass the required references via your segue.
I will concentrate on the first part of your question, since the previous answers have covered the rest pretty well.
Consider the following situation: you have a class that handles some kind of network connection - it sends a request to a server and gets a response. Outside of this class there is a viewController that has a button that triggers the request and a view which presents the response to the user.
Basically, the network handling class should be able to get some message from the viewController (button pressed) on one hand and pass the viewController the response on the other. So there should be bidirectional communication between the two classes. While the passing of the buttonPressed message to the network handling class is pretty obvious, the reverse part (passing the response) is a bit more tricky, because the network handling class should not be aware of who created it and who calls it (good OO practices and memory leaks prevention).
That's the point where the delegate pattern comes in. It allows an object to pass data to whoever is interested in it without knowing anything about the recipient. The class that passes the response only knows some 'delegate' and not another class. In addition you can take out the network handling class as is and put it in another project. Because it isn't supposed to know any other class from its original project, only some 'delegate', it can be put into another project without any modifications.
I hope it can help you to get the reason of the choice.
I think pass the object with segue, Segues are a very important part of using Storyboards in Xcode. We can go over the different types of seguesanother time, but this one shows you how to use the “Show” segue, as well as how to pass data between the two view controllers to customize the second one with whatever custom data is required.
You can easily use segues example; Under below you can send currentstring to destinationViewController inside sentstring , also ShowSegue is your segue identifier
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSegue" {
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.sentstring = currentstring
}
}
}
Navigation between viewcontrollers maintain stack of viewcontrollers.
For example aVC is firstviewcontroller then top of stack will be aVC,
now when you push or show another viewcontroller say bVC then now top of statck is bVC. So stack looks like,
aVC -> bVC(top)
now you push another cVC then,
aVC -> bVC -> cVC(top).
So top of stack is always visible to user.
at current situation, aVC and bVC and cVC are not deallocate. they are in memory. But if you pop or dismiss cVC, then it will deallocate from memory and now your top of stack looka like,
aVC -> bVC(top).
So viewcontrollers live in stack till they are not popped or removed. So, they are strog reference by default.
Segue is nothing but you can say that they are graphical representation of push or pop operation.
another thing is that delegate should be weak that because it can create retain cycle if they are strong.
you can called delegate as representative in general sense.
Now, if you are using segue, send your object in prepareForsegue and it will manage everything else.
I have a function in my AppDelegate which is similar to, DidReceiveRemoteNotification, and upon receiving a message appends a message to an array and then attempts to reload a tableview.
My app consists of just one Viewcontroller and one AppDelegate.
My code looks like this:
var myCustomViewController: ViewController = ViewController(nibName: nil, bundle: nil)
println("******didReceiveMessage*****")
myCustomViewController.messages.append(message.data.message as! String)
println(myCustomViewController.messages)
myCustomViewController.MessageTableView.reloadData()
whenever I try to call a reload function my app crashes however. Is there a way to reload my tableView from my appDelegate function?
Your view isn't loaded yet, so the table view doesn't exist yet, hence it is nil.
It isn't clear whether you're using the correct view controller, because you're creating a new one. Either call view on it to create the view (and subviews) or change the reference to the real existing view controller.
Most likely you should be accessing the root view controller of the app delegates window.
I have problem with calling methods from one UIViewController by another UIViewController.
Currently I have UIScroll view with two UIViewControllers.
I want to change something in second one and see results in first one.
I try to do this in this way:
Inside function of second UIViewController:
-(void)doSomething:(){
FirsOneViewController *firstVC = [FirsOneViewController alloc] init];
[firstVC changeUnits:0];
}
Function is called but I don't se any changes in first controller.
BR,
Paul
From your code I see you create a new instance of FirstViewController and so there is no reason why the current instance inside the scrollview would receive this message.
You need to send the changeUnits: message to the current FirstViewController, so you need a reference to it. To do this you may want to think about creating a protocol, so that you parent container (the scrollview) is notified by the SecondViewController and then notifies the FirstViewController. A simpler(and lazier) solution is make the SecondViewController have a strong reference to the FirstViewController (though this solution may bite you in the future).
As said in other answers you are creating a new instance of FirsOneViewController instead of referencing to the one you already have.
Here are three ways of doing what you are asking:
Delegation:
The FirstViewController should be the delegate of the SecondViewController (as the secondViewController is calling methods on the FirstViewController). You should tell the SecondViewController that the FirstViewController is its delegate in what ever class initialises the two viewControllers.
From what you have said so far this seems like your best option.
NSNotification:
This could be good option if you think more than one object will want to listen to the change in the SecondViewController. Just post an NSNotification in the SecondViewController and add an NSNotification listener in the FirstViewController
Singleton:
if there should only ever be one instance of the FirstViewController in existence then make it a singleton. By making a class initialiser method. so that you can create/get the current instance of the object from anywhere in your appellation.
Hope this helped.