Reputation: 1184
I have an XML file in URL where i can parse the data and get the images from the XML file. But i don't know how to use the images in other view controllers. I got an idea to store the images in array, but how can i use the array of images in other view controllers. Is it possible to store the XML parsed images in array.Am i parsing the XML correctly? Kindly suggest me an idea.
My XML Parser
//egsBase.h
#import <Foundation/Foundation.h>
@interface egsBase : NSObject{
IBOutlet UIImage *img;
}
@property(nonatomic,retain) IBOutlet UIImage *img;
@end
//egsBase.m
#import "egsBase.h"
@implementation egsBase
@synthesize img;
@end
//egsXMLParser.h
#import <Foundation/Foundation.h>
#import "egsBase.h"
@interface egsXMLParser : NSObject <NSXMLParserDelegate>{
NSMutableString *currentNodeContent;
NSMutableArray *tweets;
NSXMLParser *parser;
egsBase *currentTweet;
}
@property(nonatomic,readonly) NSMutableArray *tweets;
-(id) loadXMLByURL:(NSString *) urlString;
@end
//egsXMLParser.m
#import "egsXMLParser.h"
@implementation egsXMLParser
@synthesize tweets;
-(id) loadXMLByURL:(NSString *)urlString{
tweets = [[NSMutableArray alloc] init];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
[parser parse];
return self;
}
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if([elementName isEqualToString:@"Products"]){
currentTweet = [egsBase alloc];
}
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if([elementName isEqualToString:@"img"])
{
currentTweet.img=[UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:currentNodeContent]]]; [tweets addObject:currentTweet.img];
}
}
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
currentNodeContent = (NSMutableString *) [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
@end
Upvotes: 2
Views: 1209
Reputation: 438222
Are you parsing this correctly? There are a lot of issues, some substantive, some more stylistic. First, a couple of substantive observations:
Your foundCharacters
assumes that it will be called only once for each element. For something like a URL that will generally be true, but you're not guaranteed of this fact. It should just be appending characters to a string, and the subsequent storage of the final string and the clean up should happen in didEndElement
.
Your foundCharacter
is also storing results whether you're retrieving data for an element you care about or not (and worse, characters between elements). Only store results for foundCharacter
if you're between a didStartElement
and didEndElement
for an element name you care about.
Your didStartElement
is performing the alloc
, but it is not doing the init
, too. You should always do the init
at the same time. You can do additional setting of properties, later, too, but never neglect to call the appropriate init
method.
Your didEndElement
is retrieving an image from the server. You really should restrict the retrieval of data to just the XML, itself (e.g. in the case of an image, the URL for an image), and not go retrieving additional items from the server. This is especially important as you consider more sophisticated designs, where to minimize memory hits and improve user response time, you may want to employ lazy loading of images, meaning that you want to only retrieve the URL from the XML, and let the UI handle if and when images are retrieved. You'll regret doing the image retrieval here as you start doing more sophisticated development in the future.
You have an imbalance between the elementName
checking you're doing in didStartElement
and didEndElement
. For all of the elements for which you're retrieving data, you should balance the calls. I know you gave us an example where you were only retrieving a single element from the XML for reasons of simplicity, but in my example below, I'll assume we're retrieving two elements, and the importance of balancing these will become apparent.
For more stylistic observations:
By convention, variable names generally start with lowercase letters. Class names generally start with upper case letter. I'm assuming that "EGS" is an established class prefix (e.g. your initials or your firm's acronym), in which case the class names would probably be EGSBase
and EGSParser
. By the way, I don't really like the name EGSBase
because it's a class designed to capture a particular type of XML data, but has a generic name. Either use a generic data structure like a NSMutableDictionary
or use a custom class and give it a meaningful name (e.g. based upon your references to "products", I'm assuming is is a product, and thus I've renamed it EGSProduct
in my example below.) Call it whatever you want, but give it a meaningful name if you're using meaningful property names.
I notice that you're defining instance variables to back your properties. While that was common convention a year or two ago, latest versions of compilers make this unnecessary, and it's even inadvisable. What you've got works, but it's no longer considered best practice, and is inadvisable (since you can, with a single typo, end up with duplicative instance variables and get very strange behavior). Bottom line, do not define instance variables for your properties. Let the compiler do that for you.
I notice you've defined img
in egsBase
to be an IBOutlet
, whereas I find it extremely unlikely that you really have that linked to anything in Interface Builder. First, it's not even a UIView
descendent (it's a UIImage
, not a UIImageView
). Second, egsBase
is not, itself, a class that you'd be using for an Interface Builder control. Bottom line, this isn't an Interface Builder outlet, so it's just confusing to use IBOutlet
for img
.
I notice that you are using retain
. If you're writing ARC code, that should probably be strong
(even if you're targeting iOS 4.3). If you're not writing ARC code, you have leaks elsewhere in your code.
You are manually synthesizing your properties. You can do that, but it's not necessary. Also, best practice nowadays is to have your property's instance variables have a leading underscore. Note, if you omit the @synthesize
statement, it will synthesize the instance variable with the leading underscore for you. There is some debate in the field as to whether you should use properties exclusively (which I've done below) or use instance variables. I don't care too much, though emerging conventions might lean towards a more extensive use of properties. If you use properties, though (a) don't use the accessor methods in the initializer and dealloc methods; and (b) always use the accessor methods when setting properties.
I notice that you're defining properties and instance variables (for the parser, in particular) which are private implementation details. The emerging standard here is to limit the properties defined in the .h as those that other classes will need access to. Any other properties which are part of the private implementation of a class are generally defined in private class extension in the .m file itself. It's a minor thing, but if you observe this practice, you'll find it easier to use your classes in the future, where you (or other developers) won't get confused as to what's part of the public interface, and what's part of the private implementation.
I notice that your loadXMLByURL
returns a pointer to the class itself. Generally only classes that init
objects or create new objects would return a pointer to themselves.
Your loadXMLByURL
should probably should return either (a) a pointer to the NSMutableArray
that you constructed (returning nil
on an error); or (b) a BOOL
success/failure value.
For your custom classes, like EGSBase
, it's useful to write a description
method, so you can easily NSLog
them.
So, let me give you an example. Let's assume you have an XML that looks like:
<Products>
<products id="0">
<name>name1</name>
<img id="1">http://opentestdrive.com/images/first.png</img>
</products>
<products id="1">
<name>name2</name>
<img id="2">http://opentestdrive.com/images/second.png</img>
</products>
<products id="2">
<name>name3</name>
<img id="3">http://opentestdrive.com/images/img1.png</img>
</products>
<products id="3">
<name>name4</name>
<img id="4">http://opentestdrive.com/images/img2.png</img>
<img-subproduct id="0">http://opentestdrive.com/images/img5.png</img-subproduct>
<img-subproduct id="1">http://opentestdrive.com/images/img4.png</img-subproduct>
</products>
<products id="4">
<name>name5</name>
<img id="5">http://opentestdrive.com/images/img3.png</img>
<img-subproduct id="2">http://opentestdrive.com/images/img3.png</img-subproduct>
<img-subproduct id="3">http://opentestdrive.com/images/img2.png</img-subproduct>
</products>
<products id="5">
<name>name6</name>
<img id="6">http://opentestdrive.com/images/img4.png</img>
</products>
<products id="6">
<name>name7</name>
<img id="7">http://opentestdrive.com/images/img5.png</img>
</products>
</Products>
Then you EGSProduct
would be defined as follows:
// EGSProduct.h
#import <Foundation/Foundation.h>
@interface EGSProduct : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *imageUrlString;
@property (nonatomic, strong) NSString *imageIdentifier;
@property (nonatomic, strong) NSMutableArray *subProducts;
@end
and
// EGSProduct.m
#import "EGSProduct.h"
@implementation EGSProduct
- (NSString *)description
{
NSMutableString *result = [NSMutableString stringWithFormat:@"<EGSProduct %p; name='%@'; imageIdentifier='%@'; imageUrlString='%@'; subProducts=", self, self.name, self.imageIdentifier, self.imageUrlString];
NSMutableArray *subProductDescriptions = [NSMutableArray array];
for (EGSProduct *subProduct in self.subProducts)
{
[subProductDescriptions addObject:[subProduct description]];
}
[result appendFormat:@"%@>", [subProductDescriptions componentsJoinedByString:@"; "]];
return result;
}
@end
And your parser might look like:
// EGSParser.h
#import <Foundation/Foundation.h>
@interface EGSParser : NSObject
@property (nonatomic, strong) NSMutableArray *products;
- (BOOL)loadXMLByURL:(NSURL *)url;
@end
And
// EGSParser.m
#import "EGSParser.h"
#import "EGSProduct.h"
@interface EGSParser () <NSXMLParserDelegate>
@property (nonatomic, strong) NSXMLParser *parser;
@property (nonatomic, strong) NSMutableString *currentElementContent;
@property (nonatomic, strong) EGSProduct *currentProduct;
@property (nonatomic, strong) EGSProduct *currentSubProduct;
@end
@implementation EGSParser
- (BOOL)loadXMLByURL:(NSURL *)url
{
self.parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
self.parser.delegate = self;
return [self.parser parse];
}
#pragma mark - NSXMLParser
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
self.products = [[NSMutableArray alloc] init];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:@"products"])
{
self.currentProduct = [[EGSProduct alloc] init];
self.currentProduct.imageIdentifier = attributeDict[@"id"];
}
else if ([elementName isEqualToString:@"img-subproduct"])
{
self.currentSubProduct = [[EGSProduct alloc] init];
self.currentSubProduct.imageIdentifier = attributeDict[@"id"];
if (self.currentProduct.subProducts == nil)
{
self.currentProduct.subProducts = [NSMutableArray array];
}
self.currentSubProduct.imageIdentifier = attributeDict[@"id"];
self.currentElementContent = [[NSMutableString alloc] init];
}
else if ([elementName isEqualToString:@"name"] || [elementName isEqualToString:@"img"])
{
self.currentElementContent = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
// note, this construct of checking to see if it's nil is actually not needed
// (since sending a message to a nil object, by definition, does nothing)
// but I wanted to draw your attention to the fact that we set `currentElementContent`
// in `didStartElement` and, after saving it in our class, we set it to nil in
// `didEndElement`
if (self.currentElementContent != nil)
[self.currentElementContent appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"name"])
{
self.currentProduct.name = self.currentElementContent; // save the product name
self.currentElementContent = nil; // reset our `currentElementContent`
}
else if ([elementName isEqualToString:@"img"])
{
self.currentProduct.imageUrlString = [self.currentElementContent stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
self.currentElementContent = nil;
}
else if ([elementName isEqualToString:@"img-subproduct"])
{
self.currentSubProduct.imageUrlString = self.currentElementContent;
self.currentElementContent = nil;
[self.currentProduct.subProducts addObject:self.currentSubProduct];
self.currentSubProduct = nil;
}
else if ([elementName isEqualToString:@"products"])
{
[self.products addObject:self.currentProduct];
self.currentProduct = nil;
}
}
@end
To use this class you might do something like the following. If you want to hang on to the XML results after the parser is done (and goes out of scope), you'd just have a class property (in the example below, xmlProductsResults
. If you want this to be accessible to a variety of different view controllers, you'd save this model data in some shared class (e.g. a model singleton), a property of your app delegate, or pass it as a parameter from view controller to view controller.
NSURL *url = [NSURL URLWithString:@"http://opentestdrive.com/Products.xml"];
EGSParser *parser = [[EGSParser alloc] init];
if ([parser loadXMLByURL:url])
{
NSLog(@"success; products=%@", parser.products);
self.xmlProductsResults = parser.products;
}
else
{
NSLog(@"fail");
}
You could do a lot more with your parser (more robust validation of poorly formed XML files, reporting the errors, if any, back, etc.). Personally, I've done enough XML parsing now that I now employ a very generic parser that can parse most of the XML files I'm dealing with in a few lines, enjoying maximum reuse of the parser class, but that's a bridge too far for this conversation. I've already gone too far in this discussion.
Upvotes: 2