I am unable to change the UILabel text. The code for the the UILabel inside viewDidLoad is :
startLabel=[[UILabel alloc] initWithFrame:CGRectMake(75, 395, 200, 30)];
startLabel.text=#"Recording Sound ...";
startLabel.backgroundColor=[UIColor clearColor];
startLabel.textColor=[UIColor whiteColor];
startLabel.font=[UIFont boldSystemFontOfSize:17];
[self.view addSubview:startLabel];
Later, if I want to change the label of the text with the following code, its not changing on the app :
startLabel.text=#"Searching Database ...";
or
[startLabel setText:#"Searching Database ..."];
The UILabel is not empty, I printed it out during debugging and it shows :
(gdb) po startLabel
<UILabel: 0x2c1a30; frame = (75 395; 200 30); text = 'Searching Database ...';
clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer: 0x2ae8f0>>
So, the text of the label changes inside the UILabel, but its not updated on the screen.
Can anyone kindly let me know what
I am missing here ? Thanks.
Edit 1: I tried performSelectorOnMainThread: - didnt work for me.
Edit 2: I am using AVFoundation and ASIHTTP classes to record sound and upload the recorded file here. Nothing else. Didnt use any thread.
You may be facing an issue with threading as mentioned in the comments above. If you have a method that runs on the main thread and does some activity (such as search a database), updates that you make to the UI will not be committed until the run loop gets control. So, if you have a long, time consuming task going on on the main thread, run this code after setting the text of the label:
- (void)doSomethingTimeConsuming
... consume some time ...
... set text of label ...
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
... continue long operation ...
}
This should flush out any UI changes that you have made. Although this may be a sensible and functional hack, it doesn't beat the alternative. I highly suggest that you perform your app's time consuming tasks on a background thread, and update the UI through the main thread using performSelectorOnMainThread:withObject:waitUntilDone:. See Apple's page on iOS Thread Management.
In my case the function that was updating was called from a touch recognizer on a thread, but the place in the function where I'm changing the value of the label's text property I put it back on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
[self.someLabel setText:someString];
});
I had a UILabel showing a level number that would not update to the new level number on a UIViewController. The only viable solution I could find that worked was to call setNeedsDisplay on the main thread of the view controller that owned the UILabel
-(void)changeLevelLabel:(int)theLevel {
dispatch_async(dispatch_get_main_queue(), ^{
self.levelLabel.text = [NSString stringWithFormat:#"%d",theLevel];
[self.levelLabel setNeedsDisplay];
});
}
See http://iosdevelopmentjournal.com/blog/2013/01/16/forcing-things-to-run-on-the-main-thread/ for a more detailed explanation
Mine's a bit more unexpected, though reasonable--just not something anyone thinks too hard about.
I'm supposed to update the UILabel when I receive an NSNotification that I fired somewhere else. However, the notification was fired from a background thread so even the listener methods that update the label's text are fired in the background.
If you're relying on an NSNotification to update the label, or any of your views, do fire the NSNotification from the main UI thread.
In case you are using localize-Swift cocoapod, If you set a localized key to the UILabel, The value will be set after you set any text to the label.
Related
While reading an audio file (which can be big), I want to display a progress view on a main view controller subview (it is an UIView).
I wrote a method for that in my UIViewController:
- (void) displayFileReadingView
{
self.fileReadingView = [[UIView alloc] initWithFrame:CGRectMake(20., 100., 300., 120.)];
self.readingProgress = [[UIProgressView alloc] initWithFrame:CGRectMake(20., 90., 200., 8.)];
self.readingMessage = [[UILabel alloc] initWithFrame:CGRectMake(45., 10., 200., 20.)];
... elements parameter settings ...
[fileReadingView addSubview:self.readingMessage];
[fileReadingView addSubview:self.readingProgress];
[self.view addSubview: self.fileReadingView];
}
In the viewDidAppear methods, after all last initializations, I read an audio file (using AVURLAsset) and analyse the data to plot it.
When displayFileReadingView is called from viewDidLoad, or viewWillAppear, the view is displayed correctly. When it is called from viewDidAppear, as a first line, it is not displayed until several seconds after the end of the viewDidAppear execution. This is my first question:
• Why an UI method to allocate views, subviews and add them to the appropriate view, allow display or not depending of the calling method, since the displayFileReadingView method is not dependent of any initialization parameter from the main view managed by the owning UIViewController?
Here is the second problem. My UIProgressView is supposed to be updated every second, using a NSTimer scheduled to call updateReadingProgress
- (void) updateReadingProgress {
[self.readingProgress setProgress:myNewValue animated:YES];
[self.fileReadingView setNeedsDisplay];
}
But unfortunately, the NSTimer is not fired in time. If the allocation (using either ScheduledTimerWithTimerInterval... or TimerWithInterval... + adding in main loop (all modes tested) is made normally, it is never fired. If I put the same allocation in a dispatch_main_async() (or dispatch_after...) the timer is fired many seconds after for the first time, sometimes up to 25 seconds.
The result, depending on my code choices explained here: sometimes the view is not displayed, sometimes not. When it is, its UIProgressView subview is never updated, or updated dozen of seconds after all other calculations are done (after the owner view should be removed from display).
I also tried to replace NSTimer with CADisplayLink to fire updateReadingProgress method, using an "ordinary" call or "dispatched in main queue" included call, but in this CADisplayLink case it is never fired. Spent two days and nights in trying combinations, reading developer websites... no solution!
This is my second question:
• How can I be sure that a method updating the UI get called periodically without giant delay (some .2 or .5 second delay is not a problem?
[Using Xcode9 for iOS10 target, tested on Simulator and devices (iPhone/iPad)]
I have a seemingly simple problem that I cannot for the life of me seem to figure out. In my iOS App, I have a UICollectionView that triggers network operation upon tapping it that can take a few seconds to complete. While the information is being downloaded, I want to display a UIView that fills the cell with a UIActivityIndicatorView that sits in the square until the loading is done, and the segue triggered. The problem is that it never appears. Right now my code looks like:
myLoadView.hidden = NO;
//Network Operation
myLoadView.hidden = YES;
The App simply stops for a couple seconds, and then moves on the the next view. I'd imagine Grand Central Dispatch has somthing to do with the solution, however please keep in mind that this code takes place in prepareForSegue, and the network info needs to be passed to the next View. For this reason not finishing the download before switching scenes has an obvious problem. Any help would be VASTLY appreciated. Thanks!
iOS commits changes in the interfaces after working out a routine. Hence you should perform your network operation in a background thread and then get back back on the main and perform the "show my view now thing". Have a look the below code for reference.
myLoadView.hidden = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network Operation
dispatch_async(dispatch_get_main_queue(), ^{
myLoadView.hidden = YES;
});
});
Your network operation seems to be carried out on the main thread, aka UI thread. This blocks all further UI calls, including the call to unhide a view, until completion.
To resolve this, make your call asynchronous.
You should read this in full, if you haven't already.
As mentioned by other answers, the problem is that the UIView change doesn't happen until the current method finishes running, which is where you are blocking. Before GCD was available I would split methods in two and use performSelector:withObject:afterDelay (to run the second part also on the UI loop) or performSelectorInBackground:withObject: at the end of the first method. This would commit all the waiting animaations first, then do the actual tasks in the second method.
Well the better option for this type of indication is by using the custom HUD libraries like SVProgressHUD or MBProgressHUD
Just started to use "UIRefreshControl" today, and noticed some weird things.
If I created the UIRefreshControl in InterfaceBuilder, and wire it all up, my selector never gets called. IB says that the control should fire on the "value changed" message, and should call my code, but never does.
So I abandoned doing this in Interface Builder, and just decided to do this in the code. Not too many lines. So I put this code inside of my "viewDidLoad" routine:
self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget:self action:#selector(doStuff:) forControlEvents:UIControlEventValueChanged];
NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSTextAlignmentCenter;
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to refresh" attributes:#{NSParagraphStyleAttributeName : paragraphStyle}];
It draws fine, but the refresh-control is visible when the view first appears. I'd prefer that it not be visible until the user actually pulls down on the table. I'm making a call to a web-service, and wait for the results to come back, then I populate the table. So, you only see the control for a second or two. But it just looks weird.
So, the questions are:
1. Is this a known bug that you can't wire up the control solely from Interface Builder?
2. Is there a way to add this control to the table-view controller, without it appearing until the user actually wants to see it?
You don't need to add a refresh control, or declare one in your viewController. Adding pull-to-refresh is a two-step process.
Step 1: In your storyboard, go to your tableViewController and, where it says "Refreshing", select "Enabled".
Step 2: Add the following code to your tableViewController.m file, in viewDidLoad:
[self.refreshControl addTarget:self
action:#selector(refresh)
forControlEvents:UIControlEventValueChanged];
That's the entire process, other than doing stuff in your -refresh method. There's no more to it.
Yes, there is a bug with the UIRefreshControl that the action is never called. Keith Harrison talks about this in his blog entry UIRefreshControl Fun and Games:
Investigating with the debugger it seems that the refresh method is
never called. I can only assume that for some reason the storyboard is
not properly connecting the UIRefreshControl to our action or is
having some other strange interaction with the
UIControlEventValueChanged event. I have filed a bug report with Apple
(rdar://14178445) but there is also a simple workaround you can use.
The workaround is as described by #AMayes.
Most of the time I'm doing projects with storyboard but now I need to mantain an old project that used xib files. My problem is this, I have a ViewController with a scroller \ UIButton \ UITextField on it. If I write something like: [scroller setHidden:YES]; inside ViewDidLoad the scroller will be hidden, but, if I'll put it inside
-(IBAction)checkMember:(id)sender{
[scroller setHidden:YES];
[self checkMemberLog];
Only when those are complete the setHidden will fire up.
How can I force it to fire up instantly? What am I missing?
Try calling checkMemberLog like this:
[self performSelector:#selector(checkMemberLog) withObject:nil afterDelay:0];
Even with a delay of 0, I think this will allow your scroller to be hidden because the selector is queued on the current thread’s run loop, and has to be dequeued before it will run. Even though this happens almost instantly, I'm guessing that an update of the display caused by the setHidden: message is queued first.
If you're more comfortable with storyboard maybe it's worth your time to start a new project and import all your old header/class files. It could save you some time and frustration in the end, depending on how much there is to bring over.
I am trying to update a UIProgressView progress bar that I have in a UIView during a long resource loading process. Let's say I'm loading a bunch of bitmaps from a NIB file (like maybe a hundred). After I load 10, I issue a .progress to the UIProgressView that is part of a UIView that is already being displayed. So, I issue:
myView.myProgressView.progress=0.2;
Then, I load another 10 bitmaps, and issue:
myView.myProgressView.progress=0.4;
etc., etc. When the app runs, the progress bar doesn't advance. It simply stays at its initial position. At the risk of sounding like a complete moron, do I have to load my resources on a separate thread so the OS can update the UI, or, is there an easier way? Thanks for any assistance.
Yes. Load them on a separate thread. Or just use something like performSelector:
[self performSelector:#selector(setProgressBar) withObject:nil afterDelay:0.0];
(and create a setProgressBar function which reads the current value from a member variable and updates the UI)
You could run a step of the runloop after each update of the UI:
SInt32 result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE);
} while(result == kCFRunLoopRunHandledSource);
It could have other bad consequences (for example, enable user to interact with the UI, execute delegates of view controller such as viewDidAppear before they should be executed, etc) so be very, very careful.