Пользовательский подкласс UITabBar без построителя интерфейсов

Используя IB (будь то с раскадровкой или XIB), можно легко заставить UITabBarController использовать подкласс UITabBar, отредактировав имя класса в Identity Inspector -> Custom Class.

Как имитировать эту функцию «настраиваемого класса» IB, вообще не используя ее?

Я пробовал следующее (в моем подклассе UITabBarController):

var customTabBar = MyCustomTabBarSubclass()

override var tabBar: UITabBar {
    return customTabBar
}

Безрезультатно — панель вкладок отображается, но пуста. Проблема не в другом месте, так как returning super.tabBar из переопределенного var исправляет это.

Проблема, я думаю, в том, что я не настраиваю свой customTabBar (кадр, положение, добавляю его в иерархию представлений), но я хотел бы, чтобы UITabBarController loadView (думаю, это тот самый, не уверен) это для меня так же, как и любой другой UITabBar.

Идеи?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
1 117
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Я думаю, ты не можешь. Для этого вопроса у меня было некоторое время, чтобы просмотреть документацию 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 не имеет такого метода инициализации.

К сожалению, я неправильно понял ваше заявление о UITabBarController. Я также использую один и оборачиваю в него свой UITabBarController (initWithRootController), и подумал, что могу использовать его для настройки собственного класса UITabBar для меня. Это заставило меня по ошибке отметить ваш ответ как принятый, извините за это:/

Perceval 22.01.2019 21:16

Хорошо, я все равно принял ваш ответ, вы в основном говорите, что Apple не поддерживает это, и это правда.

Perceval 26.01.2019 09:26

Если вы решите игнорировать то, что 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 работают под капотом — я не вижу более общего способа сделать это, чем использовать среду выполнения.

Perceval 22.01.2019 23:49

Это работает, но есть шанс, что вы не пройдете проверку в App Store, потому что он устанавливает значение, которое общедоступный API не позволяет вам устанавливать.

Внутри вашего подкласса UITabBarControllerinitWithNibName:bundle: или viewDidLoad добавьте это:

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

Считайте это просто доказательством концепции, а не тем, что вы обязательно должны использовать в своем рабочем приложении, потому что технически оно использует частный метод setTabBar:.

Другие вопросы по теме