setNeedsDisplay on uiimageview doesn't refresh the view immediately - objective-c

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];
}];

Related

How to bring View to top of UIView Hierarchy

My app currently has a search bar and generates a holder view of results dropping down from the search bar. However, the ResultContainerView is behind my other Views. I am wondering how to bring that search bar container view to the top of the UIView hierarchy. I tried using [super bringSubviewToFront:_searchResultsHolderView];
but that did not work. Here is my method for when the searchbar is tapped.
- (void)_expandSearchResults
{
CCLogVerbose (#"");
_searchResultsHolderView.alpha = 0.0;
_searchResultsHolderView.hidden = NO;
[_searchResultsHolderView setFrameHeight:10];
[UIView beginAnimations:#"expandSearchResults" context:nil];
[UIView setAnimationDuration:kAnimationDuration_SearchResults];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector (searchAnimationDidStop:finished:context:)];
_searchResultsHolderView.alpha = 1.0;
[_searchResultsHolderView setFrameHeight:_searchResultsHt];
[super bringSubviewToFront:_searchResultsHolderView];
[UIView commitAnimations]; // start animation
}
And this is my reloadData method:
- (void)_reloadSearchResults:(NSString *)searchS
{
CCLogVerbose (#"");
if ([searchS length] == 0) {
_searchResults = nil;
[_searchResultsTableView reloadData];
return;
}
NSAssert (self.patientService != nil, #"The Patient Service is invalid");
[self.patientService searchPatients:searchS
expandType:AllPatientExpansions
success:^(NSArray *list) {
_searchResults = list;
[_searchResultsTableView reloadData];
}
failure:^(NSError *error) {
CCLogDebug (#"Failed: %#", error);
}];
}
[self.superview bringSubviewToFront:self];
[self bringSubviewToFront:_searchResultsHolderView];
First you need to bring self to the top of the stack. After that you can call bringSubViewToFront and bring your _searchResultsHolderView to the front.

Make rounded image with storyboards

I want to make rounded photo from Facebook, but image always scales.
So i have Storyboard with next parameters:
http://prntscr.com/5bpuqy
align center X, align center Y, width equals 72, height equals 72.
I understand, that problem may be in 72/72, but image mode in storyboard is "Aspect Fit"
I call my methods for downloading image by URL and then make it cornered with radius.
// call
[UIImage setRoundImageView:self.p_photo WithURL:[p_user fullURL] withCornerSize:37];
+ (void)setRoundImageView:(UIImageView *)imageView WithURL:(NSURL *)url withCornerSize:(CGFloat)corner
{
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[[SDWebImageManager sharedManager] downloadImageWithURL:url
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image && finished)
{
[self setRoundImage:image forImageView:imageView withCornerSize:corner];
}
}];
});
}
+ (void)setRoundImage:(UIImage *)image forImageView:(UIImageView *)imageView withCornerSize:(CGFloat)corner
{
dispatch_async(dispatch_get_main_queue(), ^{
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:corner] addClip];
[image drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
}
Your code is way more complex than it needs to be. You are manually drawing the image into the image view and that is causing the problem. Try this instead:
+ (void)setRoundImageView:(UIImageView *)imageView WithURL:(NSURL *)url
{
// you don't need to wrap this in a dispatch queue, SDWebImageManager takes care of that for you.
[[SDWebImageManager sharedManager] downloadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image && finished) {
[self setRoundImage:image forImageView:imageView];
}
}];
}
+ (void)setRoundImage:(UIImage *)image forImageView:(UIImageView *)imageView
{
// you don't need to wrap this in a dispatch queue, it will be called on the main thread.
// you don't need to manually draw the image into the image view. Just add the image to the image view and let it do its thing.
imageView.layer.cornerRadius = CGRectGetHeight(imageView.bounds) / 2;
imageView.layer.masksToBounds = YES;
[imageView setImage:image];
}
And make sure the image view is set to "Aspect Fill" mode in the storyboard.

Autolayout Constraint - Keyboard

Im stuck trying to animate a table view smoothly which has an autolayout contraint. I have a reference to the constraint "keyboardHeight" in my .h and have linked this up in IB. All i want to do is animate the table view with the keyboard when it pops up. Here is my code:
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [kbFrame CGRectValue];
CGFloat height = keyboardFrame.size.height;
[UIView animateWithDuration:animationDuration animations:^{
self.keyboardHeight.constant = -height;
[self.view setNeedsLayout];
}];
}
The thing is the animation block is instantaneous and I see white space appear before the keyboard has finished its animation. So basically I see the white background of the view as the keyboard is animating. I cannot make the animation last for as long as the keyboard is animating.
Am i approaching this the wrong way? Thanks in advance!
Try it this way:
self.keyboardHeight.constant = -height;
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
Remember this pattern because this should be the correct way to update constraint-based layouts (according to WWDC). You can also add or remove NSLayoutConstraints as long as you call setNeedsUpdateConstraints after.
If you're using UITableViewController, keyboard size should be automatically accommodated by iOS to adjust the contentInsets. But if your tableView is inside a UIViewController, you probably wanted to use this:
KeyboardLayoutConstraint in the Spring framework. Simplest solution I've found so far.
Try the next code. In this case table view lays out at the bottom edge of the screen.
- (void)keyboardWillShow:(NSNotification *)notification { // UIKeyboardWillShowNotification
NSDictionary *info = [notification userInfo];
NSValue *keyboardFrameValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
BOOL isPortrait = UIDeviceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation);
CGFloat keyboardHeight = isPortrait ? keyboardFrame.size.height : keyboardFrame.size.width;
// constrBottom is a constraint defining distance between bottom edge of tableView and bottom edge of its superview
constrBottom.constant = keyboardHeight;
// or constrBottom.constant = -keyboardHeight - in case if you create constrBottom in code (NSLayoutConstraint constraintWithItem:...:toItem:...) and set views in inverted order
[UIView animateWithDuration:animationDuration animations:^{
[tableView layoutIfNeeded];
}];
}
- (void)keyboardWillHide:(NSNotification *)notification { // UIKeyboardWillHideNotification
NSDictionary *info = [notification userInfo];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
constrBottom.constant = 0;
[UIView animateWithDuration:animationDuration animations:^{
[tableView layoutIfNeeded];
}];
}
The approach I took is to add a view which follows the size of the keyboard. Add it below your tableview, or text input or whatever and it will push things up when the keyboard appears.
This is how I set up the view hierarchy:
NSDictionary *views = #{#"chats": self.chatsListView, #"reply": self.replyBarView, #"fakeKeyboard":self.fakeKeyboardView};
[self.view addVisualConstraints:#"V:|-30-[chats][reply][fakeKeyboard]|" views:views];
And then the key bits of the keyboard-size-following view look like this:
- (void)keyboardWillShow:(NSNotification *)notification
{
// Save the height of keyboard and animation duration
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardRect = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
self.desiredHeight = CGRectGetHeight(keyboardRect);
self.duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
[self animateSizeChange];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
self.desiredHeight = 0.0f;
[self animateSizeChange];
}
- (CGSize)intrinsicContentSize
{
return CGSizeMake(UIViewNoIntrinsicMetric, self.desiredHeight);
}
- (void)animateSizeChange
{
[self invalidateIntrinsicContentSize];
// Animate transition
[UIView animateWithDuration:self.duration animations:^{
[self.superview layoutIfNeeded];
}];
}
The nice thing about letting this particular view handle its resizing is that you can let the view controller ignore it, and you can also re-use this view any place in your app you want to shift everything up.
The full file is here:
https://gist.github.com/shepting/6025439

HUD Spinner does not appear when saving image to library

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.

fade in collection of thumbnails on UIScrollView one at a time

I have a large collection of thumbnails I display on a UIScrollView and I would like them to fade in nicely one at a time. Right now they all fade in together after they are all loaded to the scrollview simply by hiding and showing the scrollview. This is OK but it can take a long time for 500 images to be loaded.
I have tried several different variations of what I though would work by using background threads.
I thought this would work for sure but no luck. I have a NSMutableArray of all of my UIImageViews that I have added to the scroll view, so once they are all in place and the scrollview is sized I call:
[self performSelectorInBackground:#selector(addThumbnailToScrollView) withObject:values];
-(void) addThumbnailToScrollView: (NSDictionary *) aDictionary {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Collection Item Count: %i, imageViewCount: %i", [self.collectionItems count],[self.imageViews count]);
UIImage *img = [Helpers getThumbnailImage:[aDictionary valueForKey:#"PhotoName"] withManufacturer:manufacturerID];
[(UIImageView*)[self.imageViews objectAtIndex:[[aDictionary valueForKey:#"i"] intValue]] setImage:img];
[(UIImageView*)[self.imageViews objectAtIndex:[[aDictionary valueForKey:#"i"] intValue]] setAlpha:0];
[UIView beginAnimations:#"fade" context:nil];
[(UIImageView*)[self.imageViews objectAtIndex:[[aDictionary valueForKey:#"i"] intValue]] setAlpha:1];
[UIView commitAnimations];
[pool drain];
}
The UIView animation cause a crash because it is not done before the objects are released I guess. Even when I remove the animation bit they image still pop onto the screen in large groups versus 1 by 1.
You can try this
-(void)animateTumbs:(NSInteger)index{
index++;
if ([self.imageViews count]
[UIView animateWithDuration:0.2
animations:^{
[(UIImageView*)[self.imageViews objectAtIndex:[[aDictionary valueForKey:#"i"] intValue]] setAlpha:1];
}
completion:^(BOOL finished){
[self animateTumbs:index];
}];
}
Or you can try:
[self performSelectorInBackground:#selector(addThumbnailToScrollView) withObject:values afterDelay:0.2];
Good luck