方法在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
在上面提供的示例中,满足了这些要求。 你的类是通过UIView
和UIResponder
从NSObject
传递的,但是这个类别有些奇怪:
- 该类别覆盖了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 Swift
: Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift
所以现在的方法是通过公共静态方法或通过单例运行你的混合代码。
方法在swift 4中调整
- 适用于iOS开发人员的前5个命令行工具
- 本地化资产目录
- 如何在Array(或某些数据结构?)中获取Assets.xcassets文件名?
- Facebook上的iOS应用程序的饲料?
- application:continueUserActivity:restorationHandler:在iOS 11中未调用
- iOS5 ARC可以安排从后台select器安排NSTimers吗?
- 使用Marathon with在Swift中使用脚本提高生产力
- SKLocalizable —使您的iOS应用本地化更轻松的新方法
- 在UIApplicationStateBackground状态中更改UIScrollView的zoomScale和contentOffset