Segue crashing - debugger doesn't display any useful error - iphone

UPDATE:
Ok, I managed to log the exception:
[<TweetDetailViewController 0x685f9f0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key toolbar.
I have a toolbar in the target controller view. Could that be causing the problem?
I have no idea what the heck is going on. I have not changed segues at all and they worked perfectly few minutes ago. I was changing some unrelated things in a detail view controller and now the app crashes during segue from the root to the view controller.
I am trying to debug this. Here is where the code crashes:
Step over:
Step over:
Step over:
Ok, here is the setter method which is being called in prepareForSegue:
- (void)setTweet:(Tweet *)newTweet
{
if (_tweet != newTweet) {
_tweet = newTweet;
}
}
I call a configuration method when entering the detail controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self configureView];
}
- (void)configureView
{
// Update the user interface for the tweet detail page
if (_tweet) {
self.tweetUserName.text = _tweet.userName;
self.tweetCreatedAt.text = _tweet.createdAt;
self.tweetText.contentInset = UIEdgeInsetsMake(-4,-8,0,0);
self.tweetText.text = _tweet.text;
if (_tweet.retweeted == YES) {
[self.retweetButton setTintColor:[UIColor grayColor]];
[self.retweetButton setEnabled:NO];
}
if (!_tweet.userProfileImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *imageUrl = _tweet.userProfileImageUrl;
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
dispatch_async(dispatch_get_main_queue(), ^{
self.tweetUserProfileImage.image = [UIImage imageWithData:data];
});
});
}
else {
self.tweetUserProfileImage.image = _tweet.userProfileImage;
}
}
}

Copied from comments, as an answer once the actual exception was revealed:
Most likely the TweetDetailViewController in your storyboard has some connection that links to an outlet called "toolbar" but the code for that class doesn't have a property with that name. That's the usual reason at least for that kind of error when a view controller is involved.

Try enabling control over the Zombie, to see if the debug gives you more details about the problem.
On edit scheme under the heading "Envirorment Variables" add NSZombieEnabled = YES
Please try and see if you have more details

Related

UIActivityViewController not dismissing

I'm trying to add a custom UIActivity to a UIActivityController. When I click on the item, it presents the view controller I want it to, but when I finish with that view controller, the original UIActivityViewController is still there. My question is, how and where do I dismiss the activity view controller? This is the code in my custom UIActivity.
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems{
self.activityTitle = #"Text Editor";
self.activityType = #"TextEdit";
self.activityImage = [UIImage imageNamed:#"Icon.png"];
if (activityItems) return YES;
else return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems{
for (NSString *path in activityItems) {
if ([path lastPathComponent]) {
self.file = path;
}
}
}
- (UIViewController *)activityViewController{
ACTextEditController *actec = [[ACTextEditController alloc] initWithFile:_file];
return actec;
}
EDIT
I've tried doing this, and I know it is called because I tried logging something in it and it was called
- (void)activityDidFinish:(BOOL)completed{
if (completed) {
[self.activityViewController dismissViewControllerAnimated:YES completion:nil];
}
}
however, it's still there when the view controller dismisses. Why?
Here's a working example:
http://www.apeth.com/iOSBook/ch26.html#_activity_view
Notice that we end up by calling activityDidFinish:. That is what tears down the original interface. It may be that that is the step you are omitting. If not, just compare what you're doing with what I'm doing, since what I'm doing works (on iPhone).

IOS 5 showing SVProgressHUD when loading feed in separate thread does not work

Im developing an IOS 5 app which takes a feed from a url and displays the posts in a tableview. I have a View controller that loads the table cells with the posts in the feed. This all works perfectly.
However, i wanted to use the SVProgressHUD to show whilst the feed is being loaded in a separate thread.
So in my -(void)viewDidLoad method I have the following:
- (void)viewDidLoad
{
[super viewDidLoad];
[SVProgressHUD showInView:self.view status:#"loading.." networkIndicator:YES];
dispatch_async(kBgQueue, ^{NSData* data = [NSData dataWithContentsOfURL: latestFeedURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];});
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(foregroundRefresh:) name:UIApplicationWillEnterForegroundNotification object:nil];
self.pull = [[PullToRefreshView alloc] initWithScrollView:(UIScrollView *) self.feedTableView];
[self.pull setDelegate:self];
[self.feedTableView addSubview:self.pull];
self.title = #"Latest";
}
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSMutableArray* latestFeed = [json objectForKey:#"posts"]; //2
self.feedUpLoads = latestFeed;
NSLog(#"objects: %#", latestFeed); //3
[self.feedTableView reloadData];
[SVProgressHUD dismiss];
}
This all works fine, im getting the data which is loaded in a background thread and my table is displaying the posts with all the detail required. The problem I have is that the SVProgressHUD is not showing at all. Even if I put the [SVProgressHUD showInView line in the fetchData method, it's still not showing. (by the way i know the SVProgressHUD code works because I can actually make it show forexample in the viewWillAppear method.
Im guessing that it's not working because at the point when I'm calling it the view does not yet fully exist? But if that's the case where should I call it so that it shows whilst the feed is being called and where should I remove it?
Any help appreciated! thanks in advance!!
For anyone else having a similar problem, this can also happen because you have a long loop or a piece of code that takes a long time to execute. If this happens, your progress bar wont be shown until after the loop, which kind of defeats the purpose.
To solve this issue you need to you this:
(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
Basically your code would look something like this:
- (IBAction)submitPost:(id)sender {
//now we show the loading bar and submit the comment
[SVProgressHUD showWithStatus:#"Submitting post" maskType:SVProgressHUDMaskTypeGradient];
SEL aSelector = #selector(submitDataOfPost);
[self performSelectorInBackground:aSelector withObject:sender];
}
This will basically load the progress bar, and in a background thread, the method you want to execute will be called. This makes sure that the UI is updated (shows the progress hud) at the same time that your code is executed.

Delegate crash when I release the view because don't found

I have the next issue I'm creating one view when one button is touched. when the view is created and loaded I make 2 request with ASIFormDataRequest one request for download one Image and the other for get some text.
The headache become when the user in the view loaded select back because if button back is pressed the view is removed form superview, but crashh if one request callback is coming and the view dont exist how can I make this like cancel the request or how can I fix that.
The crash is in the next line of code
Class: ASIHTTPRequest.m
BOOL dataWillBeHandledExternally = NO;
**if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {**
dataWillBeHandledExternally = YES;
}
With: Thread 6: EXC_BAD_ACCES (code = 1, address = 0x30047dbc)
Please hellp that has haunted me.
You want to make sure that you cancel any pending ASIHTTPRequest when you pop your view:
From: http://allseeing-i.com/ASIHTTPRequest/How-to-use#cancelling_an_asychronous_request
// Cancels an asynchronous request, clearing all delegates and blocks first
[request clearDelegatesAndCancel];
You can use try catch for it. below is how u can do in ASIHTTPRequest.m
#try {
if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {
dataWillBeHandledExternally = YES;
}
}
#catch (NSException *exception) {
dataWillBeHandledExternally = NO;
}

iPhone: Attempt to Switch Views Produces EXC_BAD_ACCESS on the Third Switch Only

I have implemented an app that shows a map with a lot of pins on it.
If you push one pin you get on a second view that shows the data behind the pin.
A button takes you back to the map.
My problem is that by the third touch on a pin the program crashes with a EXC_BAD_ACCESS in this method:
- (void) switchViews {
if(self.details == nil){
Kundendetails *detailAnsicht = [[Kundendetails alloc] initWithNibName:#"ViewList" bundle:nil];
detailAnsicht.rootViewController = self;
self.details = detailAnsicht;
detailAnsicht.map = self.map;
}
if(self.details.view.superview == nil) {
[map.view removeFromSuperview];
[self.view addSubview:details.view];
[details viewDidLoad];
} else {
[details.view removeFromSuperview];
[details release];
[self.view addSubview:map.view];
}
}
How do I isolate which line of code causes the crash? Why would it always crash only on the third touch?
I hope you could help me.
Put NSLog statements in each branch of the ifs. You will almost assuredly see that this statement causes the problem:
[details viewDidLoad];
This is because at some point you execute this:
[details release];
effectively making details inaccessible. By the way you should also almost NEVER call viwewDidLoad directly.

Why does popViewController only work every other time

I am totally stumped, here's the situation:
My app uses the Core Location framework to get the current location of the user and then pings my server at TrailBehind for interesting places nearby and displays them as a list. No problems.
To conserve batteries, I turn off the GPS service after I get my data from the server. If the user moves around while using the app and wants a new list he clicks "Refresh" on the navigation controller and the CLLocation service is again activated, a new batch of data is retrieved from the server and the table is redrawn.
While the app is grabbing data from my server I load a loading screen with a spinning globe that says "Loading, please wait" and I hide the navigation bar so they don't hit "back".
So, the initial data grab from the server goes flawlessly.
The FIRST time I hit refresh all the code executes to get a new location, ping the server again for a new list of data and updates the cells. However, instead of loading the table view as it should it restores the navigation controller bar for the table view but still shows my loading view in the main window. This is only true on the device, everything works totally fine in the simulator.
The SECOND time I hit refresh the function works normally.
The THIRD time I hit refresh it fails as above.
The FOURTH time I hit refresh it works normally.
The FIFTH time I hit refresh it fails as above.
etc etc, even refreshes succeed and odd refreshes fail. I stepped over all my code line by line and everything seems to be executing normally. I actually continued stepping over the core instructions and after a huge amount of clicking "step over" I found that the table view DOES actually display on the screen at some point in CFRunLoopRunSpecific, but I then clicked "continue" and my loading view took over the screen.
I am absolutely baffled. Please help!! Many thanks in advance for your insight.
Video of the strange behavior:
Relevant Code:
RootViewControllerMethods (This is the base view for this TableView project)
- (void)viewDidLoad {
//Start the Current Location controller as soon as the program starts. The Controller calls delegate methods
//that will update the list and refresh
[MyCLController sharedInstance].delegate = self;
[[MyCLController sharedInstance].locationManager startUpdatingLocation];
lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil];
[self.navigationController pushViewController:lv animated:YES];
[super viewDidLoad];
}
- (void)updateClicked {
//When the location is successfully updated the UpdateCells method will stop the CL manager from updating, so when we want to update the location
//all we have to do is start it up again. I hope.
[[MyCLController sharedInstance].locationManager startUpdatingLocation];
[self.navigationController pushViewController:lv animated:YES];
//LV is a class object which is of type UIViewController and contains my spinning globe/loading view.
}
-(void)updateCells {
//When the Core Location controller has updated its location it calls this metod. The method sends a request for a JSON dictionary
//to trailbehind and stores the response in the class variable jsonArray. reloadData is then called which causes the table to
//re-initialize the table with the new data in jsonArray and display it on the screen.
[[MyCLController sharedInstance].locationManager stopUpdatingLocation];
if(self.navigationController.visibleViewController != self) {
self.urlString = [NSString stringWithFormat:#"http://www.trailbehind.com/iphone/nodes/%#/%#/2/10",self.lat,self.lon];
NSURL *jsonURL = [NSURL URLWithString:self.urlString];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
NSLog(#"JsonData = %# \n", jsonURL);
self.jsonArray = [jsonData JSONValue];
[self.tableView reloadData];
[self.navigationController popToRootViewControllerAnimated:YES];
[jsonData release];
}
}
CLController Methods: Basically just sends all the data straight back to the RootViewController
// Called when the location is updated
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"New Location: %# \n", newLocation);
NSLog(#"Old Location: %# \n", oldLocation);
#synchronized(self) {
NSNumber *lat = [[[NSNumber alloc] init] autorelease];
NSNumber *lon = [[[NSNumber alloc] init] autorelease];
lat = [NSNumber numberWithFloat:newLocation.coordinate.latitude];
lon = [NSNumber numberWithFloat:newLocation.coordinate.longitude];
[self.delegate noteLat:lat];
[self.delegate noteLon:lon];
[self.delegate noteNewLocation:newLocation];
[self.delegate updateCells];
}
}
The first thought is that you may not want to send startUpdatingLocation to the CLLocationManager until after you've pushed your loading view. Often the first -locationManager:didUpdateToLocation:fromLocation: message will appear instantly with cached GPS data. This only matters if you're acting on every message and not filtering the GPS data as shown in your sample code here. However, this would not cause the situation you've described - it would cause the loading screen to get stuck.
I've experienced similarly weird behavior like this in a different situation where I was trying to pop to the root view controller when switching to a different tab and the call wasn't being made in the correct place. I believe the popToRootViewController was being called twice for me. My suspicion is that your loading view is either being pushed twice or popped twice.
I recommend implementing -viewWillAppear:, -viewDidAppear:, -viewWillDisappear: and -viewDidDisappear: with minimal logging in your LoadingViewController.
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"[%# viewWillAppear:%d]", [self class], animated);
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"[%# viewDidAppear:%d]", [self class], animated);
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"[%# viewWillDisappear:%d]", [self class], animated);
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
NSLog(#"[%# viewDidDisappear:%d]", [self class], animated);
[super viewDidDisappear:animated];
}
Then, run a test on your device to see if they are always being sent to your view controller and how often. You might add some logging to -updateClicked to reveal double-taps.
Another thought, while your #synchronized block is a good idea, it will only hold off other threads from executing those statements until the first thread exits the block. I suggest moving the -stopUpdatingLocation message to be the first statement inside that #synchronized block. That way, once you decide to act on some new GPS data you immediately tell CLLocationManager to stop sending new data.
Can you try and debug your application to see where the control goes when calling updateCells? Doesn't seem to be anything apparently wrong with the app.
Make sure that there are no memory warnings while you are in the LoadingViewController class. If there is a memory warning and your RootViewController's view is being released, then the viewDidLoad will be called again when you do a pop to RootViewController.
Keep breakpoints in viewDidLoad and updateCells. Are you sure you are not calling LoadingViewController anywhere else?
So, I never did get this to work. I observe this behavior on the device only every time I call popViewController programatically instead of allowing the default back button on the navigation controller to do the popping.
My workaround was to build a custom loading view, and flip the screen to that view every time there would be a delay due to accessing the internet. My method takes a boolean variable of yes or no - yes switches to the loading screen and no switches back to the normal view. Here's the code:
- (void)switchViewsToLoading:(BOOL)loading {
// Start the Animation Block
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.tableView cache:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:.75];
// Animations
if(loading) {
if (lv == nil) { lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil]; }
[self.view addSubview:lv.view];
[self.view sendSubviewToBack:self.tableView];
self.title = #"TrailBehind";
}
else {
[lv.view removeFromSuperview];
}
// Commit Animation Block
[UIView commitAnimations];
//It looks kind of dumb to animate the nav bar buttons, so set those here
if(loading) {
self.navigationItem.rightBarButtonItem = nil;
self.navigationItem.leftBarButtonItem = nil;
self.title = #"TrailBehind";
}
else {
UIBarButtonItem *feedback = [[UIBarButtonItem alloc] initWithTitle:#"Feedback" style:UIBarButtonItemStylePlain target:self action:#selector(feedbackClicked)];
self.navigationItem.rightBarButtonItem = feedback;
UIBarButtonItem *update = [[UIBarButtonItem alloc] initWithTitle:#"Move Me" style:UIBarButtonItemStylePlain target:self action:#selector(updateClicked)];
self.navigationItem.leftBarButtonItem = update;
[feedback release];
[update release];
}
}
Looking at your original code, I suspect this block very much:
- (void)viewDidLoad {
...
lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil];
[self.navigationController pushViewController:lv animated:YES];
[super viewDidLoad];
}
viewDidLoad is called every time the NIB is loaded, which can happen multiple times, especially if you run low on memory (something that seems likely given your remark that it only happens on device). I recommend that you implement -didReciveMemoryWarning, and after calling super, at the very least print a log so you can see whether it's happening to you.
The thing that bothers me about the code above is that you're almost certainly leaking lv, meaning that there may be an increasing number of LoadingViewControllers running around. You say it's a class variable. Do you really mean it's an instance variable? ivars should always use accessors (self.lv or [self lv] rather than lv). Do not directly assign to them; you will almost always do it wrong (as you are likely dong here).
I came across this while searching for the exact same issue, so while I'm sure you've already solved your problem by now, I figured I'd post my solution in case someone else runs across it...
This error seems to be caused when you assign two IBActions to the same UIButton in interface builder. It turned out that the button I used to push the view controller onto the stack was assigned to two IBActions, and each one was pushing a different controller onto the navigationController's stack (although you'll only end up seeing one of them - perhaps the last one to be called). So anyway, pressing the back button on the topmost view doesn't really dismiss it (or maybe it's dismissing the 2nd, unseen controller), and you have to press twice to get back.
Anyway, check your buttons and be sure they're only assigned to a single IBAction. That fixed it for me.