【问题标题】:Custom UITabBar subclass without Interface Builder没有 Interface Builder 的自定义 UITabBar 子类
【发布时间】:2026-01-03 09:15:02
【问题描述】:

使用 IB(无论是 Storyboard 还是 XIB),通过在 Identity Inspector -> Custom Class 中编辑类名,可以轻松地让 UITabBarController 使用 UITabBar 的子类。

如何在不使用它的情况下模仿 IB 的这种“自定义类”功能?

我尝试了以下方法(在我的 UITabBarController 子类中):

var customTabBar = MyCustomTabBarSubclass()

override var tabBar: UITabBar {
    return customTabBar
}

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

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

想法?

【问题讨论】:

    标签: ios swift uikit uitabbarcontroller uitabbar


    【解决方案1】:

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

    在您的UITabBarController 的子类'initWithNibName:bundle:viewDidLoad 中,添加以下内容:

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

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

    【讨论】:

      【解决方案2】:

      如果您选择无视 Apple 关于支持的内容(使用带有 Interface Builder 的自定义 UITabBar 子类,并且仅使用它),这是一个肮脏的解决方案(有效):

      它需要对 ObjC 运行时有一定的了解,因为我们要调换一些东西......本质上,问题是我不能强制 UITabBarController 实例化我希望它实例化的类(这里是 MyCustomTabBarSubclass) .相反,它总是实例化UITabBar

      但我知道它是如何实例化它的:通过调用-[[UITabBar alloc] initWithFrame:]。而且我还知道属于init 系列的所有函数都可以返回其类的实例或子类的实例(这是类簇的基础)。

      所以,我将使用它。我将用我的自定义版本来调整(=替换实现)UITabBar 的-initWithFrame: 方法,而不是调用(self = [super initWithFrame:]),而是调用“down”(self = [MyCustomTabBarSubclass.alloc initWithFrame:])。因此,返回的对象将属于 MyCustomTabBarSubclass 类,这是我想要实现的。

      注意我如何调用MyCustomTabBarSubclass.alloc——这是因为我的子类可能有 UITabBar 没有的 ivars,因此它的内存布局更大。我可能必须在重新分配之前释放自我,否则我可能会泄漏分配的内存,但我完全不确定(ARC“禁止”我打电话给-release,所以我不得不使用另一个步骤叫它的诡计)。


      编辑

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

      另外请注意,实现这一点需要编写 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 端:

      @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 子类,而不是普通的子类。如果你在纯 ObjC 中工作,事情会更容易(不要弄乱桥接头,我在这里没有涉及的主题)。

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

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

      【讨论】:

      • 我实际上开始怀疑这就是 IB 的“自定义类”在幕后工作的方式——我认为没有比使用运行时更通用的方法了。
      【解决方案3】:

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

      UITabBarControllertabBar 属性是 get-only。

      @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 中声明您不应操纵此类财产。

      您永远不应该尝试操纵 UITabBar 对象本身 存储在此属性中。如果您尝试这样做,标签栏视图 抛出异常。为标签栏配置项目 界面,您应该分配一个或多个自定义视图 控制器到 viewControllers 属性。标签栏收集 您指定的视图控制器中所需的选项卡栏项目。

      此属性提供的标签栏视图仅适用于情况 您想使用 show(from:) 方法显示操作表的位置 UIActionSheet 类的。

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

      UINavigationController(navigationBarClass: <#T##AnyClass?#>, toolbarClass: <#T##AnyClass?#>)
      

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

      【讨论】:

      • 哎呀,我误读了你关于 UITabBarController 的陈述。我也在使用一个并将我的 UITabBarController 包装在其中 (initWithRootController),并认为我可以使用它来为我设置一个自定义的 UITabBar 类。这导致我错误地将您的答案标记为已接受,对此感到抱歉:/
      • 好吧,我还是接受了你的回答,你基本上说苹果不支持这个,这是真的。