ios - Swift 中的方法调配

标签 ios objective-c swift method-swizzling

UINavigationBar 有一个小问题,我正在努力解决。当您在 View Controller 中使用 prefersStatusBarHidden() 方法隐藏状态栏时(我不想禁用整个应用程序的状态栏),导航栏失去其高度的 20pt 属于状态栏。基本上导航栏缩小了。

enter image description here enter image description here

我尝试了不同的解决方法,但我发现每一种方法都有缺点。然后我遇到了这个 category它使用方法 swizzling,将名为 fixedHeightWhenStatusBarHidden 的属性添加到 UINavigationBar 类,从而解决了这个问题。我在 Objective-C 中测试过它并且它有效。这是 headerimplementation原始 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() 方法。来自 this 中的评论回答,我了解到要么你的类需要是 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 帮助我实现了所需的功能,但显然它有几个主要缺点。

由于扩展中的 load() 方法未触发,正如 Jasper 所建议的,我将以下代码块移至应用程序委托(delegate)的 didFinishLaunchingWithOptions 方法。

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

而且我不得不在 fixedHeightWhenStatusBarHidden 属性的 getter 中将返回值硬编码为“true”,因为现在 swizzling 代码在应用程序委托(delegate)中执行,您无法从 View 中设置值 Controller 。这使得扩展在可重用性方面变得多余。

所以这个问题或多或少还是悬而未决的。如果有人有改进的想法,请回答。

最佳答案

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

类方法调配:

您在上面使用的那种。一个类的所有实例都将用新的实现替换它们的方法。新的实现可以选择性地包装旧的。

Isa 指针调配:

一个类的实例被设置为一个新的运行时生成的子类。这个实例的方法将被替换为一个新的方法,它可以选择包装现有的方法。

消息转发:

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

这些都是强大的拦截模式的变体,许多 Cocoa 的最佳功能都依赖于它。

输入 swift :

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

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

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

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

尝试将 Swizzling 代码移动到您的 AppDelegate 的 did finish launching 中:

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

关于ios - Swift 中的方法调配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25651081/

相关文章:

ios - 使用 xcodebuild -exportArchive(Xcode8.3,自动签名)时如何获取分发应用程序?

iphone - 遮蔽颜色后 iOS 上图像与 alpha 的边缘混合

ios - 同时执行多个异步请求

ios - 在需要从数据库计算的值时如何对 NSFetchedResultsController 更新使用react?

swift - Back4app - Swift - 将 PFObject 转换为类

ios - UITableViewCell 中的 ImageView 不会显示

ios - 使用自动布局实现按钮的网格布局

objective-c - 带有自动换行和截断的两行 UILabel 也在垂直顶部对齐

ios - 如何在cocos2d中填充纹理?

iphone - 动画 UIView alpha 的问题