UILabel won't update even on dispatch_async - ios

Any reason why my label won't update?
- (void)setLabel:(NSNotification*)notification {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"setLabel");
[self.label setText:#"My Label"];
});
}
I've also tried using performSelectorOnMainThread to no avail.
Note that setLabel is appears on the log.
Additional info:
I also have two other functions which does the same thing but only with a different text. The two other functions doesn't have dispatch_async but they both work. Also, the notification of the two working function was sent by NSURLConnection (method #2 in this post). While the notification of the non working function above, was sent by a call to FBRequestConnection (see this post).
For clarity, my two other working functions is as follows:
- (void)setLabel2:(NSNotification*)notification {
NSLog(#"setLabel2");
[self.label setText:#"My Label 2"];
}
- (void)setLabel3:(NSNotification*)notification {
NSLog(#"setLabel3");
[self.label setText:#"My Label 3"];
}
Yes I did try to remove dispatch_async in my code. In fact, originally there was no dispatch_async because the other two were working.

If you set a break point right after where you set the text and print description of self.label does it show the text has changed? If so it must be getting reset somewhere else after this method fires. If self.label is nil than there's your problem

Related

Objective -c, removeFromSuperview not works every time

I am trying to remove a view, sometimes it works fine , and sometimes not. I am beginner. I don't know what the problem. I am frustrated. Please let me know what the problem.my code :
-(void)hideNotification
{
btnNotification.selected=NO;
btnHome.selected=YES;
[notificationScreen.view removeFromSuperview];
notificationScreen=nil;
isNotificationScreen=NO;
}
I have also tried : dispatch_async(dispatch_get_main_queue(), ^{
[notificationScreen.view removeFromSuperview];
}); and performSelectorOnMainThread:#selector(removeFromSuperview) withObject:nil waitUntilDone:NO]; but not succeeded.
First and foremost thing I would check in this situation is to ensure updating my UI only on main thread. Trusting, you have already tried that out.
Next, per Apple Documentation, I would ensure following things:
If the view’s superview is not nil, the superview releases the view.
Never call this method from inside your view’s drawRect: method.
Finally, unsure how your notificationScreen object looks like, I would try to set a tag on the view to be removed and remove it based on the tag value. Not sure if notificationScreen refers to your current view controller in which your above code will now work. Try this out:
Set the tag of the view that you want to be removed
(myNotificationView.tag = 1) when initializing and adding to its parent view.
When you are ready to remove the notification view, do it like this
for (UIView *view in [self.view subviews] ) {
if (view.tag == 1 ) {
[view removeFromSuperview];
}
}
For step 2, you could have a strong reference to your notification view and call removeFromSuperview on that object.
First you need to check your view is subview or not. Just change code like below:
-(void)hideNotification
{
btnNotification.selected=NO;
btnHome.selected=YES;
if([notificationScreen.view isDescendantOfView:self.view]){
[notificationScreen.view removeFromSuperview];
}
notificationScreen=nil;
isNotificationScreen=NO;
}

Label taking a really long time to change

I'm learning to develop an IOS app. I'm having the following problem. I want to use a label to display a string. It takes a really long time for this string to be displayed (10-15 sec). Is this normal? The following code is inside the viewDidLoad function
NSLog(self.example); //displays almost immediately
_labelOutput.text= [NSString stringWithFormat:#"%#", self.example;//takes 15 seconds
The entire viewDidLoad function:
- (void)viewDidLoad
{
[super viewDidLoad];
double lat = 43.7000;
double lon = -79.4000;
NSArray *users = [[NSArray alloc] initWithObjects:#"user_1",#"user_2",#"user_3", nil];
id prediction = [[Prediction alloc] initWithUsers:users Lat:lat Lon:lon];
[prediction populate:^{
self.resName= [prediction generateRandom][#"id"];
NSLog([NSString stringWithFormat:#"%#", self.resName]);
_labelOutput.text= [NSString stringWithFormat:#"%#", self.resName];
}];
}
What does -[Prediction populate:] do with the block? My guess is it runs the block on a background thread or queue. You aren't allowed to modify the UI from a background thread or queue. Your app might crash or just act unpredictably. Your mysterious delay in updating the screen is a common symptom of this mistake.
You must only modify the UI from the main thread or queue. Try this:
[prediction populate:^{
self.resName= [prediction generateRandom][#"id"];
NSLog([NSString stringWithFormat:#"%#", self.resName]);
dispatch_async(dispatch_get_main_queue(), ^{
_labelOutput.text= [NSString stringWithFormat:#"%#", self.resName];
});
}];
ETA: Rob's answer is probably spot-on...if you'd posted that code initially I would have caught it as well.
Try setting the label's text in viewWillAppear or perhaps viewDidAppear instead.
Setting the "text" property of a label will normally trigger a [setNeedsDisplay] call automatically via key-value observing, and this notifies the system that the label's view needs to be redrawn on the next run loop. However, viewDidLoad is called before your view is actually visible. It's likely that because of this, either [setNeedsDisplay] is not being called, or is being ignored because the label is not yet visible...and thus, you have to wait for some other event to trigger re-drawing of subviews.
You could test this theory by adding a [self.labelOutput setNeedsDisplay] call yourself in viewDidAppear.
For swift 3 you'll want to use
DispatchQueue.main.async(execute: {
_labelOutput.LabelName.text = "Something"
})

Printing Log in iOS

I need to show log outputs in a textview then and there in my app. I tried using performSelector inside my sequence but it did not work as I thought it would. Can someone show me how to do it?
For example, when I click a button, I do lot of operations underneath, and I want to display the logs in the textview then and there, not after the entire operation is done.
Plus can't I call performSelector more than once inside the same sequence?
Below is the sequence inside the button click:
- (IBAction)Write:(id)sender {
//do some action here
DisplayString = #"Seq1 pass"
[self performSelector:#selector(updateviewText) withObject:nil afterDelay:0];
//do some more action
DisplayString = #"Seq2 pass"
[self performSelector:#selector(updateviewText) withObject:nil afterDelay:0];
....
}
This the updateviewText part:
-(void)updateviewText {
dispatch_queue_t queueNew = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queueNew,^ {
dispatch_async(dispatch_get_main_queue(),^{
[self.txtViewUseCaseLOG setText:[NSString stringWithFormat:#"%#\n%#",
self.txtViewUseCaseLOG.text,DisplayString ]];
});
});
}
The DisplayString is a global variable here.
This code doesn't setText to the textview then and there... But as I asked earlier I need those messages then and there...
You can't update the UI asynchronously. If you're doing task asynchronously and want to update the UI you have to use dispatch_sync.
And why would you when you're doing something asynchronously open another asynchronous task?
Next thing is that if you're only using DisplayString in these two methods you might better add a NSString parameter to updateViewText so you don't need the global variable.

UITextView won't scroll while background thread busy

I'm running mathematical computation in a background thread. Attempting to post results in real time in a UITextView. However the results don't show up until the background thread completes. Why not?
I kick off a method in the background,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[self v2];
});
The background thread method is of the form,
- (void) v2 {
NSString *result;
// ... loop a bunch of times generating lots of results
for (bunch of stuff to compute) {
// If using dispatch_async, nothing is displayed until this method finishes
// If dispatch_sync then it does display and update
result = [self computeNextValue];
dispatch_async(dispatch_get_main_queue(), ^() {
textView.text = result;
});
} // end computation
}
This actually hadn't been much of a problem until I started trying to scroll the view. Painfully slow. So I created a NSTimer to periodically scroll the UITextView. However, even though the timer popped and the method is run to request the scroll, the UITextView doesn't scroll until the background method completes.
First, make sure that you are using weakSelf rather than self within the block.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[weakSelf v2];
});
...
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^() {
weakSelf.textView.text = result;
});
But that won't cause the delayed update. I am very suspect of the queue's priority, DISPATCH_QUEUE_PRIORITY_LOW. Try using DISPATCH_QUEUE_PRIORITY_DEFAULT instead.
I think I figured it out, at least empirically.
It looks like setting text on a UITextView or programmatically initiating scrolling sets up an animation sequence thats run in another thread. What seems to be happening is that the requests to set the text of the UITextView is coming in faster than the ability of UITextView to setup the animation. When the property changes again, it apparently cancels the earlier animation that it was setting up on another thread. As I continue to flood it with change requests, its never able to finish what it wants to do before the value of 'text' has changed again.
My solution involves multiple steps.
In my original approach I was setting textView.text very rapidly to some new value. I set up a timer to periodically request the UITextView to scroll.
In my modification, I calculate the new value of the result string, but do not set it to the UITextView. Instead the text is set on the textview periodically based on the timer. This allows the text view to catch up.
However, I noticed that this still wasn't reliable. If I happened to set the text again while it was still scrolling, weird effects would occur, such as a very slow scroll. It seems that the scroll animation and repeated settings of text were still causing a problem.
So solve this problem, I created a property to indicate if the view is scrolling. Set the view controller as the UITextField delegate. When I request the view to scroll, I set the flag to indicate its scrolling. Only update the content and request scroll if its not already scrolling. Ends up working great. Doesn't matter how fast I set the timer, it ends up waiting appropriately.
// ViewController.h
#property BOOL isViewScrolling;
// ViewController.m
// initialize property in viewDidLoad
- (void)viewDidLoad {
self.isViewScrolling = FALSE;
textView.delegate = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(scrollIt) userInfo:nil repeats:TRUE];
}
- (void)scrollIt {
NSLog(#"scrollit thread=%d", [[NSThread currentThread]isMainThread]);
if (!self.isViewScrolling) {
textView.text = self.result;
NSRange range = NSMakeRange(textView.text.length - 1, 1);
self.isViewScrolling = TRUE;
[textView scrollRangeToVisible:range];
}
}
// UITextView delegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
NSLog(#"Animation stopped");
self.isViewScrolling = FALSE;
}
Try changing the predefined dispatch queue string to end with background.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^() {
weakSelf.textView.text = result;
});
Also you should add a UIActivityIndicator and start animating at the beginning of the operation, then stop animating after the textview.text field has been updated.
It's a nice feature to have to show the user there is a process currently being done
Also I would stay away from NSThread as I have read a few forums and docs from apple that emphasize the use of GCD and block operations.

SetNeedsDisplay not working

I saw many threads related to this issue, but none addresses my case (I think).
My case should be simple, I have a Custom UIView in my controller, from my controller, I use the [self.myView setNeedsDisplay] and it works perfectly.
I'm having problems when Im trying to call this from inside of the UIView itself...I have a notification being sent from another class, and it is being received by my View (this works) with the information it passes, I update internal properties of this view and than I'm calling the [self setNeedsDisplay] wishing to have my screen updated with the new states, but nothing is happening, I used a NSLOG inside my drawRec method, and it is not being called at this time, it is only called when my controller class call the setNeedsDisplay, and when that happens, the update that should have happened before is showed on screen... I don't know why its not updating before...
Here are some code:
My controller asking for update: (works OK!)
- (void)addNodeToNetwork:(DTINode *)node
{
[self.myNetwork addNodeInTheNetwork:node];
self.gridView.nodesToDraw = [self.myNetwork.nodesInNetwork copy];
CGRect tempRec = CGRectMake(node.nodePosition.x, node.nodePosition.y node.nodePosition.x, node.nodePosition.y);
NSValue *rectObj = [NSValue valueWithCGRect:tempRec]; //transforma o cgrect num objeto
[self.gridView.fatherNodes setValue:rectObj forKey:node.nodeName];
[self.gridView setNeedsDisplay];
}
My notification method trying to update my drawing: (Not Working ! )
- (void) receiveTestNotification:(NSNotification *) notification
{
NSDictionary *userInfo = notification.userInfo;
DTINode *notificationNode = [userInfo objectForKey:#"myNode"];
NSLog(#"Im Here!");
for (DTINode *node in self.nodesToDraw)
{
NSLog(#"Here too");
if(node.nodeName == notificationNode.fatherNode)
{
CGRect temp = CGRectMake(notificationNode.nodePosition.x, notificationNode.nodePosition.y, node.nodePosition.x, node.nodePosition.y);
NSValue *tempObj = [NSValue valueWithCGRect:temp];
[self.fatherNodes setObject:tempObj forKey:notificationNode.nodeName];
[self setNeedsDisplay];
NSLog(#"Should REDRAW NOW!"); // It print this but no drawing is made!
}
}
}
I'm not pasting my drawRect here because it works, the problem is that is is not being called from inside my UIView setNeedsDisplay!
Anyone have any idea why this is not working????
After alot of testing, I saw something related to threads and the fact setNeedsDisplay should only be called in the mainThread...besides I never started a separeted thread in this classes, the class that raised the Notification was in a secondary thread...and aparently this was causing the issue...
to Solve it I just forced setNeedsDisplay to be called in the main thread..
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplay];
});
Swift 3:
DispatchQueue.main.async { [weak self] in
self?.setNeedsDisplay()
}
I think the correct way to use it is:
[self setNeedsDisplay:YES];
Although I always have problems to get that working :(

Resources