方法在Swift中调整

我正试图克服UINavigationBar一个小问题。 当您在视图控制器中使用prefersStatusBarHidden()方法隐藏状态栏时(我不想禁用整个应用程序的状态栏),导航栏将丢失属于状态栏的高度的20pt。 基本上导航栏缩小了。

在此处输入图像描述在此处输入图像描述

我正在尝试不同的解决方法,但我发现它们中的每一个都有缺点。 然后我遇到了这个使用方法调配的类,在UINavigationBar类中添加了一个名为fixedHeightWhenStatusBarHidden的属性,解决了这个问题。 我在Objective-C中进行了测试,它确实有效。 以下是原始Objective-C代码的标题和实现 。

现在,因为我在Swift中使用我的应用程序,所以我尝试将其转换为Swift。

我遇到的第一个问题是Swift扩展无法存储属性。 所以我不得不解决计算属性来声明使我能够设置值的fixedHeightWhenStatusBarHidden属性。 但这引发了另一个问题。 显然,您无法为计算属性赋值。 像这样。

 self.navigationController?.navigationBar.fixedHeightWhenStatusBarHidden = true 

我收到错误无法分配给该表达式的结果

无论如何,下面是我的代码。 它编译没有任何错误,但它不起作用。

 import Foundation import UIKit extension UINavigationBar { var fixedHeightWhenStatusBarHidden: Bool { return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue } func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize { if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden { let newSize = CGSizeMake(self.frame.size.width, 64) return newSize } else { return sizeThatFits_FixedHeightWhenStatusBarHidden(size) } } /* func fixedHeightWhenStatusBarHidden() -> Bool { return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue } */ func setFixedHeightWhenStatusBarHidden(fixedHeightWhenStatusBarHidden: Bool) { objc_setAssociatedObject(self, "FixedNavigationBarSize", NSNumber(bool: fixedHeightWhenStatusBarHidden), UInt(OBJC_ASSOCIATION_RETAIN)) } override public class func load() { method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:")) } } 

中间的fixedHeightWhenStatusBarHidden()方法被注释掉,因为它给了我一个方法重新声明错误。

我之前没有在Swift或Objective-C中进行方法调整,所以我不确定下一步要解决这个问题,甚至根本不可能。

有人可以对此有所了解吗?

谢谢。


更新1:感谢newacct,我关于属性的第一期已经解决。 但代码仍然不起作用。 我发现执行没有到达扩展中的load()方法。 从这个答案的评论中,我了解到你的类需要成为NSObject后代,在我的例子中, UINavigationBar并不直接来自NSObject但它实现了NSObjectProtocol 。 所以我不确定为什么这仍然不起作用。 另一个选择是添加@objc但Swift不允许您将其添加到扩展。 以下是更新的代码。

问题仍然存在。

 import Foundation import UIKit let FixedNavigationBarSize = "FixedNavigationBarSize"; extension UINavigationBar { var fixedHeightWhenStatusBarHidden: Bool { get { return objc_getAssociatedObject(self, FixedNavigationBarSize).boolValue } set(newValue) { objc_setAssociatedObject(self, FixedNavigationBarSize, NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN)) } } func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize { if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden { let newSize = CGSizeMake(self.frame.size.width, 64) return newSize } else { return sizeThatFits_FixedHeightWhenStatusBarHidden(size) } } override public class func load() { method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:")) } } 

更新2: Jasper帮助我实现了所需的function,但显然它带来了几个主要的缺点。

由于扩展中的load()方法没有触发,正如Jasper建议的那样,我将以下代码块移动到app delegates的didFinishLaunchingWithOptions方法。

 method_exchangeImplementations(class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits:"), class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits_FixedHeightWhenStatusBarHidden:")) 

我必须在fixedHeightWhenStatusBarHidden属性的getter中将返回值硬编码为’true’,因为现在调用代码在app委托中执行,您无法从视图控制器设置值。 这使得扩展在可重用性方面是多余的。

所以问题仍然或多或少地开放。 如果有人有想法改进它,请回答。

Objective-C,使用动态调度支持以下内容:

类方法调配:

你上面使用的那种。 类的所有实例都将使用新实现替换其方法。 新的实现可以选择包装旧的。

Isa-pointer嗖嗖声:

将类的实例设置为新的运行时生成的子类。 此实例的方法将替换为新方法,该方法可以选择性地包装现有方法。

消息转发:

在将消息转发给另一个处理程序之前,类通过执行某些工作充当另一个类的代理。

这些都是强大的拦截模式的变种,这是Cocoa的许多最佳function所依赖的。

输入Swift:

Swift延续了ARC的传统,因为编译器将代表您进行强大的优化。 它将尝试内联您的方法或使用静态调度或vtable调度。 虽然速度更快,但这些都会阻止方法拦截(在没有虚拟机的情况下)。 但是,您可以通过遵守以下内容向Swift表明您希望动态绑定(以及方法拦截):

  • 通过扩展NSObject或使用@objc指令。
  • 通过将动态属性添加到函数,例如public dynamic func foobar() -> AnyObject

在上面提供的示例中,满足了这些要求。 你的类通过UIViewUIResponderNSObject传递的,但是这个类别有些奇怪:

  • 该类别覆盖了load方法,通常会为类调用一次。 从类别中执行此操作可能并不明智,我猜测虽然它之前可能有用,但在Swift的情况下却没有。

请尝试将Swizzling代码移动到AppDelegate中,然后完成启动:

 //Put this instead in AppDelegate method_exchangeImplementations( class_getInstanceMethod(UINavigationBar.self, "sizeThatFits:"), class_getInstanceMethod(UINavigationBar.self, "sizeThatFits_FixedHeightWhenStatusBarHidden:")) 

我遇到的第一个问题是Swift扩展无法存储属性。

首先,Objective-C中的类别也不能具有存储属性(称为合成属性)。 您链接的Objective-C代码使用计算属性(它提供显式的getter和setter)。

无论如何,回到您的问题,您无法分配到您的属性,因为您将其定义为只读属性 。 要定义读写计算属性,您需要提供如下所示的getter和setter:

 var fixedHeightWhenStatusBarHidden: Bool { get { return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue } set(newValue) { objc_setAssociatedObject(self, "FixedNavigationBarSize", NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN)) } } 

虽然这不是你的Swift问题的直接答案(似乎它可能是一个错误, load不适用于NSObject的Swift扩展),但是,我确实有一个原始问题的解决方案

我发现子类化UINavigationBar并为混合解决方案提供类似的实现在iOS 7和8中有效:

 class MyNavigationBar: UINavigationBar { override func sizeThatFits(size: CGSize) -> CGSize { if UIApplication.sharedApplication().statusBarHidden { return CGSize(width: frame.size.width, height: 64) } else { return super.sizeThatFits(size) } } } 

然后在构建导航控制器时更新故事板中导航栏的类,或使用init(navigationBarClass: AnyClass!, toolbarClass: AnyClass!)来使用新类。

Swift没有在扩展上实现load()类方法。 与ObjC不同,运行时为每个类和类调用+ load,在Swift中,运行时只调用在类上定义的load()类方法; Swift 1.1忽略扩展中定义的load()类方法。 在ObjC中,每个类别都可以覆盖+加载,以便每个类/类别可以在该类别/类加载时注册通知或执行一些其他初始化(如混合)。

与ObjC一样,您不应该覆盖扩展中的initialize()类方法来执行调配。 如果要在多个扩展中实现initialize()类方法,则只调用一个实现。 这与+ load方法不同,其中运行时在应用程序启动时调用所有实现。

因此,要初始化扩展的状态,您可以在应用程序完成启动时从app委托调用该方法,或者在运行时调用+ load方法时从ObjC存根调用该方法。 由于每个扩展都可以有一个加载方法,因此它们应该是唯一命名的。 假设您有来自NSObject的SomeClass,ObjC存根的代码将如下所示:

在SomeClass + Foo.swift文件中:

 extension SomeClass { class func loadFoo { ...do your class initialization here... } } 

在SomeClass + Foo.m文件中:

 @implementation SomeClass(Foo) + (void)load { [self performSelector:@selector(loadFoo); } @end 

Swift 4的更新。

不再公开Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by SwiftMethod 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift

所以现在的方法是通过公共静态方法或通过单例运行你的混合代码。

方法在swift 4中调整