I need to update an image on the main view controller from a pop up view controller.
The button is called 'Feature2btn' on the Main View (EraViewController) but when I try the following code on the popup view controller it won't work.
It needs to be an immediate update as the main view is still showing in the background and does not reload so the change needs to be directly caused by the action on the pop up view.
- (IBAction)purchase:(id)sender {
HomeController = [[EraViewController alloc] initWithNibName:#"EraViewController" bundle:nil];
UIImage *image = [UIImage imageNamed:#"ico_plan.png"];
[(EraViewController*)HomeController setFeature2Btn:[feature2Btn setImage:[UIImage imageNamed:#"image.png"] forState:UIControlStateNormal];
}
There is (at least) two ways to do this:
You use a notification that one controller listens to and the other sends at the appropriate time.
You create a delegate protocol that the first controller implements and the second on calls.
The delegate one is a bit more complicated but generally considered good style. The notification one is not bad, either, but slightly less "elegant".
I will describe the notification based one here, because it seems ok for your case and would also allow to react to the purchase in multiple places by just registering for the notification there, too.
In the controller that has the image to be updated, register for a notification in viewDidAppear::
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateImage:) name:#"UpdateImageNotification" object:nil];
Implement the updateImage: method:
-(void)updateImage:(NSNotification*)note
{
NSString* newImageName = note.userInfo[#"imageFileKey"];
// ... update UI with the new image
}
Also make sure to deregister for that notification when the view goes away:
-(void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super viewWillDisappear:animated];
}
In the other controller, that triggers the update, fire the notification at the appropriate place:
-(IBAction)purchase:(id)sender
{
// ...
NSDictionary* userInfo = #{#"imageFileKey" : newImageName};
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UpdateImageNotification"
object:self userInfo:userInfo];
// ...
}
The object parameter in the notification context is to be used to specify if you want to listen to the notifications by any object or just by a very specific instance. In many cases the actual instance is not relevant, but you just discern the notifications by their name (like "UpdateImageNotification" in this case).
The userInfo dictionary is intended to carry along any information you need to provide with the notification. That's why I introduced a key "imageFileKey" that is associated with the new image name.
I think you made a small mistake. But before going ahead I just want to confirm whether following is the scenario. Correct me if I am wrong
1. You have mainViewController/HomeViewController (of type EraViewController), where you want to update the image
2. On mainViewController you have a popup screen, in which above code is written. The code is intended to change the button image on mainViewController/HomeViewController
If above is the scenario then I suggest following solution.
ERROR YOU MADE
You are creating a new object of EraViewController in the code you posted above and changing the image. According to OOPS concepts, new instance of that controller will be created(a second instance which is not visible on the screen) and you are applying new image in that instance. Now as that instance is not at all visible, you get a feeling that screen is not updating.
SOLUTION
There are at-least 3 solution to this problem
1. One solution will be Daniel Schneller gave in answer (with little bit modifications probably)
2. To achieve this through the delegates.
- You have to write the protocol in the PopViewController and have to implement that in the HomeViewController/mainViewController.
- As the image changes, in PopViewController, That has to be notified to the mainViewController, using delegate and protocol method.
- So that the mainViewController will get the notification and a protocol method will be executed. In that method you should have a code to update the image on the button.
3. (This is not suggested as this is not a good design)You have maintain the instance of the actual viewController which is visible on the screen (in a variable, lets say homeViewController). Then you can use following code in popupViewController
- (IBAction)purchase:(id)sender {
UIImage *image = [UIImage imageNamed:#"ico_plan.png"];
[(EraViewController*)homeViewController setFeature2Btn:[feature2Btn setImage:[UIImage imageNamed:#"image.png"] forState:UIControlStateNormal];
}
Hoep this helps.
Thanks
Try this,
- (IBAction)purchase:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateimage" object:imageName];
}
in mainviewcontroller
-(void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateiimage:) name:#"updateimage" object:nil];
}
- (void) updateiimage:(NSNotification *)notification
{
NSString * text =[notification object];
//update Image
}
Related
I'm using the NSNotification centre to detect changes of currency so I can update all the other classes. When a currency change occurs, all the other classes and views get updated, however when there is no currency change, and if you press the back button to go back to the home page the view loads on top of the already existing view.
Code for NSNotification center
if ([overviewModel.currency isEqual:#"GBP"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:self];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:self];
}
Code for handling updated data in homepage:
for (UIView *b in self.view.subviews) {
[b removeFromSuperview];
}
self.build = [[ApiRequestBuild alloc]initWithVersionKey:kAPI_VERSION_KEY requestType:kAPI_REQUEST_TYPE data:#""];
[self.build setQueryWithSection:#"homepage" value:#"" parameter:#[]];
self.request = [[ApiRequest alloc]init];
self.request.delegate = self;
[self.request sendRequestWithParams:[self.build buildConfig] toUrl:kAPI_URL_STRING];
I know why this is happening, the request gets sent again so the page loads on top of the already existing page, what I don't understand is why doesn't the remove from subview code get rid of the of the view and how would I be able to fix this? thanks
The removeFromSuperview won't work if it's being called from another thread (than main thread). Your notification will be received on the same thread it was fired from. I'll wager that you're listening to a model change event (regarding your currency state) on another thread.
Try dispatching to main queue before walking your copy of subviews to remove them all.
I want to update the view controller that is covered with a modal view in my application, I'm using swift. What I have currently is this:
The view on the right is the caller, and the views on the left are the modal views triggered by each button from the first one. They do basic edit, add new operations. These views are modally presented over the main one, and what I want to do is update the table view controllers enbedded in the two containers once I save the data from one of the modals.
I researched the use of one of the viewLoad events, but I'm kinda stuck at the moment. How can I do this?
Well you can do it in two ways
1. Delegate
2. NSNotificationCenter
In your Model class.h file
#protocol someProtoColName<NSObject>
-(void)finishedDoingMyStuff;
#end
#interface ModelClass()
#property (nonatomic,weak) id<someProtoColName> delegateObj;
Then in your ModelClass.m
-(void)someactionHappend{
//this is the method where you save your things call the delegate method inside here like this.
[self.delegateObj finishedDoingMyStuff];
}
Then in your CallerClass.h
#import ModelClass.h
#interface CallerClass:UIViewController<someProtoColName>
Inside CallerClass.m viewDidLoad
-(void)viewDidLoad{
ModelClass *model = [[ModelClass alloc]init];
model.delegateObj = self;
}
//Now declare the delegate method
-(void)finishedDoingMyStuff{
//update your code that this has happend. this will be called when your model class's button action inside which you sent the `self.delegateObj`message is invoked instantaneously.
}
NSNotificationCenter
CallerClass.m
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(someMethod) name:#"NOTIFICATIONNAME" object:nil];
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"NOTIFICATIONNAME" object:nil];
}
-(void)someMethod{
//something has happened, do your stuff
}
And in each of the Model class (if they are many or one dsnt matter)|
ModelClass.m
-(void)someactionHappend{
//this is your action method that you want to do stuff in the model
[[NSNotificationCenter defaultCenter] postNotificationName:#"NOTIFICATIONNAME" object:nil userInfo:nil];
}
Hope this helps you out.
Is there a simple way for checking if the setImage method of a UIButton has been called? I use storyboard to place my button (not that this should matter in this case). Basically I (i.e. another part of my code) would like to be notified my UIButton catImage's setImage has been called.
THere are many ways to notify another object when a particular event occurs in iOS.
Use delegate, when you set the image programatically call a delegate method in the other object to notify that event.
Send NSNotification, when the image is set send out a NSNotification. The other object should observe for this notification.
[[NSNotificationCenter defaultCenter] postNotificationName:#"Imageset" object:self userInfo:nil];
Use key value observing. So that if the value of image changes, the other object get notification.
For example:
-(void)setImageWithImageView:(UIImageView *)img withSuccessBlock:(void(^)(UIImageView *newImg))success {
UIImage *theImage = [UIImage imageNamed:#"prew_01.png"];
img = [[UIImageView alloc]initWithImage:theImage];
if (img != nil) {
success(img);
} else {
DLog(#"failure");
}
}
You can simply change some part of source and add your alternative...
I am new to this and need to be pointed in the right direction. I have a simple stopwatch app that allows the user to make inputs for the start time via a datepicker.
I want the readout of MM:SS to be displayed in a separate view (view 1) from the datepicker (view 2).
What should I search in order to learn how to do this? Right now it is all on a single view.
You can do it by using following method :
In View1 controller use this :
-(void)timerupdate
{
int minutes=10;
int seconds =10;
NSDictionary *dict =[NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat:#"%d:%d", minutes, seconds],#"mytime", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"anyname" object:self userInfo:dict];
}
In View2 controller use this :
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(anymethod:)
name: #"anyname"
object: nil];
}
- (void)anymethod:(NSNotification *)notification
{
NSLog(#"%#",[notification.userInfo objectForKey:#"mytime"]);
}
Hope this will help you..I use this for me and its working fine.
I think you are looking for a mechanism to pass data in between different UIViews (UIViewControllers).
Worse option would be to implement a singleton using singleton pattern. Then pass your data back and forth using delegation.
or create a custom class to store the date what you want and pass it to every time when you are changing your UIView or UIViewController.
Background:
All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):
[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx
or
[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440
I put my unpublish/disconnect methods in viewDidDisappear:
-(void)viewDidDisappear:(BOOL)animated{
//dispatch_async(self.opentokQueue, ^{
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
Here is a trace:
Here is the DetailViewController on Github: link here
How to reproduce:
Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish
Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream
Try this several times and eventually it will crash.
If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.
Expected result:
It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.
Other:
What is your device and OS version?
iOS 6
What type of connectivity were you on?
wifi
Zombies Enabled?
Yes
ARC Enabled?
Yes
Delegates set to nil?
Yes, as far as I know
Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.
What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.
This might be a stupid question, but:
Is there anyway to stop all processes initiated by a certain VC?
Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,
You should try to avoid managing the lifetime of limited resources using dealloc.
So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]
// This is true if the view controller is popped
if ([self isMovingFromParentViewController])
{
NSLog(#"View controller was popped");
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
...
//dispatch_async(self.opentokQueue, ^{
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
else
{
NSLog(#"New view controller was pushed");
}
}
Ref: Testing for Specific Kinds of View Transitions
Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:
You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
}
You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.
-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.
So if you push a view over it, viewWillDisappear will be called and your objects deleted.
This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.
Perhaps you should put your unpublish/disconnect code in -(void)dealloc.
This is what Apple suggests:
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.
It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.
I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.
I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:
self.session.delegate=nil;
You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue.
And better cleanup your session and subscriber in dealloc method:
- (void) dealloc {
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
[self doCloseRoomId:self.room.roomId position:self.room.position];
//[super dealloc]; //for non-ARC
}
According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects.
Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.
As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :
- (oneway void)dealloc
{
self.session.delegate = nil;
self.publisher.delegate = nil;
self.subscriber.delegate = nil;
}
Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.