Modal view with Web view makes an app crash in iOS7 - ios

I have been developing in Objective-C for two months, so I am quite new to this language and iOS environment. I am updating to iOS7 an app that is working fine for iOS6.
I am getting the next error when a modal view with a web view inside is presented, only in iOS7 and this is working in iOS6. There is a URL request inside but I cannot find what is causing the error.
'-[__NSMallocBlock__ absoluteURL]: unrecognized selector sent to instance 0x16e8b020'
This is the viewWillAppear method on the modal view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.webView.request) {
//THE NEXT LINE THROWS THE ERROR
NSURLRequest *req = [[NSURLRequest alloc] initWithURL:self.initialURL];
[self.webView loadRequest:req];
}
}
Maybe I am doing something silly but really now I do not know where to look at.
If anyone has experienced something like that before, I will appreciate some help. Thanks in advance.
EDIT:
#interface MyViewController ()
#property (copy, nonatomic) NSURL *initialURL;
#end
#implementation MyViewController
- (id)initWithURL:(NSURL *)initialURL
{
self = [super init];
if (self) {
_initialURL = initialURL;
_webView = [[UIWebView alloc] init];
_webView.backgroundColor = [UIColor clearColor];
_webView.opaque = NO;
_webView.delegate = self;
[self.view addSubview:_webView];
self.modalPresentationStyle = UIModalPresentationFormSheet;
self.view.backgroundColor = [UIColor whiteColor];
}
return self;
}
Method call:
self.modalWebViewController = [[[MyViewController alloc] initWithURL:url] autorelease];

I assume that iOS calls absoluteURL on the self.initialURL object passed to the initWithURL: method. However, the object receiving this message is an NSMallocBlock, so there seems to be something wrong. I assume that your self.initialURL object should be of type NSURL. If so, this would indicate a memory management problem causing the pointer of self.initalURL to point to somewhere else in memory (not to the object you want it to point to).
You could try to run your app with NSZombiesEnabled which prevents any objects from being actually deallocated and instead warns you if a deleted object is still accessed.
You can activate NSZombies in the scheme to run your app (click on the name of your app in Xcode's toolbar on the upper right and choose "Edit Scheme..." from the pop-up menu). In the run-configuration in the "Diagnostics" tab there is a checkbox for activating Zombie objects.

Related

Access view controller without re-initializing

Essentially I'm working with 3 view controllers.
Main view which starts a download. (Webview based which passes the download).
Modal download controller. (Tab based).
Downloader (HCDownload).
In the main view my download gets passed like so:
//Fire download
[activeDL downloadURL:fileURL userInfo:nil];
[self presentViewController:vc animated:YES completion:nil];
activeDL is initialized in viewDidLoad:
activeDL = [[HCDownloadViewController alloc] init];
If I removed the presentViewController, it still downloads, which is fine. Then i tap my Downloads button, it brings up the controller which defines the tabs like so:
center = [[CenterViewController alloc] init];
activeDL = [[HCDownloadViewController alloc] init];
completedDL = [[DownloadsViewController alloc] init];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads"
image:nil //[UIImage imageNamed:#"view1"]
tag:1];
completedDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Completed Downloads"
image:nil //[UIImage imageNamed:#"view3"]
tag:2];
[self setViewControllers:[NSArray arrayWithObjects:activeDL, completedDL, nil]];
However, it is not passing the current active download. I don't know if it's a initialization problem, or my tab issue of showing the current download.
From his github, he suggests to get the current number of downloads is to call: dlvc.numberOfDownloads which for me would be
[activeDL numberOfDownloads].
I call this in the the Downloader viewWillAppear but nothing shows.
Does anybody has any suggestions or have worked with this controller?
Any help would be appreciated.
When you call:
activeDL = [[HCDownloadViewController alloc] init];
You are creating a new download controller, which has its own internal downloads array. This library, as written, has no way to pass this information from one HCDownloadViewController object to another.
Tying downloads to VC's like this will cause problems -- I recommend you rewrite this code to split that apart.
To hack around it, try to create just one HCDownloadViewController object and pass it around.
Ok so with the last comment of the other answer, "Make activeDL a member variable instead of a local variable.", got me Googling and with some tinkering and bug fixing along the way I managed to get it all up and running perfect.
I declared it all in my AppDelegate.
AppDelegate.h
#interface SharedDownloader : HCDownloadViewController <HCDownloadViewControllerDelegate>
+ (id)downloadingView;
#end
AppDelegate.m
static HCDownloadViewController *active;
#implementation SharedDownloader
+ (id)downloadingView {
if (active == nil)
active = [[HCDownloadViewController alloc] init];
return active;
}
#end
Calling to the class for downloading in my main view controller:
-(id)init{
activeDL = [SharedDownloader downloadingView];
return self;
}
//Spot where I fire the download
if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
//More code here
[activeDL downloadURL:fileURL userInfo:nil];
}
Lastly in my tab bar controller:
-(id)init {
activeDL = [SharedDownloader downloadingView];
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
activeDL.tabBarItem = [[UITabBarItem alloc] initWithTitle:#"Active Downloads" image:nil] tag:2];
}
I believe that's all of it. In any case, thanks to Lou Franco for pointing me in the right direction.

webViewDidFinishLoad not working?

In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad

Memory warning doesn't work properly

I'm practicing on how TabViewcontroller works. Now I have 2 subclasses of UIViewcontroller.
One is HypnosisViewController , the other is TimeViewController.
What I wanted to check is how -(void)viewDidLoad works when IOS simulator gets memory warning.
And I did
Built and ran the app
The console said "HypnosisViewcontroller loaded its view."
Switched the other tab (TimeViewController)
Saw the message in the console. It says "TabViewcontroller loaded its view"
Did the simulator memory warning command in IOS simulator
The console said "HypnoTime Received memory warning."
Switched back to the HypnosisViewcontroller to see whether the console says "HypnosisViewcontroller loaded its view." again.
So the problem here is HypnosisViewcontroller is not destroyed and created again. (Because I can't see the log message when I switch back to HypnosisViewcontroller.)However I leaned the view not on the screen should be destroyed during the memory warning.
Did I miss something? Thanks in advance!
HypnosisViewController.m:
#import "HypnosisViewController.h"
#import "HypnosisView.h"
#implementation HypnosisViewController
-(void)loadView
{
//Create a view
CGRect frame = [[UIScreen mainScreen] bounds];
HypnosisView *v = [[HypnosisView alloc] initWithFrame:frame];
// Set it as *the* view of this view controller
[self setView:v];
}
-(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
self = [super initWithNibName:nil
bundle:nil];
if(self){
//Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];
//Give it a label
[tbi setTitle:#"Hypnosis"];
//Create a UIImage from a file
//This will use Hypno#2x.png on retina display devices
UIImage *i = [UIImage imageNamed:#"Hypno.png"];
// Put that image on the tab bar item
[tbi setImage:i];
}
return self;
}
-(void)viewDidLoad
{
// Always call the super implmetaion of viewDidload
[super viewDidLoad];
NSLog(#"HypnosisViewcontroller loaded its view");
}
#end
TimeViewController.m:
#import "TimeViewController.h"
#implementation TimeViewController
-(IBAction)showCurrentTime:(id)sender
{
NSDate *now = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
[timeLabel setText:[formatter stringFromDate:now]];
[timeLabel2 setText:[formatter stringFromDate:now]];
}
-(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
// Call the superclass's designated initializer
self = [super initWithNibName:nil
bundle:nil];
//Get a pointer to the application bundle object
// NSBundle *appBundle = [NSBundle mainBundle];
// self = [super initWithNibName:#"TimeViewController"
//bundle:appBundle];
if(self){
//Get the tab bar item
UITabBarItem *tbi = [self tabBarItem];
//Give it a label
[tbi setTitle:#"Time"];
//Create a UIImage from a file
//This will use Time#2x.png on retina display devices
UIImage *i = [UIImage imageNamed:#"Time.png"];
// Put that image on the tab bar item
[tbi setImage:i];
}
return self;
}
-(void)viewDidLoad
{
// Always call the super implmetaion of viewDidload
[super viewDidLoad];
NSLog(#"TimeViewcontroller loaded its view");
// [[self view] setBackgroundColor:[UIColor greenColor]];
}
#end
Memory Warnings don't cause the Controllers to destroy/unload their views anymore.
It is working properly. And HypnosisViewcontroller was destroyed and created again, because viewDidLoad will be called only when all the views are initiated. So here you see the log message again when you switch back to HypnosisViewcontroller which represent that HypnosisViewcontroller has been purged from memory and initiated again. You can try switch between these two view controllers without simulating memory warning, and you will only see the log message once.

GADInterstitial splash dismiss with warning

I want to add GADInterstitial to my iOS game, so everytime the app become active I want to present interestitial. In my AppDelegate.m, in method applicationDidBecomeActive: i call my method
-(void)splashInterstitial{
interestitial = [[GADInterstitial alloc] init];
interestitial.adUnitID = ADMOB_ID;
interestitial.delegate = self;
[interestitial loadAndDisplayRequest:[self createRequest] usingWindow:self.window initialImage:nil];
}
But everytime i close the ad i get warning:
attempt to dismiss modal view controller whose view does not currently appear. self = <GADInterstitialAppController: 0x5d3ed0> modalViewController = <GADWebAppViewController: 0x5d1ca0>
Does anyone got that warning?
I found a solution. Just always check will your initialization and visualization of banner after
viewDidAppear
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
hasLoaded = YES;
[self splashInterstitial];
}

Asynchronously loading sound resources in viewDidLoad crashes

All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.

Resources