0
votes

I'm making a news reading app. I have a ArticleDetailPagingVC which functions as a paging controller. This has a UIScrollView with multiple ArticleDetailViewController's.

Inside the ArticleDetailViewController is a UIWebView which handles the articleText.

After changing some code I got a EXC_BAD_ACCESS when trying to inject a HTML string in the UIWebView. I eventually ended up looking for NSZombie's, which I found:

MOFO NSZOMBIE

As seen in the screenshot the NSZombie points to setting the frame of the ArticleDetailViewController, which is not correct in my opinion.

If I comment out the line of code which injects the HTMLString to my UIWebView, the view is shown as it should, without any data in the UIWebView.

The WebView is created as an IBOutlet:

@property (nonatomic) IBOutlet UIWebView *webView;

Delegate is set to self (ArticleDetailViewController)

Also, its crashing before any of the UIWebView Delegate Methods are called.

I'm sure the problem is not:

  • The HTML String (it was working before & if I load a 'Hello world' string its crashing too)
  • MultiThreading (everything is handled on the mainthread for testing purposes)
  • I have no weak properties
  • I have 0 autoreleasepool's / CFRelease(object) in my code

I have no idea what could have been released too soon to create the crash So my question is, how do you debug such a NSZombie? Or any other pointers are much appreciated.

PagingVC.h

#import <UIKit/UIKit.h>
#import "DDScrollViewController.h"
#import "ThumbArticle.h"
#import "NewsArticle.h"
#import "MBProgressHUD.h"

#import "DDScrollViewDelegate.h"

@interface ArticleDetailPagingVC : UIViewController <UIScrollViewDelegate,MBProgressHUDDelegate>

//View
@property (nonatomic) IBOutlet UIScrollView *scrollView;

//Data
@property (nonatomic) ThumbArticle *selectedThumbArticle;
@property (nonatomic) NewsArticle *selectedNewsArticle;
@property (nonatomic) int indexOfSelectedArticle;
@property (nonatomic) NSMutableArray *dataList;
@property (nonatomic) NSInteger selectedPage;

@property (nonatomic) BOOL dataSet;

@property (nonatomic) MBProgressHUD *mbProcess;


-(id)initWithDataList:(NSMutableArray*)dataList;

@end

PagingVC.m

#import "ArticleDetailPagingVC.h"
#import "ArticleDetailViewController.h"

@interface ArticleDetailPagingVC ()
-(void)setupView;
-(void)setupViewWithThumbArticles;
-(void)setupViewWithNewsArticles;
@end

@implementation ArticleDetailPagingVC

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

-(id)initWithDataList:(NSMutableArray*)dataList
{
    self = [super init];
    if (self) {
        self.dataSet = NO;
        self.dataList = [NSMutableArray arrayWithArray:dataList];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (self.selectedThumbArticle) {
        self.indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedThumbArticle];
    } else if (self.selectedNewsArticle) {
        self.indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedNewsArticle];
    }
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self setupView];
}

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

#pragma mark -
#pragma mark Custom Methods

-(void)setupView
{
    if (self.dataList.count > 0) {

        id object = [self.dataList objectAtIndex:0];
        if ([object isKindOfClass:[NewsArticle class]]) {



        } else if ([object isKindOfClass:[ThumbArticle class]]) {
            ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
            articleDetailVC.selectedThumbArticle = [self.dataList objectAtIndex:self.indexOfSelectedArticle];
            articleDetailVC.view.frame = CGRectMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
            [self.scrollView addSubview:articleDetailVC.view];
            //[articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:self.indexOfSelectedArticle]];
        }

        self.scrollView.contentSize = CGSizeMake(self.dataList.count * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
        [self.scrollView setContentOffset:CGPointMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];

        self.dataSet = YES;
    }
}

-(void)setupViewWithThumbArticles
{
    //Set the selected article first
    /*
    ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
    dispatch_async(dispatch_get_main_queue(), ^{
        articleDetailVC.view.frame = CGRectMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
        [self.scrollView addSubview:articleDetailVC.view];
    });
    [self.viewControllers replaceObjectAtIndex:indexOfSelectedArticle withObject:articleDetailVC];
    [articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:indexOfSelectedArticle]];
    //Then loop through the rest to add them to the scrollview
     */
    int i = 0;
    for (ThumbArticle *article in self.dataList) {
        if (i != self.indexOfSelectedArticle) {
            ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
            dispatch_async(dispatch_get_main_queue(), ^{
                articleDetailVC.view.frame = CGRectMake(i * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
                [self.scrollView addSubview:articleDetailVC.view];
            });
            //[self.viewControllers replaceObjectAtIndex:i withObject:articleDetailVC];
        }
        i++;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        self.scrollView.contentSize = CGSizeMake(i * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
        [self.scrollView setContentOffset:CGPointMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];
    });
}

-(void)setupViewWithNewsArticles
{
    int indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedNewsArticle];
    ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
    dispatch_async(dispatch_get_main_queue(), ^{
        articleDetailVC.view.frame = CGRectMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
        [self.scrollView addSubview:articleDetailVC.view];
    });
    //[self.viewControllers replaceObjectAtIndex:indexOfSelectedArticle withObject:articleDetailVC];
    [articleDetailVC layoutViewWithNewsArticle:[self.dataList objectAtIndex:indexOfSelectedArticle]];

    int i = 0;
    for (NewsArticle *article in self.dataList) {
        if (i != indexOfSelectedArticle) {
            ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
            dispatch_async(dispatch_get_main_queue(), ^{
                articleDetailVC.view.frame = CGRectMake(i * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
                [self.scrollView addSubview:articleDetailVC.view];
            });
            //[self.viewControllers replaceObjectAtIndex:i withObject:articleDetailVC];
        }
        i++;
    }
    self.scrollView.contentSize = CGSizeMake(i * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
    [self.scrollView setContentOffset:CGPointMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];
}

#pragma mark -
#pragma mark UIScrollView Delegate Methods

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (fmodf(scrollView.contentOffset.x, scrollView.frame.size.width) == 0) {
        if (self.dataSet) {
            self.selectedPage = scrollView.contentOffset.x / self.scrollView.frame.size.width;
            ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] initWithNibName:@"ArticleDetailViewController" bundle:nil];
            articleDetailVC.view.frame = CGRectMake(self.selectedPage * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
            [self.scrollView addSubview:articleDetailVC.view];
            [articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:self.selectedPage]];
        }
    }
}

#pragma mark -
#pragma mark MBProgressHUDDelegate methods

- (void)hudWasHidden
{
    [self.mbProcess removeFromSuperview];
}

@end

ArticleDetailViewController.h

#import "DDViewController.h"
#import "ThumbArticle.h"
#import "NewsArticle.h"
#import "MBProgressHUD.h"
#import "DDAsyncParser+NewsArticles.h"

@interface ArticleDetailViewController : DDViewController <MBProgressHUDDelegate,UIWebViewDelegate,ParserDelegate>

//View
@property (nonatomic,strong) IBOutlet UIView *contentView;
@property (nonatomic) IBOutlet UIImageView *image;
@property (nonatomic) IBOutlet UILabel *labelCategory;
@property (nonatomic) IBOutlet UILabel *labelImgCaption;
@property (nonatomic) IBOutlet UILabel *labelEdition;
@property (nonatomic) IBOutlet UIWebView *webView;
@property (nonatomic) IBOutlet UIActivityIndicatorView *activity;

@property (nonatomic) NSUInteger textFontSize;

@property (nonatomic) MBProgressHUD *mbProcess;

//Data
@property (nonatomic) ThumbArticle *selectedThumbArticle;

ArticleDetailViewController.m

#import "ArticleDetailViewController.h"
#import "DDUtilities.h"
#import "DDUserDefaults.h"
#import "NewsArticle.h"
#import "DDFeedParser.h"

@interface ArticleDetailViewController ()
@property (nonatomic) NewsArticle *parsedNewsArticle;
-(void)loadData;
-(void)updateImageCaptionLabel;
-(void)populateWebView;
@end

@implementation ArticleDetailViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        [self.view addSubview:self.contentView];
        ((UIScrollView*)self.view).contentSize = self.contentView.frame.size;
        self.dataSet = NO;
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (!self.dataSet) {
        [[DDAsyncParser sharedInstance] parseArticleWithXMLURL:self.selectedThumbArticle.articleXMLUrl delegate:self];
    }
}

- (void)viewWillUnload
{
    [self.webView setDelegate:nil];
    [self.webView stopLoading];
}

- (void)viewWillDisappear:(BOOL)animated{
    [self.webView setDelegate:nil];
    [self.webView stopLoading];
}

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

#pragma mark -
#pragma mark Public Methods

-(void)layoutViewWithThumbArticle:(ThumbArticle*)article
{
    if (!self.dataSet) {
        self.selectedThumbArticle = article;
        [[DDAsyncParser sharedInstance] parseArticleWithXMLURL:self.selectedThumbArticle.articleXMLUrl delegate:self];
    }
}

-(void)layoutViewWithNewsArticle:(NewsArticle*)article
{
    if (!self.dataSet) {
        self.parsedNewsArticle = article;
        [self loadData];
    }
}

#pragma mark -
#pragma mark Private Methods

-(void)loadData
{
    self.labelCategory.text = self.parsedNewsArticle.articleCategory;
    self.labelCategory.font = kCalibriBold14;
    self.labelCategory.textColor = kGrayColor;

    self.labelEdition.text = self.parsedNewsArticle.articleEdition;
    self.labelEdition.font = kCalibriBold14;
    self.labelEdition.textColor = kGrayColor;

    [self updateImageCaptionLabel];
    [self populateWebView];

    self.dataSet = YES;
}

-(void)updateImageCaptionLabel
{
    self.labelImgCaption.text = @"";
    NSString *imgAuthor = @"";
    if (self.parsedNewsArticle.articleImgAuthor.length != 0) {
        imgAuthor = [NSString stringWithFormat:@"Foto: %@",self.parsedNewsArticle.articleImgAuthor];
    }
    NSString *imgCaption = @"";
    if (self.parsedNewsArticle.articleImgDescription.length != 0) {
        imgCaption = [NSString stringWithFormat:@"%@ \n%@",self.parsedNewsArticle.articleImgDescription,imgAuthor];
    } else {
        imgCaption = imgAuthor;
    }

    self.labelImgCaption.text = imgCaption;
    CGSize maximumLabelSize = CGSizeMake(296,9999);
    CGSize expectedLabelSize = [imgCaption sizeWithFont:self.labelImgCaption.font
                                      constrainedToSize:maximumLabelSize
                                          lineBreakMode:self.labelImgCaption.lineBreakMode];

    CGRect newFrame = self.labelImgCaption.frame;
    newFrame.size.height = expectedLabelSize.height;
    self.labelImgCaption.frame = newFrame;
}

-(void)populateWebView
{
    NSString *htmlContentString = [DDUtilities createHTMLStringForArticleDetail:self.parsedNewsArticle];
    [self.webView loadHTMLString:htmlContentString baseURL:nil];
}

-(void)checkSavedTextFontSize
{
    self.textFontSize = [[DDUserDefaults getValueForKey:@"textFontSize"]integerValue];
    if (self.textFontSize != 0) {
        NSString *jsString = [[NSString alloc] initWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'",
                              self.textFontSize];
        [self.webView stringByEvaluatingJavaScriptFromString:jsString];
    } else {
        self.textFontSize = 100;
    }
}

#pragma mark -
#pragma mark UIWebView Delegate

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    if (navigationType == UIWebViewNavigationTypeLinkClicked)
    {
        [[UIApplication sharedApplication] openURL:[request URL]];
        return NO;
    }

    return YES;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self checkSavedTextFontSize];
    CGRect frame = webView.frame;
    frame.size.height = 1;
    webView.frame = frame;
    CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
    frame.size = fittingSize;
    dispatch_async(dispatch_get_main_queue(), ^{
        self.webView.frame = CGRectMake(frame.origin.x, self.labelImgCaption.frame.origin.y + self.labelImgCaption.frame.size.height + 5.0f, frame.size.width, frame.size.height);
        self.contentView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.webView.frame.origin.y + self.webView.frame.size.height + 30.0f);
        ((UIScrollView*)self.view).contentSize = self.contentView.frame.size;
    });

    //[DDUtilities setImageView:self.image forLink:self.parsedNewsArticle.articleImgUrl placeholder:YES withActivityIndicator:self.activity];
}

-(void)webViewDidStartLoad:(UIWebView *)webView
{

}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{

}

#pragma mark -
#pragma mark MBProgressHUDDelegate methods

- (void)hudWasHidden
{
    [self.mbProcess removeFromSuperview];
}

#pragma mark -
#pragma mark ParserDelegate methods

-(void)didFinishWithObject:(id)object
{
    self.parsedNewsArticle = object;
    [self loadData];
}
1

1 Answers

3
votes

You create a controller (which has a view). You assign the view as a subview of some other view. That's it. So, ARC will helpfully destroy your article detail controller that you aren't using any more. Anything that it has set itself as the delegate of (like a web view) will now crash when it tries to call the delegate.

Solution: store the article detail controller (add a strong property) so that it is retained while the view is on display.

Or, add the controller as a chile view controller.