iphone - privada - Obtener el marco de un elemento de barra de pestañas en particular
restaurar safari iphone (9)
A la implementación de Imre le faltan un par de detalles importantes.
- Las vistas UITabBarButton no están necesariamente en orden. Por ejemplo, si tiene más de 5 pestañas en iPhone y pestañas reorganizadas, las vistas podrían estar fuera de orden.
- Si usa más de 5 pestañas, el índice fuera de límites solo significa que la pestaña está detrás de la pestaña "más". En este caso, no hay ninguna razón para fallar con una afirmación, solo use el marco de la última pestaña.
Así que cambié un poco su código y se me ocurrió esto:
+ (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;
}
¿Hay alguna manera de encontrar el marco de un UITabBarItem
particular en una UITabBar
?
Específicamente, quiero crear una animación de una imagen "cayendo" en una de las pestañas, similar a, por ejemplo, eliminar un correo electrónico en el Correo o comprar una pista en la aplicación de iTunes. Entonces necesito las coordenadas del objetivo para la animación.
Por lo que puedo decir, no hay una API pública para obtener las coordenadas, pero me encantaría equivocarme al respecto. Aparte de eso, tendré que estimar las coordenadas usando el índice de la pestaña dada en relación con el marco de la barra de pestañas.
Agregaré lo que funcionó para mí en mi simple escenario UITabBarController, todo es legítimo, pero se supone que los elementos están espaciados por igual. Bajo iOS7, devuelve una instancia de un UITabBarButton, pero si lo usará como UIView * realmente no le importa lo que sea y no está indicando la clase explícitamente. El marco de la vista devuelta es el marco que está buscando:
-(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 ];
}
Lo que hace es calcular el rect en la Barra de herramientas donde reside el botón, encuentra el centro de este rect y extrae la vista en ese punto mediante hitTest: withEvent.
En Swift 4.0:
private func frameForTabAtIndex(index: Int) -> CGRect {
var frames = view.subviews.flatMap { (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
}
En Swift 4.2:
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
}
}
Las subvistas asociadas con los elementos de la barra de pestañas en una UITabBar son de la clase UITabBarButton. Al registrar las subvistas de una UITabBar con dos pestañas:
for (UIView* view in self.tabBar.subviews)
{
NSLog(view.description);
}
usted obtiene:
<_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>>
En base a esto, la solución es algo trivial. El método que escribí para probar esto es el siguiente:
+ (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;
}
Cabe señalar que la estructura (subvistas) de UITabBar y la clase UITabBarButton en sí no forman parte de la API pública, por lo que en teoría puede cambiar en cualquier nueva versión de iOS sin notificación previa. Sin embargo, es poco probable que cambien esos detalles, y funciona bien con iOS 5-6 y versiones anteriores.
Los botones de la barra de pestañas son las únicas vistas secundarias que tienen habilitada la interacción del usuario. Verifique esto en lugar de UITabBarButton para evitar violar las API ocultas.
for (UIView* view in self.tabBar.subviews)
{
if( [view isUserInteractionEnabled] )
{
[myMutableArray addObject:view];
}
}
Una vez en el conjunto, ordénelo en función del origen x, y tendrá los botones de la barra de pestañas en su verdadero orden.
No necesita ninguna API privada, solo use la propiedad itemWidth
y itemSpacing
. Establezca estos dos valores de la siguiente manera:
NSInteger tabBar.itemSpacing = 10; tabBar.itemWidth = ([UIScreen mainScreen].bounds.size.width - self.tabBarController.tabBar.itemSpacing * (itemsCount - 1)) /itemsCount;
Tenga en cuenta que esto no afectará el tamaño o la posición de la imagen y el título utilizados para UITabBarItem.
Y luego puedes obtener el centro del ítem x como sigue:
CGFloat itemCenterX = (tabBar.itemWidth + tabBar.itemSpacing) * ith + tabBar.itemWidth / 2;
Otra solución posible pero descuidada sería la siguiente:
guard let view = self.tabBarVC?.tabBar.items?[0].valueForKey("view") as? UIView else
{
return
}
let frame = view.frame
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)
}