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?
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);
}