方法在Swift中调整

Isu*_*uru 10 objective-c ios swift method-swizzling

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

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

我正在尝试不同的解决方法,但我发现它们中的每一个都有缺点.那时,我发现这次来到类别,其使用方法交叉混合,添加了一个名为属性fixedHeightWhenStatusBarHiddenUINavigationBar解决了这个问题类.我在Objective-C中进行了测试,但它确实有效.以下是原始Objective-C代码的标题实现.

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

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

self.navigationController?.navigationBar.fixedHeightWhenStatusBarHidden = true
Run Code Online (Sandbox Code Playgroud)

我得到错误无法分配给这个表达式的结果.

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

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:"))
    }

}
Run Code Online (Sandbox Code Playgroud)

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:"))
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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

method_exchangeImplementations(class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits:"), class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
Run Code Online (Sandbox Code Playgroud)

而且我必须在getter of fixedHeightWhenStatusBarHiddenproperty 中将返回值硬编码为'true',因为现在调用代码在app委托中执行,你不能从视图控制器设置一个值.这使得扩展在可重用性方面是多余的.

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

Jas*_*ues 17

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

类方法调配:

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

Isa-pointer嗖嗖声:

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

消息转发:

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

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

输入Swift:

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

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

在上面提供的示例中,满足了这些要求.你的类从传递性派生NSObject通过UIViewUIResponder,但有一些古怪该类别:

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

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

//Put this instead in AppDelegate
method_exchangeImplementations(
    class_getInstanceMethod(UINavigationBar.self, "sizeThatFits:"), 
    class_getInstanceMethod(UINavigationBar.self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))  
Run Code Online (Sandbox Code Playgroud)

  • 注意``dynamic`修饰符暗示`@objc`,所以从`NSObject`降序或明确地提供`@objc`属性是没有必要的(但是重要的是知道编译器这样做,因为`动态`仅对Objective-C声明支持修饰符) (2认同)