Hi I am implementing a 'save image to library' feature as seen in the code snippet below. Basically, the photo saving is triggered when a user touches an image on the page.
TouchImageView *tiv = [[TouchImageView alloc]initWithFrame:blockFrame];
[tiv setCompletionHandler:^(NSString *imageUrl){
//Spinner should start after user clicks on an image
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.labelText = #"Saving photo to library";
//trigger method to save image to library
[self saveImageToLibrary];
}];
-(void) saveImageToLibrary
{
//convert url to uiimage
UIImage *selectedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
//Save image to album
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Request to save the image to camera roll
[library writeImageToSavedPhotosAlbum:[selectedImage CGImage] orientation:(ALAssetOrientation)[selectedImage imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error){
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
if (error) {
NSLog(#"error");
} else {
NSLog(#"url %#", assetURL);
//Trigger get photo from library function
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:self.imgPicker animated:YES];
}
}];
[library release];
}
The issue is that the HUD spinner does not appear (after a lag time of 3-4 seconds), my suspicion is that the 'writeImageToSavedPhotosAlbum:' is synchronous and locked the process from displaying the HUD spinner. Is this right? How do I resolve the lag in spinner display?
Yes, that is correct. Replace the call
[self saveImageToLibrary];
by
[self performSelector:#selector(saveImageToLibrary) withObject:nil afterDelay:0];
so that the HUD gets a chance to show itself before you save.
Related
I am using AVCapture to capture video and save it. But I need to provide zooming option like pinch to zoom or through a zoom button. Also video should be saved in exactly in same manner in which it is being displayed, I mean when zoomed in, it should be saved zoomed. Any help, Link is appreciated. My code for setting up AVCapture session is:
- (void)setupAVCapture{
session = [[AVCaptureSession alloc] init];
session.automaticallyConfiguresApplicationAudioSession=YES;
[session beginConfiguration];
session.sessionPreset = AVCaptureSessionPresetMedium;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
captureVideoPreviewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:captureVideoPreviewLayer];
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
NSLog(#"ERROR: trying to open camera: %#", error);
}
[session addInput:input];
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
[session addOutput:movieFileOutput];
[session commitConfiguration];
[session startRunning];
}
I faced the same problem also, and I have solved it using these two steps:
Add a PinchGestureRecognizer event something like that in your Camera Preview View Controller
- (IBAction)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer{
if([gestureRecognizer isMemberOfClass:[UIPinchGestureRecognizer class]])
{
effectiveScale = beginGestureScale * ((UIPinchGestureRecognizer *)gestureRecognizer).scale;
if (effectiveScale < 1.0)
effectiveScale = 1.0;
CGFloat maxScaleAndCropFactor = [[self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo] videoMaxScaleAndCropFactor];
if (effectiveScale > maxScaleAndCropFactor)
effectiveScale = maxScaleAndCropFactor;
[CATransaction begin];
[CATransaction setAnimationDuration:.025];
[self.previewView.layer setAffineTransform:CGAffineTransformMakeScale(effectiveScale, effectiveScale)];
[CATransaction commit];
if ([[self videoDevice] lockForConfiguration:nil]) {
[[self videoDevice] setVideoZoomFactor:effectiveScale];
[[self videoDevice] unlockForConfiguration];
}}}}
** Note that the key method for persisting the zoom level for video device is [device setVideoZoomFactor:]
2- In the IBAction of the Record Button , add this code to capture the video ( recording ) then to save the recorded video in the a certain path with certain name
- (IBAction)recordButtonClicked:(id)sender {
dispatch_async([self sessionQueue], ^{
if (![[self movieFileOutput] isRecording])
{
[self setLockInterfaceRotation:YES];
if ([[UIDevice currentDevice] isMultitaskingSupported])
{
// Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until the app returns to the foreground unless you request background execution time. This also ensures that there will be time to write the file to the assets library when the app is backgrounded. To conclude this background execution, -endBackgroundTask is called in -recorder:recordingDidFinishToOutputFileURL:error: after the recorded file has been saved.
[self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]];
}
// Update the orientation on the movie file output video connection before starting recording.
// Start recording to a temporary file.
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[#"movie" stringByAppendingPathExtension:#"mov"]];
[[self movieFileOutput] startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
}
else
{
[[self movieFileOutput] stopRecording];
}
});
}
I hope that helps you
Add UIPinchGestureRecognizer object to your and handle callback like this:
- (void) zoomPinchGestureRecognizerAction: (UIPinchGestureRecognizer *) sender {
static CGFloat initialVideoZoomFactor = 0;
if (sender.state == UIGestureRecognizerStateBegan) {
initialVideoZoomFactor = _captureDevice.videoZoomFactor;
} else {
CGFloat scale = MIN(MAX(1, initialVideoZoomFactor * sender.scale), 4);
[CATransaction begin];
[CATransaction setAnimationDuration: 0.01];
_previewLayer.affineTransform = CGAffineTransformMakeScale(scale, scale);
[CATransaction commit];
if ([_captureDevice lockForConfiguration: nil] == YES) {
_captureDevice.videoZoomFactor = scale;
[_captureDevice unlockForConfiguration];
}
}
}
I have an app. On a click on the button I want the screen of the app should be captured and MailComposer window should be opened and the image captured previously should get attached to it. The mail sent but there is no attached image file
plz help me.
thank in advance...,
My code is here....
MFMailComposeViewController *controller =[[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
[controller setSubject:#"Screen shot maker"];
[controller setMessageBody:#"Hi , <br/> This is Screen Shot Maker" isHTML:YES];
[controller addAttachmentData:[NSData dataWithContentsOfFile:#"myScreenShot.png"] mimeType:#"png" fileName:#"myScreenShot.png"];
if (controller)
[self presentModalViewController:controller animated:YES];
Use below code:
UIGraphicsBeginImageContext(self.view.bounds.size);
// [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData * data = UIImagePNGRepresentation(image);
MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
[controller addAttachmentData:data mimeType:#"application/image" fileName:#"Test.png"];
if(UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad)
{
controller.modalPresentationStyle = UIModalPresentationFormSheet;
}
else
{
controller.modalPresentationStyle = UIModalPresentationFullScreen;
}
[self presentModalViewController:controller animated:YES];
I'm using AVCaptureSession to capture an image and then send it to my server.
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (imageSampleBuffer != NULL) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
self.videoImageData = [imageData copy];
[self processImage:[UIImage imageWithData:imageData]];
NSString* response=[self uploadImage];
[activityIndicator removeFromSuperview];
self.takePictureBtn.enabled = YES;
[self setUserMessage:response];
}
}];
- (void) processImage:(UIImage *)image
{
...
[UIView beginAnimations:#"rotate" context:nil];
[UIView setAnimationDuration:0.5];
int degrees = [self rotationDegrees];
CGFloat radians =degrees * M_PI / 180.0;
self.captureImageView.transform = CGAffineTransformMakeRotation(radians);
[UIView commitAnimations];
[self.captureImageView setNeedsDisplay];
}
- (NSString*)uploadImage send the photo to the server
The behavior I'm expecting is after processImage terminates that the image will be displayed in the UIImageView captureImageView on it the activity indicator rotates but instead i get a white blanc view with the indicator rotating and only after the server post request terminates captureImageView displays the image
How can I achieve my desired behavior?
Another problem I'm facing is that my imageSampleBuffer returns a mirror image of the taken one (object on the left appears on the right)
From the docs:
This method makes
a note of the request and returns immediately. The view is not
actually redrawn until the next drawing cycle, at which point all
invalidated views are updated.
Since you’re doing other stuff after you call setNeedsDisplay the event doesn’t end and you don’t get a display immediately.
If you want to do more stuff, one approach would be to schedule the rest of the stuff in a new block, like, with
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSString *const response = [self uploadImage];
[activityIndicator removeFromSuperview];
self.takePictureBtn.enabled = YES;
[self setUserMessage:response];
}];
Hi I have integrated Facebook SDK for an iOS 6 app.The Facebook authentication & sharing works perfectly but there are no provision to close the FB Dialogue box.ie. When FB Dialogue box opens,it will be closed only after authentication success.No provision to close or navigate back.How can I make a close button.The code snippet I am using has been shown below.Thanks in Advance.
-(NSDictionary *)shareFacebook
{
NSDictionary *userInfo;
if (FBSession.activeSession.isOpen)
{
if (FBSession.activeSession.isOpen)
{
[self.closeButton setHidden:NO];
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error) {
NSUserDefaults *standardUserDefaults=[NSUserDefaults standardUserDefaults];
[standardUserDefaults setObject:user forKey:#"fbUserInfo"];
[self.manager authenticateUserUsingFB:[user objectForKey:#"email"]];
}];
}
}
else{
NSLog(#"fb session not active.");
[self openSessionWithAllowLoginUI:YES];
}
return userInfo;
}
- (void)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
NSArray *permissions = [[NSArray alloc] initWithObjects:
#"user_photos",
#"publish_actions",
#"read_stream",
#"friends_photos",
#"email" ,nil];
[FBSession setActiveSession:[[FBSession alloc] initWithPermissions:permissions]];
[[FBSession activeSession] openWithBehavior:FBSessionLoginBehaviorForcingWebView
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
NSLog(#" state=%d",state);
if(FBSessionStateOpen)
{
[self shareFacebook];
}
}];
}
Add the Facebook SDK for iOS resource bundle by dragging the FacebookSDKResources.bundle file from the FacebookSDK.framework/Resources folder into the Frameworks section of your Project Navigator.
http://developers.facebook.com/docs/getting-started/facebook-sdk-for-ios/
I know what causes this bug! The button and the icon view (there are two views on the top right corner of the dialog box - a close button and an icon view) are actually exist (you can click it to see) but not visible. This is because the project can't see actual image files which are located in FBDialog.bundle. You should copy those images from the bundle and add them to project then set images directly. Your init method may look something like this:
//This is your FBDialog.m file
- (id)init {
if (self = [super initWithFrame:CGRectZero]) {
.........
UIImage* iconImage = [UIImage imageNamed:#"fbicon.png"];
UIImage* closeImage = [UIImage imageNamed:#"close.png"];
_iconView = [[UIImageView alloc] initWithImage:iconImage];
[self addSubview:_iconView];
_closeButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
[_closeButton setImage:closeImage forState:UIControlStateNormal];
[_closeButton addTarget:self action:#selector(cancel)
forControlEvents:UIControlEventTouchUpInside];
.........
May be there is a better way to fix this bug but this worked for me.
I have a UITableView which from an external RSS feed.
When you select a row it uses navigationController and slides in from the right, the problem is that the RSS feed contains images therefore it can can take a few seconds to load and without any indication of what is going on you can mistake it for an application crash.
I decided to add a spinner so that you know that new page is loading.
Here is my code:
RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Loading New Page");
[tableView deselectRowAtIndexPath:indexPath animated:YES];
DetailsViewController *detailViewController = [[DetailsViewController alloc] initWithNibName:#"DetailsViewController" bundle:nil];
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
}
DetailsViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
NSString *imgURL = [item objectForKey:#"image"];
NSData *mydata = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imgURL]];
item_photo.image = [[UIImage alloc] initWithData:mydata];
item_title.text = [item objectForKey:#"title"];
item_date.text = [NSString stringWithFormat:#"Date: %#",[item objectForKey:#"date"]];
item_time.text = [NSString stringWithFormat:#"Time: %#",[item objectForKey:#"time"]];
item_cost.text = [NSString stringWithFormat:#"Cost: £%#",[item objectForKey:#"cost"]];
item_info.text = [item objectForKey:#"description"];
self.navigationItem.title = #"Event Type";
}
There are two problems with this code.
The Spinner does not active until after the new page has loaded.
The Spinner does not disable once loaded.
If anyone could help me with this problem i would be truly gratefully.
You are adding the activity indicator view to the view of the controller which is pushing the detail view controller, so you wont see it anyway
try moving the second group of code to the viewDidLoad method of DetailsViewController, you can call stopAnimating on the activity indicator when you are finished loading. To get a reference to the UIActivityIndicator you should add a tag
e.g. in viewDidLoad
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
spinner.tag = 12;
[self.view addSubview:spinner];
[spinner startAnimating];
[spinner release];
in the loadingFinished method (whichever method is called when finished loading)
[[self.view viewWithTag:12] stopAnimating];
You need to do some work in a background thread. If the following line is the one that takes the time:
detailViewController.item = [rssItems objectAtIndex:floor(indexPath.row/2)];
Then you could do this in the background with GCD:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// This is the operation that blocks the main thread, so we execute it in a background thread
id item = [rssItems objectAtIndex:floor(indexPath.row/2)];
// UIKit calls need to be made on the main thread, so re-dispatch there
dispatch_async(dispatch_get_main_queue(), ^{
detailViewController.item = item;
[spinner stopAnimating];
});
});
And +1 to #wattson12 - you need to add the spinner to the new view instead. Alternatively you could add the spinner to the current view, and instead put the pushViewControllercall into your GCD main-queue block.
Final point - you'll want to remove the spinner from its superview once you stop it animating. Alternatively, you can have a single instance of the spinner, and set hidesWhenStopped to YES.
This is a spinning wheel over a blurred view in swift:
func blurScence(){
let blurEffect: UIBlurEffect = UIBlurEffect(style: .Dark)
let blurView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
blurView.frame = self.view.frame
let spinner = UIActivityIndicatorView(activityIndicatorStyle:.White)
spinner.center=blurView.center
blurView.addSubview(spinner)
spinner.startAnimating()
self.view.addSubview(blurView)
}