How to get Fields Name of particular Layer with LayerID with ESRI - ios

i actually tried ways , even checked ESRI sample code QueryTaskExample. i need simply to get Fields Names or Attributes of any Layer .
i used following method...
self.queryTask = [AGSQueryTask queryTaskWithURL:[NSURL URLWithString:strMAPURL]]; // here i send like #"http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/3"
self.queryTask.delegate = self;
//return all fields in query
self.query = [AGSQuery query];
self.query.outFields = [NSArray arrayWithObjects:#"*", nil];
self.query.text = #"Fields";
[self.queryTask executeWithQuery:self.query];
i access below delegates :
//results are returned
- (void)queryTask:(AGSQueryTask *)queryTask operation:(NSOperation *)op didExecuteWithFeatureSetResult:(AGSFeatureSet *)featureSet {
//get feature, and load in to table
self.featureSet = featureSet;
[super.tableView reloadData];
}
featureSet.fields // this should be giving me Name of Fields for Layer 3....
but i am getting Array as 0 elements...... i need to get Fields in same order as there in Server Layer.

Related

Multiple Geofire queries and client-side filtering

I've been referring to this SO post for a few days now:
Filtering results with Geofire + Firebase
My issue is that for my iOS app I need to make a single list of nearby users that is ordered by credentials, for example: premium member (highest and at top of list), donator (next highest after premium), member (basic/lowest).
I've created 3 entries in my Firebase server for GeoFire locations that split users based on these credentials, and therefore need to run 3 queries to retrieve them.
GeoFire* geoFirePremium = [[GeoFire alloc] initWithFirebaseRef:[[[FIRDatabase database] reference] child:#"geofire-premium-members"]];
GeoFire* geoFireDonator = [[GeoFire alloc] initWithFirebaseRef:[[[FIRDatabase database] reference] child:#"geofire-donator-members"]];
GeoFire* geoFireRegular = [[GeoFire alloc] initWithFirebaseRef:[[[FIRDatabase database] reference] child:#"geofire-regular-members"]];
NSMutableDictionary* query1Items = [[NSMutableDictionary alloc] init];
NSMutableDictionary* query2Items = [[NSMutableDictionary alloc] init];
NSMutableDictionary* query3Items = [[NSMutableDictionary alloc] init];
CLLocation* coord = [[CLLocation alloc] initWithLatitude:34.2499 longitude:-85.4399]; // Test location
long searchDistance = 8;
float mile2Kilo = 1.60934;
float kilo2mile = 0.62137;
GFCircleQuery* query1 = [geoFirePremium queryAtLocation:coord withRadius:(CGFloat)(searchDistance * mile2Kilo)]; // Miles to Kilometers
[query1 observeEventType:GFEventTypeKeyEntered withBlock:^(NSString* key, CLLocation* location)
{
// Store results in query1Items
}];
GFCircleQuery* query2 = [geoFireDonator queryAtLocation:coord withRadius:(CGFloat)(searchDistance * mile2Kilo)];
[query2 observeEventType:GFEventTypeKeyEntered withBlock:^(NSString* key, CLLocation* location)
{
// Store results in query2Items
}];
GFCircleQuery* query3 = [geoFireRegular queryAtLocation:coord withRadius:(CGFloat)(searchDistance * mile2Kilo)];
[query3 observeEventType:GFEventTypeKeyEntered withBlock:^(NSString* key, CLLocation* location)
{
// Store results in query3Items
}];
My thoughts are to add some code that recognizes when all 3 queries complete and then merges them into 1 list.
NSMutableDictionary* mergedItems = [[NSMutableDictionary alloc] init];
// For example: { query1Items[], query2Items[], query3Items[], ... }
[query1 observeReadyWithBlock:^{
NSLog(#"Query 1 is finished");
// Check for queries 2 & 3 completion
// Perform merge if all are completed
}];
[query2 observeReadyWithBlock:^{
NSLog(#"Query 2 is finished");
// Check for queries 1 & 3 completion
// Perform merge if all are completed
}];
[query3 observeReadyWithBlock:^{
NSLog(#"Query 3 is finished");
// Check for queries 1 & 2 completion
// Perform merge if all are completed
}];
Where the JSON structure for all Firebase/GeoFire references follows:
- geofire-premium-members
- userid
- g: geohash
- l
- 0: lat
- 1: lon
- geofire-donator-members //same format
- geofire-regular-members //same format
- users
- userid
- …
Is it a good approach to use multiple queries like this? It is possible I may need to add more credentials in the future and do not know if my approach will scale well. Is there perhaps a better way to achieve what I need that maybe only uses a single query instead? I greatly appreciate any insight
There's nothing wrong with your concept. It may be over complex though as there are three queries to return the same type of data and combining the data in code and doing additional queries to get other user data.
i.e. The three queries in the code return a bunch of user id's to which you then need to do more queries to (for example) look up the individual users names
As long as all of the members in the users node have the same structure you could just put them in one node and use a child property to indicate what kind of user they are
userid_0
name: "some name"
user_type: "regular"
userid_1
name: "another name"
user_type: "donator"
Then the GeoFire query to return all users within the specified radius.
Assuming the users have to be looked up for their name (as an example) anyway, you will have the rest of the data in the user node and can simply read them in, sort by user_type (in code) and display.
That being said, if you know for sure how you want them sorted, then a simple change to the structure will allow for that easily.
userid_0
name: "some premium name"
user_type: 0
userid_1
name: "a donator name"
user_type: 1
user_id_2
name: "regular name"
user_type: 2
where 0 = a premium user, 1 = a donator user and 2 is a regular user.
Then when sorting in code, sort by user_type and the premiums will float to the top.

Error saving record CKRecordID to server: invalid attempt to update record from type 'X' to 'Y'

While I'm using CKModifyRecordsOperation to save records for multiple tables to Private Cloud Database's Default Zone, it always return the error below except table 'X':
Error saving record to server: invalid attempt to update record from type 'X' to 'Y'
The error.userInfo detail:
{
CKErrorDescription = "Error saving record CKRecordID: 0x7fd7a3d4c0c0; 1:(_defaultZone:defaultOwner) to server: invalid attempt to update record from type 'X' to 'Y'";
ContainerID = "iCloud.com...";
NSDebugDescription = "CKInternalErrorDomain: 2006";
NSLocalizedDescription = "Error saving record to server: invalid attempt to update record from type 'X' to 'Y'";
NSUnderlyingError = "CKError 0x7fa0d250c4e0: \"Invalid Arguments\" (2006); server message = \"invalid attempt to update record from type 'X' to 'Y'\"; uuid = E2E...D1E; container ID = \"iCloud.com...\"";
RequestUUID = "E2E...D1E";
ServerErrorDescription = "invalid attempt to update record from type 'X' to 'Y'";
errorKey = ck1rosofi;
}
Related code snippets:
- (void)sync
{
...
NSMutableArray * operations;
for (NSString *tableName in #[#"X", #"Y"]) {
CKModifyRecordsOperation * operation = [self _modifyRecordsOperationWithTableName:tableName];
if (operation) {
if (operations) [operations addObject:operation];
else operations = [NSMutableArray arrayWithObject:operation];
}
}
if (operations) {
[operationQueue addOperations:operations waitUntilFinished:NO];
}
}
- (CKModifyRecordsOperation *)_modifyRecordsOperationWithTableName:(NSString *)tableName
{
...
NSMutableArray * recordsToSave = [NSMutableArray array];
for (KYModel <KYModel_iCloudProtocol> *instance in unsyncedInstances) {
CKRecordID * objectID = [[CKRecordID alloc] initWithRecordName:#(instance.id).stringValue];
CKRecord * cloudRecord = [[CKRecord alloc] initWithRecordType:tableName recordID:objectID];
... setup record detail
[recordsToSave addObject:record];
}
CKModifyRecordsOperation * operation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave recordIDsToDelete:nil];
operation.database = [[CKContainer defaultContainer] privateCloudDatabase];
operation.savePolicy = CKRecordSaveAllKeys;
operation.qualityOfService = NSQualityOfServiceUserInteractive;
operation.atomic = NO;
operation.perRecordProgressBlock = ...;
operation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
if (error) {
// got error here
}
...
};
operation.modifyRecordsCompletionBlock = ...;
return operation;
}
After searching & debugging for a while, I realised that those failed records have same "Record Name" (it's an unique name for CKRecordID, which likes an unique ID for each record) already in the same zone, though they're belonging to different tables.
Solution One:
Making CKRecordID's "Record Name" Unique Among All Tables.
For example, add a table name prefix for each record name: [table_name]_[id].
The problem of my case above is that "Record Name" duplicated for records of different tables in same zone (Default Zone).
And with CKModifyRecordsOperation's CKRecordSaveAllKeys saving policy, when save record (id:1) for table Y, it finds another record (id:1) of table X at Cloud, and tries to modify it, which throws the error "invalid attempt to update record from type 'X' to 'Y'" at last.
Solution Two:
Creating Custom Zones for Each Table (Note: Custom Zone is only available for Private Cloud Database).
For example, instead of using the Default Zone, use custom zone "X_Zone" for table X, and "Y_Zone" for table Y. Then we can keep using #"id" as CKRecordID's "Record Name".
In this way, you need to provide the zoneID as well when convert your local record to CKRecord instance:
CKRecordZoneID *zoneID = [[CKRecordZoneID alloc] initWithZoneName:#"Custom_Zone_Name_Here"
ownerName:CKOwnerDefaultName];
CKRecordID *objectID = [[CKRecordID alloc] initWithRecordName:#(instance.id).stringValue zoneID:zoneID];
...
And of course, you need to create the related custom zone first:
CKRecordZone *zone = [[CKRecordZone alloc] initWithZoneName:#"Custom_Zone_Name"];
[[[CKContainer defaultContainer] privateCloudDatabase] saveRecordZone:zone completionHandler:...];
or create multiple at same time:
NSMutableArray *zones = [NSMutableArray array];
for (NSString *zoneName in #[...]) {
CKRecordZone *zone = [[CKRecordZone alloc] initWithZoneName:zoneName];
[zones addObject:zone];
}
CKModifyRecordZonesOperation * operation = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:zones recordZoneIDsToDelete:nil];
...
[[[CKContainer defaultContainer] privateCloudDatabase] addOperation:operation];
I've tested both & works as expected.
Suggestion:
If your records are stored in Private Cloud Database, I think the Solution Two will be a good choice, as the DOC said:
... Use custom zones to arrange and encapsulate groups of related records in the private database. Custom zones support other capabilities too, such as the ability to write multiple records as a single atomic transaction.
Treat each custom zone as a single unit of data that is separate from every other zone in the database. Inside the zone, you add records as you would anywhere else.
...
It'll be much more convenient to handle records for each table, like deleting the zone for table instead of querying & deleting records for the table in same zone.
BUT NOTE, if you use CKReference among tables, DO NOT use custom zones for those tables then, cause
... the CKReference class does not support cross-zone linking, so each reference object must point to a record in the same zone as the current record.
Btw, this's the answer for the Self-Answered-Question, hope it helps if someone else get the same issue. Please point it out if there's something wrong in my answer, and welcome any suggestion as well, thx in advance. :)

Using Kii Query Result

I am running a Kii Query that returns the expected number of results. However the results array contains object formatted as follow
"<KiiObject: 0x130471ae0>"
this is the output from
NSLog(#"%#",results);
I am confident that the Query is working correctly as i can add and remove objects from the bucket and the number of results in the array changes according, i just don't know how to take the results and get the corresponding object.
I have gone over everything in the Kii Doc's
link to Kii Docs
The result is to short to be the object id(uuid string) and i can't find any other reference in the docs that makes sense.
You can refer to bellow snippet
NSError *error = nil;
// Build "all" query
KiiQuery *allQuery = [KiiQuery queryWithClause:nil];
// Create an array to store all the results in
NSMutableArray *allResults = [NSMutableArray array];
// Create a placeholder for any paginated queries
KiiQuery *nextQuery;
// Get an array of KiiObjects by querying the bucket
NSArray *results = [bucket executeQuerySynchronous:allQuery
withError:&error
andNext:&nextQuery];
if (error != nil) {
// Error handling
return;
}
//obtain single KiiObject
KiiObject* firstObject = allResults.firstObject; // now you should get all the object properties
NSLog(#"MaxScore : %#",[firstObject getObjectForKey:#"maxScore"]); //i.e to get "maxScore" value
Below is the links for querying KiiObjects.
http://docs.kii.com/en/guides/ios/managing-data/object-storages/querying/

Add objects to NSMutableArray that don't reference each other

First question:
Basically I'm attempting to add NSMutableDictionary objects to an NSMutableArray by using a method addItem.
-(void)addItem:(id)ObID name:(id)ObName description:(id)ObDesc menuName:(id)ObmName menuID:(id)ObmID mid:(id)Obmid pod:(id)pod type:(id)obType price:(id)price
{
NSMutableDictionary *itemToAdd = [[NSMutableDictionary alloc]init];
[itemToAdd setValue:ObID forKey:#"id"];
[itemToAdd setValue:ObName forKey:#"name"];
[itemToAdd setValue:ObDesc forKey:#"description"];
[itemToAdd setValue:ObmName forKey:#"mname"];
[itemToAdd setValue:ObmID forKey:#"menu_id"];
[itemToAdd setValue:Obmid forKey:#"mid"];
[itemToAdd setValue:pod forKey:#"POD"];
[itemToAdd setValue:obType forKey:#"Type"];
[itemToAdd setValue:price forKey:#"Price"];
[itemToAdd setValue:#"1" forKey:#"Amount"];
[items addObject:itemToAdd];
}
However when this method is called again later the next object that is added to the overwrites the value of an object with the same keys. Im aware this is because an array simply retains a memory address, hence when the same object simply with a different type "obType" is added all of those values change.
How am I able to add objects to an NSMutableArray without them referencing each other?
Example:
Output after adding one object:
{
Amount = 1;
POD = BOTH;
Type = {
0 = Vegetarian;
1 = Aussie;
};
description = "Online Special Only - Two Large Pizza's $25 (No half halves or extra toppings!) Enjoy!";
id = 7596;
"menu_id" = 112;
mid = 112;
mname = "Deal";
name = "Hungry? Two Large Pizzas!";
}
)
Output after adding another object of same ID however of a different type:
{
Amount = 1;
POD = BOTH;
Type = {
0 = "Plain";
1 = Aussie;
};
description = "Online Special Only - Two Large Pizza's $25 (No half halves or extra toppings!) Enjoy!";
id = 7596;
"menu_id" = 112;
mid = 112;
mname = "Deal";
name = "Two Large Pizzas!";
},
{
Amount = 1;
POD = BOTH;
Type = {
0 = "Plain";
1 = Aussie;
};
description = "Online Special Only - Two Large Pizza's $25 (No half halves or extra toppings!) Enjoy!";
id = 7555;
"menu_id" = 112;
mid = 112;
mname = "Deal";
name = Two Large Pizzas!";
}
)
As can be seen both object types have changed.
Ok so I solved the problem, not 100% sure why.
I was passing in the type object as an NSMutableDictionary which if an item had 2 variations, one object in the dictionary would have a key of 0 the other 1 when passed in like this they overwrite each other.
Passing just the values in as an NSArray fixes this.
Done by passing in:
NSArray * values = [selectedItemOptions allValues];
Thanks all who helped.
I can't give you the answer, there is not enough information provided to do that, but maybe I can help you figure it out yourself. You write:
Im aware this is because an array simply retains a memory address, hence when the same object simply with a different type "obType" is added all of those values change.
That is not what happens in your code, though there are situations where what is in an array may appear to change - and that is what you are probably seeing. The line:
NSMutableDictionary *itemToAdd = [[NSMutableDictionary alloc]init];
generates a new, never before seen, mutable dictionary. You can log the address (effectively the identity(1)) of this object by adding:
NSLog(#"Created itemToAdd: %p", itemToAdd);
after the above line. The %p format will produce the address of the newly created object, and every object has a unique address. When you then get to the line:
[items addObject:itemToAdd];
The new dictionary is added to whichever array is referenced by items - and that is very important. Any variable, such as items and itemToAdd, references an object, it is not the object itself. So the array being referenced by items could change between calls to addItem:. You should check this and you can do that by adding(2):
NSLog(#"addItem called, items = %p containing %ld element", items, items.count);
for(id someThing in items)
NSLog(#" element: %p", someThing);
to the start of your method. This will first tell you the address of the array currently referenced by items and how many items that array contains. The for loop will then output the address of each element of the array (if there are any).
Further note that addObject: will always add an item to the array, it doesn't matter if the same item is already in the array - add it twice and the array appears to have two elements, both of which reference the same item. When the above code displays the count it should be increasing unless you are removing elements from the array somewhere.
Add those statements and look at the results. Among the possibilities that would produce the results you are reporting:
The array referenced by items is changing
The dictionary you add is being removed
Etc.
HTH
(1) Addresses can be reused, after an object is no longer required and destroyed by ARC its memory may be reused for a new object. So the address does not strictly identify a unique object, you just need to be aware of this
(2) I am assuming 64-bit here, if not you need to change the %ld format or add a cast.

Formatting & Manipulation database data for display in tableview

I am working on an application to display user information stored in a database in an iOS app in a table view. Currently i am logging all information from the server to the console (in the debug area) using
NSLog(#"user information is as follows: %#", JSON);
Which provides output as such:
{ email = “andrew.smith19381223#gmail.com”;
“first_name” = Andrew;
“Last_name” = Smith
id = 12
user_info= (
{
"Room" = "Lab 2";
DayNumber = 1;
lesson = 2;
id = 12;
Instructor = "MR BEERRY";
Group_No = 7;
},
{
"class_room" = "S GEO";
DayNumber = 1;
lesson = 2;
id = 12;
teacher = "Mr RING";
Group_No = 7;
},
...
I would like to use this information in table view columns and as parameters (for eg. the day no.) for the tableview.How should i store/format this information in my iOS application so it can be used for a tableview.I have been googleing for a while without luck :(
Use a JSON parser (e.g. NSJSONSerialization) in order to parse the JSON text and create a representation. The generic representation is a hierarchy of Foundation objects. In your case, the root element is a JSON Object which maps to a NSDictionary in the representation. That dictionary contains the keys #"email", #"first_name", #"Last_name", #"id", #"user_info", etc. You can access the corresponding value for a key with the method -objectForKey: which you should be familiar with.
The corresponding object for key #"user_info" is a NSArray, whose elements are objects of kind NSDictionary.
And so force.
Since the JSON is a dynamic data structure you need some knowledge in the application what that JSON actually shall be and what you expect when you receive that. That is, you may expect a certain structure and keys and certain values. In order to make your live easier, you may use that representation to initialize your "custom model" - which has properties and behavior as you require.
Once you have your model, display it as usual in a UITable view.

Resources