I am using a pin screen for login to my app. The pin screen consists of four labels and a hidden text field. When the user enters text via the keypad, I update the labels with a symbol. This works fine, except that the last label does not get actually get updated before login begins, and remains empty while the login process is completed.
These are the relevant bits of code:
//an observer has been added elsewhere
- (void)textDidChange:(NSNotification *)notification
{
UITextField *field = [notification object];
if (field == inputField)
{
NSString *newText = field.text;
if ([newText length] <= pinLength) [self updatePINDisplay];
}
}
-(void)updatePINDisplay
{
if ([pinText length] > pinLength) return;
for (NSInteger ii = 0; ii < [pinText length]; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:#"x"];
}
for (NSInteger ii = [pinText length]; ii < pinLength; ii++)
{
UILabel *label = [pinFields objectAtIndex:ii];
[label setText:[NSString string]];
}
if ([pinText length] == pinLength) [self login];
}
The problem arises because [self login] launches other processes which happen before the last pin label is updated, so the login occurs while the last box is still empty.
I have worked around the problem by replacing
[self login]
with
[self performSelector:#selector(login) withObject:nil afterDelay:0.1]
but I don't like the arbitrary time delay. I was hoping that maybe there was a delegate method that I could use to launch my login code after the label has been drawn. Something like:
-(void)labelDidGetDrawn
Any other (non-hack) solution is also welcome:-)
Thanks!
Based on your description, it sounds like the problem is that the 4th item doesn't get drawn until after the [self login] finishes, which is indicative that the login procedure takes some time. In iOS, drawing doesn't happen immediately, which is why you're only getting the draw if you defer the login until after the OS has an opportunity to update the display.
You have used one reasonable solution here. Another (arguably less of a hack) is to have your -[self login] spawn the login on a separate thread, or at least using an asynchronous mechanism (such as the asynchronous modes of NSURLConnection, assuming you're making a network request). Then your main thread will quickly return control to iOS and your box will draw.
With Grand Central Dispatch, you could do most of this by having the -[self login] place the network code on a background thread, and have the background thread call back to your main thread when complete. However, this can cause some problems if you want to respond to user events during the login process.
If you can, using NSURLConnection asynchronously, and setting up the delegate to report back to you when the operation is complete is probably the best choice, as it gives you the operation to cancel the NSURLConnection during the login process if the user requests it.
How about:
[label setNeedsDisplay:YES];
if ([pinText length] == pinLength) [self login];
Yes, that notification exists, in a way. The label will be drawn during the next iteration of the run loop. So do your login at the end of the next run loop iteration, for instance using a performSelector:afterDelay:0 or maybe using
dispatch_async (dispatch_get_main_queue (), ^{ [self login]; });
But a) this depends on the order of execution of rendering versus timers and dispatch_queues. If rendering happens before timer execution, you're all set.
And b) don't block the main thread. Try to perform the login in a background thread/concurrent queue, or do it asynchronously on the main thread if you're using, e.g., NSURLConnection.
Related
I'm very new to Xcode and Objective C. So most probably this is going to be an easy question (:
I'm trying to create an app that would do something if Caps Lock key is pressed.
And I need to see if Caps lock is pressed even if app is not in focus.
I've managed to check Caps Lock state even if my app is not in focus.
But I have an issue with getting out from while loop.
I cannot understand why my app cannot see what is happening to Caps lock key once it gets inside while loop.
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_spam1 = NO;
[NSEvent addGlobalMonitorForEventsMatchingMask:NSFlagsChangedMask handler:^(NSEvent* event){
[NSThread sleepForTimeInterval:1];
if(event.keyCode == 0x39){ //CAPS LOCK key was pressed
if([event modifierFlags] & NSAlphaShiftKeyMask){
_spam1 = YES;
NSLog(#"caps lock is on");
} else {
_spam1 = NO;
NSLog(#"caps lock is off”);
}
//if I comment this part
//I can see if caps lock is on or off just fine
while(_spam1){
NSLog(#"Spam %#" , event);
NSLog(#"Spam %lu" , [event modifierFlags] & NSAlphaShiftKeyMask);
[NSThread sleepForTimeInterval:1];
if([event modifierFlags] & NSAlphaShiftKeyMask){
//_spam1 = YES;
} else {
_spam1 = NO;
NSLog(#“stop while loop”);
break;
}
}
}
}];
}
Work with framework, not agains:
Create a new subclass of NSView and override one of following methods. Set the new subclassed view in Interface builder. ( read more in Event handling guide )
So your code should look like this:
//multiKey handling
unichar SPACE_CHARACTER = 0x0020;
- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
id responder = [[self window] firstResponder];
if (responder != self)
{
return [super performKeyEquivalent:theEvent];
}
NSUInteger numberOfPressedCharacters = [[theEvent charactersIgnoringModifiers] length];
NSEventType eventType = [theEvent type];
if (eventType == NSKeyDown && numberOfPressedCharacters == 1)
{
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
if (key == SPACE_CHARACTER)
{
[self spaceBarPressed];
return YES;
}
}
return NO;
}
or
//single key handling (no modifiers like shift, ctrl...)
- (void)keyDown:(NSEvent *)theEvent
{
// code belongs here
}
Maybe I'm missing something, but why do you have the while loop at all, as your code comment says it works without it?
In outline what is happening is as follows:
Your call to addGlobalMonitorForEventsMatchingMask requests the system to call the block you pass every time an event occurs (that matches). This is done asynchronously, and for this to happen you must be going around your event loop.
The standard application model is event driven, the system sets up the main event loop and calls your code to handle the various events - including any required calls to your event monitoring block. After your code handles the event it returns, the event loop goes around and obtains the next event, calls your code to handle it, etc., etc.
When your block is called it is passed a reference to the event it needs to process. In a single call that event will not be changed asynchronously by the system - the system obtains the event as part of the event loop processing and calls your block, until that call returns the event loop cannot continue.
So after all that preamble what happens with your while loop is the call to sleepForTimeInterval causes your application to pause within the call to the block - the block does not return and so its caller, the event loop, does not continue either. On waking up your loop continues to process exactly the same event it was passed when called, your block then continues around its loop getting nowhere...
HTH
I try to subscribe to a signal with throttle, but it never executes.
I have a UISearchController (Attention: UISearchController from iOS8, not the older UISearchDisplayController, which works quiet better and has thousands of working tutorials and examples in the web) and want to make API-Requests while the user is typing.
To let the traffic being low, i don't want to start API-Requests with each key the user is pressing, but when the user stops for a while, say 500 ms after the last keypress.
Since we're unable to reference the TextField in the SearchBar of the UISearchController, we use the delegates from UISearchController:
To get the latest typed text of the Textfield in the Searchbar, I use this:
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchText = searchController.searchBar.text;
// strip out all the leading and trailing spaces
NSString *strippedString = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if([strippedString isEqualToString:self.currentFilter]) {
return;
}
self.currentFilter = strippedString;
}
The property currentFilter keeps the current search string.
Also, i have a RACObserve on the currentFilter-Property to react on every change which is made to this property:
[RACObserve(self, currentFilter) subscribeNext:^(NSString* x) {
NSLog(#"Current Filter: %#", x);
// do api calls and everything else
}];
Now i want to throttle this signal. But when i implement the call to throttle, nothing happens. SubscribeNext will never be called:
[[RACObserve(self, currentFilter) throttle:500] subscribeNext:^(NSString* x) {
NSLog(#"%#", x); // will never be called
}];
How to achieve to throttle inputs in a searchbar? And what is wrong here?
UPDATE
i found a workaround besides ReactiveCocoa thanks to #malcomhall. I moved the code within the updateSearchResultsForSearchController-delegate method into a separate method and schedule it with performSelector and cancel this scheduler with cancelPreviousPerformRequestsWithTarget.
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(useTextSearchQuery) object:nil];
[self performSelector:#selector(useTextSearchQuery) withObject:nil afterDelay:1.0];
}
Anyway, i want still understand how "throttle" from ReactiveCocoa is working and why not in this case :)
-throttle: accepts an NSTimeInterval, which is a floating-point specification of seconds, not milliseconds.
Given the code in the question, I expect you would see results after 500 seconds have elapsed.
Here's my scenario....
I have a Core MIDI app that detects Note On and Note Off messages which is working nicely.
I have have some midiSend methods that send messages back to the controller to illuminate LEDs - also working fine.
What I want to do now is on the Note Off message have the LED blink on and off. This is my code:
[midiListener performSelectorOnMainThread:#selector(startTimer:) withObject:midiMsgParts waitUntilDone:YES];
-(void)startTimer:(NSDictionary *)dict {
ledIntervalCount = 0;
ledIntervalTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:#selector(ledIntervalLoop:)
userInfo:dict
repeats:YES];
}
-(void)ledIntervalLoop:(NSTimer *)inboundTimer{
NSDictionary *userInfo = [NSDictionary dictionaryWithDictionary:[inboundTimer userInfo]];
NSLog(#"%#", userInfo);
UInt32 onCommand = [[userInfo objectForKey:#"noteOn"] intValue];
//UInt32 offCommand = [[userInfo objectForKey:#"noteOff"] intValue];
UInt32 theNote = [[userInfo objectForKey:#"note"] intValue];
ledIntervalCount++;
if (ledIntervalCount > 3) {
[ledIntervalTimer invalidate];
ledIntervalTimer = nil;
} else {
if(ledIntervalCount %2){
[self sendNoteOnIlluminate:onCommand midiNote:theNote];
}else{
[self sendNoteOnCommand:onCommand midiNote:theNote];
}
}
}
So I'm using an NSTimer to alternate the LED on/off commands. It works fine when I press a single button but not when I press multiple ones at the same time. It seems like it only picks on the last call to the startTimer method.
This is where I think I need to implement a dispatch queue with GCD. So that each NSTimer will execute in full without being interrupted by the method calls that follow.
Am I correct? Will GCD allow me to have the NSTimer run concurrently?
GCD is a new concept to me so some guidance on how I might implement it would help. I've read through some of the reference guides but need to see some example code in the context of my scenario. I guess what I'm asking here is, what part of my code would go in the block?
AH you invalidate the timers anyway... after 3 tries. ALL -- you need X counters for X timers, you have 1 counter for X timer
instead of one long ledIntervalCount, have a NSMutableArray with ledIntervalCounts! One per timer
then in the userInfo for the timer, provide the index to the counter that is to be used
The problem was that I was calling the class from within a method wrapped in an autorelease. I now run this on the main thread and it works fine.
We are trying to get a background task working for the purpose of including an activity indicator in a workhouse screen. From our understanding, this requires one to create a background thread to run it on. I also understand that no GUI updates can be performed on the background thread.
Given that, here is the general pattern of what needs to happen.
a.) Pre-validate fields. Make sure user did not enter any invalid data
b.) Setup background task.
c.) Process results from background task
This is what it looks like in code so far:
-(IBAction)launchtask:(id)sender
{
//validate fields
[self validateFields];
/* Operation Queue init (autorelease) */
NSOperationQueue *queue = [NSOperationQueue new];
/* Create our NSInvocationOperation to call loadDataWithOperation, passing in nil */
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(backgroundTask)
object:nil];
/* Add the operation to the queue */
[queue addOperation:operation];
[operation release];
//TO DO: Add any post processing code here, BUT how do we know when it is done???
ConfirmationViewController *otherVC;
//show confirm
//if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
//{
// otherVC = [[ConfirmationViewController alloc] initWithNibName:#"ConfirmationViewPad" bundle:nil];
//}
//else
{
otherVC = [[ConfirmationViewController alloc] initWithNibName:#"ConfirmationView" bundle:nil];
}
//TO DO: Let's put this in a struct
otherVC.strConfirmation = strResponse;
otherVC.strCardType = strCardType;
otherVC.strCardNumber = txtCardNumber.text;
otherVC.strExpires = txtExpires.text;
otherVC.strCustomerEmail = txtEmail.text;
[self.navigationController pushViewController:otherVC animated:YES];
[otherVC release];
otherVC = nil;
}
So far, that works pretty well except that we don't yet have a way to know when the background task is complete. Only when it is complete, can we process the results of the background task. Right now, it doesn't work because there is not synchronization to the two. How to solve?
One other thing, noticed that a spinner is now displayed in the status bar. That is a good thing but it doesn't seem to be going away after the background task has completed? What to do?
Thanks in advance.
Your options are, briefly:
key value observe the 'operationCount' property on NSOperationQueue and wait for it to reach 0 (or, equivalently, the 'operations' property and check the count)
have your operations fire off a little notification that they're done (probably on the main thread with performSelectorOnMainThread:...) and wait until the correct number of notifications have been received.
[EDIT: I see you've asked specifically about the old SDK 3.0. In that case, observe operations and check count because the operationCount property postdates SDK 3.0]
There's no automatic system for starting and stopping a spinner in the general case. You'll have to talk to it yourself. However, a neat thing about a spinner is that it continues spinning even if the main thread is blocked, so if you're thread hopping just for that purpose then you don't actually need to.
A spinner appears in the status bar to show data fetches, I believe. If it continues spinning then you still have URL requests ongoing, whether or not you're actually waiting for the results.
I've got a while loop, that runs for many seconds and that's why I want to update a progress bar (NSProgressIndicator) during that process, but it updates only once after the loop has finished. The same happens if I want to update a label text, by the way.
I believe, my loop prevents other things of that application to happen. There must be another technique. Does this have to do with threads or something? Am I on the right track? Can someone please give me a simple example, how to “optimize” my application?
My application is a Cocoa Application (Xcode 3.2.1) with these two methods in my Example_AppDelegate.m:
// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
[progressbar setDoubleValue:0.0];
[progressbar startAnimation:sender];
running = YES; // this is a instance variable
int i = 0;
while (running) {
if (i++ >= processAmount) { // processAmount is something like 1000000
running = NO;
continue;
}
// Update progress bar
double progr = (double)i / (double)processAmount;
NSLog(#"progr: %f", progr); // Logs values between 0.0 and 1.0
[progressbar setDoubleValue:progr];
[progressbar needsDisplay]; // Do I need this?
// Do some more hard work here...
}
}
// This method runs when a stop button is clicked, but as long
// as -startIt is busy, a click on the stop button does nothing.
- (IBAction)stopIt:(id)sender {
NSLog(#"Stop it!");
running = NO;
[progressbar stopAnimation:sender];
}
I'm really new to Objective-C, Cocoa and applications with a UI. Thank you very much for any helpful answer.
If you are building for Snow Leopard, the easiest solution is in my opinion to use blocks and Grand Central Dispatch.
The following code shows you how your startIt: method would look like when using GCD.
Your stopIt: method should work fine as you wrote it. The reason why it wasn't working before is that mouse events happen on the main thread and thus the button didn't respond to you because you were doing work on the main thread. This issue should have been resolved now as the work has been put on a different thread now with GCD. Try the code, and if it doesn't work, let me know and I will see if I made some errors in it.
// This method runs when a start button is clicked.
- (IBAction)startIt:(id)sender {
//Create the block that we wish to run on a different thread.
void (^progressBlock)(void);
progressBlock = ^{
[progressbar setDoubleValue:0.0];
[progressbar startAnimation:sender];
running = YES; // this is a instance variable
int i = 0;
while (running) {
if (i++ >= processAmount) { // processAmount is something like 1000000
running = NO;
continue;
}
// Update progress bar
double progr = (double)i / (double)processAmount;
NSLog(#"progr: %f", progr); // Logs values between 0.0 and 1.0
//NOTE: It is important to let all UI updates occur on the main thread,
//so we put the following UI updates on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[progressbar setDoubleValue:progr];
[progressbar setNeedsDisplay:YES];
});
// Do some more hard work here...
}
}; //end of progressBlock
//Finally, run the block on a different thread.
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue,progressBlock);
}
You can try this code ..
[progressbar setUsesThreadedAnimation:YES];
I believe, my loop prevents other things of that application to happen.
Correct. You need to break this up somehow.
One way would be a timer, with you whittling away the queue a little at a time in the timer callback. Another would be to wrap the code to handle one item in an NSOperation subclass, and create instances of that class (operations) and put them into an NSOperationQueue.
Does this have to do with threads or something?
Not necessarily. NSOperations run on threads, but the NSOperationQueue will handle spawning the thread for you. A timer is a single-threaded solution: Every timer runs on the thread you schedule it on. That can be an advantage or a disadvantage—you decide.
See the threads section of my intro to Cocoa for more details.
This worked for me, which is a combination of answers from others that did not seem to work (for me at least) on their own:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//do something
dispatch_async(dispatch_get_main_queue(), ^{
progressBar.progress = (double)x / (double)[stockList count];
});
//do something else
});