Is there a way to find the frame of a particular UITabBarItem
in a UITabBar
?
Specifically, I want to create an animation of an image \"falling\
Swift 5:
Just put this in your View Controller that handles your tabBar.
guard let tab1frame = self.tabBar.items![0].value(forKey: "view") as? UIView else {return}
then use like so:
let tab1CentreXanc = tab1frame.centerXAnchor
let tab1CentreY = tab1frame.centerYAnchor
let tab1FRAME = tab1frame (centerX and Y and other parameters such as ".origins.x or y"
Hope that helps.
If you want to reference the parameters from other tabs, just change the index from [0] to whatever you want
The subviews associated with the tab bar items in a UITabBar are of class UITabBarButton. By logging the subviews of a UITabBar with two tabs:
for (UIView* view in self.tabBar.subviews)
{
NSLog(view.description);
}
you get:
<_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>>
Based on this the solution is kind of trivial. The method I wrote for trying this out is as follows:
+ (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;
}
It should be noted that the structure (subviews) of UITabBar and the class UITabBarButton itself are not part of the public API, so in theory it can change in any new iOS version without prior notification. Nevertheless it is unlikely that they would change such detail, and it works fine with iOS 5-6 and prior versions.
Another possible but sloppy solution would be the following:
guard let view = self.tabBarVC?.tabBar.items?[0].valueForKey("view") as? UIView else
{
return
}
let frame = view.frame
In 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
}
You do not need any private API, just use the UITabbar property itemWidth
and itemSpacing
. Set these two values like following:
NSInteger tabBar.itemSpacing = 10;
tabBar.itemWidth = ([UIScreen mainScreen].bounds.size.width - self.tabBarController.tabBar.itemSpacing * (itemsCount - 1)) /itemsCount;
Note this will not impact the size or position of image and title used for UITabBarItem.
And then you can get the ith
item's center x like following:
CGFloat itemCenterX = (tabBar.itemWidth + tabBar.itemSpacing) * ith + tabBar.itemWidth / 2;
Imre's implementation is missing a couple of imho important details.
So I changed his code a little bit and I came up with this:
+ (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;
}