没有 Interface Builder 的自定义 UITabBar 子类

Custom UITabBar subclass without Interface Builder

使用 IB(无论是故事板还是 XIB),通过在身份检查器中编辑 class 名称,可以轻松地让 UITabBarController 使用 UITabBar 的子class ->自定义 Class。

如何在完全不使用 IB 的情况下模仿 IB 的 "custom class" 功能?

我尝试了以下(在我的 UITabBarController 的子 class 中):

var customTabBar = MyCustomTabBarSubclass()

override var tabBar: UITabBar {
    return customTabBar
}

无济于事 – 标签栏显示,但空白。问题不在其他地方,因为 returning super.tabBar 来自覆盖的 var 修复了它。

我想问题是我没有设置我的 customTabBar(框架、位置,将其添加到视图层次结构),但我想要 UITabBarController loadView(我想就是那个,不确定)为我做这件事,就像它设置任何其他人一样 UITabBar.

想法?

我认为你不能。对于这个问题,我有时间浏览了 UITabBarUITabBarController.

的文档

UITabBarControllertabBar 属性 是一个只读

@available(iOS 3.0, *)
    open var tabBar: UITabBar { get } // Provided for -[UIActionSheet showFromTabBar:]. Attempting to modify the contents of the tab bar directly will throw an exception.

此外,在UITabBarControllerdocumentation中声明你不应该操纵这样的属性。

You should never attempt to manipulate the UITabBar object itself stored in this property. If you attempt to do so, the tab bar view throws an exception. To configure the items for your tab bar interface, you should instead assign one or more custom view controllers to the viewControllers property. The tab bar collects the needed tab bar items from the view controllers you specify.

The tab bar view provided by this property is only for situations where you want to display an action sheet using the show(from:) method of the UIActionSheet class.

只是想补充:但与此 UITabBarController 不同的是,子类化 UINavigationController 使您能够使用 UINavigationBar:

的子类来初始化此类子类
UINavigationController(navigationBarClass: <#T##AnyClass?#>, toolbarClass: <#T##AnyClass?#>)

很遗憾,UITabBarController没有这样的初始化方法。

如果您选择忽略 Apple 所说的支持内容(使用带有 Interface Builder 的自定义 UITabBar subclass,并且仅使用它),这里有一个肮脏的解决方案(有效):

它需要对 ObjC 运行时有一定的了解,因为我们要弄乱一些东西...本质上,问题是我不能强制 UITabBarController 实例化我想要的 class实例化(此处为 MyCustomTabBarSubclass)。相反,它总是实例化 UITabBar.

但我知道它是如何实例化它的:通过调用 -[[UITabBar alloc] initWithFrame:]。而且我也知道属于 init 家族的所有函数都允许 return 它们的 class 或子 class 的实例(这是 return 的实例(这是 class 的基础=62=]簇)。

所以,我要用这个。我将使用我的自定义版本调整(= 替换实现)UITabBar 的 -initWithFrame: 方法,而不是调用 (self = [super initWithFrame:]) 将调用 "down" (self = [MyCustomTabBarSubclass.alloc initWithFrame:]).因此,returned object 将属于 class MyCustomTabBarSubclass,这正是我想要实现的目标。

请注意我是如何调用 MyCustomTabBarSubclass.alloc – 这是因为我的 subclass 可能具有 UITabBar 没有的 ivars,从而使其在内存布局中更大。我可能必须在重新分配之前释放自己,否则我可能会泄漏分配的内存,但我一点也不确定(而且 ARC "forbids" 我要调用 -release,所以我会使用另一步技巧来调用它)。


编辑

(首先,此方法也适用于任何使用 IB 自定义 classes 的情况)。

另请注意,实现此功能需要编写 ObjC 代码,因为 Swift 不允许我们调用 alloc,例如 – 没有双关语意。这是代码:

IMP originalImp = NULL;
id __Swizzle_InitWithFrame(id self, SEL _cmd, CGRect frame)
{
    Class c = NSClassFromString(@"MyBundleName.MyCustomTabBarSubclass");
    self = [c alloc]; //so that we'll return an instance of MyCustomTabBarSubclass
    if (self) {
        id (*castedImp)(id, SEL, CGRect) = (id (*)(id, SEL, CGRect))originalImp;
        self = castedImp(self, _cmd, frame); //-[super initWithFrame:]
    }
    return self;
}

您还必须确保实际的混合操作只执行一次(例如,dispatch_once)。这是实际调配的代码:

Method method = class_getInstanceMethod(NSClassFromString(@"UITabBar"), @selector(initWithFrame:));

IMP swizzleImp = (IMP)__Swizzle_InitWithFrame;
originalImp = method_setImplementation(method, swizzleImp);

这就是 ObjC 方面的内容。 Swift-side :

@objc class MyCustomTabBarSubclass: UITabBar {
    lazy var anIvar: Int = 0 //just a example obviously
    // don't forget to make all your ivars lazy or optional
    // because the initialisers WILL NOT BE CALLED, as we are
    // circumventing the Swift runtime normal init mechanism
}

在初始化 UITabBarController 之前,不要忘记调用执行 swizzling 的 ObjC 代码。

就是这样!你已经欺骗了 UITabBarController 来实例化你自己的 UITabBar 子class,而不是普通的。如果你在纯 ObjC 中工作,事情就更容易了(不要搞乱桥接 headers,这是我没有在这里讨论的主题)。

强制免责声明: 弄乱 ObjectiveC 运行时显然不是一件容易的事。确保您没有更好的解决方案——恕我直言,仅出于避免此类修补的目的而使用 XIB 比实施我的建议更好。

可能出现的问题示例:如果您在应用中使用多个标签栏,您可能不希望所有标签栏都是 MyCustomTabBarSubclass 实例。使用我上面的代码而不修改会导致 all 标签栏成为 MyCustomTabBarSubclass 的实例,所以你必须找到一种方法来直接告诉 __Swizzle_InitWithFrame是否调用原始实现。

这可行,但您可能无法通过 App Store 审核,因为它设置了 public API 不允许您设置的值。

在您的 UITabBarController's subclass' initWithNibName:bundle:viewDidLoad 中,添加:

MyCustomTabBar *customTabBar = [[MyCustomTabBar alloc] initWithFrame:CGRectZero];
customTabBar.delegate = self;
[self setValue:customTabBar forKey:@"tabBar"];

考虑这只是一个概念证明,而不是您必须在生产应用程序中使用的东西,因为它在技术上使用私有 setTabBar: 方法。