Trying to prevent memory leak - ios

I am trying to do some experiment.
- (IBAction)btn1Action:(id)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"popvc2id" sender:self];
});
NSLog(#"TAP");
}
When button will tap it will take 1 second to perform segue and when this button tapped again it will trigger segue twice, so two instance of ViewController will be created.
In instruments I can see two instances but one of them is leaked VC object.
Now what I am trying to do is
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
VC2 *vc2 = [segue destinationViewController];
[ary addObject:vc2];
if(ary.count > 1) {
VC2 *vc = (VC2*)ary[1];
vc = nil;
[ary removeObjectAtIndex:1];
}
[ary removeAllObjects];
NSLog(#"-> %#", vc2);
}
to keep a record of VC objects and try to destroy the second obj, so I can prevent memory leak.
But its not working, how to I can fix it?

If you want to cancel your previous request. My suggestion is using NSObject CancelPreviousRequest method
How to implement:
- (IBAction)btn1Action:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(delayedAction) object:nil];
[self performSelector:#selector(delayedAction) withObject:nil afterDelay:1];
}
-(void)delayedAction{
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"popvc2id" sender:self];
});
}

- (IBAction)btn1Action:(id)sender {
__block UIButton * btn = (UIButton*) sender;
btn.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"popvc2id" sender:self];
btn.enabled = YES;
});
NSLog(#"TAP");
}
Wrote by memory, may be compile errors here

Related

dismissViewControllerAnimated is not dissmissing ViewController

So....I have a View Controller and when I press a button, another View Controller appears:
- (IBAction)searchButtonPressed:(id)sender {
[self presentViewController:self.controllerSearch animated:YES completion:nil];
}
Inside view controller number 2 is a table view and when a row is selected in a table this code runs:
NSString *phrase = nil; // Document password (for unlocking most encrypted PDF files)
NSString *filePath2 = filePath; assert(filePath2 != nil); // Path to first PDF file
LazyPDFDocument *document = [LazyPDFDocument withDocumentFilePath:filePath2 password:phrase];
if (document != nil) // Must have a valid LazyPDFDocument object in order to proceed with things
{
LazyPDFViewController *lazyPDFViewController = [[LazyPDFViewController alloc] initWithLazyPDFDocument:document];
lazyPDFViewController.delegate = self; // Set the LazyPDFViewController delegate to self
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
}
else // Log an error so that we know that something went wrong
{
NSLog(#"%s [LazyPDFDocument withDocumentFilePath:'%#' password:'%#'] failed.", __FUNCTION__, filePath2, phrase);
}
Now I am using LazyPDFKit and it comes with this delegate method:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
I put a break point and I can see my code goes into the delegate method, but the LazyPDFViewController does not go away.
I have tried the following:
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
but that takes me back a few view controllers to far.
Am I missing something?
Additional code in my first view Controller .h
#property (strong, nonatomic) UISearchController *controllerSearch;
and in first view controller .m
- (UISearchController *)controller {
if (!_controllerSearch) {
// instantiate search results table view
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
LHFileBrowserSearch *resultsController = [storyboard instantiateViewControllerWithIdentifier:#"SearchResults"];
// create search controller
_controllerSearch = [[UISearchController alloc]initWithSearchResultsController:resultsController];
_controllerSearch.searchResultsUpdater = self;
// optional: set the search controller delegate
_controllerSearch.delegate = self;
}
return _controllerSearch;
}
If you are pushing the view controller:
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
Then the code in the delegate doesn't make sense, because it assumes it is a modal view controller that needs to be dismissed:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
But you've added it to the navigation stack (I assume).
If you can't pop it again from the navigation controller at this point you are missing some code in your example.
Are you sure your delegate is firing on the main thread? Try:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}
try this:
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[[viewController presentingViewController] dismissViewControllerAnimated:YES completion:nil];
}
your code :
[[[self presentingViewController] presentingViewController] dismissViewControllerAnimated:YES completion:nil];
just went too far.
I just made the demo project based on your situation. And I am not facing any issue with it. So I think there might be some issue regarding how you are presenting the second controller.
In your button click, try this code:
- (IBAction)searchButtonPressed:(id)sender {
UIStoryboard *main = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
//idSecondVC is the storyboard id of second view controller
SecondVC *SecondVC = [main instantiateViewControllerWithIdentifier:#"idSecondVC"];
[self presentViewController:SecondVC animated:YES completion:nil];
}
And in your controller number 2, I just used the above code:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
cell.textLabel.text = [NSString stringWithFormat:#"Cell %ld",indexPath.row];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self openLazyPDF];
}
- (void)openLazyPDF
{
NSString *phrase = nil; // Document password (for unlocking most encrypted PDF files)
NSArray *pdfs = [[NSBundle mainBundle] pathsForResourcesOfType:#"pdf" inDirectory:nil];
NSString *filePath = [pdfs firstObject]; assert(filePath != nil); // Path to first PDF file
LazyPDFDocument *document = [LazyPDFDocument withDocumentFilePath:filePath password:phrase];
if (document != nil) // Must have a valid LazyPDFDocument object in order to proceed with things
{
LazyPDFViewController *lazyPDFViewController = [[LazyPDFViewController alloc] initWithLazyPDFDocument:document];
lazyPDFViewController.delegate = self; // Set the LazyPDFViewController delegate to self
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
}
else // Log an error so that we know that something went wrong
{
NSLog(#"%s [LazyPDFDocument withDocumentFilePath:'%#' password:'%#'] failed.", __FUNCTION__, filePath, phrase);
}
}
#pragma mark - LazyPDFViewControllerDelegate methods
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
// dismiss the modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
}
And for me everything is working fine.
Looks like you need to the same macro for present as dismiss. So, you wrote
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController pushViewController:lazyPDFViewController animated:YES];
#else // present in a modal view controller
lazyPDFViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
lazyPDFViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:lazyPDFViewController animated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
You thus need
#if (DEMO_VIEW_CONTROLLER_PUSH == TRUE)
[self.navigationController popViewControllerAnimated:YES];
#else // presented in a modal view controller
[self dismissViewControllerAnimated:YES completion:NULL];
#endif // DEMO_VIEW_CONTROLLER_PUSH
It's possible that you have switched off the main thread and you can always add an assert to be check or, as has been suggested, use a dispatch_async to be certain.
NSAssert([NSThread isMainThread)];
I prefer the assert, when I know all the flows through a piece of code, since it shows my assumptions to the future me (or another) and does not leave code that looks like it knows something I do not (oh, they are using dispatch_async onto main so there must be some other thread magic going on deeper down).
- (void)dismissLazyPDFViewController:(LazyPDFViewController *)viewController
{
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[self dismissLazyPDFViewController:viewController];
});
return;
}
if (viewController.navigationController)
{
[viewController.navigationController popViewControllerAnimated:YES];
}
else
{
[viewController dismissViewControllerAnimated:YES completion:nil];
}
}

UISearchController's UISearchDisplayController not showing

Couple of months ago I asked a question to show the UISearchDisplayController and it worked like a charm but
Now I am presenting another controller after user taps on the searchResultsController's tableview cell and it selects a cell and hides the searchResultsController to show MapView but once you tap the search bar searchResultsController doesn't shows up but once you press cancel button and tap search bar again it starts working again.
Scenario is exactly the Apple's Maps app opening up favourite view along with recent view controller with segments.
Here's my code for UISearchControllerDelegate:
- (void)willPresentSearchController:(UISearchController *)searchController
{
mainViewController *__weak weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
mainViewController *__strong strongSelf = weakSelf;
strongSelf.searchController.searchResultsController.view.hidden = NO;
});
}
- (void)didPresentSearchController:(UISearchController *)searchController
{
self.searchController.searchResultsController.view.hidden = NO;
}
And here is my code for UISearchBarDelegate.
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar{
mainViewController *__weak weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
mainViewController *__strong strongSelf = weakSelf;
strongSelf.searchController.searchBar.showsCancelButton = YES;
});
self.searchController.searchResultsController.view.hidden = NO;
return YES;
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar{
mainViewController *__weak weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
mainViewController *__strong strongSelf = weakSelf;
strongSelf.searchController.searchBar.showsCancelButton = NO;
});
return YES;
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
mainViewController *__weak weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
mainViewController *__strong strongSelf = weakSelf;
strongSelf.searchController.searchResultsController.view.hidden = YES;
});
[searchBar resignFirstResponder];
}];
}

Send captured image to another UIViewController with prepareForSegue

I'm trying to send the captured image to another UIViewController.
So when I press captured button, it's taking a photo and I can save the image in my camera roll. But when I want to see image in another view I can't see it.
This is my code :
- (IBAction)captureButton:(id)sender
{
[self.cameraViewController captureImageWithCompletionHander:^(id data) {
self.image = ([data isKindOfClass:[NSData class]]) ? [UIImage imageWithData:data] : data;
UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
[self performSegueWithIdentifier:#"savingSegue" sender:self];
}];
}
And this is the prepare for segue method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"savingSegue"]) {
PhotoSaveViewController *pvc = [[PhotoSaveViewController alloc] init];
[pvc.imageView setImage:self.image];
}
}
If your second view is a PhotoSaveViewController then replace your prepareForSeque method with this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"savingSegue"]) {
PhotoSaveViewController *pvc = (PhotoSaveViewController *)segue.destinationViewController;
if (pvc)
[pvc.imageView setImage:self.image];
}
}
Indeed, it's not your job to instantiate the destination UIViewController, your UINavigationController have already done that.
I guess that imageView on PhotoSaveViewController it is outlet and it is not set at this moment. You should declare there some other property like
#property (nonatomic, retain) UIImage *inputImage;
and assign to that in prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"savingSegue"]) {
PhotoSaveViewController *pvc = segue.destinationViewController;
pvc.inputImage = self.image;
}
}
Then do the following in the PhotoSaveViewController:
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView.image = self.inputImage;
}
Another approach you can create it from code totally. Assign Storyboard ID to the PhotoSaveViewController and then do the following:
- (IBAction)captureButton:(id)sender {
[self.cameraViewController captureImageWithCompletionHander:^(id data) {
self.image = ([data isKindOfClass:[NSData class]]) ? [UIImage imageWithData:data] : data;
UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil);
PhotoSaveViewController *dvc = [self.storyboard instantiateViewControllerWithIdentifier:#"PhotoSaveViewControllerID"];
dvc.inputImage = self.image;
[self.navigationController pushViewController:dvc animated:YES];
}];
}

MWPhotoBrowser will not display photo

I'm trying to implement MWPhotoBrowser in my project. I've added all the delegates, but it still will not display the photo.
Here's my didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"MWPhotoBrowserSegue" sender:self]
}
This is my segue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue identifier] isEqualToString:#"MWPhotoBorwserSegue"])
{
photoBrowser = [[MWPhotoBrowser alloc]initWithDelegate:self];
UINavigationController * nc = [[UINavigationController alloc]initWithRootViewController:photoBrowser];
nc.modalTransitionStyle = UIModalTransitionStyleCrossDisolve;
[photos addObject:[MWPhoto photoWithImage:[UIImage imageNamed:#"picasa.jpg"]];
[photoBrowser setCurrentPhotoIndex:0];
[self.navigationController presentViewController:nc animated:YES completion:nil];
}
}
The #ofPhotosInBrowser:
-(NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser
{
return self.photos.count;
}
photoAtIndex:
- (MWPhoto *)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index {
if (index < self.photos.count)
return [self.photos objectAtIndex:index];
return nil;
}
Here's what I get after the didSelectRowAtIndexPath: is called:
Edit for Solution:
MWPhotoBrowser will not display a photo unless you put self.title in the initWithStyle: method. I also forgot to add the viewWillDisappear and viewWillAppear
I think u need to call reload data on it. Check Mwphotobrowser.h for proper way to do it.
try this [[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self presentViewController:yourNewNavigationController animated:YES completion:nil];
}];

activity indicator appearing only for a second

I have 2 views
1) A
2) B
When I segue from view A to view B, it takes a long while to load, so I added an activity indicator in the segue.
My problem is, when I segue over to view B, my screen freezes(loads) in view A and only for a split second, it shows the activity indicator before going onto view B.
How do I make sure the activity indicator appears before it starts to load.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
[self useActivityIndicator];
if ([[segue identifier] isEqualToString:#"ShowAdsDetail"])
{
//do anything that needs to be done
}
}
-(void)useActivityIndicator{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityView startAnimating];
subView.hidden = NO;
}
I had a similar issue with a UIActivityIndicator that was animating too fast. The segue was pushing the next view controller right away and barely showing the activity indicator.
In case this can help others - I was able to fix it by implementing performSelector:withObject:afterDelay and then, in the removeSpinner method, calling my performSegueWithIdentifier method.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
[self.spinner startAnimating];
[self performSelector:#selector(removeSpinner:) withObject:self.spinner afterDelay:2.0];
}
- (void)removeSpinner: (UIActivityIndicatorView *)spinner {
[self.spinner stopAnimating];
[self.spinner removeFromSuperview];
[self performSegueWithIdentifier:#"showMyPlans" sender:nil];
}
Just change your code like:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self useActivityIndicator];
});
if ([[segue identifier] isEqualToString:#"ShowAdsDetail"])
{
//do anything that needs to be done
}
}
-(void)useActivityIndicator
{
dispatch_async(dispatch_get_main_queue(),^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[activityView startAnimating];
subView.hidden = NO;
});
}

Resources