2

To give a bit of context of why I'm asking this: basically I would like to change the location of the myLocationButton of the google map on iOS. So I first fetch the actual button like so:

@implementation GMSMapView (UIChanges)

- (UIButton *)myLocationButton
{
    UIButton *myLocationButton;
    for (myLocationButton in [settingView subviews])
    {
        if ([myLocationButton isMemberOfClass:[UIButton class]])
            break;
    }
    return myLocationButton;
}

Then I try to change it's position in the screen using NSLayoutConstraints (directly changing values of the frame property of the button does nothing with google maps SDK 1.8+):

UIButton *myLocationButton = [mapView_ myLocationButton];
[myLocationButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[myLocationButton constraintRightEqualTo:[myLocationButton superview] constant:0];
[myLocationButton constraintTopEqualTo:[myLocationButton superview] constant:50];

where constraintRightEqualTo is defined in a category as:

- (void)constraintRightEqualTo:(UIView *)toView constant:(CGFloat)constant
{
    NSLayoutConstraint *cn = [NSLayoutConstraint constraintWithItem:self
                                                          attribute:NSLayoutAttributeRight

                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:toView
                                                          attribute:NSLayoutAttributeRight
                                                         multiplier:1 constant:constant];

    [toView addConstraint:cn];
}

so far so good? ok.

Now this works just fine in iOS8.. however when I run this in iOS 7 it crashes with this famous error:

-[TPMURequestStatusNotificationManager makeActionButtonResponsive]:810 - makeActionButtonResponsive 2014-10-08 16:03:20.775 SmartTaxi[13009:60b] * Assertion failure in -[GMSUISettingsView layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-08 16:03:20.779 SmartTaxi[13009:60b] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. GMSUISettingsView's implementation of -layoutSubviews needs to call super.'

the problem is that GMSUISettingsView is not calling [super layoutSubviews]..

I've seen this kind of error before.. the thing is that it happens with public classes such as UITableViewCell, as opposed to this private one GMSUISettingsView which is hidden in the guts of the google maps SDK for iOS. Had it been public.. i could have easily swizzled the method layoutsubviews inside of it using an approach similar to this answer. But it's not a public method. How can I in runtime change the definition of it's layoutsubviews to get around this problem? (suggestions to accomplish the same goal using simpler methods are also welcome)


UPDATE

so based on feedback + a bit more research I did the following:

@interface AttackerClass : UIView @end
@implementation AttackerClass

- (void)_autolayout_replacementLayoutSubviews
{
    struct objc_super superTarget;
    superTarget.receiver = self;
    superTarget.super_class = class_getSuperclass(object_getClass(self));

    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
    NSLog(@":: calling send super")
    // PROBLEM: recursive call.. how do I call the *original* 
    // GMSUISettingsView implementation of layoutSubivews here?
    // replacing this with _autolayout_replacementLayoutSubviews will
    // throw an error b/c GMSUISettingsView doesn't have that method defined
    objc_msgSend(self, @selector(layoutSubviews)); 
    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
}            
@end


Method attackerMethod = class_getInstanceMethod([AttackerClass class], @selector(_autolayout_replacementLayoutSubviews));
Method victimMethod = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews));

method_exchangeImplementations(victimMethod, attackerMethod);

The problem with this approach is that anytime GMSUISettingsView calls layoutSubviews.. it is in effect calling _autolayout_replacementLayoutSubviews.. which then recursively calls GMSUISettingsView layoutsubviews.. so my app goes into an infinite recursive loop. this answer solved that problem by using a category.. but I can't in this case b/c GMSUISettingsView is a private class..

another way of asking the same question: how can i retain a reference to the unaltered version of GMSUISettingsView's layoutSubviews and use it in _autolayout_replacementLayoutSubviews so that I don't fall into this recursive call problem.

ideas?

Community
  • 1
  • 1
abbood
  • 23,101
  • 16
  • 132
  • 246
  • That first piece of code is buggy. It will always return *something* and that *something* won't always be a `UIButton` or `nil`, which is the intended semantic I would imagine. Not to mention the uninitialized variable and the use of `isMemberOfClass` rather than `isKindOfClass`. – Droppy Oct 08 '14 at 13:31
  • @Droppy for argument's sake.. let' assume it's not buggy and it reliably returns what i'm expecting – abbood Oct 08 '14 at 13:34
  • if all you need to do is send `layoutSubviews` to the objects `super` class, cant you do this with `objc_msgSendSuper` instead of swizzling? – Brad Allred Oct 08 '14 at 14:46
  • @BradAllred tell me more? can you show me a code example of using `objc_msgSendSuper`? – abbood Oct 08 '14 at 14:48
  • that method doesn't seem to be available in the iOS runtime library.. only in osx runtime – abbood Oct 08 '14 at 14:49
  • yes it is. it is part of the objective-c runtime and this link clearly says ios in the url. https://developer.apple.com/library/ios/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/doc/uid/TP40001418 – Brad Allred Oct 08 '14 at 14:52
  • also se this SO answer which is also iOS http://stackoverflow.com/questions/10328915/call-super-without-super-keyword – Brad Allred Oct 08 '14 at 14:53

2 Answers2

2

this did it.. i'm not sure if this counts as an actual answer since i just got around the problem by simply calling [self layoutIfNeeded] instead of [self layoutSubviews]

void _autolayout_replacementLayoutSubviews(id self, SEL _cmd)
{
    // calling super
    struct objc_super superTarget;
    superTarget.receiver = self;
    superTarget.super_class = class_getSuperclass(object_getClass(self));
    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));

    // to get around calling layoutSubviews and having
    // a recursive call
    [self layoutIfNeeded];

    objc_msgSendSuper(&superTarget, @selector(layoutSubviews));
}

- (void)replaceGMSUISettingsViewImplementation
{
    class_addMethod(NSClassFromString(@"GMSUISettingsView"), @selector(_autolayout_replacementLayoutSubviews), (IMP)_autolayout_replacementLayoutSubviews, "v@:");

    Method existing = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews));
    Method new = class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(_autolayout_replacementLayoutSubviews));

    method_exchangeImplementations(existing, new);
}
abbood
  • 23,101
  • 16
  • 132
  • 246
  • Use `objc_msgSend` to send `@selector(_autolayout_replacementLayoutSubviews)` to `self`. (Rather than calling `[self layoutIfNeeded]`.) This has the effect of `[self _autolayout_replacementLayoutSubviews]` which you already identified as the desired behavior. – Nate Chandler Oct 30 '14 at 03:10
1

UPDATE

If you want to preserve original implementation you can get it and call it inside the blockImp. Then after that call [[aClass superclass] layoutSubviews] or call it with function pointer. Note that all this code need some error check and exception prevents.

//get method encoding
const char * encoding = method_getTypeEncoding(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews)));

void(^oldImplementation)(id aClass, SEL aSelector) = imp_getBlock(method_getImplementation(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews))));

id blockImp = ^void(id aClass, SEL aSelector)
{
    //old imlpementation
    oldImplementation(aClass, aSelector);

    //calling [super layoutSubviews]
    IMP aImp = [[aClass superclass] methodForSelector:@selector(layoutSubviews)];
    id (*func)(id, SEL) = (void *)aImp;
    func([aClass superclass], @selector(layoutSubviews));
};
IMP newImp = imp_implementationWithBlock(blockImp);
class_replaceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews), newImp, encoding);

ORIGINAL ANSWER

I'm sorry but I didn't fully understand do you want to completely override 'layoutsubviews' or to have original implementation and change just part of it. If it's the first you could use class_replaceMethod instead of swizzling. It will have performance hits though! Something like this should do the trick:

//get method encoding
const char * encoding = method_getTypeEncoding(class_getInstanceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews)));

id blockImp = ^void(id aClass, SEL aSelector)
{
    //what you want to happen in layout subviews
};
IMP newImp = imp_implementationWithBlock(blockImp);
class_replaceMethod(NSClassFromString(@"GMSUISettingsView"), @selector(layoutSubviews), newImp, encoding);

I've not tested this on device/simulator but something like this should work for you. I assume you know it's not a really bright idea to manipulate private classes ;)

hris.to
  • 6,235
  • 3
  • 46
  • 55
  • if it's not a bright idea to manipulate private classes then please suggest a better way of solving the problem i'm facing above without manipulating private classes – abbood Oct 08 '14 at 13:44
  • how do you ensure that `blockImp` still does what the original `layoutSubviews` does.. but also with adding [super layoutsubviews] as well? – abbood Oct 08 '14 at 13:47
  • aSelector above returns this `aSelector SEL "(\xae\xff\xbf/\xa1\xfa\x03 /4\x13\xe4\xfa\xa9\x01(" 0xbfffae08`.. so calling `oldImplementation(aClass, aSelector);` crashes.. – abbood Oct 08 '14 at 14:32
  • hey take a look at my updated question.. now i'm stuck in a recursive call – abbood Oct 09 '14 at 05:45