I have a simple app that downloads search results in XML when the user types in a UISearchBar. The download+parsing is threaded and once done it fires an NSNotification to tell the ViewController with the table view to [tableView reloadData];
Here is the code that receives the notification fired once results are in:
- (void)receivedResults:(id)notification {
results = [notification object];
DLog(#"Received %i results",[results count]);
[[self tableView] reloadData];
}
I get the log output "Received 4 results", but the table view doesn't reload the data until I scroll/drag it a couple pixels. I am using the built-in UITableViewCellStyleSubtitle cell style and im not changing the height or ding anything fancy with the table view.
What am I doing wrong?
I was able to get the same thing to work. But the issue was that the reload data needed to be called on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
I think this is more practical than the performSelectorOnMainThread option
Call
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
instead of
[self.tableview reloadData]
My problem was that I was posting a NSNotification from a background thread. To avoid this, simply wrap your postNotificationMethod in a dispatch_async method like this:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"FinishedDownloading" object:result];
});
When this notification will be received, the tableView.reloadData will be called on the main thread.
I have the same problem, and I have tried all the solution I can find on google. But All of them don't work.
At last I found that I add observer before viewDidLoad, and then [self.tableView reloadData] is not working.
First I call the setObserverForUserInfoDatabase in the viewDidLoad of my root navigation view controller. Because I think it was called earlier. The function is like this:
- (void)setObserverForUserInfoDatabase:(NSString *)name {
[[NSNotificationCenter defaultCenter] addObserverForName:name
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
[self loadUserInfo];
[self.tableView reloadData];
NSLog(#"User Panel Notification \n %#", self);
}];}
Then I move the code into viewDidLoad of the viewController itself.
- (void)viewDidLoad {
NSLog(#"View Did Load");
[super viewDidLoad];
[self setObserverForUserInfoDatabase:UserInfoDataBaseAvailabilityNotification];
}
And then everything works fine.
I don't know the reason yet. If anyone knows please tell me.
reload your tableView in viewWillLayoutSubviews
Related
I am using NSNotification and when notification arrive in other class then i want to change somethings in GUI.
This is how i post my notification, and i m not sure if it is good way to post it or not?
[[NSNotificationCenter defaultCenter]
postNotificationName:#"postDetailsNotification"
object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys: result, #"arrayDetails", nil]];
so in other class i catch it like that.
-(void)registerNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivePostDetailsNotification:)
name:#"postDetailsNotification"
object:nil ];
}
- (void) receivePostDetailsNotification:(NSNotification *) notification
{
NSDictionary * info = [notification userInfo];
inputDetails = [info objectForKey:#"arrayDetails"];
NSLog(#"notification arriveddd=%#",[[inputDetails PostDetail] Text]);
[self customTxtMessageViewHeight];
}
In customTXtMessageViewHelight method i only check content size of txtMessage(it is a textview) and i resize it.
-(void)customTxtMessageViewHeight
{
CGFloat fl;
//MeasureHeightOfUITextView is a method to count textview height and it works without problem
fl=[nesneResizeTextViewHeight measureHeightOfUITextView:txtMessage ];
txtMessage.frame=CGRectMake(txtMessage.frame.origin.x, txtMessage.frame.origin.y, txtMessage.frame.size.width, fl);
imgMessageBackground.frame=CGRectMake(imgMessageBackground.frame.origin.x, imgMessageBackground.frame.origin.y, imgMessageBackground.frame.size.width, fl);
NSLog(#"size1=%fl",fl);
NSLog(#"textviewsize=%fl",txtMessage.frame.size.height);
}
Logs are correct but txtMessage's height does not change. There is no problem about iboutlets because txtMessage's height is changing if i try it in viewDidLoad method.
so then after some reading articles i get it NSNotification works in background thread and i tried to call customTxtMessageViewHeight method like that;
[self performSelectorOnMainThread:#selector(customTxtMessageViewHeight) withObject:self waitUntilDone:NO];
But nothing changed.
After i tried to change how i post NSNotification
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter]
postNotificationName:#"postDetailsNotification"
object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys: result, #"masivDetails", nil]];
});
I thought it will make it work on mainThread but it didnt work also.
I really confused and will be glad to any help.
Thanks.
Notifications are sent / received on the same thread they are posted on.
If the logs are all fine then it looks like your frame is being re-set by something else. The usual candidate for this is Autolayout - if you're using Autolayout then you don't resize things by setting frames, you resize them by updating constraints. Otherwise, setting the frame triggers a layout pass which resets the frame back to where it was.
I have a uitable in one of the Tabview of a tabbarcontroller application. Now depending on some action in other tabview, the uitable should reload (refresh) for updated data at backgrouns. However, I am not able to get it using either reloadData or beginupdates-endUpdates.
Can someone please help in this kind of scenario.
Thanks in advance.
I would recommend use a combination of NSNotification as well as viewWillAppear/viewDidAppear for this.
When the viewWillAppear - reload the tableview (You may refine it by looking for change in data since you last time displayed the data)
- (void)viewWillAppear:(BOOL)animated
{
// Your other code here...
[self.tableview reloadData];
}
Once the view has appeared and in background the data is changed by some other object - ask that object to send notification and in your tableview's viewcontroller register for that notificaiton in viewWillAppear & deregister in viewWillDisappear
The other object should send/post notification like this (just after the data change)-
[[NSNotificationCenter defaultCenter] postNotificationName:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
All the below code in your viewController (which has table to be updated) -
Register like this -
- (void)viewWillAppear:(BOOL)animated
{
// Your other code here...
[self.tableview reloadData];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNewDataReceivedNotification:) name:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
}
Notification handler in your view controller -
- (void)handleNewDataReceivedNotification:(NSNotification *)notification
{
// Your other code here...
[self.tableview reloadData];
}
And de-register like this -
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"com.yourcompany.appname.XYZdataChangeNotification" object:nil];
}
All the above code can be refined but it should give you an idea. Please feel free to ask if any questions/concerns.
i want update the text of my label every time i receive notification from nsmanageObjContext.
this is my code for add the observer:
- (IBAction)requestFotPhoto {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification
object:self.facebook.managedObjectContext];
and this is the method for update the label:
-(void)updateLabel
{
NSString *text = [NSString stringWithFormat:#"Downalad %i pictures",[Photo NumeberOfAllPhotosFromContext:self.facebook.managedObjectContext]];
dispatch_async(dispatch_get_main_queue(), ^{
//UIKIT method
NSLog(#"text %#",text);
[self.downlaodLabel setText:text];
});
}
i assume that updateLabel is execute in a another thread, so i execute the instructions for update the label on the main thread, but this code has no effect. where is the problem?
obviously the NSlog print the right message!
thanks!
In your situation you don't need to use dispatch_async, because notification handlers are run in the main thread. They are executed in a main loop on idle moments — sorry if I'm wrong with techincal words, english is not native for me.
And one more thing: you should't reference self from blocks, because self points to your block, and block points to self — they're not going to be released. If you really want to do it, you can read this question.
seems like:
your should move your NSNotificationCenter addObserver code, from your (IBAction)requestFotPhoto (seems is some button click event handler, which only run after user tapped) to viewDidLoad
shold like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:NSManagedObjectContextDidSaveNotification object:self.facebook.managedObjectContext];
}
and for noficacation handler, not use dispatch_async
should like this:
- (void)updateLabel:(NSNotification *) notification {
NSLog (#"updateLabel: notification=%#", notification);
if ([[notification name] isEqualToString: NSManagedObjectContextDidSaveNotification]) {
NSDictionary *passedInUserInfo = notification.userInfo;
NSString *yourText = [passedInUserInfo objectForKey:#"dataKey"];
//UIKIT method
NSLog(#"yourText=%#",yourText);
[self.downlaodLabel setText:yourText];
}
}
and somewhere else should send the text:
NSString *newText = #"someNewText";
NSDictionary *passedInfo = #{#"dataKey": newText};
[[NSNotificationCenter defaultCenter] postNotificationName:NSManagedObjectContextDidSaveNotification object: self userInfo:passedInfo];
for more detail pls refer another post answer
I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];
I hope I have better luck with someone helping me on this one:
I have a UIPickerView where a user makes a selection and then presses a button. I can gladly obtain the users choice, as shown in my NSLog, and when this is done, I want to send a notification to another view controller that will show a label with the option selected. Well, although it seems everything is done right, somehow it does not work and the label stays intact. Here is the code:
Broadcaster:
if ([song isEqualToString:#"Something"] && [style isEqualToString:#"Other thing"])
{
NSLog (#"%#, %#", one, two);
[[NSNotificationCenter defaultCenter] postNotificationName:#"Test1" object:nil];
ReceiverViewController *receiver = [self.storyboard instantiateViewControllerWithIdentifier:#"Receiver"];
[self presentModalViewController:receiver animated:YES];
}
Observer:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification) name:#"Test1" object:nil];
}
return self;
}
-(void)receiveNotification:(NSNotification*)notification
{
if ([[notification name] isEqualToString:#"Test1"])
{
[label setText:#"Success!"];
NSLog (#"Successfully received the test notification!");
}
else
{
label.text = #"Whatever...";
}
}
I think you have a syntax error in your selector: #selector(receiveNotification). It should probably be #selector(receiveNotification:) with the colon since your method accepts the NSNotification *notification message. Without it, it's a different signature.
The issue is likely that the notification is sent (and therefore received) on a different thread than the main thread. Only on the main thread will you be able to update UI elements (like a label).
See my answer to this question for some insight into threads and NSNotifications.
Use something like:
NSLog(#"Code executing in Thread %#",[NSThread currentThread] );
to compare your main thread versus where your recieveNotifcation: method is being executed.
If it is the case that you are sending the notification out on a thread that is not the main thread, a solution may be to broadcast your nsnotifications out on the main thread like so:
//Call this to post a notification and are on a background thread
- (void) postmyNotification{
[self performSelectorOnMainThread:#selector(helperMethod:) withObject:Nil waitUntilDone:NO];
}
//Do not call this directly if you are running on a background thread.
- (void) helperMethod{
[[NSNotificationCenter defaultCenter] postNotificationName:#"SOMENAME" object:self];
}
If you only care about the label being updated on the main thread, you can perform that operation on the main thread using something similar to:
dispatch_sync(dispatch_get_main_queue(), ^(void){
[label setText:#"Success!"];
});
Hope that was helpful!