Image Link
As you can see from the output, the switch statement completely skips over case 'view1'. And I'm having trouble understanding what the warnings mean.
Try to change the method signature to
- (void)switchViewTo:(NSString *)view
{
if ([view isEqualToString:#"view1"]) {
NSLog(#"view 1");
} else if ([view isEqualToString:#"view2"]) {
NSLog(#"view 2");
} else {
NSLog(#"whatever");
}
}
In the designated initializer you call [self switchToView:#"view1"];
Related
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;
}
I'm using UIKeyCommand to map certain shortcuts (for example "b", arrow keys, "t", "p", etc.) to a functionality inside my UIViewController subclass. The app is kind of a vector graphics software, which allows addition of text objects inside the canvas. The problem arises when a textView or textField inside the view controller is being edited. While it gets the first responder status, it doesn't receive the shortcut keys (for example writing "beaver" will result in "eaver").
Is there a correct way to handle shortcut keys AND use text objects inside a single view controller?
The solution I found to work best is to go through the responder chain to find the active responder and then check whether it is a UITextField/UITextView or something else. In case it is, return nil from the - (NSArray *)keyCommands method, otherwise return the shortcuts.
Here's the code itself:
#implementation UIResponder (CMAdditions)
- (instancetype)cm_activeResponder {
UIResponder *activeResponder = nil;
if (self.isFirstResponder) {
activeResponder = self;
} else if ([self isKindOfClass:[UIViewController class]]) {
if ([(UIViewController *)self parentViewController]) {
activeResponder = [[(UIViewController *)self parentViewController] cm_activeResponder];
}
if (!activeResponder) {
activeResponder = [[(UIViewController *)self view] cm_activeResponder];
}
} else if ([self isKindOfClass:[UIView class]]) {
for (UIView *subview in [(UIView *)self subviews]) {
activeResponder = [subview cm_activeResponder];
if (activeResponder) break;
}
}
return activeResponder;
}
#end
And this goes inside the keyCommands method:
- (NSArray *)keyCommands {
if ([self.cm_activeResponder isKindOfClass:[UITextView class]] || [self.cm_activeResponder isKindOfClass:[UITextField class]]) {
return nil;
}
UIKeyCommand *brushTool = [UIKeyCommand keyCommandWithInput:#"b"
modifierFlags:kNilOptions
action:#selector(brushToolEnabled)
discoverabilityTitle:NSLocalizedString(#"Brush tool", #"Brush tool")];
UIKeyCommand *groupKey = [UIKeyCommand keyCommandWithInput:#"g"
modifierFlags:UIKeyModifierCommand
action:#selector(groupKeyPressed)
discoverabilityTitle:NSLocalizedString(#"Group", #"Group")];
UIKeyCommand *ungroupKey = [UIKeyCommand keyCommandWithInput:#"g"
modifierFlags:UIKeyModifierCommand|UIKeyModifierShift
action:#selector(ungroupKeyPressed)
discoverabilityTitle:NSLocalizedString(#"Ungroup", #"Ungroup")];
return #[groupKey, ungroupKey, brushTool];
}
My solution was to override canPerformAction:withSender: and return false if the the view controller (that has the shortcut keyCommands) is not the first responder. This makes the walk down the responder chain unsuccessful in finding a target that accepts the key command and instead the key press is sent to the first responder as UIKeyInput as normal and the character appears in the text field. e.g.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
if(action == #selector(brushKeyCommand:)){
return self.isFirstResponder;
}
return [super canPerformAction:action withSender:sender];
}
This is where my build fails in JSQSystemSoundPlayer. I cannot get around this.
if (asAlert) {
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF];
}
else {
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF];
}
According to the header file, I think you need to include a completion block (which you can set to nil if you don't want anything doing).
if (asAlert) {
[[JSQSystemSoundPlayer sharedPlayer] playAlertSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF completion:nil];
} else {
[[JSQSystemSoundPlayer sharedPlayer] playSoundWithFilename:fileName fileExtension:kJSQSystemSoundTypeAIFF completion:nil];
}
Following code of mine generates crash in ARC mode:
MxTextField.m
+enableAllTextFields:(BOOL)enable InViews:(__weak UIView*) view
{
#try
{
NSArray* textFields = view.subViews;
for(int idx = 0; idx < textFields.count; idx++)
{
__weak UIView* view = [textFields objectAtIndex:idx];
if(view.subViews.count > 0)
[MxTextField enableAllTextFields:enable InView:view];
else
NSLog(#"No SubViews");
if([view class] == [MxTextField class])
[(MxTextField*) view setEnabled:enable];
}
}
#catch(NSException exception)
{
NSLog(#"%s : %#",__func__,exception);
}
}
After Some Loop on the execution of this function It crashes by showing breakpoint at the end of the function saying EXC_BAD_ACCESS. Can anyone help me out that what goes wrong in this implementation?
Any help will be thankful.
Putting aside many other problems the only reason for a crash that I can see from the posted code is that your method is supposed to return an object but does not do so.
Explanation: While it's not common to leave out the return type in Objective-C it's perfectly legal. It means that the method returns an object of type id.
Since your method lacks a return statement the returned value is undefined. This confuses ARC and probably makes it autorelease the random value in the return register which, eventually, leads to the crash.
Here's a proper version of your method:
+ (void)forAllTextFieldsIn:(UIView *)view setEnabled:(BOOL)enabled
{
if ([view isKindOfClass:[MxTextField class]])
[(MxTextField *)view setEnabled:enabled];
for (UIView *subview in view.subviews)
[self forAllTextFieldsIn:subview setEnabled:enabled];
}
The problem could be the method adopted for iteration and also try-catch is not a good practice, use fast-enumeration for faster and reliable result . The below code could resolve your problem
+(void)enableAllTextField:(BOOL)enable inView:(UIView *)contrainerView
{
for (UIView *subview in contrainerView.subviews) {
if(subview.subviews.count>0)
[MxTextField enableAllTextField:enable inView:subview];
else if ([subview isKindOfClass:[MxTextField class]]) {
MxTextField *textField = (MxTextField *)subview;
[textField setEnabled:enable];
}
}
}
I've written a category on UIView that allows me to walk the view hierarchy:
UIView+Capture.h
typedef void(^MSViewInspectionBlock)(UIView *view, BOOL *stop);
#interface UIView (Capture)
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block;
#end
UIView+Capture.m
#implementation UIView (Capture)
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block
{
BOOL stop = NO;
[self inspectViewHeirarchy:block stop:stop];
}
#pragma - Private
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL)stop
{
if (!block || stop) {
return;
}
block(self, &stop);
for (UIView *view in self.subviews) {
[view inspectViewHeirarchy:block stop:stop];
if (stop) {
break;
}
}
}
#end
Which you can use like so:
[[[UIApplication sharedApplication] keyWindow] inspectViewHeirarchy:^(UIView *view, BOOL *stop) {
if ([view isMemberOfClass:[UIScrollView class]]) {
NSLog(#"Found scroll view!");
*stop = YES;
}
}];
Everything works fine, except setting stop to YES. This appears to have absolutely no effect whatsoever. Ideally, I'd like this to halt the recursion, so when I've found the view I want to take some action on I don't have to continue to traverse the rest of the view hierarchy.
I'm pretty dense when it comes to using blocks, so it may be something completely obvious. Any help will be greatly appreciated.
The way you're using a block is exactly the same as using a C function. So there's nothing special you really need to know about blocks. Your code should work but note the difference between passing stop as a BOOL * to your block and to create a new local when you recurse.
It looks like you're expecting calls down to inspectViewHierarchy:stop: to affect the outer stop variable. That won't happen unless you pass it as a reference. So I think what you want is:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL *)stop
...and appropriate other changes.
I assume you want to return all the way out from the top-level inspectViewHierarchy when the user sets stop to YES.
(Incidentally, you spelled “hierarchy” wrong and you should use a prefix on methods you add to standard classes.)
#implementation UIView (Capture)
- (void)micpringle_visitSubviewsRecursivelyWithBlock:(MSViewInspectionBlock)block
{
BOOL stop = NO;
[self inspectViewHierarchy:block stop:&stop];
}
#pragma - Private
- (void)micpringle_visitSubviewsRecursivelyWithBlock:(MSViewInspectionBlock)block stop:(BOOL *)stop
{
block(self, stop);
if (*stop)
return;
for (UIView *view in self.subviews) {
[view micpringle_visitSubviewsRecursivelyWithBlock:block stop:stop];
if (*stop)
break;
}
}
#end
- (BOOL) inspectViewHeirarchy:(MSViewInspectionBlock)block
{
BOOL stop = NO;
block(self, &stop);
if (stop)
return YES;
for (UIView *view in self.subviews) {
if ([view inspectViewHeirarchy:block])
return YES;
}
return NO;
}
Try this:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block
{
__block BOOL stop = NO;
[self inspectViewHeirarchy:block stop:stop];
}
Blocks, by nature, copy the variables and context in which they are declared.
Even though you are passing the boolean as a reference, it's possible that it's using a copy of the context and not the true stop.
This is just a wild guess but, inside inspectViewHierarchy:stop: do something like:
- (void)inspectViewHeirarchy:(MSViewInspectionBlock)block stop:(BOOL)stop
{
if (!block || stop) {
return;
}
// Add these changes
__block BOOL blockStop = stop;
block(self, &blockStop);
for (UIView *view in self.subviews) {
[view inspectViewHeirarchy:block stop:stop];
if (stop) {
break;
}
}
}
This may be a long shot and I'm not 100% sure it will work without having your project, but it's worth a shot.
Also, refactor your method so "heirarchy" is actually spelled "hierarchy" :] It's good for reusability and for keeping a good code base ;)
wouldn't you want to check the status of 'stop' directly after you invoke the block? It doesn't help to invoke it after you call inspectViewHierarchy:stop: because you are passing a copy of 'stop' to that method instead of the reference.