I am adding 54 annotations to an MKMapView in a loop like this:
for (Item *item in items) {
[mapView addAnnotation:item];
}
I am logging when this completes and it never takes long, but there is a very long delay before the annotations appear on my map view. What is going on? How can I get the annotations to appear faster?
Update: the data set I am using is not small and I am holding it entirely in memory. I have noticed the occasional level 1 or 2 memory warning. Could this impact MKMapView performance?
There's nothing wrong with that code for adding annotations. The problem is probably somewhere else. Maybe you're getting the annotations from a web service. Maybe your annotation view objects are very large or complex.
If you want to try a different approach, use addAnnotations: to add all annotations at once, but I don't think it will make a difference.
Related
I have a view controller that has a private NSArray variable. The variable is initialised in the viewDidLoad method. A few questions arise for the case when the didReceiveMemoryWarning is called:
Should I set the private variable to nil?
If I set it to nil in what method must it be recreated? Does the view controller call the viewDidLoad method to recreate it?
I'm asking because other methods of the view need this variable and won't work if it's nil.
Thank you!
Typically you unload a private property by assigning nil via the setter (e.g. self.propertyName = nil). Or you could set the ivar to nil after calling release, e.g. [_propertyName release]; _propertyName = nil;, but the former is preferable.
The didReceiveMemoryWarning method is called when there is a low memory situation. It is called on every view controller, including the one(s) responsible for the currently visible UI!
Therefore, you can't just unload data arbitrarily when you get a call to didReceiveMemoryWarning -- the view controller might need that data if it is currently visible on the display.
The general principle is that didReceiveMemoryWarning can get rid of any resources it can to assist freeing up memory, but only those that aren't immediately needed. For example, in an OpenGL game you wouldn't unload textures that are currently visible on the display. However, see my last paragraph.
Typically you reload the resources by checking they are loaded when you need them, and if not, loading them.
It's not worth niling/releasing tiny resources such as a single normally sized string. You should concentrate on items taking up significant amounts of memory.
Recent advances in behind the scenes memory management mean you're less likely to need to actually unload data these days - the operating system can unload and reload uncompressed image data and the like behind the scenes.
As Hot Licks mentions, the simulator has an option for simulating a memory warning. It's worth triggering this memory warning at various points in your app to see how it behaves.
Create custom getter that load data lazily. Something such as this snippet is good for non-mutithreading eviroment:
- (NSArray*) dataArray {
if(_dataArray) return _dataArray;
_dataArray = [self lordata];
return _dataArray;
}
In this way data are always reloaded if you "release" them in memory warnings
The ViewDidLoad method is called only once, when your ViewController is initialized. If you must reload some data to your NSArray, you should call your own methods to do that when needed.
If this array is used by various parts of the code, maybe you should think about redesigning your code structure to avoid a huge concentration of data inside only one object.
Edit: As pointed by #occulus in the comments below, it is not called when the View is initialized, but when the View is loaded by the ViewController.. my mistake
As an example, I had an app that downloaded data to a very long table view (potentially 1000s of records). To support this I implemented a "sparse" array, allowing empty elements that would be "faulted in" over the net when referenced (with a "downloading" indicator in the table cell while downloading).
This was rigged so that when didReceiveMemoryWarning occurred the array would be purged, using a least-recently-used algorithm to delete the oldest N% of the array. Recovery would be automatic -- the emptied cells would reload when they were referenced.
Not that I recommend this specific scheme, but note the general features of having a lot of data, having a way to "prioritize" what should be deleted, and having a "soft" way to reload the data (ideally only reloading the parts that are needed in the near future).
Its better to set your variable to nil. I mean release the memory it is holding in didReceiveMemoryWarning and set a dirty flag.
You can always check the dirty flag in array's getter(you can write your own) and repopulate it. It is probably not the best way. It entirely depends on the array's usage.
I have 4-5 kinds of different annotations classes in mapView.
With following code I expect only AnnotationType1 should respond to for loop.
for (AnnotationType1* annotation in mymap.annotations)
{
NSLog(#"annotation class is %#", [annotation class]);
}
But as is evident from console I get other classes also.
annotation class is AnnotationType1
annotation class is AnnotationType2
annotation class is AnnotationType3
annotation class is AnnotationType4
what will be the best way to perform actions only on say AnnotationType1 annotation?
First, as you've discovered, fast iteration doesn't work the way you thought it did. mymap.annotations returns the same array of annotation objects no matter what -- it doesn't have any idea what kind of pointer you're assigning them to.
Second, it's usually considered a bad idea to count on a view (such as MKMapView) to store data (like your annotations). It's fine for the map view to know about the annotations -- it must know about them to do its job properly. But I wouldn't recommend counting on the map view to maintain the app's state. You probably have the annotation objects stored somewhere in your data model -- if so, that'd be a better place to get the list of annotations.
Third, you can filter the array using a predicate. See this answer for help using a predicate to filter by class name.
Alright, I know this is a vague conceptual question, but I really need help here. Thanks in advance if you decide to take the time to read this. I would never even consider writing this much except this is such a great forum with so many helpful people I thought this would be the best place to ask.
This is all related to the question here (you don't have to look at it - I explain everything below): Pass parameter when initializing table
I've been working for days on the same problem, but I'm realizing there must be something big I'm missing. I've googled and googled and I even bought (and about 50% read) two Obj-C books a few days ago, but I'm still grasping at something that seems like it should be incredibly easy. I'm obviously pretty new to OOP, but I have mediocre skills in HTML, perl, sql, python as well as some of the ancient stuff like pascal and basic. I'm a n00b, but not a moron (well, actually this experience is changing my mind a bit).
Anyway, my end goal here is to simply create an app with 8 "normal" buttons on the first view (level 1), each doing basically does the same thing - which is to show a simple table view (level 2) with data in cells that can be clicked to continue to drill down to details (level 3). Very basic and straightforward concept. The only difference between the 8 possible level 2's is the data that will be shown. I've already constructed sql queries that work just as I want for each button.
So, here's where I stand: I have a perfectly working app that does everything from level 2 down exactly as I expect - the queries work, the tables are beautiful - so that's great.
Also, I have another nav-based app that launches on "level 1" and shows me 8 buttons (I hide the nav bar on level 1). If I click any of the buttons on level 1, the level 2 view (which is a nav bar + a table) slides into view exactly like I want. The problem is the table is just blank. No matter what I do, I can't get the level 2 in the second app to show me the data, even though I can show all of that data in the first app perfectly. For the life of me, I can't figure out how to "link" level 1 with level 2.
Hopefully you can understand this gap I'm trying to bridge. Since there are 8 possibilities for level 2 (with only very slight differences in sql queries on the same sql table), I initially tried coming up with a way of "passing" an integer to the level 2 view (in the first app) and then selecting the sql query based on what was passed (see the link above for that fiasco). Once I got that working, I planned to figure out how to make the buttons do the "passing" later. However, after about 16 hours screwing with that, I just gave up and decided to make 8 different table view controllers, all with nearly identical code except the query. That way, if I could just get a SINGLE button on level 1 to simply push to just ONE of the level 2's with NO parameters I would be a horrible but successful programmer.
Unfortunately, even that hasn't worked out for me. I have tried every possible control-drag and window/view/table combination I can think of in Interface Builder, but no matter what I try, the data never loads into the table view, even though it works great in my first app. I have gone through every line of code - they are the same except something has to "call" or "launch" the level 2 part and I'm just not getting it.
So, I'm going to break with convention/expectations here and not post any code in my question. I just want to know - how can this possibly be so difficult?? I am very analytically minded and I catch on quickly, but I have to say I have never been so humbled by a technical challenge in my life.
Can anyone explain to me, at a conceptual level, what I need to be doing here or what I'm missing? Even if you give me a link to something to read I would appreciate it very much. I have watched tens of hours of tutorials on youtube, but I'm always up for more.
Of course I'm willing to share my code, but there is so much of it and I'm so new at this I really don't know where the relevant parts are. Plus, I actually want to learn how all of this works so I can help others. If there is such a thing as PM on here I'll email it to you if you're willing to take a look. Once I get it working, I will post the code here. I have to believe there are other people looking for the same kind of thing as I am. However, more importantly, I just want to know, from a high level, what is the correct way to approach my problem? If you look at my link you can see what I've been trying (which was to pass an integer to the method that populates the table), but as I said, I basically gave up on that because I wasn't getting anywhere. People are trying to help me, but I'm an idiot.
Thanks for bearing with my agonizingly long message. If you made it this far and have some suggestions for me I'm all ears. I'll be honest, though - if you tell me I should just scrap the whole thing and use core data I'll cry. I really don't think I have the time to figure out a whole different way of managing data. As I said, I'm pretty happy with the database and the query parts of my app - it's just managing the freaking views and passing data between them that is killing me!
Any help is appreciated - thank you all so much.
If I understand your question correctly, you are asking how to initialize a view controller and pass in some data to alter its behavior. The key concept here to understand is how objects are initialized in Objective-C. One of the most common questions that developers who are new to iOS have is:
How can I pass data between my views?
Yes, eight different links there. (Okay, that eighth link is a little bit off topic, but it's close enough.) There are several ways of doing this and I'll go through them briefly. I'll also describe custom initializers, which are a relevant point as well.
Let's pretend we were building a catalog application which shows a bunch of products in various categories. Imagine that our app opens to a list of products, much like the Apple Store App. Say that when the user taps on a product we want to show a product page.
You can set properties on the "next" view controller. - Simply, we can create a UIViewController subclass and set the productID property (which we made up). Let's call our new UIViewController a ProductPageViewController. Here's how this would look:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
//Create a ProductPageViewController
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//set the property on our ProductPageViewController
[ppvc setProductID:42];
//We would usually present the PPVC here.
//After presenting, remember to release the view controller
}
In the first line, we create the product view controller. We call alloc, then init. (The functions are wrapped - that is, we call init directly on the result of the alloc method.)
Then, we set a property of our view. The view can now be configured in viewWillAppear and all is well.
You can share the data through a persistent store. - This method works a little differently. The view controllers don't communicate at all, except for the first one presenting a second one. Whenever a value in the first view changes (that you want to persist), you write it to Core Data or NSUserDefaults. Then, the new view reads the value as it needs it.
In your first view controller:
//A method to store the data
- (void)storeData:(id)pageID{
[[NSUserDefaults setObject:pageID forKey:#"pageID"];
}
- (void)showNewPPVC{
ProductPageViewController *ppvc = [[ProductPageViewController alloc] initWithNibName:#"ProductPageViewController" bundle:nil];
//Show and then release the PPVC
}
You can use custom initializers. - This is probably the most intuitive way to do it, once you understand the concept, because this is the only one where data is actually "passed". (As opposed to method 2 where no data is directly shared and method 1 where data is passed as a property.)
Notice that in the earlier examples, I used the initWithNibName:Bundle method. You might also notice that UITableViewControllers use a different initializer, initWithStyle:. Those two initializers take in some information for the new object so that it knows how to load. Let's look at the first one first:
- (id)initWithNibName:(NSString *)nibNameOrNil Bundle:(NSBundle *)bundleNameOrNil;
The first argument tells the view controller which nib file to load up. I'm going to ignore the second argument for now, since I've never seen anything passed in except nil. Moving right along to the second example:
- (id)initWithStyle:(UITableViewStyle)style;
You can pass in one of two UITableViewStyle values here. This is one way to define the style of a table view (the other way being to modify a nib file directly).
Let's extend this concept a bit to our example. I'm now going to show you how to make your own custom initializer. Let's initialize our ProductPageViewController instance:
- (id) initWithProductID:(int)productID;
That's simple enough. Now, we need to implement the method and actually do something with the product ID. We'll start with the barebones code here, required to "mimic" the functionality of the default initializer.
- (id) initWithProductID:(int)productID{
self = [super init];
return self;
}
This method will return an initialized copy of our ProductPageViewController, however, it won't load up our UI from a NIB yet, or if this were a UITableViewController, it wouldn't set the UITableViewStyle. Let's work with a NIB first and then I'll show how to work a UITableViewController. So...
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
return self;
}
Now. we have an initialized ProductPageViewController, loaded from a NIB, but it doesn't do anything yet. Notice how we don't expose the NibName and Bundle arguments, but we just pass them in ourselves. If you want, you could theoretically expose those too. Now, let's take that productID and do something with it.
- (id) initWithProductID:(int)productID{
self = [super initWithNibName:#"ProductPageViewController" Bundle:nil];
if(self){
self.prodID = productID;
}
return self;
}
With our latest changes, our "PPVC" now knows about the productID. It can query the database as you want and do things with the results. You can then run different queries based on this productID.
Two More Quick Tips:
Perhaps you want to pass in several arguments. Of course you can simply add them to them method signature - (id) initWithProductID:(int)productID andCategoryID(int)categoryID, but what happens if you have five, six, or fifty six (yea, that's a lot) arguments? I'd advise passing in a collection or array of arguments.
To use custom initializers with UITableView, you pass in a UITableViewStyle instead of a NIB name. Here's what it might look like:
- (id) initWithProductID:(int)productID{
self = [super initWithStyle:UITableViewStyleGrouped];
if(self){
self.prodID = productID;
}
return self;
}
When making your subsections, I'd suggest a combination of persistent data and custom initializers. I also advise taking a peek at the viewDidLoad and viewWillAppear methods.
Apple's docs tell you this method should be as lightweight as possible, what's a standard use here? Resetting the annotation pins?
Tells the delegate that the region
displayed by the map view is about to
change.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
Parameters
mapView
The map view whose visible region is
about to change.
animated
If YES, the change to the new region
will be animated. If NO, the change
will be made immediately.
This method is called whenever the
currently displayed map region
changes. During scrolling, this method
may be called many times to report
updates to the map position.
Therefore, your implementation of this
method should be as lightweight as
possible to avoid affecting scrolling
performance.
The problem with this delegate method is "During scrolling, this method may be called many times to report updates to the map position" (so you need IF/THEN or CASE/BREAK, etc to keep it "lightweight").
You don't NEED to use this method at all (not required), but if you do wish to incorporate some sort of functionality (such as removing worthless pins, etc), then example code to keep it lightweight would be:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated{
if(!animated){
//Instantaneous change, which means you probably did something code-wise, so you should have handled anything there, but you can do it here as well.
} else {
//User is most likely scrolling, so the best way to do things here is check if the new region is significantly (by whatever standard) away from the starting region
CLLocationDistance *distance = [mapView.centerCoordinate distanceFromLocation:originalCoordinate];
if(distance > 1000){
//The map region was shifted by 1000 meters
//Remove annotations outsides the view, or whatever
//Most likely, instead of checking for a distance change, you might want to check for a change relative to the view size
}
}
}
Hi
Normally all the methods like
'- (NSFetchedResultsController *)fetchedResultsController '
are placed in the code of view controllers. I find it a bit messy to write Data Fetching code along with lifecycle methods or table delegate methods.
So my point is should I refactor the CoreData methods to some other helper class say DataLoader and then call them in view controllers?
Is this a wrong thing to do or am I going to loose some coding benefits of Core Data methods.
I would say moving the fetchedResultsController to a helper class is a good idea.
A problem I encounter often is to get the spelling of attributes right.
For example I do a predicate and want to filter on an attribute called #"isSelected". There is no check by the compiler nor by the linker to check the string isSelected. I will have to double check each line where the string has been used.
A search&replace won't work on the misspellings because I don't know what bugs have been introduced.
When I get the predicate wrong then no results will be fetched. Problem is that I don't know if there are no matching rows or if I have filtered wrong. I will need to check at runtime and that consumes time.
For predicates the saved templates exist, so predicates are not a perfect example. But think about value forKey: and we are at square one.
Now if all the fetchedResultsController are in one file then checking would become easier. At least it reduces the possibility of missing that little misspelling in a far away and rarely used class.
...or am I going to loose some coding benefits of Core Data methods.
I tend to say no, but other please feel free to jump in.
#Mann yes of course you can do that without loosing any coding benefits.....if u don't want to write Data Fetching code in you view Controller do not write there.....Make any other class lets say it DataLoader and write the fetching code in method of this class......and call this method by making the object of DataLoader class ....u will be able to fetch data from database
Hope u get it!