I have a crosshair on the iphone screen and when a pavilion (dot on map) moves under it i want to start zooming on the map.
I all ready got the detection to work when a pavilion comes under the crosshair and the moment it is not under it anymore.
For now i post it in here in psuedo code:
- (void) checkForPavilionUnderCrosshair {
if(new pavilion under crosshair) {
// start zooming
}
else if(just left pavilion under crosshair){
// stop zooming
}
}
So what i need to do now is keep triggering:
mapView setZoom:(mapView.zoom+0.1) animated:NO];
And being able to stop that progress when the crosshair moves off the pavilion.
I did some searches on stackoverflow but the posts i found did not include stopping it for example. I have no experience with timing things in programs so could someone help me a litle by telling what i'm looking for?
I tried
[self performSelector:#selector(zoomTest) withObject:self afterDelay:0.0];
If i keep touching the map and move my finger then it keeps checkForPavilionUnderCrosshair just like i want.
But the perform selector get's fired after i stop touching the screen, so if i touch the screen for 20 seconds it fires 20 seconds to late.
Any workaround for that?
You can call [self performSelector:#selector(checkForPavilionUnderCrosshair) withObject:nil afterDelay:1.0] at the end of your method to have it called again after the specified time period. Note that the delay is in seconds.
If you want something more advanced, you can look at NSTimer over at the Apple docs, or see this SO question for an example and explanation.
If you decide to use the simple performSelector method, you can simply not call the method when you don't want to repeat anymore. If you choose to use NSTimer, call invalidate on the timer to stop it.
Related
I have two problems both with loops.
First question, is it possible to make loop(while or something else) that would wait my command.
For example, I need to choose a possible move and until I have chance for next move I must have chance to choose.
While(eat==1){
if(CGRectContainsPoint([BOX9 frame], [touch locationInView:self.view])){
eat=0;
}
}
My problem is that while spinning in circle and BOX9 can't be pressed because spinning and spinning. I need some loop that spin circle but will allow selection of next command. I hope I was clear, English is not my strong point.
Second problem
Second problem is with while loop. On touch I show image from UIimageView controller to another. Sometimes I have more movements, and I need use while because i never know how movements I wold have. And I would like to make this movements slower.
If i write in code that i want uiimage1.image=someimage; that work fine. But problem is when I say for example:
uiimage1.image=someimage;
while(eat==1){
uiimage2.image=uiimage1.image;
uiimage1.image=NULL;
uiimage3.image=uiimage1.image;
}
(This is only example code,that I wrote now,to better explain problem.)
If i making some waiting inside "while loop", always I getting that slower show is of only first image,second two changes are fast.
I try with transition effect to make slower, but that works only when I load some picture in imagecontroller with touch. If i wrote in code, transition wont work.
All is in touchesBegin function.
Thank's for help.
So, from what I can tell, you are making a game, and want to animate things. Your problem is that your while loops are on the main thread, so until they finish no user input is accepted and the screen doesn't refresh.
There are several ways to solve this, all of which involve moving the execution of things that happen in "real-time," by which I mean things that are presented to the user, off of the main thread.
The first: You make an NSTimer with scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:. Your selector handles the animation of things in "real time". When touchesBegan is called, you set the flag eating. This looks roughly like this in code
-(void) setUp
{
NSTimer scheduleTimerWithTimeInterval: 1.0
target: self
selector: #selector(runLoop)
userInfo: NULL
repeats: YES];
}
-(void) runLoop
{
if(eatingDuration && eating)
{
eatingDuration--;
//do something because we're eating
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//if we need to set eating to yes do it
eating = YES;
eatingDuration = whatEverWeNeedItToBe;
}
This might be thought of as a "run loop," because it is called repeatedly until you get rid of the timer. It is probably the best method to do this.
The second: You use the animateWithDuration:animations: class method of UIView to animate each individual element.
The question is simple but extremely complicated: in UIResponder there are 4 methods for handling touches.
- touchesEnded:withEvent:event
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesCancelled:withEvent:
How do I detect if a touch stopped on the screen?
The problem is that I have to detect if something moved under a stationary touch (not cancelled or ended. It just doesn't move) And because it doesn't move, none of these methods gets called.
My idea was this:
I could add the touches to a NSMutableArray but then I'd have to update it for any touch move (and that's a lot). Also this creates more problems, I need to detect which of the touches stopped and if any ended. And because I get an NSSet from UIResponder, I don't have an organized array so... all kinds of problems.
I'm waiting for ideas.
i fixed it..and also found out something really cool about UITouch
what i did: in
– touchesBegan:withEvent:
for(UITouch*touch in touches){
[touchesSet addObject:touch]; } //touchesSet is a set that i store all the touches on the screen
in
- touchesEnded:withEvent:
for(UITouch*touch in touches){
[touchesSet removeObject:touch]; }
in
– touchesCancelled:withEvent:
[touchesSet removeAllObjects];
doing this i have a NSSet of all the touches on the screen at any given time, with position and UITouchPhase
The solution depends a little upon what you're trying to do (and you don't really describe what business problem or user experience you're going for). But assuming you're just trying to detect when a continuous gesture paused but hadn't been completed:
You could have touchesMoved keep track of where and when it was last invoked. E.g. if you have a subclassed gesture recognizer, give it a property of CGPoint lastLocation or something like that which you could inquire upon.
You could then setup a NSTimer that would be triggered a certain amount of time later, which would test for your "stopped" condition. E.g. if your NSTimer is called every 0.1 seconds and you're waiting for no change in location for, say 1 second, then that would qualify as a stopped condition.
And if you're looking to see if "something moved under a stationary touch", you could add this to your NSTimer routine.
I am customizing my drawRect: method, which serves to draw a NSImage if it has been "loaded" (loading taking a few seconds worth of time because I'm grabbing it from a WebView), and putting off drawing the image till later if the image has not yet been loaded.
- (void)drawRect:(NSRect)dirtyRect
{
NSImage *imageToDraw = [self cachedImage];
if (imageToDraw != nil) {
[imageToDraw drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
} else {
//I need help here
[self setNeedsDisplay:YES];
}
}
My question is how to do the latter. [self cachedImage] returns nil if the image is unavailable, but anytime within the next few seconds it may become available and at that time I want to draw it because the custom view is already on screen.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn), but that doesn't work.
Any pointers as to where I can go from here?
EDIT:
I am very much aware of the delegate methods for WebView that fire when the loadRequest has been completely processed. Using these, however, will be very difficult due to the structure of the rest of the application, but I think I will try to somehow use them now given the current answers. (also note that my drawRect: method is relatively light weight, there being nothing except the code I already have above.)
I currently have about 10+ custom views each with custom data asking the same WebView to generate images for each of them. At the same time, I am grabbing the image from an NSCache (using an identifier corresponding to each custom view) and creating it if it doesn't exist or needs to be updated, and returning nil if it is not yet available. Hence, it's not as easy as calling [view setNeedsDisplay:YES] from - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame or another method.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn)
This would be incredibly inefficient, even if it worked.
anytime within the next few seconds it may become available and at that time I want to draw it
So, when that happens, call [view setNeedsDisplay:YES].
If you have no means of directly determining when the image becomes available, you'll have to poll. Set up a repeating NSTimer with an interval of something reasonable -- say 0.25 second or so. (This is also pretty inefficient, but at least it's running only 4 times per second instead of 60 or worse. It's a tradeoff between two factors: how much CPU and battery power you want to use, and how long the delay is between the time the image becomes available and the time you show it.)
my drawRect: method is relatively light weight, there being nothing except the code I already have above.
Even if you do nothing at all in -drawRect:, Cocoa still needs to do a lot of work behind the scenes -- it needs to manage dirty rects, clear the appropriate area of the window's backing store, flush it to the screen, etc. None of that is free.
Well, usually there is some delegate method that is called, when a download of something finishes. You should implement that method and call setNeedsDisplay:YES there.
The documentation for webkit:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/ResourceLoading.html#//apple_ref/doc/uid/20002028-CJBEHAAG
You have to implement the following method in your webview delegate:
- webView:resource:didFinishLoadingFromDataSource:
There you can call [view setNeedsDisplay:Yes]
I have an IBAction for when a button is clicked:
- (IBAction)importButtonClicked:(id)sender
And I want a series of events to take place like:
[_progressLabel becomeFirstResponder]; // I tried this but to no effect
_progressLabel.stringValue = BEGIN_IMPORT_STRING;
[_importButton setEnabled:FALSE];
_fileField.stringValue = #"";
[_progressIndicator startAnimation:nil];
But what ends up happening is the _progressIndicator animation takes place before the _progressLabel text appears. And often times the text won't appear untili the _progressIndicator animation has stopped. How do I fix that?
Put the work you're doing which takes time (I assume that's what the progress indicator is for) on a separate thread. You don't have to do this manually in Cocoa, but instead, use Grand Central Dispatch (GCD), NSOperationQueue or such a construct available. You'll find lots of resources on GCD.
I have a NSArray of UIImageViews that I want to loop over and quickly swap out an "on" and "off" state. I wrote the code to do so in a for loop instead a method that was called when the user tapped a UIButton ( the button's action ).
Here's that loop:
for(int i = 0; i < [Images count]; i++) {
if( i > 0 ){
[self toggleImageViewOff:[Images objectAtIndex:i - 1]];
}
[self toggleImageViewOn:[Images objectAtIndex:i]];
[NSThread sleepForTimeInterval:0.5f];
}
The UI did not update as I expected as I only ever saw the last UIImageView in the "on" state. I figured that the drawing update of the views must occur in the main thread this code was also executing in. So I learned about performSelectorInBackground:withObject: . Performing the toggleImageViewOn/Off methods using this made the loop work. The problem is if I make the sleep interval too short I can have an "on" update after an "off" with Threads operating out of order.
So I had the bright idea of moving the whole loop with the sleep into its own method and calling that from the action method using performSelectorInBackground:withObject: . I tried that and I'm back to not getting an updated view until the loop is over.
That's a long winded way to get to my question:
What's the best way to animate this to guarantee the on/off code fires in the right order and still get view updates, even at high speeds? ( i.e. looping very quickly )
I tried to think about how I'd do it with CoreAnimation, but I can't seem to get my head around how to do it there.
For bonus, here are the toggle methods:
- (void)toggleImageViewOn:(UIImageView *)theImageView {
[theImageView setImage:[UIImage imageNamed:#"on.png"]];
}
- (void)toggleImageViewOff:(UIImageView *)theImageView {
[theImageView setImage:[UIImage imageNamed:#"off.png"]];
}
Did you set up an animation context (UIView class method does that) around this for loop? Without it changes are immediate instead of animated.
The problem is that you are not giving any of the UIImages time to draw. The drawing code is optimised to only draw what's needed - rendering all those intermediate stages is optimised out.
Sleeping the main thread doesn't actually give it chance to run.
Bill is right in that you need to set up an animation context around your loop. This will capture all of the UIView changes you make and then play them out. The easiest way to do this is using Core Animation. Core animation 'records' changes in UIElemenets and plays them back. Your code (without the sleep) will work just fine in a Core Animation block.
Apple have a reasonable cookbook for Core Animation on their site
You're on the right track with moving the loop to a background thread, but you also need to make sure that you give the main run loop a chance to update the UI. You should be able to replace the direct calls to toggleImageViewOn: and toggleImageViewOff: with something like
[self performSelectorOnMainThread:#selector(toggleImageViewOn:) withObject:[Images objectAtIndex:i] waitUntilDone:NO];
This will do the UI update on the main thread, and by not waiting until the update is done you give the main run loop a chance to reach its end. You run into the same issue with things like progress bars, where they won't change until the loop ends unless you do your updates from a background thread with a UI update call like the one above.
Hey Patrick. Have a look at UIImageView's animationImages property, as well as the animationRepeatCount and animationDuration properties. If you put your on/off images into an array and assign that as the animationImages property, you should be able to control the repeat and duration to get the desired effect.
Hope that helps!
Thanks for that. I've already looked into UIIMageView's animationImages property. That's not exactly what I'm attempting to do. I'm cycling between several UIImageView's that are placed near each other to give the impression that a light is moving between them and cycling over them. So an individual UIImageView's animation is separate from each other as I need to swap the image as necessary in code.
Calling peformSelectorOnMainThread:withObject:waitUntilDone: from the loop on the background thread does indeed update the view as quickly as I can think I will ever need. I'm curious why I need to do the UIImageView swap on the Main thread? Why wouldn't changing it on the background thread and then using NSThread's sleepForTimeInterval allow the main thread to update the drawing anyway?
I guess I need to go read up on the run loop and where drawing updates occur.
Thanks so much for the help. ( I'm also going to try some additional suggestions from Bill Dudney, that I think will work based on CoreAnimation )