Получение рамки определенного элемента панели вкладок
Есть ли способ найти кадр конкретного UITabBarItem
в UITabBar
?
В частности, я хочу создать анимацию изображения, "падающего" на одну из вкладок, аналогичную, например, удаление электронной почты в Mail или покупка трека в приложении iTunes. Поэтому мне нужны координаты цели для анимации.
Насколько я могу судить, нет никакого открытого API для получения координат, но хотелось бы ошибаться в этом. Короче, мне придется оценивать координаты, используя индекс данной вкладки относительно рамки рамки табуляции.
Ответы
Ответ 1
Внедрение Imre содержит пару важных деталей imho.
- Представления UITabBarButton необязательно в порядке. Например, если у вас более 5 вкладок на iPhone и перераспределенные вкладки, просмотры могут быть не в порядке.
- Если вы используете более 5 вкладок, индекс за пределами границ означает, что вкладка находится за вкладкой "больше". В этом случае нет причин для отказа с утверждением, просто используйте рамку последней вкладки.
Итак, я немного изменил свой код, и я придумал это:
+ (CGRect)frameForTabInTabBar:(UITabBar*)tabBar withIndex:(NSUInteger)index
{
NSMutableArray *tabBarItems = [NSMutableArray arrayWithCapacity:[tabBar.items count]];
for (UIView *view in tabBar.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UITabBarButton")] && [view respondsToSelector:@selector(frame)]) {
// check for the selector -frame to prevent crashes in the very unlikely case that in the future
// objects thar don't implement -frame can be subViews of an UIView
[tabBarItems addObject:view];
}
}
if ([tabBarItems count] == 0) {
// no tabBarItems means either no UITabBarButtons were in the subView, or none responded to -frame
// return CGRectZero to indicate that we couldn't figure out the frame
return CGRectZero;
}
// sort by origin.x of the frame because the items are not necessarily in the correct order
[tabBarItems sortUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
if (view1.frame.origin.x < view2.frame.origin.x) {
return NSOrderedAscending;
}
if (view1.frame.origin.x > view2.frame.origin.x) {
return NSOrderedDescending;
}
NSAssert(NO, @"%@ and %@ share the same origin.x. This should never happen and indicates a substantial change in the framework that renders this method useless.", view1, view2);
return NSOrderedSame;
}];
CGRect frame = CGRectZero;
if (index < [tabBarItems count]) {
// viewController is in a regular tab
UIView *tabView = tabBarItems[index];
if ([tabView respondsToSelector:@selector(frame)]) {
frame = tabView.frame;
}
}
else {
// our target viewController is inside the "more" tab
UIView *tabView = [tabBarItems lastObject];
if ([tabView respondsToSelector:@selector(frame)]) {
frame = tabView.frame;
}
}
return frame;
}
Ответ 2
Подразделы, связанные с элементами панели вкладок в UITabBar, относятся к классу UITabBarButton. Записывая подпрограммы UITabBar с двумя вкладками:
for (UIView* view in self.tabBar.subviews)
{
NSLog(view.description);
}
вы получаете:
<_UITabBarBackgroundView: 0x6a91e00; frame = (0 0; 320 49); opaque = NO; autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x6a91e90>> - (null)
<UITabBarButton: 0x6a8d900; frame = (2 1; 156 48); opaque = NO; layer = <CALayer: 0x6a8db10>>
<UITabBarButton: 0x6a91b70; frame = (162 1; 156 48); opaque = NO; layer = <CALayer: 0x6a8db40>>
Исходя из этого, решение является тривиальным. Метод, который я написал для этого, выглядит следующим образом:
+ (CGRect)frameForTabInTabBar:(UITabBar*)tabBar withIndex:(NSUInteger)index
{
NSUInteger currentTabIndex = 0;
for (UIView* subView in tabBar.subviews)
{
if ([subView isKindOfClass:NSClassFromString(@"UITabBarButton")])
{
if (currentTabIndex == index)
return subView.frame;
else
currentTabIndex++;
}
}
NSAssert(NO, @"Index is out of bounds");
return CGRectNull;
}
Следует отметить, что структура (subviews) UITabBar и сам класс UITabBarButton не являются частью публичного API, поэтому теоретически он может изменяться в любой новой версии iOS без предварительного уведомления. Тем не менее маловероятно, что они изменят такие детали, и он отлично работает с iOS 5-6 и предыдущими версиями.
Ответ 3
Другим возможным, но неаккуратным решением будет следующее:
guard let view = self.tabBarVC?.tabBar.items?[0].valueForKey("view") as? UIView else
{
return
}
let frame = view.frame
Ответ 4
В Swift 4.2:
private func frameForTab(atIndex index: Int) -> CGRect {
var frames = view.subviews.compactMap { (view:UIView) -> CGRect? in
if let view = view as? UIControl {
return view.frame
}
return nil
}
frames.sort { $0.origin.x < $1.origin.x }
if frames.count > index {
return frames[index]
}
return frames.last ?? CGRect.zero
}
Ответ 5
Расширяя UITabBar
extension UITabBar {
func getFrameForTabAt(index: Int) -> CGRect? {
var frames = self.subviews.compactMap { return $0 is UIControl ? $0.frame : nil }
frames.sort { $0.origin.x < $1.origin.x }
return frames[safe: index]
}
}
extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Ответ 6
Кнопки панели вкладок являются единственными подменю, в которых включено взаимодействие с пользователем. Проверьте это вместо UITabBarButton, чтобы избежать нарушения скрытых API.
for (UIView* view in self.tabBar.subviews)
{
if( [view isUserInteractionEnabled] )
{
[myMutableArray addObject:view];
}
}
Как только в массиве, отсортируйте его на основе начала x, и у вас будут кнопки панели вкладок в их истинном порядке.
Ответ 7
Я добавлю, что сработало для меня в моем простом сценарии UITabBarController, все законно, но в нем есть предположение, что элементы распределены одинаково. В iOS7 он возвращает экземпляр UITabBarButton, но если вы будете использовать его как UIView *, вам все равно, что это такое, и вы явно не указываете класс. Кадр возвращаемого представления - это кадр, который вы ищете:
-(UIView*)viewForTabBarItemAtIndex:(NSInteger)index {
CGRect tabBarRect = self.tabBarController.tabBar.frame;
NSInteger buttonCount = self.tabBarController.tabBar.items.count;
CGFloat containingWidth = tabBarRect.size.width/buttonCount;
CGFloat originX = containingWidth * index ;
CGRect containingRect = CGRectMake( originX, 0, containingWidth, self.tabBarController.tabBar.frame.size.height );
CGPoint center = CGPointMake( CGRectGetMidX(containingRect), CGRectGetMidY(containingRect));
return [ self.tabBarController.tabBar hitTest:center withEvent:nil ];
}
Что он делает, так это вычисление rect в UITabBar, где находится кнопка, находит центр этого прямоугольника и выкапывает представление в этой точке через hitTest: withEvent.
Ответ 8
Swift + iOS 11
private func frameForTabAtIndex(index: Int) -> CGRect {
guard let tabBarSubviews = tabBarController?.tabBar.subviews else {
return CGRect.zero
}
var allItems = [UIView]()
for tabBarItem in tabBarSubviews {
if tabBarItem.isKind(of: NSClassFromString("UITabBarButton")!) {
allItems.append(tabBarItem)
}
}
let item = allItems[index]
return item.superview!.convert(item.frame, to: view)
}
Ответ 9
Вам не нужен какой-либо частный API, просто используйте свойство UITabbar itemWidth
и itemSpacing
. Задайте эти два значения следующим образом:
NSInteger tabBar.itemSpacing = 10;
tabBar.itemWidth = ([UIScreen mainScreen].bounds.size.width - self.tabBarController.tabBar.itemSpacing * (itemsCount - 1)) /itemsCount;
Примечание. Это не повлияет на размер или положение изображения и названия, используемых для UITabBarItem.
И затем вы можете получить центр элементов ith
x следующим образом:
CGFloat itemCenterX = (tabBar.itemWidth + tabBar.itemSpacing) * ith + tabBar.itemWidth / 2;