获取特定选项卡栏项目的框架

40

有没有办法找到UITabBar中特定的UITabBarItem框架?

具体而言,我想创建一个图像“落入”其中一个选项卡的动画,类似于例如在邮件中删除电子邮件或在iTunes应用程序中购买曲目。因此,我需要动画的目标坐标。

据我所知,没有公共API可以获取这些坐标,但很乐意接受错误的信息。如果没有这样的API,我将根据给定选项卡相对于选项卡栏框架的索引来猜测坐标。


嗨,你在 Swift 中找到这个问题的解决方案了吗?如果是的话,能帮我一下吗? - Rahul
11个回答

26

我认为Imre的实现缺少一些重要的细节。

  1. UITabBarButton视图不一定按顺序排列。例如,如果您在iPhone上有超过5个选项卡并重新排列了选项卡,则视图可能会失序。
  2. 如果使用超过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;
}

15
考虑到UITabBarButton是一个私有的iOS类,而且苹果公司的AppStore审核过程变得越来越严格,我认为最好不要在任何地方引用该类,即使是通过NSClassFromString()。但是它是UIControl子类,而所有的UIControl都实现了-(CGRect)frame方法。因此,您可以使用以下代码来简化一堆条件:if ([view isKindOfClass:[UIControl class]]) [tabBarItems addObject:view]; - Greg Combs
在设备旋转后,这似乎返回了不正确的结果。我从纵向旋转到横向,但它返回纵向值。当我从横向旋转到纵向时,它返回横向值。我在viewDidLayoutSubviews中运行此代码。有什么想法吗? - BFeher
在iOS 10中,assert被触发了。当这种情况发生时,所有子视图的原点都为零,因此视图尚未布局。我可能会等到它完成布局后再调用此方法。 - Aaron Ash

21

另一种可能但不够规范的解决方案如下:

guard let view = self.tabBarVC?.tabBar.items?[0].valueForKey("view") as? UIView else 
{
    return
}
let frame = view.frame

18

在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;
}

需要注意的是,UITabBar的结构(子视图)以及UITabBarButton类本身并不属于公共API的一部分,因此理论上可以在任何新的iOS版本中进行更改而没有事先通知。但是很少有可能更改这样的细节,并且它在iOS 5-6和之前的版本中运行良好。


+1 对于 iPad 是有效的,但这个解决方案对于 iPhone/iPod 并不适用。它会给出错误的坐标。 - iLearner
在iPhone上,iOS 9.2表现良好。我已经按照Greg Combs的建议使用了UIControl作为一种子视图类进行比较。 - rafalkitta

15

通过扩展 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
    }

}

14

在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
}

这在iPhone上运行得很好,但在iPad上似乎工作不正常。在iPad上,项目(或至少-图像)放置在中心,而这返回等宽的框架。 - KlimczakM
@KlimczakM 这应该返回实际项目的框架 - 如果您知道图像大小,可以在flatMap调用内创建给定大小的CGRect,居中于view.center - buildsucceeded

6

选项卡按钮是唯一启用了用户交互的子视图。为避免违反隐藏的API,请检查此选项而不是UITabBarButton。

for (UIView* view in self.tabBar.subviews)
    {
        if( [view isUserInteractionEnabled] )
        {
            [myMutableArray addObject:view];
        }
}

将其放入数组后,基于原点x进行排序,您将按照真实顺序拥有选项卡按钮。


3

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)
}

3

在我简单的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 ];
}

它的作用是计算UITabBar中按钮所在的矩形区域,找到该矩形的中心点,并通过hitTest:withEvent方法获取该点上的视图。

太好了!这个方法不那么hacky了。 - brynbodayle
8
无法在iPad上使用,因为每个项目的宽度不等于选项卡栏的宽度除以项目数量。 - ZYiOS

2

Swift 5:

只需将以下代码放入处理选项卡的视图控制器中即可。

guard let tab1frame = self.tabBar.items![0].value(forKey: "view") as? UIView else {return}

然后这样使用:
let tab1CentreXanc = tab1frame.centerXAnchor
let tab1CentreY = tab1frame.centerYAnchor
let tab1FRAME = tab1frame (centerX and Y and other parameters such as ".origins.x or y"

希望能对您有所帮助。

如果您想引用其他标签页中的参数,只需将索引从[0]更改为您想要的任何值即可。


0

您不需要任何私有API,只需使用UITabbar属性itemWidthitemSpacing。将这两个值设置如下:

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;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接