问题描述
为了说明我问这个问题的原因:基本上我想更改 iOS 上谷歌地图的 myLocationButton 的位置.所以我首先像这样获取实际的按钮:
@implementation GMSMapView (UIChanges) - (UIButton *)myLocationButton { UIButton *myLocationButton; for (myLocationButton in [settingView subviews]) { if ([myLocationButton isMemberOfClass:[UIButton class]]) break; } return myLocationButton; }
然后我尝试使用 NSLayoutConstraints 更改它在屏幕中的位置(直接更改按钮的 frame 属性的值对 google maps SDK 1.8+ 没有任何作用):
UIButton *myLocationButton = [mapView_ myLocationButton]; [myLocationButton setTranslatesAutoresizingMaskIntoConstraints:NO]; [myLocationButton constraintRightEqualTo:[myLocationButton superview] constant:0]; [myLocationButton constraintTopEqualTo:[myLocationButton superview] constant:50];
其中 constraintRightEqualTo 在类别中定义为:
- (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]; }
到目前为止这么好?好的.
现在这在 iOS8 中工作得很好.但是当我在 iOS 7 中运行它时,它会因这个著名的错误而崩溃:
<块引用>-[TPMURequestStatusNotificationManagermakeActionButtonResponsive]:810 - makeActionButtonResponsive2014-10-08 16:03:20.775 SmartTaxi[13009:60b] * 中的断言失败-[GMSUISettingsView layoutSublayersOfLayer:],/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-0816:03:20.779 SmartTaxi[13009:60b] * 由于未捕获而终止应用程序异常'NSInternalInconsistencyException',原因:'自动布局执行 -layoutSubviews 后仍然需要.GMUISettingsView 的-layoutSubviews 的实现需要调用 super.'
问题是 GMSUISettingsView 没有调用 [super layoutSubviews]..
我以前见过这种错误.问题是它发生在诸如 UITableViewCell 之类的公共类上,而不是隐藏在谷歌地图 SDK 内部的私有类 GMSUISettingsViewIOS.如果它是公开的.. 我可以很容易地 swizzled 方法 layoutsubviews 在其中使用类似于 this 答案的方法.但这不是公共方法.如何在运行时更改它的 layoutsubviews 的定义来解决这个问题?(也欢迎使用更简单的方法实现相同目标的建议)
<小时>更新
所以根据反馈 + 更多研究,我做了以下工作:
@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);
这种方法的问题是,任何时候 GMSUISettingsView 调用 layoutSubviews.. 它实际上是调用 _autolayout_replacementLayoutSubviews.. 然后递归调用 GMSUISettingsView layoutsubviews.. 所以我的应用程序进入无限递归循环.this answer 通过使用类别解决了这个问题.. 但在这种情况下我不能 b/c GMSUISettingsView 是私人课程..
问同样问题的另一种方式:如何保留对未更改版本 GMSUISettingsView's layoutSubviews 的引用并在 _autolayout_replacementLayoutSubviews 中使用它,这样我就不会陷入这种递归调用问题.
想法?
推荐答案
做到了.. 我不确定这是否算作实际答案,因为我只是通过简单地调用 [self layoutIfNeeded] 而不是 [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); }