Unsure how I'm getting EXC_BAD_ACCESS KERN_INVALID_ADDRESS - ios

I've looked through a ton of other StackOverflow posts about this error and all of them provide very reasonable solutions the problem. In other words, they generally pinpoint something in the code that isn't being auto-retained, but should be and then it subsequently causes a crash.
The problem I'm having is that the line of code that Crashlytics is telling me doesn't seem to have anything that could possibly be dealloc'd.. at least that I know of. Hopefully, you'll be able to see something I'm not seeing.
I'm not able to replicate the crash myself, but Crashlytics tells me I've had 146 of these crashes across 28 different users in the last 3 months.
My MainMenuDrawerViewController is a UITableViewController that sits in my left-side drawer (using MMDrawerController).
The crash happens in -tableView:didSelectRowAtIndexPath: on the following line:
[self updateCenterWithViewControllerIdentifiedBy:#"ReportsViewController"];
The only two objects on that line are self and a string literal, so I don't understand what could possibly be dealloc'd and causing the EXC_BAD_ACCESS.
Here is the overall method (with irrelevant code cut out):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
// removed other case statements
case DrawerRowReports: {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self performSegueWithIdentifier:#"ShowReportList" sender:self];
} else {
[self updateCenterWithViewControllerIdentifiedBy:#"ReportsViewController"];
}
break;
}
// removed other case statements
default:
break;
}
}
The -updateCenterWithViewControllerIdentifiedBy: function instantiates a View Controller from the storyboard using the given identifier, then instantiates an MMNavigationController with the first view controller as the root, then updates the mm_drawerController to put that MMNavigationController into the center position.
I'll include that method as well below, BUT the Crashlytics report doesn't say the bad access happens inside the method, it says it happens at the line above.
- (nullable id) updateCenterWithViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier {
return [self updateCenterWithViewControllerIdentifiedBy:storyboardIdentifier withCloseAnimation:YES];
}
- (nullable id) updateCenterWithViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier withCloseAnimation:(BOOL)withCloseAnimation {
return [self updatePosition:DrawerCenter withViewControllerIdentifiedBy:storyboardIdentifier withValueDictionary:nil withCloseAnimation:withCloseAnimation];
}
- (nullable id) updatePosition:(DrawerPosition)position withViewControllerIdentifiedBy:(nullable NSString*)storyboardIdentifier withValueDictionary:(nullable NSDictionary*)configDictionary withCloseAnimation:(BOOL)withCloseAnimation {
//BaseViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
UIViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
if (configDictionary != nil) {
for (NSString *fieldname in [configDictionary allKeys]) {
[viewController setValue:[configDictionary objectForKey:fieldname] forKey:fieldname];
}
}
UINavigationController * nav = [[MMNavigationController alloc] initWithRootViewController:viewController];
if (position == DrawerCenter) {
[self.mm_drawerController setCenterViewController:nav
withCloseAnimation:withCloseAnimation
completion:nil];
} else if (position == DrawerRight) {
[self.mm_drawerController setRightDrawerViewController:nav];
} else if (position == DrawerLeft) {
[self.mm_drawerController setLeftDrawerViewController:nav];
} else {
NSLog(#"unknown drawer position: %ld", (long)position);
}
return viewController;
}

Related

How to prevent a UIWindow to be a key window?

When I show an alert with UIAlertController, the alert itself presented in a new window. (for now at least) And when the alert window dismisses, system seems to set a random window key-window.
I am presenting a new "banner" window to render some banners over status-bar (AppStore compatibility is out of topic here), and usually, this "banner" window becomes next key window, and causes many problems on user input and first responder management.
So, I want to prevent this "banner" window to become a key window, but I cannot figure out how. For now, as a workaround, I am just re-setting my main window to be a key window again as soon as that "banner" window becomes key window. But it doesn't feel really good.
How can I prevent a window to become a key window?
As a workaround, we can set main window key again as soon as the "banner" window becomes a key like this.
class BannerWindow: UIWindow {
weak var mainWindow: UIWindow?
override func becomeKeyWindow() {
super.becomeKeyWindow()
mainWindow?.makeKeyWindow()
}
}
Faced with this too. It seems that it's enough to just make:
class BannerWindow: UIWindow {
override func makeKey() {
// Do nothing
}
}
This way you don't need to keep a reference to a previous keyWindow, which is especially cool if it might get changed.
For Objective-C it's:
#implementation BannerWindow
- (void)makeKeyWindow {
// Do nothing
}
#end
I've been trying to solve this problem for years. I finally reported a Radar for it: http://www.openradar.me/30064691
My workaround looks something like this:
// a UIWindow subclass that I use for my overlay windows
#implementation GFStatusLevelWindow
...
#pragma mark - Never become key
// http://www.openradar.me/30064691
// these don't actually help
- (BOOL)canBecomeFirstResponder
{
return NO;
}
- (BOOL)becomeFirstResponder
{
return NO;
}
- (void)becomeKeyWindow
{
LookbackStatusWindowBecameKey(self, #"become key window");
[[self class] findAndSetSuitableKeyWindow];
}
- (void)makeKeyWindow
{
LookbackStatusWindowBecameKey(self, #"make key window");
}
- (void)makeKeyAndVisible
{
LookbackStatusWindowBecameKey(self, #"make key and visible window");
}
#pragma mark - Private API overrides for status bar appearance
// http://www.openradar.me/15573442
- (BOOL)_canAffectStatusBarAppearance
{
return NO;
}
#pragma mark - Finding better key windows
static BOOL IsAllowedKeyWindow(UIWindow *window)
{
NSString *className = [[window class] description];
if([className isEqual:#"_GFRecordingIndicatorWindow"])
return NO;
if([className isEqual:#"UIRemoteKeyboardWindow"])
return NO;
if([window isKindOfClass:[GFStatusLevelWindow class]])
return NO;
return YES;
}
void LookbackStatusWindowBecameKey(GFStatusLevelWindow *self, NSString *where)
{
GFLog(GFError, #"This window should never be key window!! %# when in %#", self, where);
GFLog(GFError, #"To developer of %#: This is likely a bug in UIKit. If you can get a stack trace at this point (by setting a breakpoint at LookbackStatusWindowBecameKey) and sending that stack trace to nevyn#lookback.io or support#lookback.io, I will report it to Apple, and there will be rainbows, unicorns and a happier world for all :) thanks!", [[NSBundle mainBundle] gf_displayName]);
}
+ (UIWindow*)suitableWindowToMakeKeyExcluding:(UIWindow*)notThis
{
NSArray *windows = [UIApplication sharedApplication].windows;
NSInteger index = windows.count-1;
UIWindow *nextWindow = [windows objectAtIndex:index];
while((!IsAllowedKeyWindow(nextWindow) || nextWindow == notThis) && index >= 0) {
nextWindow = windows[--index];
}
return nextWindow;
}
+ (UIWindow*)findAndSetSuitableKeyWindow
{
UIWindow *nextWindow = [[self class] suitableWindowToMakeKeyExcluding:nil];
GFLog(GFError, #"Choosing this as key window instead: %#", nextWindow);
[nextWindow makeKeyWindow];
return nextWindow;
}

APNS to open a certain part of an application

I've just implemented a commenting feature in my app. Ideally when someone leaves a comment, I'd like all notified people be able to swipe the push notification and open the app on that post.
I assume you want to open the concerned page directly. There are many ways to go about this, and it depends on how your app is laid out.
If you want to open an inner page upon app launch, you can programmatically trigger the segues that the user would otherwise need to make manually. (this ensures the back/home buttons work as opposed to loading the desired page directly).
Here's an excerpt from one of my own code, your use case may not be the same, but this is all i can do unless you give us more details.
- (BOOL) navigateToRespectiveSectionforPushNot:(NSDictionary*)pushNot
{
id rootVC = self.window.rootViewController;
NSLog(#"ROOT CLASS : %#", [rootVC class]);
if ([rootVC isKindOfClass:[SWRevealViewController class]])
{
NSLog(#"Root Class looking good... mission Navigate!!");
SWRevealViewController *homeVC = (SWRevealViewController*) rootVC;
NSString *category = [[pushNot objectForKey:pushPayloadKeyaps] objectForKey:pushPayloadKeyCategory];
NSString *subCat = [[pushNot objectForKey:pushPayloadKeyaps] objectForKey:pushPayloadKeySubCategory];
NSLog(#"category : %# , subcat : %#",category,subCat);
//The code for the page to which i'm supposed to navigate to is contained in the push notification payload
if ([category isEqualToString:pushCategoryItemChat])
{
[homeVC.rearViewController performSegueWithIdentifier:#"chatPush" sender:nil];
UINavigationController *nc = (UINavigationController*)homeVC.frontViewController;
NSLog(#"FrontView Class : %#",[nc.viewControllers[0] class]);
UITableViewController *tvc = (UITableViewController*)nc.viewControllers[0];
NSDictionary *send = #{chatPushTargetUserId:subCat,chatPushTargetUserName:#"",chatPushTargetUserImage:#""};
[tvc performSegueWithIdentifier:#"seguePushDemoVC" sender:send];
return YES;
}
//communityPush historyPush
else if ([category isEqualToString:pushCategoryItemCommunity])
{
if ([subCat isEqualToString:pushSubCatItemNewRequest])
{
[homeVC.rearViewController performSegueWithIdentifier:#"communityPush" sender:nil];
return YES;
}
else if ([subCat isEqualToString:pushSubCatItemAccepted])
{
[homeVC.rearViewController performSegueWithIdentifier:#"communityPush" sender:nil];
return YES;
}
}
else if ([category isEqualToString:pushCategoryItemHistory])
{
[homeVC.rearViewController performSegueWithIdentifier:#"historyPush" sender:nil];
return YES;
}
}
else
{
UIAlertView *whoa = [[UIAlertView alloc] initWithTitle:#"WHOA!!" message:#" That wasn't supposed to happen. You are not even logged in. Call 911..." delegate:nil cancelButtonTitle:#"mmKay.." otherButtonTitles:nil, nil];
[whoa show];
}
return NO;
}
I hope the code is self explanatory. cheers

UISplitViewController crashes when using showDetailViewController

In my master view I have 4 static table rows. 2 of these rows drill down into a detail view within the master view and the other 2 replace the contents of the detail view. I control what happens with the didSelectRowAtIndexPath() method by calling showViewController() and showDetailViewController() functions as appropriate:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
Master2TVC *m2tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-2"];
[self showViewController:m2tvc sender:self];
} else if (indexPath.row == 1) {
Master3TVC *m3tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-3"];
[self showViewController:m3tvc sender:self];
} else if (indexPath.row == 2) {
Detail2VC *d2vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-2"];
[self showDetailViewController:d2vc sender:self];
} else if (indexPath.row == 3) {
Detail3VC *d3vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-3"];
[self showDetailViewController:d3vc sender:self];
}
}
The template for the Master-Detail template creates a reference from the master view to the detail view:
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
If I understand things correctly, this reference exists so that the master side can send messages to the detail side. In my case the class of my detail view will change (Detail3VC, Detail2VC, etc) so I've decided to remove this line and do the messaging another way; however, now when I load any of my new detail views and change the rotation of the iPad the App sometimes crashes with the error EXC_BAD_ACCESS.
From what I understand EXC_BAD_ACCESS usually means there is an object hanging around somewhere that shouldn't be. I have been unable to find anything in the Apple documentation that talks about having to change anything else when using showDetailViewController() call. In fact, I thought that the reason for using showDetailViewController() is so that the splitViewController manages all the details and you don't have to in your own classes.
Anyone can see the error here?
I have confirmed the crash you encountered. It always, not sometimes, happens when changing iPad's rotation.
Anyway, it seems that we need to implement -targetDisplayModeForActionInSplitViewController: of UISplitViewControllerDelegate and return any value except UISplitViewControllerDisplayModeAutomatic.
- (UISplitViewControllerDisplayMode)targetDisplayModeForActionInSplitViewController:(UISplitViewController *)svc {
return UISplitViewControllerDisplayModePrimaryOverlay;
}

UIButton EXC_BAD_ACCESS on setTitle:forState

I have a view controller that's instantiated from IB. It contains a UIButton whose action creates a UIPopoverController whose delegate updates the title of the UIButton through:
- (void) popoverSelected:(NSString*)string {
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
popoverSelected is a delegate method for the UIPopoverController, which contains a simple UITableView.
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedSort = [_sortTypes objectAtIndex:indexPath.row];
if (_delegate != nil) {
[_delegate popoverSelected:selectedSort];
}
}
The popover is instantiated by the TouchUpInside action on the self.button through:
- (IBAction)sortButtonPressed:(id)sender {
if (_sortPicker == nil) {
// Create the picker view controller
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
if (_sortPickerPopover == nil) {
// The colour picker popover is not showing. Show it
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:_sortPicker];
[_sortPickerPopover presentPopoverFromRect:_sortButton.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
} else {
// if it's showing, we want to hide it
[_sortPickerPopover dismissPopoverAnimated:YES];
_sortPickerPopover = nil;
}
}
This has no issues the first time the button's title is updated, but second time around I get an EXC_BAD_ACCESS when executing setTitle: in popoverSelected.
I can't see anywhere that I'm releasing the button accidentally (and the object definitely still exists at this point). The project is using ARC.
With NSZombies I've occasionally reached [__NSArrayI valueRestriction] unrecognised selector sent to instance which makes even less sense.
Are there any obvious approaches I can take to debug this further?
Instead of checking _sortPickerPopover == nil to know whether to show it, you should check [_sortPickerPopover isPopoverVisible]. Also, I would put the construction code into autoloaders.
- (UIPopoverController *)sortPickerPopover
{
if (!_sortPickerPopover) {
_sortPickerPopover = [[UIPopoverController alloc] initWithContentViewController:self.sortPicker];
}
return _sortPickerPopover;
}
- (SortPickerViewController *)sortPicker
{
if (!_sortPicker) {
_sortPicker = [[SortPickerViewController alloc] initWithStyle:UITableViewStylePlain];
// Set this as the delegate
_sortPicker.delegate = self;
}
return _sortPicker;
}
- (IBAction)sortButtonPressed:(UIButton *)sender
{
if ([self.sortPickerPopover isPopoverVisible]) {
[self.sortPickerPopover dismissPopoverAnimated:YES];
} else {
[self.sortPickerPopover presentPopoverFromRect:sender.frame
inView:sender
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
/***
* NOTE: Delegate methods should always pass the calling object as the first
* object. Additionally, the name is not very descriptive of what is actually
* being performed and does not use should/will/did naming conventions.
* You should consider changing this method to something like:
* - (void)sortPickerViewController:(SortPickerViewController *)sortPicker
* didSelectSortMethod:(NSString *)sortMethod
**/
- (void)popoverSelected:(NSString *)string
{
[self.sortButton setTitle:string forState:UIControlStateNormal];
[self.sortPickerPopover dismissPopoverAnimated:YES];
}
Once these changes are made, the only other possible source of problems is the implementation of your SortPickerViewController. I'll look that over for you if you can post that view controller as well.

Can't reload a collection view - null

I'm developing a simple app which downloads images from Dribbble, but I'm having problem reloading the data for my collection view. I have two views set up with ViewDeck, center is my main view which contains the collection view and another view contains table view with settings and from there I'm trying to call a method in the first view and reload data when item is tapped but it just doesn't work.
I tried to call the same method from the main window using button -> worked like a charm but from the second window it just doesn't update the data.
I tried to debug somehow and seems like my collection is null when the reload is called, no idea why.
SettingsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *appDelegate = [[JKViewController alloc] init];
appDelegate.dataHasChanged = YES;
[appDelegate refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
MainView
- (void)refresh{
NSLog(#"refresh");
if(dataHasChanged)
{
switch (listType) {
case 0:
[self refreshWithList:SPListPopular];
break;
case 1:
[self refreshWithList:SPListEveryone];
break;
case 2:
[self refreshWithList:SPListDebuts];
break;
case 3:
[self refreshWithList:SPListPopular];
break;
default:
[self refreshWithList:nil];
break;
}
dataHasChanged = NO;
NSLog(#"Should refresh");
}
NSLog(#"%d", [self->shots count]);
NSLog(#"Collection view: %#",self.collectionView.description);
NSLog(#"self.list: %#",self.list);
NSLog(#"List type: %d", listType);
}
This doesn't work :/, but when I call it from button in the MainView it works.
- (IBAction)changeList:(id)sender {
[self refreshWithList:SPListDebuts];
}
Does anyone know what could be the issue?
Edit - Solved
Getting the right instance of the centerViewController
JKViewController *mainController = ((UINavigationController*)self.viewDeckController.centerController).visibleViewController.navigationController.viewControllers[0];
The reason that you are not seeing your data being updated is because you are creating a new view controller and telling that to refresh. This new view controller has been initialized but not added to your view hierarchy. What you want to do is message the existing view controller like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *mainViewController = self.viewDeckController.centerViewController;
mainViewController.dataHasChanged = YES;
[mainViewController refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
Also, please note that I have changed the variable name in my revision. Naming a UIViewController instance 'appDelegate' is very confusing.

Resources