Action Trigger when I hold UIButton for 2 second in iPhone - ios4

I have a UIButton in my application and an action which is triggered when I TouchDown UIButton.
Is it possible to detect a Touch-and-Hold on the UIButton on the iPhone? I want my action to trigger when the user holds the button for 2 seconds or more.
Any Ideas?

UILongPressGestureRecognizer is what you need. For example,
UILongPressGestureRecognizer *longPress_gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(doAction:)];
[longPress_gr setMinimumPressDuration:2]; // triggers the action after 2 seconds of press
[yourButton addGestureRecognizer:longPress_gr];
To let your action get triggered only once(ie., when the 2 seconds duration is over), make sure you have your doAction: method looks something like this,
- (void)doAction:(UILongPressGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// Your code here
}
}

On the other way you can use this NBTouchAndHoldButton. This is exactly what you want, and it is very easy to implement it:
TouchAndHoldButton * pageDownButton = [TouchAndHoldButton buttonWithType:UIButtonTypeCustom];
[pageDownButton addTarget:self action:#selector(pageDownAction:) forTouchAndHoldControlEventWithTimeInterval:0.2];
Good luck!

Related

Threading issue with UICollectionview loading images from CloudKit

I'm having a threading issue loading images in a collectionview where the data is coming from cloudkit. I know this is a threading/blocking issue because before I implemented CK, I dumped some images in a folder on my desktop and read/parsed them from there and had no issue. With CK, I just created a handful of records via the dashboard and I'm successfully getting the expected records returned and use the images from those results to populate the CV cells. I store the CK query results in an array and use the size of that array to set the numberOfItemsInSection delegate.
Here's the issue...in the numberOfItemsInSection delegate method, I'm calling the model class, which executes the CK query. Since that is obviously a network call, I put that in a background thread. From logging, I can see the query execute and the results come back very quickly - within 2-3 seconds. However, the CV cells never display and I don't see the custom cell get initialized (via logging). But if I tap the camera button and take a photo, which I've implemented, I take the resulting image and add it to the array, then call reloadData on the CV and all the cells (and images) appear, including the new image just taken with the camera.
By accident, I found out a hack that somewhat works, which is calling reloadData on the CV inside the background thread of the numberOfItemsInSection delegate method. As a result, I thought I might have stumbled on to the solution by switching back to the main thread when calling reloadData, but that put it in a sort of endless loop of continuously calling the numberOfItemsInSection method and cellForItemAtIndexPath and made it to where it lagged to a point that you could barely scroll and tapping on any of the cells wouldn't do anything.
At this point, after trying many, many various things, I'm at a complete loss on how to fix this. I know this is probably a pretty easy solution as it's very common to load images asynchronously to populate a collectionview or tableview. Can someone please provide some guidance? Thanks in advance!!!
#property (nonatomic) NSInteger numberOfItemsInSection;
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSLog(#"***numberOfItemsInSection***");
dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
dispatch_async(fetchQ, ^{
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self.myCollectionView reloadData]; // should be done on main thread!
});
NSLog(#"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);
return self.numberOfItemsInSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell"; // string value identifier for cell reuse
ImageViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
NSLog(#"cellForItemAtIndexPath: section:%ld row:%ld", (long)indexPath.section, (long)indexPath.row);
cell.layer.borderWidth = 1.0;
cell.layer.borderColor = [UIColor grayColor].CGColor;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
ImageData *imageData = [self.imageLoadManager imageDataForCell:indexPath.row]; // maps the model to the UI
dispatch_async(dispatch_get_main_queue(), ^{
if (imageData.imageURL.path) {
cell.imageView.image = [UIImage imageWithContentsOfFile:imageData.imageURL.path];
[cell setNeedsLayout];
} else {
// if imageURL is nil, then image is coming in from the camera as opposed to the cloud
cell.imageView.image = imageData.image;
[cell setNeedsLayout];
}
});
return cell;
}
before returning self.numberOfItemsInSection you should wait until the async call is finished. You can do that using semaphores. But then why are you doing this async? You are just getting the count of an array. And then you shouldn't reloadData there. Where do you start your CloudKit query? are you doing that onViewDidLoad? That is also an async operation. When that completes just doe a reloadData of your collectionView. Besides that doing this would be enough:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.imageLoadManager.imageDataArray count];
}
If you really want to use async there, then you do have to wait for the result. You could change your code to something like:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSLog(#"***numberOfItemsInSection***");
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t fetchQ = dispatch_queue_create("load image data", NULL);
dispatch_async(fetchQ, ^{
self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
[self.myCollectionView reloadData]; // should be done on main thread!
dispatch_semaphore_signal(sema);
});
NSLog(#"numberOfItemsInSection: %ld", (long)self.numberOfItemsInSection);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return self.numberOfItemsInSection;
}
And then why do you go to the main queue in cellForItemAtIndexPath? It's already executed on the main queue.

IOS 7 Safari like History dragging gesture using uiwebview

If you see in ios 7 safari app have left and right dragging to see history of webpages. It also show content of previous page while dragging . I want similar functionality using UIWebiview.
I can get forward and backward page but how that dragging functionality with displaying previous content will be implemented?
Note: this is iOS 7 only. For iOS 6 you should use the PanGestureRecognizer and detect if it's left or right.
Your need two properties:
#property (nonatomic, strong) UIImageView * imgvcChild1;
#property (retain, strong) IBOutlet UIWebView *webView;
First of all, add the gesture recognizer to the webView:
[[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(EdgeLeftPanDetected:)];
panLeft.edges = UIRectEdgeLeft;
[self.webView addGestureRecognizer:panLeft];
Control the gesture:
- (void) EdgeLeftPanDetected:(UIScreenEdgePanGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
UIGraphicsBeginImageContext ( self.webView.frame.size );
[self.webView.layer renderInContext:UIGraphicsGetCurrentContext() ];
UIImage *grab = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if ( _imgvcChild1 ) [ _imgvcChild1 removeFromSuperview ];
_imgvcChild1 = [[ UIImageView alloc ] initWithImage:grab ];
_imgvcChild1.frame = self.webView.frame;
_imgvcChild1.userInteractionEnabled = YES;
_imgvcChild2 = [self.arrayImagenes lastObject];
if ([self.webView canGoBack]) {
[self.webView goBack];
}
[ self.view addSubview:_imgvcChild1 ];
}
if (gesture.state == UIGestureRecognizerStateChanged) {
_imgvcChild1.frame = CGRectMake([ gesture locationInView:_imgvcChild1.superview ].x, _imgvcChild1.frame.origin.y, _imgvcChild1.frame.size.width, _imgvcChild1.frame.size.height);
}
}
if (gesture.state == UIGestureRecognizerStateEnded) {
[_imgvcChild1 removeFromSuperview];
}
}
This a prototype, it needs a lot of work, it's just for going back and the page where you go back doesn't have animation. If you want to have animation in the previous page you'll need to create a image before loading the new page and store it on an NSMutableArray and then show it with a different animation (this image starts like -1/4 of screen and goes like 1/4 of the speed of imgvcChild1)
You'll need another gesture recognizer for the right and another array of UIImageViews if you want to go forward too.
Few days back i too have similar kind of requirement. I did lot of research for this, but no luck. I read somewhere that this kind of gesture (History dragging) are managed by OS or the browser itself.
History dragging gesture are not available for UIWebView. As of now you cannot do this with UIWebView.
The one gesture will implement to achieve near to this functionality is UIPanGestureRecognizer but, you still not get the content of the previous page(history).

What is the best way to implement a timer that will be stopped and restarted in a Java Swing Application?

Ok, I'm working on my final dilemna for my project. The project is an IPv4 endpoint updater for TunnelBroker's IPv6 tunnel. I have everything working, except for the timer. It works, however if the user disables the "automatic update" and reenables it, the application crashes. I need the timer to be on an thread outside of the EDT (in such a way that it can be destroyed and recreated when the user unchecks/checks the automatic update feature or changes the amount of time between updates).
What I'm pasting here is the code for the checkbox that handles automatic updates, and the timer class. Hopefully this will be enough to get an answer on how to do this (I'm thinking either it needs to be a worker, or use multi-threading--even though only one timer will be active).
private void jCheckBox1ItemStateChanged(java.awt.event.ItemEvent evt) {
// TODO add your handling code here:
// if selected, then run timer for auto update
// set time textbox to setEditable(true) and get the time from it.
// else cancel timer. Try doing this on different
// class to prevent errors from happening on reselect.
int updateAutoTime = 0;
if (jCheckBox1.isSelected())
{
updateAutoTime = Integer.parseInt(jTextField4.getText())*60*1000;
if (updateAutoTime < 3600000)
{
updateAutoTime = 3600000;
jTextField4.setText(new Integer(updateAutoTime/60/1000).toString());
}
updateTimer.scheduleAtFixedRate(new TimerTask() {
public void run()
{
// Task here ...
if (jRadioButton1.isSelected())
{
newIPAddress = GetIP.getIPAddress();
}
else
{
newIPAddress = jTextField3.getText();
}
strUsername = jTextField1.getText();
jPasswordField1.selectAll();
strPassword = jPasswordField1.getSelectedText().toString();
strTunnelID = jTextField2.getText();
strIPAddress = newIPAddress;
if (!newIPAddress.equals(oldIPAddress))
{
//fire the tunnelbroker updater class
updateIP.setIPAddress(strUsername, strPassword, strTunnelID, strIPAddress);
oldIPAddress = newIPAddress;
jLabel8.setText(newIPAddress);
serverStatus = updateIP.getStatus().toString();
jLabel6.setText(serverStatus);
}
else
{
serverStatus = "No IP Update was needed.";
jLabel6.setText(serverStatus);
}
}
}, 0, updateAutoTime);
}
else
{
updateTimer.cancel();
System.out.println("Timer cancelled");
System.out.println("Purged {updateTimer.purge()} tasks.");
}
}
As I mentioned, this works once. But if the user deselects the checkbox, it won't work again. And the user can't change the value in jTextField4 after they select the checkbox.
So, what I'm looking for is this:
How to make this so that user can select and deselect the checkbox as they want (even if it's multiple times in a row).
How to make this so the user can change the value in jTextField4, and have it automatically cancel the current timer, and start a new one with the new value (I haven't done anything with the jTextField4 at all, so I'll have to create an event to cover it later).
Thanks, and have a great day:)
Patrick.
Perhaps this task would be better suited to a javax.swing.Timer. See Timer.restart() for details.
Note that Timer is relatively inaccurate over long time periods. One way to account for that is to have it repeat frequently but perform it's assigned task only one a certain time has been reached or passed.
Would I be able to wrap everything in the "task" portion of the call to Swing Timer, or do I have to create another class that handles the task?
You might want to wrap the grunt work in a SwingWorker to ensure the EDT is not blocked.
..I'm assuming that I would have to create the timer as a class-level declaration .. correct?
Yes, that is what I was thinking.

NSTimer in NSOperation subclass

Dear community.
I try to setup NSTimer:
#interface GetExternalInfo : NSOperation {
NSTimer *keepAliveTimerMain;
#property (nonatomic,retain) NSTimer *keepAliveTimerMain;
.m:
#synthesize keepAliveTimerMain
-(void) main;
{
self.keepAliveTimerMain = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(keepAlive:) userInfo:nil repeats:YES];
[keepAliveTimerMain fire];
[[NSRunLoop currentRunLoop] addTimer:self.keepAliveTimerMain forMode: NSDefaultRunLoopMode];
BOOL timerState = [keepAliveTimerMain isValid];
NSLog(#"STAT:Timer Validity is: %#", timerState?#"YES":#"NO");
- (void)keepAlive:(NSTimer *)theTimer
{
BOOL currentState = [self isCancelled];
NSLog(#"STAT:cancelled state is %#.\n",currentState?#"YES":#"NO");
}
In Logs
2011-02-02 18:58:31.041
snow[54705:5d07] STAT:cancelled state
is NO. 2011-02-02 18:58:31.042
snow[54705:5d07] STAT:Timer Validity
is: YES
i see this only once. No next repeat attempts every 5 seconds
any opinions in this case? GC is enabled.
Do you have a runloop in the current thread? The timer needs a runloop to be able to fire. I notice you call -fire manually which explains why -keepAlive is called, but this doesn't actually start the timer.
You need to add your timer to a run loop, something like [[NSRunLoop currentRunLoop] addTimer:keepAliveTimerMain forMode: NSDefaultRunLoopMode];.
EDIT: The code you posted shows that you manually fire the timer before adding it to the runloop. This will fire and invalidate the timer, so you are actually trying to schedule an invalid timer on the runloop. That is why you only see the NSLog message once.

CLLocation manager updates from background thread

I'm launching a localization request using Grand Central Dispatch :
- (void) findGroceriesNearMe {
dispatch_queue_t downloadQueue = dispatch_queue_create("Groceries downloader", NULL);
dispatch_async(downloadQueue, ^{
CLLocationCoordinate2D userLocation = [LocationManagerController findMeWithCaller:self];
dispatch_async(dispatch_get_main_queue(), ^{
[self userSuccessFullyFound:userLocation];
});
});
dispatch_release(downloadQueue);
}
It calls a static method in my Singleton class LocationManager Controller :
+ (CLLocationCoordinate2D) findMeWithCaller: (UIViewController *) viewController {
LocationManagerController *locationManagerController = [LocationManagerController locationManagerController];
[locationManagerController startUpdates];
while(![locationManagerController getterDone]){
//mystique pour nous-- a approfondir
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
In the startUpdates method, the CLLocationManager, property of LocationManagerController, is initialized and asked to startUpdatingLocation.
Finally, the method when location updates happen :
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
locationDenied = NO;
NSLog(#"%f,%f",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
// On vérifie que la newLocation est récente
if (abs(howRecent) > 10.0) {
return;
}
// Test if it's not an invalid measurement
if (newLocation.horizontalAccuracy < 0) return;
// Test the measurement to see if it meets the desired accuracy
if (newLocation.horizontalAccuracy <= manager.desiredAccuracy)
{
latitude = newLocation.coordinate.latitude;
longitude = newLocation.coordinate.longitude;
locationDefined = YES;
[self setterDone:YES];
}
}
My problem is that the locationManager only send 3 location updates and then stops sending updates even though I didn't ask it to stop. So basically, I never get out of the while(![locationManagerController getterDone]) loop.
By the way, before trying to implement this using GCD, it was working fine so I guess the issue has to do with my implementation of multi-threading.
Any idea ?
Edit
I don't get any error in the console. The program just keeps running but I'm stuck in that while loop and nothing else happens after the 3 first location updates.
Thanks !
From CLLocationManager class reference:
Configuration of your location manager object must always occur on a
thread with an active run loop, such as your application’s main
thread.
A guess. If you are sitting at your desk and testing with your simulator the accuracy may not get better what you want
if (newLocation.horizontalAccuracy <= manager.desiredAccuracy)
So you may get stuck in your loop. Try with higher accuracy while at your desk. Also consider if the accuracy is never better that what you want since it maybe that the gps reception is not good.
Let me know if that helps or if I was way off the mark :-)
-- Fasttouch

Resources