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 + 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)
}
When initializing a tab bar, give it a tag:
let tabItem = UITabBarItem(title: "my title", image: nil, tag: 1000)
Then you can fetch the tab bar using:
let myTabItem = tabBarController?.tabBar.viewWithTag(1000)
Tab bar buttons are the only sub views that have user interaction enabled. Check for this instead of UITabBarButton to avoid violating hidden API's.
for (UIView* view in self.tabBar.subviews)
{
if( [view isUserInteractionEnabled] )
{
[myMutableArray addObject:view];
}
}
Once in the array, sort it based on the origin x, and you will have the tab bar buttons in their true order.
I'll add what worked for me in my simple UITabBarController scenario, everything is legit but it has an assumption that items are spaced equally. Under iOS7 it returns an instance of an UITabBarButton, but if you'll be using it as UIView* you really don't care what it is and you aren't stating the class explicitly. The frame of the returned view is the frame you're looking for:
-(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 ];
}
What it does is calculate the rect in the UITabBar where the button resides, finds the center of this rect and digs out the view at that point via hitTest:withEvent.
redead's answer definitely works so please upvote it.
I needed to add an activity indicator to the tabBar and to do that I needed the tabBar's rect inside the window so I had to add some additional code to his answer:
// 1. get the frame for the first tab like in his answer
guard let firstTab = tabBarController?.tabBar.items?[0].valueForKey("view") as? UIView else { return }
print("firstTabframe: \(firstTab.frame)")
// 2. get a reference to the window
guard let window = UIApplication.shared.keyWindow else { return }
// 3. get the firstTab's rect inside the window
let firstTabRectInWindow = firstTab.convert(firstTab.frame, to: window)
print("firstTabRectInWindow: \(firstTabRectInWindow)")
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
}
}