Loading links from an XML file onto a UIWebView (S

2019-08-24 18:43发布

I have been trying to parse an XML file in order to get articles from a website using background fetch.

Someone gave me an answer to this question here.

However, his answer resulted in an SIGABRT error.

Could someone please look at my code and see what my problem is? Thanks in advance.

My CODE:

NewsViewController.h (This file loads a uitableview that runs the background fetch to get the articles):

 #import <UIKit/UIKit.h>

 @interface NewsViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>


 @property (weak, nonatomic) IBOutlet UITableView *tblNews;

 - (IBAction)removeDataFile:(id)sender;

 -(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

 @end 

NewsViewController.m:

 #import "NewsViewController.h"
 #define NewsFeed @"http://www.teamfortress.com/rss.xml"
 #import "XMLParser.h"


 @interface NewsViewController ()

 @property (nonatomic, strong) UIRefreshControl *refreshControl;
 @property (nonatomic, strong) NSArray *arrNewsData;
 @property (nonatomic, strong) NSString *dataFilePath;
 -(void)refreshData;
 -(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;

 @end

 @implementation NewsViewController

 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
 {
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self) {
         // Custom initialization
     }
     return self;
 }

 - (void)viewDidLoad
 {
     [super viewDidLoad];
     // 1. Make self the delegate and datasource of the table view.
     [self.tblNews setDelegate:self];
     [self.tblNews setDataSource:self];

     // 2. Specify the data storage file path.
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     NSString *docDirectory = [paths objectAtIndex:0];
     self.dataFilePath = [docDirectory stringByAppendingPathComponent:@"newsdata"];

     // 3. Initialize the refresh control.
     self.refreshControl = [[UIRefreshControl alloc] init];

     [self.refreshControl addTarget:self
                        action:@selector(refreshData)
              forControlEvents:UIControlEventValueChanged];

     [self.tblNews addSubview:self.refreshControl];


     // 4. Load any saved data.
     if ([[NSFileManager defaultManager] fileExistsAtPath:self.dataFilePath]) {
         self.arrNewsData = [[NSMutableArray alloc] initWithContentsOfFile:self.dataFilePath];

         [self.tblNews reloadData];
     }

 }

 - (IBAction)removeDataFile:(id)sender {
     if ([[NSFileManager defaultManager] fileExistsAtPath:self.dataFilePath]) {
         [[NSFileManager defaultManager] removeItemAtPath:self.dataFilePath error:nil];

         self.arrNewsData = nil;

         [self.tblNews reloadData];
     }
 }

 -(void)refreshData{
     XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:NewsFeed];
     [xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {

         if (success) {
             [self performNewFetchedDataActionsWithDataArray:dataArray];

             [self.refreshControl endRefreshing];
         }
         else{
             NSLog(@"%@", [error localizedDescription]);
         }
     }];
 }

 -(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
     // 1. Initialize the arrNewsData array with the parsed data array.
     if (self.arrNewsData != nil) {
         self.arrNewsData = nil;
     }
     self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];

     // 2. Reload the table view.
     [self.tblNews reloadData];

     // 3. Save the data permanently to file.
     if (![self.arrNewsData writeToFile:self.dataFilePath atomically:YES]) {
         NSLog(@"Couldn't save data.");
     }
 }

 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
     return 1;
 }

 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
     return self.arrNewsData.count;
 }


 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
     UITableViewCell *cell = [tableView  dequeueReusableCellWithIdentifier:@"idCellNewsTitle"];

     if (cell == nil) {
         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"idCellNewsTitle"];
     }

     NSDictionary *dict = [self.arrNewsData objectAtIndex:indexPath.row];

     cell.textLabel.text = [dict objectForKey:@"title"];
     cell.detailTextLabel.text = [dict objectForKey:@"pubDate"];

     return cell;
 }


 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
     return 80.0;
 }

 - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{

 }

 - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

     if([segue.identifier isEqualToString:@"detail"]){


         DetailViewController *detail = segue.destinationViewController;
         NSIndexPath *indexPath = [self.tblNews indexPathForSelectedRow];
         detail.item = [self.arrNewsData objectAtIndex:indexPath.row];

     }
 }


 -(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
     XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:NewsFeed];
     [xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
         if (success) {
             NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
             NSString *latestTitle = [latestDataDict objectForKey:@"title"];

             NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
             NSString *existingTitle = [existingDataDict objectForKey:@"title"];

             if ([latestTitle isEqualToString:existingTitle]) {
                 completionHandler(UIBackgroundFetchResultNoData);

                 NSLog(@"No new data found.");
             }
             else{
                 [self performNewFetchedDataActionsWithDataArray:dataArray];

                 completionHandler(UIBackgroundFetchResultNewData);

                 NSLog(@"New data was fetched.");
             }
         }
         else{
             completionHandler(UIBackgroundFetchResultFailed);

             NSLog(@"Failed to fetch new data.");
         }
     }];
 }

 @end

XMLParser.h (This file parses the XML to get the title, date it was published, link, etc.)

#import <Foundation/Foundation.h>

@interface XMLParser : NSObject <NSXMLParserDelegate> {
NSMutableDictionary *item;
NSMutableString * currentLink;
}

@property (retain, nonatomic) NSMutableString *currentLink;

-(id)initWithXMLURLString:(NSString *)xmlUrlString;
-(void)startParsingWithCompletionHandler:(void(^)(BOOL success, NSArray *dataArray, NSError *error))completionHandler;

@end

XMLParser.m:

 #import "XMLParser.h"

 @interface XMLParser()

 @property (nonatomic, strong) NSXMLParser *xmlParser;

 @property (nonatomic, strong) NSOperationQueue *operationQueue;

 @property (nonatomic, strong) NSMutableArray *arrParsedData;

 @property (nonatomic, strong) NSString *currentElement;

 @property (nonatomic, strong) NSString *newsTitle;

 @property (nonatomic, strong) NSString *newsPubDate;

 @property (nonatomic, strong) NSString *newsLink;

 @property (nonatomic, strong) void (^completionHandler)(BOOL, NSArray *, NSError *);

 @property (nonatomic) BOOL isNewsItem;

 @property (nonatomic) BOOL allowedData;


 -(void)parse;
 -(void)endParsingWithError:(NSError *)error;

 @end


 @implementation XMLParser

 -(id)initWithXMLURLString:(NSString *)xmlUrlString{
     self = [super init];
     if (self) {
         self.xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:xmlUrlString]];

         self.xmlParser.delegate = self;

         self.operationQueue = [NSOperationQueue new];

         self.currentElement = @"";

         self.isNewsItem = NO;

         self.allowedData = NO;
     }

     return self;
 }


 #pragma mark - Public method implementation

 -(void)startParsingWithCompletionHandler:(void (^)(BOOL, NSArray *, NSError *))completionHandler{
     self.completionHandler = completionHandler;

     NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                         selector:@selector(parse)
                                                                          object:nil];
     [self.operationQueue addOperation:operation];
 }


 #pragma mark - Private method implementation

 -(void)parse{
     if (self.xmlParser != nil) {
         [self.xmlParser parse];
     }
 }

 -(void)endParsingWithError:(NSError *)error{
     BOOL success = (error == nil) ? YES : NO;

     self.completionHandler(success, self.arrParsedData, error);
 }



 #pragma mark - NSXMLParserDelegate method implementation

 -(void)parserDidStartDocument:(NSXMLParser *)parser{
     if (self.arrParsedData != nil) {
         [self.arrParsedData removeAllObjects];
         self.arrParsedData = nil;
     }

     self.arrParsedData = [[NSMutableArray alloc] init];
 }


 -(void)parserDidEndDocument:(NSXMLParser *)parser{
     [self performSelectorOnMainThread:@selector(endParsingWithError:) withObject:nil waitUntilDone:NO];
 }


 -(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{

     if ([elementName isEqualToString:@"item"]) {
     item = [[NSMutableDictionary alloc] init];
     self.currentLink = [[NSMutableString alloc] init];
     self.isNewsItem = YES;
 }

     if (self.isNewsItem) {
         if ([elementName isEqualToString:@"title"] ||
             [elementName isEqualToString:@"pubDate"] ||
             [elementName isEqualToString:@"link"]) {

             self.allowedData = YES;
         }
     }

     self.currentElement = elementName;
 }


 -(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{

     if ([elementName isEqualToString:@"item"]) {
         self.isNewsItem = NO;

         [item setObject:self.currentLink forKey:@"link"];


         NSDictionary *dict = @{@"title":    self.newsTitle,
                                @"pubDate":  self.newsPubDate,
                                @"link":     self.newsLink
                                };

         [self.arrParsedData addObject:dict];
     }

     if (self.isNewsItem) {
         if ([elementName isEqualToString:@"title"] ||
             [elementName isEqualToString:@"pubDate"] ||
             [elementName isEqualToString:@"link"]) {

             self.allowedData = NO;
         }
     }
 }


 -(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
     if (self.allowedData) {
         if ([self.currentElement isEqualToString:@"title"]) {
             self.newsTitle = string;
         }
         else if ([self.currentElement isEqualToString:@"pubDate"]){
             self.newsPubDate = string;
         }
        else if ([self.currentElement isEqualToString:@"link"]){
             self.newsLink = string;
             [self.currentLink appendString:string];
        }
     }
 }


 -(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
     [self performSelectorOnMainThread:@selector(endParsingWithError:) withObject:parseError waitUntilDone:NO];
 }


 -(void)parser:(NSXMLParser *)parser validationErrorOccurred:(NSError *)validationError{
     [self performSelectorOnMainThread:@selector(endParsingWithError:) withObject:validationError waitUntilDone:NO];
 }


 @end

DetailViewController.h (This is the file that encompasses a uiviewcontroller containing my UIWebView):

 #import <UIKit/UIKit.h>

 @interface DetailViewController : UIViewController<UIWebViewDelegate> {
     NSDictionary *item;
 }

 @property (retain, nonatomic) NSDictionary *item;
 @property (retain, nonatomic) IBOutlet UIWebView *itemSummary;

 @end

DetailViewController.m:

 #import "DetailViewController.h"

 @interface DetailViewController ()

 @end

 @implementation DetailViewController
 @synthesize item;
 @synthesize itemSummary = _itemSummary;

 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
 {
     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     if (self) {
         // Custom initialization
     }
     return self;
 }

 - (void)viewDidLoad
 {
     [super viewDidLoad];

     _itemSummary.delegate = self;
     _itemSummary.scalesPageToFit = YES;

     NSURL* url = [NSURL URLWithString:[item objectForKey:@"link"]];
     [_itemSummary loadRequest:[NSURLRequest requestWithURL:url]];
     // Do any additional setup after loading the view.
 }

 - (void)didReceiveMemoryWarning
 {
     [super didReceiveMemoryWarning];
     // Dispose of any resources that can be recreated.
 }

 /*
 #pragma mark - Navigation

 // In a storyboard-based application, you will often want to do a little preparation  before navigation
 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
 {
     // Get the new view controller using [segue destinationViewController].
     // Pass the selected object to the new view controller.
 }
 */

 @end

If you would like to run the project yourself, you can find it on Github here

NOTE When using the link, the Xcode project is under "Your Guide to TF2".

Thanks to anyone that helps!

CRASH LOG

2014-04-08 16:33:05.611 Your Guide to TF2[499:70b] -[UINavigationController fetchNewDataWithCompletionHandler:]: unrecognized selector sent to instance 0x1090aa930
2014-04-08 16:33:05.755 Your Guide to TF2[499:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UINavigationController fetchNewDataWithCompletionHandler:]: unrecognized selector sent to instance 0x1090aa930'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010189a795 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x00000001015fd991 objc_exception_throw + 43
    2   CoreFoundation                      0x000000010192bbad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x000000010188c09d ___forwarding___ + 973
    4   CoreFoundation                      0x000000010188bc48 _CF_forwarding_prep_0 + 120
    5   Your Guide to TF2                   0x0000000100004819 -[AppDelegate application:performFetchWithCompletionHandler:] + 345
    6   UIKit                               0x000000010026e0b5 -[UIApplication _handleOpportunisticFetchWithSequenceNumber:] + 170
    7   UIKit                               0x000000010025b200 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1761
    8   UIKit                               0x000000010025ebe8 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
    9   UIKit                               0x000000010026faab -[UIApplication handleEvent:withNewEvent:] + 3092
    10  UIKit                               0x000000010026ff1e -[UIApplication sendEvent:] + 79
    11  UIKit                               0x00000001002602be _UIApplicationHandleEvent + 618
    12  GraphicsServices                    0x00000001039a2bb6 _PurpleEventCallback + 762
    13  GraphicsServices                    0x00000001039a267d PurpleEventCallback + 35
    14  CoreFoundation                      0x000000010181c819 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
    15  CoreFoundation                      0x000000010181c5ee __CFRunLoopDoSource1 + 478
    16  CoreFoundation                      0x0000000101845ab3 __CFRunLoopRun + 1939
    17  CoreFoundation                      0x0000000101844f33 CFRunLoopRunSpecific + 467
    18  UIKit                               0x000000010025e4bd -[UIApplication _run] + 609
    19  UIKit                               0x0000000100260043 UIApplicationMain + 1010
    20  Your Guide to TF2                   0x0000000100004ce3 main + 115
    21  libdyld.dylib                       0x0000000101f295fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

AppDelegate.m

 #import "AppDelegate.h"
 #import "NewsViewController.h"
 #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue &  0xFF))/255.0 alpha:1.0]

 @implementation AppDelegate

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
     [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
     // Override point for customization after application launch.
     // Uncomment to change the background color of navigation bar
     [[UINavigationBar appearance] setBarTintColor:UIColorFromRGB(0xb35326)];

     // Uncomment to change the color of back button
     //[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];

     // Uncomment to assign a custom backgroung image
     //[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"tf2_logo.png"] forBarMetrics:UIBarMetricsDefault];

     // Uncomment to change the back indicator image
     /*
      [[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"back_btn.png"]];
      [[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"back_btn.png"]];
      */

     // Uncomment to change the font style of the title
     /*
      NSShadow *shadow = [[NSShadow alloc] init];
 shadow.shadowColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8];
      shadow.shadowOffset = CGSizeMake(0, 1);
      [[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys:
      [UIColor colorWithRed:245.0/255.0 green:245.0/255.0 blue:245.0/255.0 alpha:1.0], NSForegroundColorAttributeName,
 shadow, NSShadowAttributeName,
      [UIFont fontWithName:@"HelveticaNeue-CondensedBlack" size:21.0], NSFontAttributeName, nil]];
      */


     return YES;
 }

 -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
     NSDate *fetchStart = [NSDate date];

NewsViewController *viewController = (NewsViewController *)self.window.rootViewController;

[viewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
    completionHandler(result);

    NSDate *fetchEnd = [NSDate date];
    NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
    NSLog(@"Background Fetch Duration: %f seconds", timeElapsed);

     }];
 }

2条回答
乱世女痞
2楼-- · 2019-08-24 19:12

Ok.. looked at your code again.

A couple of things that are missing.

In your NewsViewController.h

@interface NewsViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>{
    NSArray *items;
    NSMutableArray *itemArray;
}

Need to include the Array and MutableArray

Also add the properties in NewsViewController.h

@property (retain, nonatomic) NSArray *items;
@property (retain, nonatomic) NSMutableArray *itemArray;

In your implementation file.

@synthesize items, itemArray;

In your XMLParser implementation

@synthesize currentLink;

Finally on your storyboard, you have the segue coming from the view controller instead of the cell row.

Delete your current segue and add another one coming from the cell to the DetailViewController view.

I pushed the changes as well.

查看更多
在下西门庆
3楼-- · 2019-08-24 19:26

The problem is in your -[AppDelegate application:performFetchWithCompletionHandler:] This line is wrong:

NewsViewController *viewController = (NewsViewController *)self.window.rootViewController;

The root view controller is a UINavigationController, but you're assuming it is a NewsViewController. That should be:

UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
NewsViewController *viewController = (NewsViewController *)[[navController viewControllers] objectAtIndex:0];
查看更多
登录 后发表回答