Using IB (be it with a Storyboard or a XIB), one can easily have the UITabBarController use a subclass of UITabBar, by editing the class name in the Identity Inspector -> Custom Class.
How to mimic this "custom class" feature of IB, without using it at all ?
I tried the following (in my subclass of UITabBarController) :
var customTabBar = MyCustomTabBarSubclass()
override var tabBar: UITabBar {
return customTabBar
}
To no avail – the tab bar is displayed, but blank. The issue is not elsewhere since return
ing super.tabBar
from the overriden var
fixes it.
The issue, I guess, is that I'm not setting up my customTabBar
(frame, position, adding it to the view hierarchy), but I'd like to have the UITabBarController loadView
(I think that's the one, not sure) do it for me, the same way it sets up any other UITabBar
.
Ideas ?
If you choose to disregard what Apple says about what's supported (using a custom UITabBar subclass with Interface Builder, and only with it), here's a dirty solution (that works) :
It requires mild knowledge of the ObjC runtime, because we're going to swizzle stuff around... Essentially, the issue is that I can't force UITabBarController to instantiate the class I want it to instantiate (here, MyCustomTabBarSubclass). Instead, it always instantiates
UITabBar
.But I know how it instantiates it : by calling
-[[UITabBar alloc] initWithFrame:]
. And I also know that all functions belonging to theinit
family are allowed to return either an instance of their class, or of a subclass (that's the basis of Class Clusters).So, I'm going to use this. I'm going to swizzle (= replace the implementation) of UITabBar's
-initWithFrame:
method with my custom version of it, that, instead of calling up (self = [super initWithFrame:]
) will call "down" (self = [MyCustomTabBarSubclass.alloc initWithFrame:]
). Thus, the returned object will be of class MyCustomTabBarSubclass, which is what I'm trying to achieve.Note how I'm calling
MyCustomTabBarSubclass.alloc
– this is because my subclass potentially has ivars that UITabBar does not have, thus making it larger in its memory layout. I might have to release self before reallocating it, otherwise I could be leaking the allocated memory, but I'm not sure at all (and ARC "forbids" me to do call-release
, so I'd have to use another step of trickery to call it).EDIT
(First thing, this method would also work for any case where IB's custom classes are of use).
Also note that implementing this requires writing ObjC code, as Swift does not allow us to call
alloc
, for instance – no pun intended. Here's the code :You'll also have to ensure that the actual swizzling operation is only performed once (such as,
dispatch_once
). Here's the code that actually swizzles :So that's it for the ObjC side. Swift-side :
And before you initialise your UITabBarController, don't forget to call the ObjC code that performs the swizzling.
That's it ! You have cheated UITabBarController into instantiating your own subclass of UITabBar, and not the vanilla one. If you're working in pure ObjC, things are even easier (no messing with bridging headers, a subject I didn't cover here).
Obligatory DISCLAIMER : Messing with the ObjectiveC runtime is obviously not something to do lightly. Ensure you have no better solution – IMHO, using a XIB only for the purpose of avoiding such tinkering is a better idea than implementing my suggestion.
A example of issue that could arise : if you're using multiple tab bars in your app, you might not want all of them to be MyCustomTabBarSubclass instances. Using my code above without modifications would result in all tab bars to be instances of MyCustomTabBarSubclass, so you'd have to find a way to tell
__Swizzle_InitWithFrame
directly call the original implementation, or not.This works, but there's a chance you might not pass App Store review because it's setting a value that public API doesn't allow you to set.
Inside your
UITabBarController
's subclass'initWithNibName:bundle:
orviewDidLoad
, add this:Consider this just a proof of concept, not something you should necessarily use in your production app because it's technically using a private
setTabBar:
method.I think you can't. For this question, I had some time to scan through the documentations of
UITabBar
andUITabBarController
.The
tabBar
property ofUITabBarController
is a get-only.Furthermore, it is stated in
UITabBarController
's documentation that you shouldn't manipulate such property.Just wanna add: but unlike this
UITabBarController
, subclassing theUINavigationController
gives you the power to init such subclass with a subclass ofUINavigationBar
:Unfortunately,
UITabBarController
does not have such kind of init method.