I have the following code:
NSDictionary *dict = #[#{#"Country" : #"Afghanistan", #"Capital" : #"Kabul"},
#{#"Country" : #"Albania", #"Capital" : #"Tirana"}];
I want to list many countries and capitals, and then randomize i.e a country and put it on the screen, then the user should be able to pick the correct capital..
How to I put the Country? Like dict.Country[0] or something like that?
What is wrong with the code? I get the error "Initializer element is not a compile-time constant" and the warning "Incompatible pointer types initializing 'NSDictionary *_strong' with an expression of type 'NSArray *'.
Can I make a third String in the Dictionary, containing a flag file.. for example
#"Flagfile" : #"Albania.png"
and later put it in a image view?
I want like a loop with a random number I (for example) and put like (I know this is not right, but I hope you get the point)
loop..
....
text= dict.Country[I];
button.text= dict.Capital[I];
Imageview=dict.Flagfile[I];
.....
....
Your top level element is an NSArray (#[], with square brackets, makes an array) of two NSDictionary's. To access an attribute in one of the dictionaries, you would do array[index][key], e.g. array[0][#"Country"] would give you #"Afghanistan". If you did NSArray *array = ... instead of NSDictionary *dict = ...
If you want to pick a country at random, you can get a random number, get it mod 2 (someInteger % 2) and use that as your index, e.g. array[randomNumber % 2][#"Country"] will give you a random country name from your array of dictionaries.
If you store an image name in the dictionaries, you can load an image of that name using UIImage's +imageNamed: method.
Here's more complete instruction on mbuc91's correct idea.
1) create a country
// Country.h
#interface Country : NSObject
#property(strong,nonatomic) NSString *name;
#property(strong,nonatomic) NSString *capital;
#property(strong,nonatomic) NSString *flagUrl;
#property(strong,nonatomic) UIImage *flag;
// this is the only interesting part of this class, so try it out...
// asynchronously fetch the flag from a web url. the url must point to an image
- (void)flagWithCompletion:(void (^)(UIImage *))completion;
#end
// Country.m
#import "Country.h"
#implementation Country
- (id)initWithName:(NSString *)name capital:(NSString *)capital flagUrl:(NSString *)flagUrl {
self = [self init];
if (self) {
_name = name;
_capital = capital;
_flagUrl = flagUrl;
}
return self;
}
- (void)flagWithCompletion:(void (^)(UIImage *))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.flagUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
completion(image);
} else {
completion(nil);
}
}];
}
#end
2) Now, in some other class, use the Country
#import "Country.h"
- (NSArray *)countries {
NSMutableArray *answer = [NSMutableArray array];
[answer addObject:[[Country alloc]
initWithName:#"Afghanistan" capital:#"Kabul" flagUrl:#"http://www.flags.com/afgan.jpg"]];
[answer addObject:[[Country alloc]
initWithName:#"Albania" capital:#"Tirana" flagUrl:#"http://www.flags.com/albania.jpg"]];
return [NSArray arrayWithArray:answer];
}
- (id)randomElementIn:(NSArray *)array {
NSUInteger index = arc4random() % array.count;
return [array objectAtIndex:index];
}
-(void)someMethod {
NSArray *countries = [self countries];
Country *randomCountry = [self randomElementIn:countries];
[randomCountry flagWithCompletion:^(UIImage *flagImage) {
// update UI, like this ...
// self.flagImageView.image = flagImage;
}];
}
You cannot initialize an NSDictionary in that way. An NSDictionary is an unsorted set of key-object pairs - its order is not static, and so you cannot address it as you would an array. In your case, you probably want an NSMutableDictionary since you will be modifying its contents (see Apple's NSMutableDictionary Class Reference for more info).
You could implement your code in a few ways. Using NSDictionaries you would do something similar to the following:
NSMutableDictionary *dict = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:#"Afghanistan", #"Country",
#"Kabul", #"Capital", nil];
You would then have an array of dictionaries, with each dictionary holding the details of one country.
Another option would be to create a simple model class for each country and have an array of those. For example, you could create a Class named Country, with Country.h as:
#import <Foundation/Foundation.h>
#interface Country : NSObject
#property (nonatomic, retain) NSString *Name;
#property (nonatomic, retain) NSString *Capital;
//etc...
#end
Related
I am aware of how normal NSArray concatenation works in Objective-C. This is not that question.
I have data that is being incrementally updated from a web service. My object has the following class definition (with a lot removed):
// NoteTemplate.h
#import <UIKit/UIKit.h>
#interface NoteTemplate
#property (copy, nonatomic) NSString *objectId;
I am caching a list of these on-device and checking at launch to see if there are any new or updated NoteTemplate objects in my database to load. So, I end up with two arrays:
NSArray <NoteTemplate *> *oldArray
NSArray <NoteTemplate *> *newArray
If there are no updates, then all I need to do is simply concatenate the two arrays together and that's that.
If there are updates, however, I want to combine the two arrays, but whenever there is a common objectId, the item in newArray should take precedence over the item in oldArray.
Thus far, I am brute-forcing it like this:
- (void)updateNoteTemplatesWithArray:(NSArray *)newTemplates {
NSArray *oldTemplates = [self getNoteTemplates];
NSMutableArray *combined = [NSMutableArray arrayWithArray:newTemplates];
for (NoteTemplate *noteTemplate in oldTemplates) {
NSArray *matches = [combined filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id blockTemplate, NSDictionary<NSString *,id> *bindings) {
return [((NoteTemplate *)blockTemplate).objectId isEqualToString:noteTemplate.objectId];
}]];
if (matches.count == 0) {
[combined addObject:noteTemplate];
}
}
[self setNoteTemplates:[combined copy]];
}
Is there a more optimized way to do this? I can't see that this will affect performance at all, so perhaps an optimization is unnecessary. Still, this approach feels hacky and way over-engineered.
To extend #Larme's suggestion with Set usage you can try the following approach:
#interface NoteTemplate: NSObject
#property (copy, nonatomic) NSString *objectId;
#property (copy, nonatomic) NSString *text;
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text;
#end
#implementation NoteTemplate
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text {
self = [super init];
if (self != nil) {
_objectId = objectId;
_text = text;
}
return self;
}
- (BOOL)isEqual:(id)object {
return [self.objectId isEqualToString:[object objectId]];
}
#end
And the usage code:
NoteTemplate *nt1 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"old set"];
NoteTemplate *nt2 = [[NoteTemplate alloc] initWithObjectId:#"2" text:#"old set"];
NoteTemplate *nt3 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"new set"];
NoteTemplate *nt4 = [[NoteTemplate alloc] initWithObjectId:#"3" text:#"new set"];
NSSet <NoteTemplate *> *oldSet = [NSSet setWithObjects:nt1, nt2, nil];
NSSet <NoteTemplate *> *newSet = [NSSet setWithObjects:nt3, nt4, nil];
NSMutableSet <NoteTemplate *> *mergedSet = [newSet mutableCopy];
[mergedSet unionSet:oldSet];
for (NoteTemplate *note in mergedSet) {
NSLog(#"Set item %# %#", note.objectId, note.text);
}
After executing this code you'll see in the log:
Set item 3 new set
Set item 1 new set
Set item 2 old set
I assume that's what you were looking for.
I don't know if I'd call this elegant but it's a less brutish approach. Instead of filtering combined at every pass through the loop, get all the new IDs in advance and check the ID list in the loop.
NSMutableArray <NoteTemplate *> *combined = [NSMutableArray arrayWithArray:newTemplates];
NSArray <NSString *> *newTemplateIds = [newTemplates valueForKey:#"objectId"];
for (NoteTemplate *oldTemplate in oldTemplates) {
if (![newTemplateIds containsObject:oldTemplate.objectId]) {
[combined addObject:oldTemplate];
}
}
I am using Afnetworking version 3.0 and trying to load some data. The data is coming in the response object properly and I am trying to parse that in an NSMutableArray. However this is crashing giving me error-
"warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available."
My Data Parsing class is following-
DataProcessing.h-
#import <Foundation/Foundation.h>
#import "Book.h"
#import <AFNetworking/AFHTTPSessionManager.h>
#import <MBProgressHUD/MBProgressHUD.h>
#interface DataProcessing : NSObject
-(void)getAllTheBooks;
-(void)setBookList:(NSMutableArray*)list;
-(NSMutableArray*)getBookList;
#end
DataProcessing.m:
#import "DataProcessing.h"
#interface DataProcessing(){
Book *individualBook;
}
#property(nonatomic, strong) NSMutableArray *bookList;
#property(nonatomic, strong) BookDetails *bookDetails;
#end
#implementation DataProcessing
-(void)getAllTheBooks{
NSMutableArray *bookList =[[NSMutableArray alloc]init];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:#"https://natasha....../items" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSArray *bookCollectionArray = (NSArray*)responseObject;
for (NSDictionary *dict in bookCollectionArray) {
individualBook = [[Book alloc]init];
individualBook.bookID = [dict objectForKey:#"id"];
individualBook.bookLink = [dict objectForKey:#"link"];
individualBook.bookTitle = [dict objectForKey:#"title"];
[bookList addObject:individualBook];
}
[self setBookList:bookList];
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
-(void)setBookList:(NSMutableArray*)list{
self.bookList = [[NSMutableArray alloc]init];
self.bookList = list;
}
-(NSMutableArray*)getBookList{
return self.bookList;
}
#end
My Controller's viewDidLoad is as following-
- (void)viewDidLoad {
[super viewDidLoad];
DataProcessing *processing = [[DataProcessing alloc]init];
[processing getAllTheBooks];
NSMutableArray *array = [processing getBookList];
NSLog(#"%#", array);
}
When it crashes, it shows something like this -
Can anyone please help?
Hmm, I'm not 100% sure this would work but you could try initializing the mutable array in an init method, then just have the self.bookList = list; inside the setter method.
There's no need to allocate a new NSMutableArray inside of the bookList property's setter method, assign it to the bookList property, and then set it to the list being passed in. Instead of accessing the bookList property using self inside of the accessor methods it is advised to use the backing variable or ivar (instance variable) instead, which is prefixed by an underscore (e.g. _bookList) and known as direct access.
The reason you should use direct access instead of self.bookList is because self.bookList = [[NSMutableArray alloc] init] is translated to [self setBookList:[[NSMutableArray alloc] init]];, which means that inside of your setter method for the bookList property you will be calling the setter method again and causing a recursive loop. For example, the code you have as of now will translate in to something like the following:
-(void)setBookList:(NSMutableArray*)list{
[_bookList release];
[self setBookList:[[NSMutableArray alloc]init]];
[self setBookList:list];
[_bookList retain];
}
Thus, on the second call to setBookList:, you're trying to release the bookList property after you have already released it, which is why you're getting the EXC_BAD_ACCESS error. If you're not doing any type of validation or in need of custom code inside of the setter method you do not have to define it as the setter method will be automatically defined for you. However, for reference, below is the proper way to define your setter method without any modification (in compliance with the aforementioned and ARC):
- (void)setBookList:(NSMutableArray *)bookList
{
if (_bookList != bookList)
_bookList = bookList;
}
I´m working on a class which retrieves links of a youtube playlist by using the YouTube Objective-C API.
Each URL is stored as a String strURL in an array called arrayURL, (for now) arrayURL is finally stored in arrayFinal, which I want to access from a different class.
Within the function I get the information I want (NSLog(#"%#", self.arrayFinal);). But when calling the function for example from another class (e.g. ViewController) I will always get an empty array back.
ViewController.m
YTDataHandler *youtubeObj = [YTDataHandler new];
[youtubeObj initYoutubeArray];
NSLog(#"%#", [youtubeObj arrayFinal]);
YTDataHandler.h
#import <Foundation/Foundation.h>
#interface YTDataHandler : NSObject
{
NSMutableArray *arrayFinal;
}
- (void)initYoutubeArray;
- (void)getYoutubeContent;
#property (nonatomic, retain) NSMutableArray *arrayFinal;
#end
YTDataHandler.m
#import "YTDataHandler.h"
#import "GTLServiceYouTube.h"
#import "GTLYouTube.h"
#import "GTLYouTubePlaylistSnippet.h"
#import "GTLYouTubeResourceId.h"
#implementation YTDataHandler
#synthesize arrayFinal;
- (void)initYoutubeArray {
self.arrayFinal = [[NSMutableArray alloc] init];
[self getYoutubeContent];
}
- (void)getYoutubeContent {
NSMutableArray *arrayURL = [[NSMutableArray alloc] init];
// Create a service object for executing queries
GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
// API key
service.APIKey = #"YOUR_API_KEY";
// Create a query
GTLQueryYouTube *query = [GTLQueryYouTube queryForPlaylistItemsListWithPart : #"id, snippet, contentDetails"];
query.playlistId = #"UUa0XHGDbBL8re8UgO-OWNPA";
query.maxResults = 20;
query.type = #"video";
// Execute the query
GTLServiceTicket *ticket = [service executeQuery : query
completionHandler : ^(GTLServiceTicket *ticket, id object, NSError *error) {
// This callback block is run when the fetch completes
if (error == nil) {
GTLYouTubePlaylistItemListResponse *items = object;
// iteration of items and subscript access to items.
for (GTLYouTubePlaylistItem *item in items) {
// IDs of videos
NSString *strVideoId = [item.snippet.resourceId JSONValueForKey : #"videoId"];
// encode and extend the videoId, result as an URL
NSString *strEncoded = [strVideoId stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding];
NSString *strURL = [NSString stringWithFormat:#"http://www.youtube.com/watch?v=%#", strEncoded];
// store strURL
[arrayURL addObject : (#"%#", strURL)];
}
// finally store arrayURL
[self.arrayFinal addObject : (#"%#", arrayURL)];
NSLog(#"%#", self.arrayFinal);
} else {
NSLog(#"Error: %#", error);
}
}];
}
#end
What am I doing wrong in the // Execute the query-part?
In your view controller, when you make the call to getYoutubeContent on your YTDataHandler instance, you cannot expect the array to be immediately populated. It will be eventually populated when the query executes and the handler code is executed. In your handler code it always works because you are in the same thread, and you use arrayFinal when it is ready. In your view controller you need to wait until the array is ready, before you start using it. E.g. you could set a boolean flag on your YTDataHandler which your view controller can register a key-value observing callback (KVO), so that you can get automatically notified when the array is ready.
ps.
Why don't you use:
[arrayURL addObject: strURL];
instead of:
[arrayURL addObject : (#"%#", strURL)];
?
I read a lot of docs about this but I can't really understand how it precisely works.
I would like to save my apps data in JSON format on the disc of the phone.
I have a array of objects of this type:
#interface ObjectA : NSObject
#property (strong, nonatomic) NSMutableArray* names1;
#property (strong, nonatomic) NSMutableArray* names2;
#property (strong, nonatomic) NSMutableArray* names3;
#property (strong, nonatomic) NSMutableArray* names4;
#property (strong, nonatomic) NSString* nameObjectA;
#property (assign) int number;
By using JSONModel, how can I transforme a "NSMutableArray *ObjectA" in a JSON file and after that read this file back in the app.
Thanks.
- (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary {
if(self = [self init]) {
// Assign all properties with keyed values from the dictionary
_nameObjectA = [jsonDictionary objectForKey:#"nameAction"];
_number = [[jsonDictionary objectForKey:#"number"]intValue];
_actions1 = [jsonDictionary objectForKey:#"Action1"];
_actions2 = [jsonDictionary objectForKey:#"Action2"];
_actions3 = [jsonDictionary objectForKey:#"Action3"];
_actions4 = [jsonDictionary objectForKey:#"Action4"];
}
return self;
}
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
// Create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
// Get the data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
// Now create a NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// Create a new array to hold the locations
NSMutableArray *actions = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "actions"
NSArray *array = [jsonDictionary objectForKey:#"actions"];
// Iterate through the array of dictionaries
for(NSDictionary *dict in array) {
// Create a new Location object for each one and initialise it with information in the dictionary
Action *action = [[Action alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[actions addObject:action];
}
// Return the array of actions objects
return actions;
}
The demo app that comes with JSONModel includes an example how to store your app's data via a JSONMOdel: https://github.com/icanzilb/JSONModel
Check the code in this view controller: https://github.com/icanzilb/JSONModel/blob/master/JSONModelDemo_iOS/StorageViewController.m
The logic is that you can export your model to a json string or json compliant dictionary and then save those to the disc using the standard APIs. Check the code
In ObjectA you define two methods -- toDictionary and initWithDictionary. Roughly:
-(NSDictionary*) toDictionary {
return #{#"names1":names1, #"names2":names2, #"names3":names3, #"names4":names4, #"nameObjectA":nameObjectA, #"number":#(number)};
}
- (id) initWithDictionary:(NSDictionary*) json {
self = [super init];
if (self) {
self.names1 = json[#"names1];
... etc
self.nameObjectA = json[#"nameObjectA"];
self.number = json[#"number"].intValue;
}
return self;
}
Run the dictionary created by toDictionary through NSJSONSerialization to produce an NSData and write that to a file. To read, fetch the NSData from the file, run back through NSJSONSerialization, and use initWithDictionary.
Of course, this assumes that the contents of your dictionaries are "JSON legal" -- strings, numbers, NSArrays, or other NSDictionarys.
And, if the arrays/dictionaries being initialized are mutable, one should specify the "MutableContainers" option on NSJSONSerialization.
I am making a NSObjectClass that has a method in it that returns self.
This is what it looks like roughtly
storageclass.h
// storageclass vars go here
- (storageclass)assignData:(NSDictionary *)dictionary;
storageclass.m
//#synthesise everything
- (storageclass)assignData:(NSDictionary *)dictionary {
//assign values from dictionary to correct var types (i.e. NSString, Int, BOOL)
//example
Side = [dictionary valueForKey:#"Side"];
return self;
}
Then what I want to do is use this class by passing a NSDictionary var through its method to return a object of type storageclass that I can then use to access the vars using dot notation.
this is how I am trying to access this class at the moment
accessorViewController.h
storageclass *store;
#property (strong, nonatomic) storageclass *store;
accessorViewController.m
#synthesize store;
- (void)getstoreready {
[store assignData:someDictionary];
nslog(#"%#", store);
}
this NSLog returns nothing and in the debugger all of stores class vars are empty showing nothing has been assigned. I am 100% positive the dictionary vars being used in the assignData method have the correct valueForKey values.
I think it has something to do with how I am using it here [store assignData:someDictionary]; how do i catch the turned data so I can use it?
any help would be appreciated.
The store object is never initialized so it will be nil thats obvious isn't it. Initialize the store object first, then call its instance methods onto it. And by doing that, you'll have a storageclass object which is properly assigned with some dictionary already.
And if you want to have a storageclass object like your code shows, you should make your (storageclass)assignData:(NSDictionary *)dictionary method a class method instead of an instance method by putting a + sign
+(storageclass*)assignData:(NSDictionary *)dictionary;
Then properly initialize it and assign the data (dictionary to variables) accordingly and return it to the caller. For example :-
in .m file
+(storageclass*)assignData:(NSDictionary *)dictionary{
storageclass *test = [[storageclass alloc] init];
if (test) {
test.someDict = dictionary;
}
return test;
}
Then use this class method in your view controller as
- (void)getstoreready {
store = [storageClass assignData:someDictionary];
nslog(#"%#", store);
}
Also Do follow the naming convention for classes and instances. A class's name must start with a capital letter only and the opposite for any class instances.
In User.h
#interface User : NSObject
#property (nonatomic, copy) NSString *name;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)usersFromArray:(NSArray *)array;
#end
In User.m
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
if (dictionary)
{
self.name = dictionary[#"kUserName"];
}
}
return self;
}
+ (NSArray *)usersFromArray:(NSArray *)array
{
NSMutableArray *users = [NSMutableArray array];
for (NSDictionary *dict in array) {
User *user = [[User alloc]initWithDictionary:dict];
[users addObject:user];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES];
return [users sortedArrayUsingDescriptors:#[descriptor]];
}
In ViewController.m
import "User.h"
self.currentArray = [User usersFromArray:array];