UIBarButtonItem mit benutzerdefinierter Ansicht, die unter iOS 7 nicht richtig ausgerichtet ist, wenn sie als Elemente der linken oder rechten Navigationsleiste verwendet wird


75

Der folgende Code funktioniert über iOS 6:

UIButton *myButton = nil;
myButton = [UIButton buttonWithType:UIButtonTypeCustom];
myButton.bounds = CGRectMake(0,0,44,30);
// setup myButton's images, etc.

UIBarButtonItem *item = nil;
item = [[UIBarButtonItem alloc] initWithCustomView:customButton];

So soll die Schaltfläche ausgerichtet werden:

Normale Positionierung

Unter iOS 7 scheint die Schaltfläche jedoch um zu viele Pixel von rechts oder links versetzt zu sein:

Falsche Positionierung unter iOS 7

Wie kann ich meine benutzerdefinierten Balkenschaltflächenelemente richtig ausrichten?


Antworten:


67

Funktioniert bis iOS11!

Sie können negative flexible Leerzeichen und die Eigenschaft rightBarButtonItems anstelle von rightBarButtonItem verwenden:

UIBarButtonItem *spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
spacer.width = -10; // for example shift right bar button to the right

self.navigationItem.rightBarButtonItems = @[spacer, yourBarButton];

2
Ich kann nicht verstehen, warum wir etwas so Einfaches hacken müssen ... Das gibt es schon seit mindestens einem Jahr. Keine Lösung Apple?
Petar

3
Es verstößt gegen das Human Interface Guide. Deshalb ist es nicht anpassbar. Sie hoffen, dass Sie es in Ruhe lassen. Dies ist ein Problem bei Designern, die die Richtlinie nicht lesen.
smileBot

Obwohl ich einen Designerfreund vor solchen Dingen gewarnt habe, kann ich bestätigen, dass es funktioniert. Wir haben jetzt eine übergroße Zubehöransicht.
Will

1
Klappt wunderbar. Und es ist die einzige Möglichkeit, auf allen iOS 6 ~ 8-Systemen ohne Unterklassen zu arbeiten UINavigationBar. Gute Antwort!
Liuyaodong

Ja. Apple ändert das Layout der Navigationsleiste.
Malex

57

Um diesen Fehler zu beheben, müssen Sie UIButton in Unterklassen unterteilen, damit Sie ihn überschreiben können alignmentRectInsets. Nach meinen Tests müssen Sie je nach Position der Schaltfläche ein UIEdgeInsets mit einem positiven Versatz nach rechts oder einem positiven Versatz nach links zurückgeben. Diese Zahlen ergeben für mich keinen Sinn (mindestens eine davon sollte nach gesundem Menschenverstand negativ sein), aber genau das funktioniert:

- (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;
    if (IF_ITS_A_LEFT_BUTTON) {
        insets = UIEdgeInsetsMake(0, 9.0f, 0, 0);
    } 
    else { // IF_ITS_A_RIGHT_BUTTON
        insets = UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    return insets;
}

Besonderer Dank geht an @zev für den Vorschlag, AlignmentRectInsets anzupassen.


1
Muss ich diesen Code nur unter iOS7 anwenden? Was ist mit iOS6? Vielen Dank.
Ricardo

Chris: In meinem Code habe ich die Position festgelegt, wenn ich die Schaltfläche zugewiesen / initiiert habe. Da es sich sowieso um eine Unterklasse handelt, habe ich einen benutzerdefinierten Wert @propertyfür die Position links oder rechts hinzugefügt . Ricardo: Dies ist nur für iOS 7.
Jaredsinclair

3
Dies funktioniert, aber wenn ich eine Ansicht aus dem Navigationsstapel öffne und "zurück" animiert, ist die Schaltfläche etwa eine halbe Sekunde lang immer noch falsch versetzt und springt dann an die richtige Stelle. Irgendeine Idee, wie dies verhindert werden kann?
Kyle Begeman

Ich habe das gleiche Problem, als ich dies implementiert habe. Jetzt springt mein Knopf von seiner Position. Ich habe und diese Antwort stackoverflow.com/questions/15261148/… verwendet, aber das hat mir auch nicht geholfen. Irgendwelche Ideen?
Lily

@KyleBegeman & Lily - Ich sehe dieses Verhalten unter iOS 7 nicht. Verwenden Sie diesen Code unter iOS 6?
Jaredsinclair

28

Ich habe alle oben genannten Antworten ausprobiert und nichts hat bei mir funktioniert. Und hier ist, was funktioniert, wenn jemand dies brauchen würde:

(Keine Unterklasse erforderlich)

// Add your barButtonItem with custom image as the following 
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStyleBordered target:self action:@selector(categoryButtonPressed)];
// set your custom image
[barButton setImage:categoryImage];
// finally do the magic
barButton.imageInsets = UIEdgeInsetsMake(0.0, -20, 0, 0);

- Sehen Sie sich das Ergebnis an.

Beachten Sie den Abstand zwischen der linken und der rechten Schaltfläche (die rechte Schaltfläche hat das Standardverhalten).

Geben Sie hier die Bildbeschreibung ein


10

Ok, ich bin in die andere "Richtung" gegangen. Ich habe alles über Storyboard mit iOS 7 richtig ausgerichtet (vorausgesetzt, es wird weiterhin so funktionieren). Und dann unter Verwendung des Beschreibungsunterklassenansatzes unterschreibe ich die Unterklasse UIButtonmit der folgenden Implementierung.

- (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;
    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
        if ([self isLeftButton]) {
            insets = UIEdgeInsetsMake(0, -10, 0, 0);
        } else {
            insets = UIEdgeInsetsMake(0, 0, 0, -10);
        }
    } else {
        insets = UIEdgeInsetsZero;
    }

    return insets;
}

- (BOOL)isLeftButton {
    return self.frame.origin.x < (self.superview.frame.size.width / 2);
}

Dieser Code wird also nur ausgeführt, wenn das Gerät vor iOS 7 ist.

Danke für den Einblick @jaredsinclair!


Funktioniert einwandfrei, obwohl Sie wahrscheinlich UIEdgeInsetsMake (0, 0, 0, 10) anstelle von -10 für die rechte Tastenkombination möchten.
Şafak Gezer

Während Animationen unter iOS 6 stieß ich tatsächlich auf Probleme damit, also zog ich dies heraus und ging diesen Weg, stackoverflow.com/a/19169682/250190
Chris Wagner

3

@jaredsinclair

Hier ist ein Blick auf meinen Code.

// Create the tweetly button that will show settings
self.tweetlyDisplay = [NavButton buttonWithType:UIButtonTypeCustom];
[self.tweetlyDisplay setFrame:CGRectMake(0, 0, 90, 44)];
[self.tweetlyDisplay setBackgroundColor:[UIColor clearColor]];
[self.tweetlyDisplay setBackgroundImage:[UIImage imageNamed:@"settings.png"] forState:UIControlStateNormal];
[self.tweetlyDisplay setAdjustsImageWhenHighlighted:NO];
[self.tweetlyDisplay addTarget:self action:@selector(tweetlyPressed:) forControlEvents:UIControlEventTouchUpInside];

// Add the Tweetly button as the left bar button item
// This had a glitch that moves the image to the right somewhat
UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc] initWithCustomView:self.tweetlyDisplay];
self.navigationItem.leftBarButtonItem = leftBarButton;

Sehen Sie etwas, das nicht richtig ist?

Hier ist das Ergebnis. Das zweite Bild ist nicht so sichtbar, weil ich den Screenshot zeitlich aufnehmen musste und er sich noch im Übergang befindet, aber Sie können deutlich sehen, wie er nicht richtig versetzt ist.

Gutes normales Bild:

Dies ist das gute Bild, bevor Sie herum navigieren

Schlechtes Versatzbild:

Sie können sehen, dass der Versatz jetzt wieder ausgeschaltet ist, bevor er einrastet

Nach etwa einer halben Sekunde kehrt das Bild zum ursprünglichen Bildort zurück.

Hier ist mein Code für NavButton.h und .m:

/**********************************************

 NavButton.h

 **********************************************/

#import <UIKit/UIKit.h>

@interface NavButton : UIButton

@end






/**********************************************

 NavButton.m

**********************************************/


#import "NavButton.h"

@implementation NavButton {

    int imageHeight;

}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code

        imageHeight = 44;

    }
    return self;
}

/*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
 - (void)drawRect:(CGRect)rect
 {
 // Drawing code
 }
 */


- (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;
    if ([self isLeftButton]) {
        insets = UIEdgeInsetsMake(0, 9.0f, 0, 0);
    }
    else { // IF ITS A RIGHT BUTTON
        insets = UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    return insets;
}


- (BOOL)isLeftButton {
    return self.frame.origin.x < (self.superview.frame.size.width / 2);
}


// THIS IS THE TRICK.  We make the height of the background rect match the image.
-(CGRect)backgroundRectForBounds:(CGRect)bounds
{
    CGRect bgRect = bounds;
    bgRect.origin.y = (bounds.size.height - imageHeight)/2.0f;
    bgRect.size.height = imageHeight;

    return bgRect;
}


@end

Das sieht alles gut für mich aus. Es kann sein, dass die oben vorgeschlagene Problemumgehung in diesem speziellen Fall nicht funktioniert. Sie könnten ein Design mit dieser Schaltfläche oben rechts ausprobieren, nein?
Jaredsinclair

Oben rechts hat schon einen eigenen Button! Ich muss nur sehen, was ich sonst noch tun kann. Haben Sie eine Idee, wie Sie feststellen können, wann der Root-View-Controller nach dem Popup des Stacks erneut geladen wird? Vielleicht kann ich die Schaltfläche einfach zur Navigationsleistenansicht anstatt als Balkenschaltflächenelement hinzufügen und sie dann ausblenden, wenn Sie einen Ansichts-Controller drücken, und sie dann wieder einblenden, wenn Sie zum Root-Ansichts-Controller
wechseln

Das Hinzufügen eigener Ansichten zu einer Navigationsleiste ist eine Tüte voller Verletzungen. Ich würde das nicht tun, wenn ich keine andere Wahl hätte. Sie können versuchen, dies mithilfe der UINavigationControllerDelegate-Methoden herauszufinden.
Jaredsinclair



1

Kein großer Fan von Unterklassen UIButtonoder Methoden, die aus Marius 'Antwort hervorgehen: https://stackoverflow.com/a/19317105/287403

Ich habe nur einen einfachen Wrapper verwendet und das x des Rahmens der Schaltfläche in eine negative Richtung verschoben, bis ich die richtige Position gefunden habe. Das Tippen auf die Schaltfläche scheint ebenfalls in Ordnung zu sein (obwohl Sie die Breite der Schaltfläche bei Bedarf an den negativen Versatz anpassen können).

Hier ist der Code, mit dem ich einen neuen Zurück-Button generiere:

- (UIBarButtonItem *) newBackButton {
    // a macro for the weak strong dance
    @weakify(self);
    UIButton *backButton = [[UIButton alloc] init];
    [backButton setTitle:@"Back" forState:UIControlStateNormal];
    backButton.titleLabel.font = [UIFont systemFontOfSize:17];
    CGFloat width = [@"Back" boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:17]} context:nil].size.width + 27;
    backButton.frame = CGRectMake(-17.5, 0, width + 17.5, 44);
    [backButton setImage:[UIImage imageNamed:@"UINavigationBarBackIndicatorDefault"] forState:UIControlStateNormal];
    [backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 14, 0, 0)];
    [backButton setTitleColor:mTCOrangeColor forState:UIControlStateNormal];
    backButton.contentEdgeInsets = UIEdgeInsetsZero;
    backButton.imageEdgeInsets = UIEdgeInsetsZero;
    [backButton addEventHandler:^(id sender) {
        // a macro for the weak strong dance
        @strongify(self);
        // do stuff here
    } forControlEvents:UIControlEventTouchUpInside];
    UIView *buttonWrapper = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
    [buttonWrapper addSubview:backButton];
    return [[UIBarButtonItem alloc] initWithCustomView:buttonWrapper];
}

1

Ich möchte zu @jaredsinclair eine bestätigte Antwort hinzufügen. Die folgenden Überschreibungsmethoden gelten für diejenigen, deren Navigationsschaltfläche Text und / oder Bild enthält. Andernfalls werden Text und Bild nicht ausgerichtet (nur das Hintergrundbild):

- (UIEdgeInsets) titleEdgeInsets {
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"))
{
    if (IF_ITS_A_LEFT_BUTTON) {
    {
        return UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    else
    { // IF_ITS_A_RIGHT_BUTTON
        return UIEdgeInsetsMake(0, 9.0f, 0, 0);
    }
}
return [super titleEdgeInsets];
}

- (UIEdgeInsets)imageEdgeInsets {
if(SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"))
{
    if (IF_ITS_A_LEFT_BUTTON)
    {
        return UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    else
    { // IF_ITS_A_RIGHT_BUTTON
        return UIEdgeInsetsMake(0, 9.0f, 0, 0);
    }
}

return [super imageEdgeInsets];
}

ps das makro ist:

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)

0

* Lösung gefunden, lesen Sie am Ende der Antwort. * *

@jaredsinclair

Ich habe einen ähnlichen Fall wie Kyle Begeman

Dies ist die Schaltfläche

 - (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
    }
    return self;
}

- (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;

    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")){
        if ([self isLeftButton]) {
            insets = UIEdgeInsetsMake(0, 9.0f, 0, 0);
        } else {
            insets = UIEdgeInsetsMake(0, 0, 0, 9.0f);
        }
    }else{
        insets = UIEdgeInsetsZero;
    }

    return insets;
}

- (BOOL)isLeftButton {
    return self.frame.origin.x < (self.superview.frame.size.width / 2);
}

und ich benutze es in diesem Fall

PBNavigationBarButton *dashboardButton = [PBNavigationBarButton buttonWithType:UIButtonTypeCustom];
    UIImage *dashboardImage = [UIImage imageNamed:@"btn_dashboard.png"];
    UIImage *dashboardImageHighlighted = [UIImage imageNamed:@"btn_dashboard_pressed.png"];

    NSInteger halfImageWidth = ceilf([dashboardImage size].width/2.0f);

    [dashboardButton setBackgroundImage:[dashboardImage stretchableImageWithLeftCapWidth:halfImageWidth topCapHeight:0] forState:UIControlStateNormal];
    [dashboardButton setBackgroundImage:[dashboardImageHighlighted stretchableImageWithLeftCapWidth:halfImageWidth topCapHeight:0] forState:UIControlStateHighlighted];
    [dashboardButton addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    [dashboardButton sizeToFit];

    UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc]  initWithCustomView:dashboardButton];

Alles sieht gut aus, außer dass sich die Schaltfläche neu positioniert, wenn ich zur vorherigen VC zurückkehre. Wie ein kleiner Sprung. Für mich springt es sofort nicht nach einer Sekunde. Und es passiert, nachdem ich die popVC von NavigationViewController aus gemacht habe.

Edit: Die Antwort unten, die Swizzling-Methode von Marius hat mir auch geholfen.


0

Ich habe mir eine verkürzte Version von Jaredsinclairs Ansatz ausgedacht:

- (UIEdgeInsets)alignmentRectInsets {
    return [self isLeftButton] ? UIEdgeInsetsMake(0, 9.0f, 0, 0) : UIEdgeInsetsMake(0, 0, 0, 9.0f); 
}

- (BOOL)isLeftButton {
    return self.frame.origin.x < (self.superview.frame.size.width / 2);
}

Klappt wunderbar.


0

In iOS 7 können Sie einfach einen Dummy-Barbutton hinzufügen, um den linken Platz zu fixieren. Sie sollten den Dummy als ersten und den rechten als letzten hinzufügen

Beispiel für links: Fügen Sie dies nach dem Festlegen Ihrer Originalelemente oder in viewdidload hinzu, wenn Sie Schaltflächen mithilfe des Storyboards festlegen.

NSMutableArray *buttons = [[NSMutableArray alloc] init];
UIBarButtonItem *spacerItem = [[UIBarButtonItem alloc] init];
[buttons addObject:spacerItem];
for(UIBarButtonItem *item in self.leftBarButtonItems){
    [buttons addObject:item];
}
[self setLeftBarButtonItems:[NSArray arrayWithArray:buttons] animated:NO];

0

Es wird einfach angepasst imageEdgeInsets, um diesen Fehler zu beheben.

Versuche dies.

    UIButton *actionBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
    [actionBtn setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];
    [actionBtn addTarget:self action:@selector(goToSetting) forControlEvents:UIControlEventTouchUpInside];
    actionBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 9, 0, -9);

    UIBarButtonItem *profileBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:actionBtn];
    self.topViewController.navigationItem.rightBarButtonItems = @[profileBarButtonItem];

0

Getestet in iOS 11

 public lazy var navigationBackBarButton : UIBarButtonItem = {

        let backBarButtonIcon = <Image Literal here>
        let backBarButton = UIBarButtonItem(image: backBarButtonIcon, style: .plain, target: self, action: #selector(self.backBarButtonTouched(_:)))
        backBarButton.imageInsets = UIEdgeInsets(top: 0.0, left: -9.0, bottom: 0.0, right: 0.0)
        return backBarButton

    }()

0

2018, iOS 11+, Swift 4.x, das hat bei mir funktioniert.

Kombinieren Sie die Top-Antworten:

Eigentum

internal lazy var button_Favorite: UIButton = {
    let button = UIButton(type: .custom)
    button.setImage(.favNormal, for: .normal)
    button.contentHorizontalAlignment = .right
    button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -9.0)
    return button
}()

In viewDidLoad:

    let barButton = UIBarButtonItem(customView: self.button_Favorite)
    self.button_Favorite.frame = CGRect(x: 0, y: 0, width: 40.0, height: 44.0)
    let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
    negativeSpacer.width = -10.0
    self.navigationItem.rightBarButtonItems = [negativeSpacer, barButton]

-1

Ändern Sie Folgendes für Ihre UIButton

Steuerung -> Ausrichtung -> Horizontal nach rechts ausrichten

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.