【发布时间】:2014-10-08 13:26:00
【问题描述】:
为了说明我问这个问题的原因:基本上我想更改 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 中运行它时,它会因这个著名的错误而崩溃:
-[TPMURequestStatusNotificationManager makeActionButtonResponsive]:810 - makeActionButtonResponsive 2014-10-08 16:03:20.775 SmartTaxi[13009:60b] * 中的断言失败 -[GMSUISettingsView layoutSublayersOfLayer:],/SourceCache/UIKit_Sim/UIKit-2935.137/UIView.m:8794 2014-10-08 16:03:20.779 SmartTaxi[13009:60b] * 由于未捕获而终止应用程序 异常'NSInternalInconsistencyException',原因:'自动布局 执行 -layoutSubviews 后仍然需要。 GMUISettingsView 的 -layoutSubviews 的实现需要调用 super.'
问题是GMSUISettingsView 没有调用[super layoutSubviews]..
我以前见过这种错误。问题是它发生在诸如UITableViewCell 之类的公共类上,而不是隐藏在谷歌地图SDK 中的私有类GMSUISettingsView IOS。如果它是公开的.. 我可以很容易地在其中使用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 中使用它,这样我就不会陷入这个递归调用问题。
想法?
【问题讨论】:
-
第一段代码有问题。它总是会返回 something 并且 something 并不总是
UIButton或nil,这是我想象的预期语义。更不用说未初始化的变量和使用isMemberOfClass而不是isKindOfClass。 -
@Droppy 为了论证..假设它没有错误并且它可靠地返回了我所期望的
-
如果您只需将
layoutSubviews发送到对象super类,您就不能使用objc_msgSendSuper来代替swizzling 吗? -
@BradAllred 告诉我更多?你能给我看一个使用
objc_msgSendSuper的代码示例吗? -
该方法似乎在 iOS 运行时库中不可用.. 仅在 osx 运行时
标签: ios objective-c iphone google-maps-sdk-ios objective-c-runtime