This question already has answers here:
how to reload tableview of another uiviewcontroller in current viewcontroller
(4 answers)
Closed 7 years ago.
I have two view controllers. First one has a table view and using the second view controller I'm calling a method on first view controller. In that method I'm trying to adding an item to the objects array and refresh the table view.
[postsArr insertObject:post atIndex:postsArr.count];
dispatch_async(dispatch_get_main_queue(), ^{
[self.newsTableView reloadData];
});
I'm calling this method in 2nd view controller,
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
[self.appContext.creator createWithText:userText completion:^(UserData *userData,NSError *error) {
if (error == nil){
if (userData != nil) {
[self.parent addNew:((UserData*)[userData.list objectAtIndex:0]) withImage:nil];
}
}else{
[self showAlertWith:#"Error" with:#"Error occurred!"];
}
}];
});
How may I refresh the table view from another view controller?
Add this on the top of the interface of your second viewcontroller
#protocol SecondViewControllerDelegate <NSObject>
- (void)addNewItem:(id)item;
#end
#interface SecondViewController : UIViewController
#property (nonatomic, weak) id <SecondViewControllerDelegate> delegate;
#end
The point in your firstViewController from where you are instantiating your secondViewController for navigation add secondviewController.delegate as self.
self.secondViewController.delegate = self;
From the point where you get response in your secondViewController and you want to addItem in your firstViewController call delegate method from here and pass that item to firstViewController in that delegate method.
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
[self.appContext.creator createWithText:userText completion:^(UserData *userData,NSError *error) {
if (error == nil){
if (userData != nil) {
[self.delegate addNewItem:((UserData*)[userData.list objectAtIndex:0]) withImage:nil];
}
}else{
[self showAlertWith:#"Error" with:#"Error occurred!"];
}
}];
});
Add the implementation of addNewItem in your firstViewController
- (void)addNewItem:(id)item{
[postsArr insertObject:post atIndex:postsArr.count];
dispatch_async(dispatch_get_main_queue(), ^{
[self.newsTableView reloadData];
});
}
You can use NSNotificationCenter
Fire notification when you need to reload the table
Just follow the link to know how implement notifications
Send and receive messages through NSNotificationCenter in Objective-C?
There Are Many methods through you can achieve your Goal
NSNotification
Delegates
KeyValueObserver
But I found most reliable is
#property (copy) void (^valueTypeChangedBlock) (NSarray arrayTypeObject);
Add This Property In .h file
And In .m File Add This
self.valueTypeChangedBlock = ^(NSarray NewArr) {
postsArr = NewArr
[self.newsTableView reloadData];
};
Where Ever U want to change the array from which table view Reload
Add the new array here
self.valueTypeChangedBlock (self.NewArray);
Related
Hi im developing an app that has a parent view that then used containers to embed other views as seen below.
For now im only working with the left and centre container which are both table views. The main view or the Project screen view is my parent controller and i want it to pass data to and from the two child controller and i know for this the best option is to use delegates. However each example i have looked at that uses delegates, created and initialises a new view controller so for example lets say the left container embeds a view using the leftviewcontroller. Each example has this line of code.
LeftViewController *sampleProtocol = [[LeftViewController alloc]init];
LeftViewController.delegate = self;
Im thinking i dont need to create a new LeftViewController since it is embeded it is already in my list of child controllers. So my queston is how would i get the controller from the list of child controllers and set the parent as the delegate. I know i it is an array and i can use objectAtIndex but how do i know the order of items in the array will not change can i not call it but a tag or identifier? Thank you for any help sorry if the question is not that clear its my first time setting up delegates.
i know for this the best option is to use delegates.
In this case, I wouldn't be so sure. I think the best option would be to have a robust model and use KVO and notifications to signal updates between view controllers.
The direct answer to your question is not too bad.
for (UIViewController *viewController in self.childViewControllers) {
if ([viewController isKindOfClass:[LeftViewController class]]) {
LeftViewController *leftViewController = (id)viewController;
leftViewController.delegate = self;
break;
}
}
I think a minor improvement on this would be to use the segue. Make sure each of the containers have a named segue. In this example, the left view controller has a segue with the identifier "Load Child LeftViewController".
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Load Child LeftViewController"]) {
LeftViewController *leftViewController = segue.destinationViewController;
leftViewController.delefate = self;
}
}
Its always better to use NSNotificationCenter for such complex mechanism.
*** put following code in LeftController.m ***
// *** Register a Notification to recieve a Data when something happens in Center controller ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"hasSomeData"
object:nil];
// *** create a method to receive Notification data ***
- (void)receivedNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"hasSomeData"])
{
// do your stuff here with data
NSLog(#"data %#",[notification object]);
}
}
*** when something happen in center controller post a notification to inform Left Controller ***
[[NSNotificationCenter defaultCenter] postNotificationName:#"hasSomeData" object:self];
//Secondvc.h
#protocol Sendmessage<NSObject>
#required
-(void)Object:(NSArray *)tosend;
#end
#interface Secondvc:UIViewcontroller{
id <Sendmessage> delegate;
}
#property(strong,nonatomic) id <Sendmessage> delegate;
#end
//Secondvc.m
#implementation Secondvc
#synthesize delegate;
-(void)viewDidLoad{
//Do Something here!
}
//Pass Some Value When a button event occured in Second vc
-(IBAction)Send_Data{
[self dismissViewControllerAnimated:Yes completion:nil];
[self.delegate Object:[NSArray Arraywithobjects:#"Hello",nil]];
}
#end
//FirstVc.h
#import "Secondvc.h"
#interface FirstVc<Sendmessage>
#end
//FirstVc.m
#implementation FirstVc
-(void)viewDidLoad{
Secondvc* Object=[[Secondvc alloc]init];
Object.delegate=self;
}
#pragma mark Secondvc Deklegate method implementation
-(void)Object:(NSArray *)tosend{
NSLog(#"Recieved data Form Second VC Is:\n%#",tosend);
}
#end
HTH!Enjoy Coding.
Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;
I've spent a few hours on this trying to work it out myself but I give up!
I have a master-detail arrangement where the user input screen needs to call a function on another class to post to a web service. Upon completion of the asynchronous call, the class will then call a specified function. In this case, I'm just testing and all I want to do is go back to the main screen after the user input is accepted by the web service.
When the uses taps a button on the input screen (SetLocationViewController), the asynchronous operation is called in the class APIPostClass. After it is complete, I want SetLocationViewController to segue back to MasterViewController.
In APIPostClass.m in (called after the asynchronous op finishes)
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
In SetLocationViewController.m
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
Calling doSegue from an action on SetLocationViewController.m does work so I know my segue is ok but the above doesn't work. I get the error reason: 'Receiver () has no segue with identifier 'SetLocationViewControllerManualUnwind''
I'm guessing the reason is because of the alloc init way of initialising of the VC, but I don't know any better. Thus, how can I call a function on another class as if it was being called by it's own class?
Create a delegate it would be much more reliable and fast than Notifications.
#protocol APIPostDelegate <NSObject>
#required
-(void)OnRequestSucess;
#end
In your APIPost add new property for delegate
#interface APIPost : NSObject
#property (weak) id<APIPostDelegate> delegate;
In SetLocationViewController implement APIPostDelegate
SetLocationViewController.h
SetLocationViewController :NSObject<APIPostDelegate>
SetLocationViewController.m
-(void)OnRequestSucess
{
[self doSegue];
}
before you make call to method on APIPost, assign self to delegate property.
APIPost *apipost=[[APIPost alloc]init];
apipost.delegate=self;
[apipost <your api method>];
APIPost.m
[self.delegate OnRequestSucess];
Hope this helps.
There are a few methods to make it happens:-
Use Delegate
Use NSNotification.
The way described by Artur above (For SplitViewController Only - iPad)
You should use delegate whenever it is possible but it might not be too straight forward. NSNotification is more straight forward but it is not a good practice and not a good programming style.
I will only share the NSNotification method as it is easier to implement.
In SetLocationViewController.m
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSegue) name:#"calldoSegue" object:nil];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"calldoSegue" object:nil];
}
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
In APIPostClass.m
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
[[NSNotificationCenter defaultCenter]postNotificationName:#"calldoSegue" object:nil];
}
The above code should work but again, this is not a good practice. You should try to learn the Delegate method.
The answer is here: Performing segue from another class
In my APIPostClass.h, I setup the view controller:
#interface APIPostClass : NSObject {
SetLocationViewController *setLocationViewController;
}
#property(nonatomic, strong) SetLocationViewController *setLocationViewController;
#end
In my APIPostClass.m, I synthesize it:
#synthesize setLocationViewController;
then, instead of this (as in my question):
-(void)callWhenDone {
NSLog(#"callWhenDone loaded.");
SetLocationViewController *SLVClassInstance = [[SetLocationViewController alloc] init];
[SLVClassInstance doSegue];
}
I have:
-(void)callWhenDone {
NSLog(#"callWhenDone loaded");
[self.setLocationViewController doSegue];
}
Over in SetLocationViewController.m, the segue method remains unchanged:
-(void) doSegue {
NSLog(#"doSegue loaded");
[self performSegueWithIdentifier:#"SetLocationViewControllerManualUnwind" sender:self];
}
But when I call my API, I need to "attach" (forgive my terminology) the view controller to it. This is what I had:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance = [[APIPostClass alloc] init];
[APIPostClassInstance APICall: ... ....
}
But this is what works after bringing all of the above:
- (IBAction)btnTestAPICall:(id)sender {
NSLog(#"User tapped API button");
APIPostClass *APIPostClassInstance= [[APIPostClass alloc] init];
UIViewController *currentVC=self;
APIPostClassInstance.setLocationViewController = currentVC;
[APIPostClassInstance APICall: ... ...
I hope this will help someone else!
I've searched a lot and still couldn't find an answer to this...
I'm working on an iphone App (for college) in xcode 5.0 using storyboards.
I have a View Controller with a table view and everything works fine (data sources, delegate...). Items are stored in an array (call it "array1"). I have an ADD button which brings up a list of items which I want to add (if checked) to array1. I store the checked items in another array ("array2"). The thing is, when I pass array2 to the previous view controller I lose all data in array1 (it becomes nil).
The code I'm using to pass data from array2 to the VC is:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"updateFavoritesSegue"]) {
FavoritesViewController *targetVC = (FavoritesViewController*) segue.destinationViewController;
[targetVC updateFavorites:[self newFavoritesArray]];
}
}
The updateFavorites method is implemented as below.
-(void) updateFavorites:(NSArray *)newFavorites {
[self.favorites addObjectsFromArray:newFavorites];
[self.favoritesTableView reloadData];
}
Thank you very much for your help.
Why don't you just use some handler?
secondVC.h
#property (nonatomic, copy) void (^didFinishPickingItemsHandler)(NSArray *items);
then from the firstVC:
- (void)showSecondScreen {
MYSecondVC *secondVC = /* initialisation code here */
__weak MyFirstVC *self_ = self;
secondVC.didFinishPickingItemsHandler = ^(NSArray *items) {
/* update you data here with your array1 and received picked items */
[self.navigationController popViewControllerAnimated:YES];
};
[self.navigationController pushViewController:secondVC animated:YES];
}
I have a TableViewController with a button that triggers an [NSURLConnection sendAsynchronousRequest...] event, and also loads a modal segue through performSegueWithIdentifier:sender: which targets a small view controller. The purpose of this overlay view controller is to show a loading graphic and to prevent user interaction while the data is sent through the NSURLConnection.
In the completion block of the NSURLConnection, I call a method which removes the data in the TableViewController (its just a batch listing), and then calls dismissViewControllerAnimated:completion: on the overlay view controller.
Everything works except for dismissing the overlay view controller, which throws a warning in the debugger which says:
"Warning: Attempt to dismiss from view controller while a presentation or dismiss is in progress!"
I have found various questions and answers about this error, particularly about using the performSelector:object:withDelay methods, but so far nothing has worked.
This is particularly annoying because I use a similar process in another area of the app, except that the dismissViewController is called from selecting a UITableViewCell, and this works fine...
The relevant bits of my code are shown below:
#import "ViewBatch.h"
#interface ViewBatch ()
#property (strong, nonatomic) LoadingOverlayViewController *loadingOverlay;
#end
#implementation ViewBatch
#synthesize loadingOverlay;
....
- (IBAction)exportBatch:(id)sender
{
if ([productArray count] > 0) {
[self performSegueWithIdentifier:#"loadingSegue" sender:self];
[self processData];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"loadingSegue"]) {
loadingOverlay = segue.destinationViewController;
}
}
- (void)processData
{
// Code to create a file and NSURLRequest...
// ....
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:queue
completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if ([responseData length] > 0 && error == nil)
{
// Not used for this request yet...
}
else if ([responseData length] == 0 && error == nil)
{
// Success...
[self didSendData];
}
else if (error != nil)
{
// Connection error...
NSLog(#"Error: %#", error);
}
}];
}
- (void)didSendData
{
// Reset the batch...
[productArray removeAllObjects];
[self.tableView reloadData];
[loadingOverlay dismissViewControllerAnimated:YES completion:NULL];
}
And the loading view controller is just:
#import <UIKit/UIKit.h>
#interface LoadingOverlayViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *statusLabel;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
#end
....
....
#import "LoadingOverlayViewController.h"
#interface LoadingOverlayViewController ()
#end
#implementation LoadingOverlayViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.activityIndicator startAnimating];
}
#end
I'm not sure about the exact cause of the problem, but here are some observations about your code.
You do not need an overlay in order to prevent user interaction. Simply turn off user interaction with -NSApplication beginIgnoringInteractionEvents.
You do not need an entire view controller in order to show an overlay view. Just show the view. For example I often place a large UIActivityIndicatorView in the middle of my view while an NSURLConnection is happening.
You do not need an NSOperationQueue in order to use NSURLConnection asynchronously. It is already asynchronous. Just create the NSURLConnection and wait for the delegate messages to arrive. As it is, you are bolluxing yourself because you are setting up a secondary queue, the message arrives on that queue, and you call didSendData which calls reloadData on the table - in the background, which is illegal. If you did this the normal way, your delegate messages would arrive on the main thread which is exactly what you want.
Never mind, I got it worked out.
Amazing what half an hours break can do to organise one's thoughts...
The process was finishing and calling dismissViewController before the actual view was even finished loading. A simple delegate call from the viewDidLoad on the overlay sorted things out.
I had this problem as well. I noticed it was a result from copying and pasting a button in my nib file. The result of this was that I had to IBActions tied two 1 UIButton.