web-dev-qa-db-fra.com

Obtention du cadre d'un élément de barre d'onglets particulier

Existe-t-il un moyen de trouver le cadre d'un UITabBarItem particulier dans un UITabBar?

Plus précisément, je veux créer une animation d'une image "tombant" dans l'un des onglets, similaire à par ex. supprimer un e-mail dans le courrier ou acheter une piste dans l'application iTunes. J'ai donc besoin des coordonnées cibles pour l'animation.

Pour autant que je sache, il n'y a pas d'API publique pour obtenir les coordonnées, mais j'aimerais avoir tort à ce sujet. En dehors de cela, je devrai estimer les coordonnées en utilisant l'index de l'onglet donné par rapport au cadre de la barre d'onglets.

35
Daniel Dickison

L'implémentation d'Imre manque quelques détails importants à mon humble avis.

  1. Les vues UITabBarButton ne sont pas nécessairement en ordre. Par exemple, si vous avez plus de 5 onglets sur iPhone et des onglets réorganisés, les vues peuvent être hors service.
  2. Si vous utilisez plus de 5 onglets, l'index hors limites signifie uniquement que l'onglet est derrière l'onglet "plus". Dans ce cas, il n'y a aucune raison d'échouer avec une assertion, utilisez simplement le cadre du dernier onglet.

J'ai donc changé un peu son code et j'ai trouvé ceci:

+ (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;
}
25
Matthias Bauch

Les sous-vues associées aux éléments de la barre d'onglets dans un UITabBar sont de la classe UITabBarButton. En enregistrant les sous-vues d'un UITabBar avec deux onglets:

for (UIView* view in self.tabBar.subviews)
{
    NSLog(view.description);
}

vous obtenez:

<_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>>

Sur cette base, la solution est plutôt banale. La méthode que j'ai écrite pour essayer ceci est la suivante:

+ (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;
}

Il convient de noter que la structure (sous-vues) de UITabBar et la classe UITabBarButton elle-même ne font pas partie de l'API publique, donc en théorie, elle peut changer dans toute nouvelle version iOS sans notification préalable. Néanmoins, il est peu probable qu'ils modifient ces détails, et cela fonctionne bien avec iOS 5-6 et les versions antérieures.

17
Imre Kelényi

Une autre solution possible mais bâclée serait la suivante:

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

Dans 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
}
10
buildsucceeded

En étendant 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
    }

}
7
Lausbert

Les boutons de la barre d'onglets sont les seules sous-vues pour lesquelles l'interaction utilisateur est activée. Vérifiez cela au lieu de UITabBarButton pour éviter de violer les API cachées.

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

Une fois dans le tableau, triez-le en fonction de l'origine x, et vous aurez les boutons de la barre d'onglets dans leur véritable ordre.

5
TigerCoding

J'ajouterai ce qui a fonctionné pour moi dans mon scénario UITabBarController simple, tout est légitime mais il suppose que les éléments sont espacés également. Sous iOS7, il renvoie une instance d'un UITabBarButton, mais si vous l'utilisez comme UIView *, vous ne vous souciez vraiment pas de ce que c'est et vous ne spécifiez pas la classe explicitement. Le cadre de la vue renvoyée est le cadre que vous recherchez:

-(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 ];
}

Ce qu'il fait est de calculer le rect dans l'UITabBar où réside le bouton, trouve le centre de ce rect et creuse la vue à ce point via hitTest: withEvent.

3
maksa

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

Swift 5:

Mettez-le simplement dans votre View Controller qui gère votre tabBar.

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

puis utilisez comme ceci:

let tab1CentreXanc = tab1frame.centerXAnchor
let tab1CentreY = tab1frame.centerYAnchor
let tab1FRAME = tab1frame (centerX and Y and other parameters such as ".origins.x or y"

J'espère que cela pourra aider.

Si vous souhaitez référencer les paramètres d'autres onglets, changez simplement l'index de [0] en celui que vous voulez

1
MFK

Lors de l'initialisation d'une barre d'onglets, attribuez-lui une balise:

let tabItem = UITabBarItem(title: "my title", image: nil, tag: 1000)

Ensuite, vous pouvez récupérer la barre d'onglets en utilisant:

let myTabItem = tabBarController?.tabBar.viewWithTag(1000)

0
Rush B

Vous n'avez besoin d'aucune API privée, utilisez simplement la propriété UITabbar itemWidth et itemSpacing. Définissez ces deux valeurs comme suit:

NSInteger tabBar.itemSpacing = 10; tabBar.itemWidth = ([UIScreen mainScreen].bounds.size.width - self.tabBarController.tabBar.itemSpacing * (itemsCount - 1)) /itemsCount;

Notez que cela n'affectera pas la taille ou la position de l'image et du titre utilisés pour UITabBarItem.

Et puis vous pouvez obtenir le centre de l'élément ith comme suit:

CGFloat itemCenterX = (tabBar.itemWidth + tabBar.itemSpacing) * ith + tabBar.itemWidth / 2;

0
Bigyelow