Используя IB (будь то с раскадровкой или XIB), можно легко заставить UITabBarController использовать подкласс UITabBar, отредактировав имя класса в Identity Inspector -> Custom Class.
Как имитировать эту функцию «настраиваемого класса» IB, вообще не используя ее?
Я пробовал следующее (в моем подклассе UITabBarController):
var customTabBar = MyCustomTabBarSubclass()
override var tabBar: UITabBar {
return customTabBar
}
Безрезультатно — панель вкладок отображается, но пуста. Проблема не в другом месте, так как return
ing super.tabBar
из переопределенного var
исправляет это.
Проблема, я думаю, в том, что я не настраиваю свой customTabBar
(кадр, положение, добавляю его в иерархию представлений), но я хотел бы, чтобы UITabBarController loadView
(думаю, это тот самый, не уверен) это для меня так же, как и любой другой UITabBar
.
Идеи?
Я думаю, ты не можешь. Для этого вопроса у меня было некоторое время, чтобы просмотреть документацию UITabBar
и UITabBarController
.
Свойство tabBar
для UITabBarController
доступно только для чтения.
@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.
Кроме того, в документацияUITabBarController
указано, что вы не должны манипулировать таким свойством.
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 не поддерживает это, и это правда.
Если вы решите игнорировать то, что Apple говорит о том, что поддерживается (используя собственный подкласс UITabBar с Interface Builder и только с ним), вот грязное решение (которое работает):
Для этого требуется небольшое знание среды выполнения ObjC, потому что мы собираемся перебирать вещи... По сути, проблема в том, что я не могу заставить UITabBarController создавать экземпляр класса, который я хочу создать (здесь MyCustomTabBarSubclass). Вместо этого он всегда создает экземпляр UITabBar
.
Но я знаю, как он создает экземпляр: вызывая -[[UITabBar alloc] initWithFrame:]
. И я также знаю, что всем функциям, принадлежащим к семейству init
, разрешено возвращать либо экземпляр своего класса, либо подкласса (это основа кластеров классов).
Итак, я собираюсь использовать это. Я собираюсь swizzle (= заменить реализацию) метода UITabBar -initWithFrame:
моей пользовательской версией, которая вместо вызова вверх (self = [super initWithFrame:]
) будет вызывать «вниз» (self = [MyCustomTabBarSubclass.alloc initWithFrame:]
). Таким образом, возвращаемый объект будет класса MyCustomTabBarSubclass, чего я и пытаюсь добиться.
Обратите внимание, как я вызываю MyCustomTabBarSubclass.alloc
— это потому, что мой подкласс потенциально имеет ivars, которых нет в UITabBar, что делает его больше в его структуре памяти. Возможно, мне придется освободить себя, прежде чем перераспределять его, иначе у меня может быть утечка выделенной памяти, но я совсем не уверен (и 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;
}
Вы также должны убедиться, что фактическая операция swizzling выполняется только один раз (например, dispatch_once
). Вот код, который действительно работает:
Method method = class_getInstanceMethod(NSClassFromString(@"UITabBar"), @selector(initWithFrame:));
IMP swizzleImp = (IMP)__Swizzle_InitWithFrame;
originalImp = method_setImplementation(method, swizzleImp);
Вот и все, что касается стороны ObjC. Свифт-сайд:
@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, не забудьте вызвать код ObjC, который выполняет swizzling.
Вот и все ! Вы обманули UITabBarController, создав экземпляр собственного подкласса UITabBar, а не ванильного. Если вы работаете в чистом ObjC, все еще проще (не нужно возиться с перекрывающими заголовками, тему, которую я здесь не рассматривал).
Обязательное ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Возиться со средой выполнения ObjectiveC, очевидно, нелегко. Убедитесь, что у вас нет лучшего решения — ИМХО, использование XIB только для того, чтобы избежать такой переделки, — лучшая идея, чем реализация моего предложения.
Пример проблемы, которая может возникнуть: если вы используете несколько панелей вкладок в своем приложении, вы можете не захотеть, чтобы все они были экземплярами класса MyCustomTabBarSubclass. Использование моего кода выше без модификаций приведет к тому, что панели вкладок все будут экземплярами MyCustomTabBarSubclass, поэтому вам придется найти способ указать __Swizzle_InitWithFrame
напрямую вызывать исходную реализацию или нет.
На самом деле я начинаю подозревать, что «пользовательские классы» IB работают под капотом — я не вижу более общего способа сделать это, чем использовать среду выполнения.
Это работает, но есть шанс, что вы не пройдете проверку в App Store, потому что он устанавливает значение, которое общедоступный API не позволяет вам устанавливать.
Внутри вашего подкласса UITabBarController
initWithNibName:bundle:
или viewDidLoad
добавьте это:
MyCustomTabBar *customTabBar = [[MyCustomTabBar alloc] initWithFrame:CGRectZero];
customTabBar.delegate = self;
[self setValue:customTabBar forKey:@"tabBar"];
Считайте это просто доказательством концепции, а не тем, что вы обязательно должны использовать в своем рабочем приложении, потому что технически оно использует частный метод setTabBar:
.
К сожалению, я неправильно понял ваше заявление о UITabBarController. Я также использую один и оборачиваю в него свой UITabBarController (
initWithRootController
), и подумал, что могу использовать его для настройки собственного класса UITabBar для меня. Это заставило меня по ошибке отметить ваш ответ как принятый, извините за это:/