UIProgressView does not update - ios6

If it matters:
- I am using storyboards
- Core data
- xcode 4.6
My app has a UITableViewController for a specific view. On that view, if the user clicks a button the software goes through a few processes where data is downloaded from an Internet API and saved into core data. I'm confident this is a resource hog, which is why I am attempting to complete those processes in separate threads.
Note:
- There is an order of operations because legs are dependent on exchanges. Exchanges are dependent on the race and positions. Positions are dependent on the race. Otherwise I would have executed everything asynchronously.
Issues:
- This is my first time working with Grand Central Dispatch. I'm not sure I am doing it correctly.
- If I comment out the data processing the UIProgressView is visible and updates as expected. With the data processing in place the system seems too bogged down to even display the UIProgressView.
The methods managing the downloads and progress is below.
- (IBAction)downloadNow:(id)sender {
[progressView setHidden:NO];
[progressView setProgress:0.1 animated:YES];
dispatch_sync(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
});
dispatch_sync(backgroundQueue, ^(void){
[self savePositions];
[self updateProgress:0.3];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
});
dispatch_sync(backgroundQueue, ^(void){
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
});
dispatch_sync(backgroundQueue, ^(void){
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
}
(void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
[progressView setProgress:completedPercentage animated:YES];
}
}
Any help is greatly appreciated.

The method dispatch_sync will block the calling thread, which I believe in your case is the main thread. So I think it is better to wrap those dispatch_sync blocks into one dispatch_async block.
As an example:
dispatch_async(backgroundQueue, ^(void){
[self saveRace];
[self updateProgress:0.2];
[self savePositions];
[self updateProgress:0.3];
[self downloadExchanges];
[self saveExchanges];
[self updateProgress:0.4];
[self downloadLegs];
[self saveLegs];
[self updateProgress:0.5];
Utilities *utilities = [[Utilities alloc] init];
[utilities calculateStartTimes:race with:managedObjectContext];
[self updateProgress:1.0];
});
After that you can wrap the updating of the progressView to be done in main thread because it is a UI update.
- (void)updateProgress:(double)completedPercentage {
if (completedPercentage == 1.0) {
[self goHome];
} else if ([importExchanges count] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[progressView setProgress:completedPercentage animated:YES];
});
}
}

Related

First table cell is covered by contact input field on MFMessageComposeViewController

- (void)sendSmsWithSubject:(NSString *)subject andBody:(NSString *)body {
MFMessageComposeViewController *smsCompose = [[MFMessageComposeViewController alloc] init];
smsCompose.subject = subject;
smsCompose.body = body;
smsCompose.messageComposeDelegate = self;
[self presentViewController:smsCompose animated:YES completion:nil];
}
As you can see on the attached screenshot, the first table cell "TestA" is covered by the text field on MFMessageComposeViewController. Above are the code snippet.
It seems like a bug on MFMessageComposeViewController, however, after doing some searches on stackoverflow, etc, there is no record for that "issue". Is there anything wrong or missed on my code? Thanks in advance for your help.
Notes: This UI issue is not happen on iOS10 devices.
Finally figured out that, in my case, the issue was caused by:
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
As fix/workaround:
- (void)sendSmsWithSubject:(NSString *)subject andBody:(NSString *)body {
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentAutomatic];
MFMessageComposeViewController *smsCompose = [[MFMessageComposeViewController alloc] init];
smsCompose.subject = subject;
smsCompose.body = body;
smsCompose.messageComposeDelegate = self;
[self presentViewController:smsCompose animated:YES completion:nil];
}
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
// Disable contentInsetAdjustmentBehavior when leave MFMessageComposeViewController.
[[UIScrollView appearance] setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
[self dismissViewControllerAnimated:YES completion:nil];
}
Hope this is helpful to someone else who meet the same issue. Thanks.

IOS Object respawn on timer after I set its ID to nil

DemoViewController is responsible for showing tutorial to the user. Contains animations and timer to repeat gesture demo while ignored by the user. Is instantiated form DataViewController. Is nil-ed, but later respawns on its internal timer. I need it to be completely gone so it is not created again when user returns to the first page.
dataViewController.h
#import "DemoViewController.h"
#property (strong,nonatomic) DemoViewController *demoController;
dataViewController.h
-(void) viewWillAppear:(BOOL)animated {
// demoPageNumber is 0
if ((self.demoController== nil) && ([_pageNumber isEqualToNumber:demoPageNumber])){
self.demoController = [[DemoViewController alloc] initWithView:self.view];
}
}
-(void) viewWillDisappear:(BOOL)animated{
[self.demoController free]; // invalidate timer, nil all internal objects
self.demoController=nil; // should free object
}
DemoViewController.m
-(void) free{
[animationRespawnTimer invalidate];
animationRespawnTimer=nil;
}
-(void) respawnDemoWithSelector:(SEL)selector{
NSLog(#"Timer fired %#", self);
[self resetTimer];
animationRespawnTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:selector
userInfo:nil
repeats:NO];
}
-(void) showScrollDemo{
NSLog(#"showScrollDemo fired");
[self stopPreviousAnimations];
scrollHandView.frame = CGRectMake(315.0, 700.0, 100, 100);
scrollHandView.hidden=NO;
scrollHandView.alpha=1;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
scrollHandView.alpha=0;
}
completion:^(BOOL finished){
scrollHandView.hidden=YES;
[self respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
}
When the page is loaded and if this is the first page, demoController is instantiated, and on exit from the page nil-ed after clean-up (custom free method). According to my understanding this should delete the demoController object with all its contents including the timer. And debug area shows exactly that! Until when on the new page the demoController timer mystically respawns from nowhere with previous object ID.
17:59:14.041 viewWillAppear begin (self.demoController null)
18:00:05.346 viewWillAppear, <DemoViewController: 0x7580310> //demoController created
18:00:15.786 in the demoController method the "showScrollDemo" is fired
18:00:19.834 viewWillAppear end <DemoViewController: 0x7580310>
Page was loaded, demo performed fine. Now I'm flipping the page. viewWillDisappear event is fired.
18:01:17.966 viewWillDisappear begin, send "free" message to demoController
18:01:17.966 "free" was performed from <DemoViewController: 0x7580310>
18:01:34.059 viewWillDisappear end (self.demoController null)
So, the "self.demoController" is null. Then the demoController respawns itself with previous ID
18:02:36.514 Timer fired <DemoViewController: 0x7580310>
Why? Timer can not respawn, it is set to repeats:NO.
I assume that it is the completion block of the animation that calls respawnDemoWithSelector and creates a new timer.
According to this answer: https://stackoverflow.com/a/9676508/1187415, you can stop all running
animations with
[self.view.layer removeAllAnimations];
Alternatively, you can add a boolean property done to the DemoViewController which is set
to YES in the free method, and checked in the completion block of the animation:
if (!self.done)
[self respawnDemoWithSelector: #selector(showScrollDemo)];
UPDATE: The animation blocks capture a strong reference to self, thus preventing
the object from being deallocated. The standard solution to that "retain-cycle" problem
(assuming that you use ARC) is to use a weak reference to self. That would look like this:
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:3.0
delay: 0.0
options: (UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionRepeat )
animations:^{
[UIView setAnimationRepeatCount:3];
weakSelf.scrollHandView.frame = CGRectMake(315.0, 300.0, 100, 100);
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
weakSelf.scrollHandView.alpha=0;
}
completion:^(BOOL finished){
weakSelf.scrollHandView.hidden=YES;
[weakSelf respawnDemoWithSelector: #selector(showScrollDemo)];
}
];
}
];
weakSelf does not hold a strong reference to the DemoViewController and is set to nil
automatically if the object it points to is deallocated. In that case, all message sent to weakSelf inside the blocks do just nothing.

ivar is releasing under ARC - how do I retain it for use in another method?

I have been struggling with something for weeks and it has brought a real halt to my progress. I have asked a question a few times on SO, people have been helpful but no-one has cracked what I am doing wrong. It seems a fairly simple thing so hopefully someone out there will have a lightbulb moment and solve this. I am implementing a TWRequest, the result is coming back in a dictionary, I am looping through the results to extract a part of the tweet and creating an array of these 'text' components. Straight adter the loop through I am peinting the log of the array - _twitterText and it prints fine. Staright after this method is complete it seems as though _twitterText is being dumped. I have created it in my .h file as a strong property and created an ivar in viewdidload. Still no joy. how do I retain this array to use in another Method?
Here is my .h file....
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "CustomCell.h"
#import "AppDelegate.h"
#import <Twitter/Twitter.h>
#interface MyViewController : UITableViewController <CLLocationManagerDelegate>
{
CLLocationManager *here;
}
#property(strong) NSDictionary *dict;
#property(strong) CLLocationManager *here;
#property (strong, nonatomic) NSMutableArray *twitterText;
- (void)fetchTweets;
#end </p>
Here is my .m implementation file......
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
#synthesize dict;
#synthesize twitterText = _twitterText;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_twitterText = [[NSMutableArray alloc] init];
here = [[CLLocationManager alloc] init];
here.delegate = self;
[here startUpdatingLocation];
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
NSLog(#"phrase carried over is %#", delegate.a);
[self fetchTweets];
}
- (void)fetchTweets
{
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
#"http://search.twitter.com/search.json?q=%40wimbledon"]
parameters:nil requestMethod:TWRequestMethodGET];
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if ([urlResponse statusCode] == 200)
{
// The response from Twitter is in JSON format
// Move the response into a dictionary and print
NSError *error;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
//NSLog(#"Twitter response: %#", dict);
NSArray *results = [dict objectForKey:#"results"];
//Loop through the results
for (NSDictionary *tweet in results) {
// Get the tweet
NSString *twittext = [tweet objectForKey:#"text"];
// Save the tweet to the twitterText array
[_twitterText addObject:twittext];
}
NSLog(#"MY ************************TWITTERTEXT************** %#", _twitterText);
}
else
NSLog(#"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
//cell.venueDetails.text = [_twitterText objectAtIndex:indexPath.row];
NSLog(#"MY ************************OTHER BIT THAT WONT PRINT************** %#", _twitterText);
return cell;
}
So the issue here is that your completion handler that you pass to -[TWTweet performRequestWithHandler:] isn't going to (can't) fire until the network connection is complete and the server responds to your request. That could take hundreds of milliseconds or even seconds to complete. (Or it may never happen).
Meanwhile while that is happening, the UITableView wants to draw itself and so will ask you how many sections/rows you have and then will ask you for a cell for each row. So when the table view asks, you should return the actual number of rows you have to draw at that time:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.twitterText count]; // the actual number of rows we have right now
}
So then the next step you need is to reload the table when your data is actually in from the server. That will prompt your table view to ask again for the number of sections and rows, and then ask for cells for each section and row. So somewhere in your completion block after you've processed all your data you will need to do this:
dispatch_async(dispatch_get_main_queue(), ^{
// you'll need an outlet to the UITableView
// here I assume you call that 'tableView'
// then just ask it to reload on the main thread
[self.tableView reloadData];
});
I hope that helps?

CoreData leaks with fetched results arrays

I'm using CoreData to store objects like cars, trips, data recorded from GPS, etc.
When I fetch what I want to show a list of trips, some stats for a trip, or add a new car in my settings view controller, I use pretty much this kind of request:
- (void)getDataTrip
{
// Fetched data trips.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DataTrip" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
// Set predicate and sort orderings...
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timestamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idTrip = %#", self.idTrip];
[fetchRequest setPredicate:predicate];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"failed with error: %#", error);
}
// Set the array.
[self setDataTripArray:mutableFetchResults];
// Memory management.
[fetchRequest release];
[mutableFetchResults release];
}
Sometimes, I have leaks when I do the [self setDataTripArray:mutableFetchResults]; and sometimes not. In this case, when I get the data for a trip, it leaks all the time when I use the navigation controller to come back to the root view controller (displaying a list of trips), and/or when I change tab.
Anyway, it just leaks and it's all the time coming from fetching data from CoreData, and give this array to my local array var.
Please let me know if you see how to fix this! It made the app crash after a while.
Thanks!
SOLUTION
I found that I do a retain on my object dataTripArray object when creating another UIViewController that I use to create graphs for my scroll view.
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// Replace the placeholder if necessary.
GraphController *controller = [self.graphControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[GraphController alloc] initWithPageNumber:page data:[self.dataTripArray retain]];
[self.graphControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// Add the controller's view to the scroll view.
if (controller.view.superview == nil)
{
CGRect frame = _scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[self.scrollView addSubview:controller.view];
}
}
I just removed the retain and the leak is no longer coming up. Fixed!

calloutAccestoryControlTapped method won't be used

I have a problem using the -(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control method. I tried many ways to test it but it just won't appear. Can anyone find a mistake
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)anno{
MKPinAnnotationView *retval=nil;
if(retval ==nil){
retval=[[[MKPinAnnotationView alloc]initWithAnnotation:anno reuseIdentifier:#"Annotations"]autorelease];
UIButton *disclosure=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
retval.rightCalloutAccessoryView=disclosure;
[retval setPinColor:MKPinAnnotationColorGreen];
retval.animatesDrop=YES;
retval.canShowCallout=YES;
}
return retval;
}
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccesoryControlTapped:(UIControl *)control{
NSLog(#"Tap");
PhotoDetail *detail=[[PhotoDetail alloc]initWithNibName:#"PhotoDetail" bundle:nil];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:detail];
[self presentModalViewController:nav animated:YES];
}
Make sure you implemented the
MKMapViewDelegate protocol
in the parent ViewController.

Resources