recursive swizzling causes crash - ios

I'm using DAKeyboardControll on my app .
it has a method with name : swizzled_addSubview and implement Like this:
- (void)swizzled_addSubview:(UIView *)subview
{
if (!subview.inputAccessoryView)
{
if ([subview isKindOfClass:[UITextField class]])
{
UITextField *textField = (UITextField *)subview;
if ([textField respondsToSelector:#selector(setInputAccessoryView:)])
{
UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero];
nullView.backgroundColor = [UIColor clearColor];
textField.inputAccessoryView = nullView;
}
}
else if ([subview isKindOfClass:[UITextView class]]) {
UITextView *textView = (UITextView *)subview;
if ([textView respondsToSelector:#selector(setInputAccessoryView:)] && [textView respondsToSelector:#selector(isEditable)] && textView.isEditable)
{
UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero];
nullView.backgroundColor = [UIColor clearColor];
textView.inputAccessoryView = nullView;
}
}
}
[self swizzled_addSubview:subview];
}
problem
recently in my new version that compatible with AutoLayout , i receive some crash on this method , and reasons of them :
-[UIView(DAKeyboardControl) swizzled_addSubview:] ,
EXC_BAD_ACCESS KERN_PROTECTION_FAILURE at 0x0090dffc
i know this problem has happened for many many call , but why it can't work correctly ?
this crash happened only for 8 users for 54 times , 50 % of them has a jailbreak Device , but another person has a non- jailbreak Device !

Method swizzling causes issues at run time. I also got same kind of exception, To get rid of this, I have used BABFrameObservingInputAccessoryView, which is working fine for me. https://github.com/brynbodayle/BABFrameObservingInputAccessoryView

Most likely your method isn't actually swizzled, so you get a recursive call which will obviously crash. That's the tricky thing about swizzling: It exchanges two methods, so every call to addSubview would call swizzled_addSubview and the call to swizzled_addSubview actually calls addSubview.

Related

AdMob 6.12.0 - Prevent Scrolling (iOS)

- (void)preventGADBannerViewBounceScrolling:(GADBannerView*)bannerView {
for (UIWebView *webView in bannerView.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
webView.scrollView.scrollEnabled = NO;
webView.scrollView.bounces = NO;
}
}
}
I have been using the above code to stop the AdMob banner from scrolling.
I just updated the SDK to the latest (6.12.0) and having this code and calling it with the following...
[self.view addSubview:self.adMobBannerView];
[self preventGADBannerViewBounceScrolling:(GADBannerView *)_adMobBannerView];
Does nothing on the latest SDK, I was wondering if anyone has had this issue and resolved it?
Also when on this subject, I have noticed some developers have made their banners so if the user clicks then it opens up in a web view within the application and has a "Done" button at the right hand corner so the user does not fully leave the application when they press on the in-app adverts, I think that is genius...
If anyone could tell me how that is done I would appreciate that greatly!
It looks like UIWebView is wrapped into one more view in the new SDK, so it's better to go through the whole subview tree:
- (void)walkSubviewsOfView:(UIView *)v block:(void (^)(UIView *))block {
block(v);
for (UIView *subview in v.subviews) {
[self walkSubviewsOfView:subview block:block];
}
}
- (void)disableBannerWebViewBouncing {
[self walkSubviewsOfView:_bannerView block:^(UIView *v) {
for (UIGestureRecognizer *r in v.gestureRecognizers) {
if ([NSStringFromClass(r.class) isEqual:#"UIWebTouchEventsGestureRecognizer"])
r.enabled = NO;
}
if ([v isKindOfClass:[UIScrollView class]])
((UIScrollView *)v).bounces = NO;
}];
}
Of course this is not a future proof solution as well, I'd prefer there would be a corresponding property in the SDK.
I went with a non-block version of aleh's answer, I found it easier to read:
- (void)removeScrollingFromView:(UIView *)view
{
for (UIView *subview in view.subviews) {
[self removeScrollingFromView:subview];
}
if ([view isKindOfClass:[UIWebView class]]) {
((UIWebView *)view).scrollView.scrollEnabled = NO;
((UIWebView *)view).scrollView.bounces = NO;
}
}
I know I won't have any scrolling ads, but if you do, just disable bouncing and not scrolling. If you vote up my answer, please consider giving aleh a vote up too!

In iOS ARC my recursive function crashes application with EXC_BAD_ACCESS

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];
}
}
}

Remove Next / Previous buttons (inputAccessoryView) for Custom Keyboard in iOS8 WebView

Please bear with me, I searched a lot over the internet and I couldn't find a solution since it's a new API.
I am trying to create a custom keyboard for iOS 8. It works perfectly fine except in WebView!
It has previous-next button, which are in inputAccessoryView. I know it's read-only property for webview but since iOS 8 allows the users to have custom keyboard I assume this view should be editable somewhere. Has anybody run into the same issue? Any help would be appreciated.
You may try and improve this. try call this function inside Your UIKeyboardDidShowNotification event handler.
-(void) removeKeyboard {
UIWindow *keyboardWindow = nil;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) {
if (![[testWindow class] isEqual : [UIWindow class]]) {
keyboardWindow = testWindow;
break;
}
}
// Locate UIWebFormView.
for (UIView *possibleFormView in [keyboardWindow subviews]) {
if ([[possibleFormView description] hasPrefix : #"<UIInputSetContainerView"]) {
for (UIView* peripheralView in possibleFormView.subviews) {
for (UIView* peripheralView_sub in peripheralView.subviews) {
// hides the backdrop (iOS 8)
if ([[peripheralView_sub description] hasPrefix : #"<UIKBInputBackdropView"] && peripheralView_sub.frame.size.height == 44) {
[[peripheralView_sub layer] setOpacity : 0.0];
}
// hides the accessory bar
if ([[peripheralView_sub description] hasPrefix : #"<UIWebFormAccessory"]) {
for (UIView* UIInputViewContent_sub in peripheralView_sub.subviews) {
CGRect frame1 = UIInputViewContent_sub.frame;
frame1.size.height = 0;
peripheralView_sub.frame = frame1;
UIInputViewContent_sub.frame = frame1;
[[peripheralView_sub layer] setOpacity : 0.0];
}
CGRect viewBounds = peripheralView_sub.frame;
viewBounds.size.height = 0;
peripheralView_sub.frame = viewBounds;
}
}
}
}
}
}
Hope this helps...
This is the level of views in accessory:
(UIWebFormAccessory) -> (UIToolbar) -> (UIImageView,UIToolbarButton,UIToolbarButton)
Removing or scaling to zero height the accessory bar will cause black area. You can play around the mainview's frame or bounds. or if You just want to get rid of the buttons, just try to have a 0 opacity on the UIToolbutton , the toolbutton is inside uitoolbar view
Use this , it works like a charm and it is much cleaner than the other solutions. It's hacking with the inputAccessoryView for UIWebBrowserView (inner UIWebView).
Good luck with coding!

Casting UIViews to custom Views

So I have a UIScrollView that is populated with a series of MyCustomViews that are subclasses of a standard UIView. In the delegate callback "scrollViewDidScroll I am trying to loop through all the subviews and call a specific function on them but I don't think the typecasting is working. Here is my code below:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
for(UIView *subView in [scrollView subviews){
MyCustomView *customView = (MyCustomView *)subView;
[customView myMethod];
}
}
When I call "myMethod" on customView, the program crashes saying an unrecognized selector was sent to instance. I believe that my type-casting is the issue as the method myMethod works in other situations. So how do I remedy this situation?
Solution 1:
If you do the following, you don't even need to cast your object to MyCustomView *. It can be of any type, e.g. UIView.
if([subView respondsToSelector:#selector(myMethod)]) {
[subView performSelector:#selector(myMethod)];
}
Solution 2:
You can check the object type before doing the cast.
if([subView isKindOfClass:[MyCustomView class]]) {
MyCustomView *customView = (MyCustomView *)subView;
[customView myMethod];
}
For "catch" this issue, use
if([customView respondsToSelector:#selector(myMethod)]){
[customView myMethod];
}
and with this, the app don't crash.
Also in your for use for(MyCustomView* customView in [scrollView subviews]){

Callout button crashes app - MapKit

UICalloutView willRemoveSubview:]: message sent to deallocated instance;
This only happens when I tap the callout button, but not on the first and not on the 2nd tap, but from the 3rd tap. So I tap the custom AnnotationView, callout pops, thats good. I tap it again, callout pops, all good. I tap another one, boom crash with that message. It only happes if is set the right accesoryview to be a button.
One key aspect to keep in mind..only happens in iOS 6... (go figure).
I am really stuck on this one – some help would be appreciated.
if ([annotation isKindOfClass:[RE_Annotation class]])
{
RE_Annotation *myAnnotation = (RE_Annotation *)annotation;
static NSString *annotationIdentifier = #"annotationIdentifier";
RE_AnnotationView *newAnnotationView = (RE_AnnotationView *)[mapViews dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if(newAnnotationView)
{
newAnnotationView.annotation = myAnnotation;
}
else
{
newAnnotationView = [[RE_AnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:annotationIdentifier];
}
return newAnnotationView;
}
return nil;
Also, this is my initwithannotation method:
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if(self)
{
RE_Annotation *myAnnotation = annotation;
self = [super initWithAnnotation:myAnnotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor clearColor];
annotationView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map_pin_pink.png"]];
annotationView.frame = CGRectMake(0, 0, kWidth - 2 *kBorder, kHeight - 2 * kBorder);
[self addSubview:annotationView];
[annotationView setContentMode:UIViewContentModeScaleAspectFill];
self.canShowCallout = YES;
self.rightCalloutAccessoryView = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain]; ///if i take it out it doesnt crash the app. if i leave it it says that message
}
return self ;
}
In the initWithAnnotation method, there is this line:
self.rightCalloutAccessoryView =
[[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
///if i take it out it doesnt crash the app. if i leave it it says that message
By "it", you must be referring to the retain and this implies the app is not using ARC.
Based on that, you should make the following corrections:
In viewForAnnotation, you need to autorelease the view when you alloc+init it otherwise there's a leak:
newAnnotationView = [[[RE_AnnotationView alloc]
initWithAnnotation:myAnnotation
reuseIdentifier:annotationIdentifier] autorelease];
In initWithAnnotation, remove the retain when creating the callout button since buttonWithType returns an auto-released object (and you don't want to over-retain it):
self.rightCalloutAccessoryView =
[UIButton buttonWithType:UIButtonTypeInfoLight];
Another possibly unrelated issue is that in initWithAnnotation, the code is calling super initWithAnnotation twice. This seems unnecessary and may be harmful. Remove the second call.
The above changes at least fix the issues with the code shown. However, there may be other, similar memory-management related issues in the rest of the app that may still cause crashes. Check and solve all issues reported by Analyzer.
Regarding the fact that "it only happens in iOS 6": iOS 6 may be less forgiving of memory-management errors or an internal change in the SDK may be exposing or manifesting these errors earlier. Regardless, the above fixes are necessary.
An unrelated point is there should be no need to manually create a UIImageView and addSubview it to the annotation view. You can just set the annotation view's image property.

Resources