Пользовательский подкласс UITabBar без Interface Builder - PullRequest
0 голосов
/ 22 января 2019

Используя 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.

Идеи?

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Если вы решите игнорировать то, что Apple говорит о том, что поддерживается (с помощью пользовательского подкласса UITabBar с Interface Builder и только вместе с ним), вот грязное решение (которое работает):

Это требует слабых знаний осреда выполнения ObjC, потому что мы собираемся перебирать вещи вокруг ... По сути, проблема в том, что я не могу заставить UITabBarController создать экземпляр класса, который я хочу создать (здесь MyCustomTabBarSubclass).Вместо этого он всегда создает экземпляр UITabBar.

Но я знаю, как он это создает: вызывая -[[UITabBar alloc] initWithFrame:].И я также знаю, что всем функциям, принадлежащим семейству init, разрешено возвращать либо экземпляр своего класса, либо подкласса (это основа кластеров классов).

Итак, я собираюсь использовать это.Я собираюсь изобразить (= заменить реализацию) метода -initWithFrame: UITabBar моей собственной версией, которая вместо вызова up (self = [super initWithFrame:]) будет вызывать "down" (self = [MyCustomTabBarSubclass.alloc initWithFrame:]).Таким образом, возвращаемый объект будет иметь класс MyCustomTabBarSubclass, чего я и пытаюсь достичь.

Обратите внимание, как я звоню MyCustomTabBarSubclass.alloc - это потому, что у моего подкласса потенциально есть ивары, которых нет у UITabBar, таким образом, увеличивая размер памяти.Возможно, мне придется освободить себя перед тем, как перераспределить его, иначе я мог бы вытечь выделенную память, но я совсем не уверен (а ARC «запрещает» мне делать вызов -release, поэтому мне пришлось бы сделать еще один шагобмана, чтобы назвать это).


EDIT

(Во-первых, этот метод также будет работать для любого случая, когда используются пользовательские классы 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-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, не забудьте вызвать код ObjC, который выполняет Swizzling.

Вот и все!Вы обманули UITabBarController в создании своего собственного подкласса UITabBar, а не ванильного.Если вы работаете в чистом ObjC, все становится еще проще (не связывайтесь с мостовыми заголовками, тема, которую я здесь не освещал).

Обязательный ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Работа с ObjectiveCэто явно не то, что нужно делать легко.Убедитесь, что у вас нет лучшего решения - ИМХО, лучше использовать XIB только для того, чтобы избежать подобных ошибок, чем реализовать мое предложение.

Пример проблемы, которая может возникнуть: если вы используете в приложении несколько панелей вкладок, возможно, вы не захотите, чтобы все они были экземплярами MyCustomTabBarSubclass.Использование приведенного выше кода без изменений приведет к тому, что все панели вкладок станут экземплярами MyCustomTabBarSubclass, поэтому вам нужно будет найти способ указать __Swizzle_InitWithFrame напрямую вызывать исходную реализацию или нет.

0 голосов
/ 22 января 2019

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

Никогда не пытайтесь манипулировать самим объектом UITabBar, хранящимся вэто свойство.Если вы попытаетесь это сделать, в представлении панели вкладок возникнет исключение.Чтобы настроить элементы для интерфейса панели вкладок, вам следует вместо этого назначить один или несколько пользовательских контроллеров представления свойству viewControllers.Панель вкладок собирает необходимые элементы панели вкладок из указанных контроллеров представления.

Представление панели вкладок, предоставляемое этим свойством, предназначено только для ситуаций, когда вы хотите отобразить лист действий с помощью метода show (from :)класса UIActionSheet.

Просто хочу добавить: но в отличие от этого UITabBarController, подкласс UINavigationController дает вам возможность инициировать такой подкласс с подклассом UINavigationBar:

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

К сожалению, UITabBarController не имеет такого метода инициализации.

...