I'm having a problem when I'm loading map view in iOS 6. But it's working 100% with previous OS versions.
Here's the code:
//.h file
MKMapView *galleryListMap; //map view
NSMutableArray *copyOfAllRecords; //array with locations
And here is the code structure in my .m file. I have synthesized variables and released them in dealloc method.
//this will runs in a background thread and populateMap method will make all the annotations
//[self performSelectorInBackground:#selector(populateMap) withObject:nil];
//here is populateMap method
-(void)populateMap{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSArray *existingpoints = galleryListMap.annotations;
//removes alll the existing points
[galleryListMap removeAnnotations:existingpoints];
//assign point to map view
for(int i = 0; i < [copyOfAllRecords count]; i++) {
Gallery *gallery = (Gallery *)[copyOfAllRecords objectAtIndex:i];//array which has the elements
//create location based on the latitude and longtitude
CGFloat latDelta = gallery.latitude;
CGFloat longDelta = gallery.longitude;
CLLocationCoordinate2D newCoord = {latDelta, longDelta};
//adds the notations
AddressAnnotation *addrAnnotation = [[[AddressAnnotation alloc] initWithCoordinate:newCoord]autorelease];
//assing the location the map
[addrAnnotation setId:i];
[addrAnnotation setTitle:gallery.name];
if([gallery.exhibition length]==0 && !gallery.isResturant){
[addrAnnotation setSubtitle:#""];
}else{
[addrAnnotation setSubtitle:gallery.exhibition];
}
**//gives the exception on this line
[galleryListMap addAnnotation:addrAnnotation];**
}
MKCoordinateRegion region;
MKCoordinateSpan span = MKCoordinateSpanMake(0.2, 0.2);
Gallery *lastGalleryItem = [copyOfAllRecords lastObject];
CLLocationCoordinate2D location = {lastGalleryItem.latitude, lastGalleryItem.longitude};
region.span=span;
region.center=location;
[galleryListMap setRegion:region animated:TRUE];
[galleryListMap regionThatFits:region];
galleryListMap.showsUserLocation = YES;
[pool release];
}
- (MKAnnotationView *) mapView:(MKMapView *)mkmapView viewForAnnotation:(AddressAnnotation *) annotation{
static NSString *identifier = #"currentloc";
if([annotation isKindOfClass:[AddressAnnotation class]]){
MKPinAnnotationView *annView = [[[MKPinAnnotationView alloc]initWithAnnotation:addAnnotation reuseIdentifier:identifier]autorelease];
Gallery *galItem = (Gallery *)[copyOfAllRecords objectAtIndex:annotation.annotationId];
CGRect viewFrame;
UIView *myView;
UIImage *tagImage;
//checks whether the resturant or not
if (galItem.isResturant) {
viewFrame = CGRectMake( 0, 0, 41, 45 );
myView = [[UIView alloc] initWithFrame:viewFrame];
tagImage= [UIImage imageNamed:#"map_restaurant"];
}else{
viewFrame = CGRectMake( 0, 0, 41, 45 );
myView = [[UIView alloc] initWithFrame:viewFrame];
tagImage= [UIImage imageNamed:galItem.mapMarker];
}
UIImageView *tagImageView = [[UIImageView alloc] initWithImage:tagImage];
[myView addSubview:tagImageView];
[tagImageView release];
UIGraphicsBeginImageContext(myView.bounds.size);
[myView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[annView setImage:viewImage];
[myView release];
int recordCount =[copyOfAllRecords count];
if (recordCount != 0 ) {
UIButton *annotationButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[annotationButton addTarget:self action:#selector(showLinks:) forControlEvents:UIControlEventTouchUpInside];
annotationButton.tag = [annotation annotationId];
annView.rightCalloutAccessoryView = annotationButton;
}
if([annotation annotationId]==recordCount-1){
//hides loading screen
//[HUD hide:YES];
self.mapAlreadyLoaded = YES;
}
return annView;
}
return nil;
}
The exception I get is:
*** Terminating app due to uncaught exception 'NSGenericException',
reason: '*** Collection <__NSArrayM: 0x21683870> was mutated while being enumerated.'
*** First throw call stack:
(0x32dc62a3 0x3259897f 0x32dc5d85 0x37c4d33b 0x37c50373 0x86e63 0x37dcc67d 0x36e52311 0x36e521d8)
But if I use:
[self populateMap];
//[self performSelectorInBackground:#selector(populateMap) withObject:nil];
the application works fine.
Any idea why this is happening?
There are a number of reasons this could be happening - the error is telling you that at some point you (or an underlying iOS library) tried to enumerate an array that changed while that process was on-going.
The fact that it works when you populateMap off the main thread and doesn't work when you call it on a secondary thread suggests there's some sort of race condition going on.
Bear in mind that not all of UIKit is thread safe (most of it isn't) - so there might be an issue there. You add the annotations to your map whilst still on a background thread:
[galleryListMap addAnnotation:addrAnnotation];
...this is also the line on which your crash happens. It stands to reason that when you add an annotation to a MapView it probably iterates through all its current annotations to update the display. Because you're making these calls off a background thread this could introduce lots of problems.
Instead, try this:
[galleryListMap performSelectorOnMainThread:#selector(addAnnotation:)
withObject:addrAnnotation
waitUntilDone:YES]
This will force the map view to add the annotation on the main thread. As a general rule, there are only a few things in UIKit that are thread-safe (the apple documentation has a complete list).
Related
I was making a program called iTahDoodle when i got an error, My error is "Method definition for 'addTask:' not found"
Here is my code:
#import "AppDelegate.h"
// Helper function to fetch the path to our ro-do data stored on disk
NSString *docPath()
{
NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[pathList objectAtIndex:0] stringByAppendingPathComponent:#"data.td"];
}
#implementation AppDelegate
#pragma mark - Application delegate callbacks
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//attempt to load an existing to-do dataset from an array stored to disk
NSArray *plist = [NSArray arrayWithContentsOfFile:docPath()];
if (plist) {
//if there was a dataset available, copy it into our instance variable
tasks = [plist mutableCopy];
} else {
//otherwise, just create an empty one to get us started.
tasks = [[NSMutableArray alloc] init];
}
//create and configure the uiwondow instance
//a cgrect is a struct with an origin (x,y) and size (width,height)
CGRect windowFrame = [[UIScreen mainScreen] bounds];
UIWindow *theWindow = [[UIWindow alloc] initWithFrame:windowFrame];
[self setWindow:theWindow];
//define the frame rectangles of three UI elements
//CGrectMake () creates a CGRect from (x,y, width, height}
CGRect tableFrame = CGRectMake(0, 80, 320, 380);
CGRect fieldFrame = CGRectMake(20, 40, 200, 31);
CGRect buttonFrame = CGRectMake(228, 40, 72, 31);
// create and configure the table view
taskTable = [[UITableView alloc] initWithFrame:tableFrame
style:UITableViewStylePlain];
[taskTable setSeparatorStyle:UITableViewCellSeparatorStyleNone];
//create and configure the text field where new tasks will be typed
taskField = [[UITextField alloc] initWithFrame:fieldFrame];
[taskField setBorderStyle:UITextBorderStyleRoundedRect];
[taskField setPlaceholder:#"Type a task, Tap Insert"];
// create and configure a rounded rect insert button
insertButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[insertButton setFrame:buttonFrame];
//buttons behave using a target/action callback
// configure the insert button's action to call this object's -addTask: method
[insertButton addTarget:self
action:#selector(addTask:)
forControlEvents:UIControlEventTouchUpInside];
//give the button a title
[insertButton setTitle:#"Insert"
forState:UIControlStateNormal];
// add our three ui elements to the window
[[self window] addSubview:taskTable];
[[self window] addSubview:taskField];
[[self window] addSubview:insertButton];
// finalize the window and put it on the screen
[[self window] setBackgroundColor:[UIColor whiteColor]];
[[self window] makeKeyAndVisible];
return YES;
}
I believe that is all the code you will need to see, but if it isn't add a comment.
You need to implement the method in your .m file.
I have attempted to insert a Cordova CleaverView (CDVViewController) into a UIViewController(SenchaViewController) instantiated from a storyboard in order to render a javascript Sencha based view.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (![ self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
CDVViewController *cdvc = [CDVViewController new];
cdvc.wwwFolderName = #"www";
cdvc.startPage = #"sencha.html";
cdvc.useSplashScreen = NO;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
cdvc.view.frame = CGRectMake(0, 0, 768, 1004);
} else {
cdvc.view.frame = CGRectMake(0, 0, 320, 460);
}
[self.view addSubview:cdvc.view];
[self.view bringSubviewToFront:self.menuButton];
[self.view bringSubviewToFront:self.favButton];
cdvc = nil;
NSString *jsSetIdString = [NSString stringWithFormat:#"fetchGuid = function(){return '%#';}", [player valueForKey:#"playerID"]];
[[cdvc webView] stringByEvaluatingJavaScriptFromString:jsSetIdString];
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
}
My problem is after I remove the parent ViewController(SenchaViewController) which owns the CDVViewController's view instance, the CDVViewController does not get released and is still running in the background as I can still see javascript logging in the console. The causes for me to have several CDVViewController all running at once.
This is how the app adds the Parent View Controller:
- (void)renderPlayer:(NSNotification*)notification {
SenchaViewController* newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SenchaView"];
NSDictionary *player = notification.object;
// Set active player
if( player != nil && [player valueForKey:#"playerID"] != #"" ){
// Attach Player Dictionary to Sencha View
newTopViewController.player = player;
// Send it to the top
CGRect frame = self.slidingViewController.topViewController.view.frame;
self.slidingViewController.topViewController = newTopViewController;
self.slidingViewController.topViewController.view.frame = frame;
}
This is how the Parent View controller which owns the CDVController is being removed
_topViewController.view removeFromSuperview];
[_topViewController willMoveToParentViewController:nil];
[_topViewController removeFromParentViewController];
Am I missing something? I was assuming ARC would dealloc this for me
Cordova has some dirty aspects, and this is one of them. Removing a CDVViewController is not enough to get it released. You have to also call the [controller dispose] method.
I want to load a UIView as the first page and some UIImageViews after that. The UIImageViews load fine, but the first UIView shows blank.
I designed the UIView in storyboard. It has some UIButtons and few other controls. What am I doing wrong?
I created the project from the default pageController template.
ModelController.m
- (id)init
{
self = [super init];
if (self) {
// Create the data model.
/*
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
_pageData = [[dateFormatter monthSymbols] copy];
*/
NSMutableArray *imageViews = [[NSMutableArray alloc] init];
FirstViewController *firstViewController = [[FirstViewController alloc] init];
firstViewController.view.frame = CGRectMake([UIScreen mainScreen].bounds.origin.x,[UIScreen mainScreen].bounds.origin.y,[[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width);
[imageViews addObject:firstViewController];
for (int i=1; i<=19; i++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:#"picture%i.jpg", i]]];
imageView.frame = CGRectMake([UIScreen mainScreen].bounds.origin.x,[UIScreen mainScreen].bounds.origin.y,[[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width);
[imageViews addObject:imageView];
}
_pageData = imageViews;
}
return self;
}
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if (([self.pageData count] == 0) || (index >= [self.pageData count])) {
return nil;
}
// Create a new view controller and pass suitable data.
DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"DataViewController"];
dataViewController.dataObject = [self.pageData objectAtIndex:index];
if(index == 0)
{
//[dataViewController addChildViewController:[self.pageData objectAtIndex:index]];
FirstViewController *firstViewController = [self.pageData objectAtIndex:index];
[dataViewController.view addSubview:firstViewController.view];
}
else
{
UIImageView *imageView = [self.pageData objectAtIndex:index];
[dataViewController.view addSubview:imageView];
}
return dataViewController;
}
You haven't provided enough context to be certain, but it appears that what you need to load from the storyboard initially is an instance of FirstViewController, not an instance of UIView, so you'd need to do something like this instead of what you're currently doing:
FirstViewController *firstViewController = [self.storyboard instantiateInitialViewController];
// Note: If you need to adjust the frame of the controller's view, use the screen's
// application frame rather than its bounds.
controller.view.frame = [UIScreen mainScreen].applicationFrame;
Also, this seems like a bad idea, since you later fill the rest of the array with instances of UIImageView:
[imageViews addObject:firstViewController];
Why not add the controller's view to the array? Then you could eliminate the conditional logic in your viewControllerAtIndex:storyboard: method. Also, it's not clear why you're passing the storyboard in as an argument rather than using the built-in property, or for that matter, what class the code you posted belongs to, and what its relation is to the rest of your app. Maybe you could provide a little more info about that.
I got it to working by adding a xib file for FirstViewController. What ever I design in the xib shows up well. Previously I was designing it in the storyboard.
I created view-based application.
I have got a sampleGameViewContrioller.xib, which contains main View, and child View, which connected with class Behavior.
SampleViewController.xib:
View
View <- Behavior
In sampleViewContoller.m I create instance of the class Behavior:
Behavior *b = [[Behavior alloc] initilizeWithType:#"type" position:rect mapArray:arrMap];
Behavior.m:
-(id) initilizeWithType:(NSString *)type position:(CGRect) pos mapArray:(NSMutableArray *) mapArr {
self = [super initWithFrame:CGRectMake(0, 0, 1024, 570)];
if (self != nil) {
if ([type isEqualToString:#"type"]) {
arrMap = mapArr;
self.rectForDraw = pos;
self.file = [[NSString alloc]initWithString:#"girl.png"];
UIImageView *imgg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"girl.png"]];
imgg.frame = rect;
[self addSubview:imgg];
timer = [NSTimer scheduledTimerWithTimeInterval:0.015 target:self selector:#selector(moving:) userInfo:nil repeats:YES];
}
}
return self;}
But it doesn't work. Image doesn't add to my View.
if I add imageView in -(void)drawRect:(CGRect) rect, it's working:
- (void)drawRect:(CGRect)rect {
self.img = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"girl.png"]];
self.img.frame = CGRectMake(100, 100, 100, 100);
[self addSubview:self.img]; }
But i should send parameters of drawing in class Behavior through constructor. How to add imageView without method drawRect, with my constructor?
sorry for my English :)
If u want to add an image view over simple UIView then just do [self.view addSubview:imageview]; If you are getting that Image from Behaviour class method then return UIImageView from that method and the add it to your view
Try this:
Behavior *b = [[Behavior alloc] init];
Then write one method in Behaviour class
-(UIImageView*) initilizeWithType:(NSString *)type position:(CGRect) pos mapArray:(NSMutableArray *) mapArr {
// Your Logic
return imgView; //UIImageView object
}
Then call this method in your viewcontroller as
UIImageView *imgView=[b initilizeWithType:#"YOUR STRING" position:YOUR RECT pos mapArray:YOUR ARRAY ];
imgg.frame = rect;
This line assigns your imageView frame to... nothing as far as I can see. Should this be pos or rectForDraw? When adding to your view in drawRect you are explicitly setting a real frame so this is probably where the difference lies.
I am using some methods to load a new .xib and go back to the main menu. However after about five time it crashes by using too much memory. I need to be able to go back to the main menu and to the game many times. Any other methods I should use for the navigation controls.
Main Menu part:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
Game part to return to main menu:
[self.navigationController popViewControllerAnimated:NO];
Here is the viewdidLoad
{
- (void)viewDidLoad {
[super viewDidLoad];
[self StartTimer];
TotalSeconds = 0;
GameCenterTotalSeconds = 0;
timeSec = 0;
timeMin = 0;
Background = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 480, 320)] autorelease];
Background.image = [UIImage imageWithContentsOfFile:[ [ NSBundle mainBundle] pathForResource:#"Background" ofType:#"png"]];
[self.view addSubview:Background];
timeLabel.textColor = [UIColor redColor];
[self.view addSubview:timeLabel];
NumberLabel = [[[UIImageView alloc] initWithFrame:CGRectMake(0, -4, 60, 70)] autorelease];
NumberLabel.image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"Number" ofType:#"png"]];
[self.view addSubview:NumberLabel];
QuestionNumber = [[[UILabel alloc] initWithFrame:CGRectMake(23, 17, 20, 20)] autorelease];
QuestionNumber.text = #"1";
QuestionNumber.textColor = [UIColor redColor];
QuestionNumber.backgroundColor = [UIColor clearColor];
[QuestionNumber setFont:[UIFont fontWithName:#"Marker Felt" size:30]];
[self.view addSubview:QuestionNumber];
numberLives = 1;
appDelegate = (OppositeMoronTestAppDelegate *)[[UIApplication sharedApplication]delegate];
musicButton = [[[UIButton buttonWithType:UIButtonTypeCustom] retain] autorelease];
musicButton.frame = CGRectMake(5, 283, 35, 35);
musicButton.backgroundColor = [UIColor clearColor];
if (appDelegate.shouldPlayMusic == YES) {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOn" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOff) forControlEvents:UIControlEventTouchUpInside];
} else {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOff" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOn) forControlEvents:UIControlEventTouchUpInside];
}
[self.view addSubview:musicButton];
[self showQuestion1];
}
}
try autorelease on your view controller:
GameViewController* game = [[[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil] autorelease];
The navigation controller will take ownership of the view controller passed to it, so you don't have to keep a reference to it. But you can't keep allocating GameViewControllers over and over without releasing them. autorelease is useful for that. You could also release it after you've passed it to the navigation controller if you prefer:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
[game release];
game = nil;
EDIT:
So if you're already releasing the game object, then it must be a memory leak within the GameViewController class itself.
Annything you alloc, copy or retain in your GameViewController class you're supposed to release in the dealloc method (and maybe also in the viewDidUnload method if you're alloc/copy/retaining in the viewDidLoad method).
The iOS Memory Management Programming Guide might be helpful if you want to get into more detail.
If you want to post the relevant code from the GameViewController class, I'm sure someone will be able to help you pin down the memory leak.
You can also try the Leaks tool in Instruments
EDIT 2:
I'm assuming you have several IBOutlets connected to properties in your GameViewController class...
don't know if you're already doing this, but in your viewDidUnload method AND on your dealloc method you have to set all of these IBOutlets properties to nil in order to release them, like so:
- viewDidUnload
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
}
- dealloc
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
[super dealloc];
}
In general, if you have any properties declared with retain, that means that when you set that property the object will be retained. If you set that property to nil, the object that was there will be released for you. So any properties with the retain keyword should be set to nil (or the backing ivar released).