web-dev-qa-db-de.com

iOS 11 UINavigationBar-Leistenschaltflächenelemente ausrichten

Ich aktualisiere meine Anwendung auf iOS 11 und sehe einige Probleme mit der Navigationsleiste. Einige meiner Probleme haben hier bereits Fragen gestellt, daher werde ich sie in dieser Frage nicht erwähnen.

Das besondere Problem besteht darin, dass die Schaltflächenelemente der Navigationsleiste unterschiedliche Abstände aufweisen. Meine Hauptschaltflächen für die linke und rechte Leiste befinden sich jetzt näher an der horizontalen Mitte des Bildschirms, und ich kann sie nicht in die Nähe der Bildschirmränder verschieben. In der Vergangenheit habe ich eine benutzerdefinierte Unterklasse UIButton verwendet und Elemente für Balkenschaltflächen mit benutzerdefinierter Ansicht erstellt. Die Ausrichtungslösung war alignmentRectInsets und contentEdgeInsets, jetzt konnte ich mit diesem Ansatz nicht die erwarteten Ergebnisse erzielen.

Bearbeiten:
Ich habe iOS 11 Beta 2 erneut getestet und das Problem bleibt bestehen.

Edit 2: Ich habe mit iOS Beta 3 erneut getestet und das Problem bleibt bestehen.

16

Es gibt einen guten Artikel dazu: http://www.matrixprojects.net/p/uibarbuttonitem-ios11/

Mit dieser Option können wir die rechten Tastenelemente mindestens so weit nach rechts schieben, bis der Abstand zum UINavigationBar 8 Pixel beträgt.

Wirklich gut erklärt.

4
Mayur Kothawade

Jetzt können Sie in iOS 11 UINavigationBarContentView zum Anpassen von linken und rechten Einschränkungen und UIStackView zum Anpassen von Schaltflächen (oder anderen Elementen) verwalten.

Dies ist meine Lösung für eine Navigationsleiste mit Elementen links und rechts. Es wird auch behoben, wenn Sie mehrere Tasten auf einer Seite haben.

- (void) updateNavigationBar {
    for(UIView *view in self.navigationBar.subviews) {
        if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {

            // Adjust left and right constraints of the content view 
            for(NSLayoutConstraint *ctr in view.constraints) {
                if(ctr.firstAttribute == NSLayoutAttributeLeading || ctr.secondAttribute == NSLayoutAttributeLeading) {
                    ctr.constant = 0.f;
                } else if(ctr.firstAttribute == NSLayoutAttributeTrailing || ctr.secondAttribute == NSLayoutAttributeTrailing) {
                    ctr.constant = 0.f;
                }
            }

            // Adjust constraints between items in stack view
            for(UIView *subview in view.subviews) {
                if([subview isKindOfClass:[UIStackView class]]) {
                    for(NSLayoutConstraint *ctr in subview.constraints) {
                        if(ctr.firstAttribute == NSLayoutAttributeWidth || ctr.secondAttribute == NSLayoutAttributeWidth) {
                            ctr.constant = 0.f;
                        }
                    }
                }
            }
        }
    }
}


Wie Sie sehen, ist es nicht notwendig, weitere Einschränkungen hinzuzufügen, wie es andere Leute getan haben. Es gibt bereits definierte Einschränkungen, die geändert werden können.

8
JonyMateos

Nach ungefähr zwei Tagen ist hier die einfachste und sicherste Lösung, die ich finden konnte. Diese Lösung funktioniert nur mit benutzerdefinierten Schaltflächenelementen der Ansichtsleiste, deren Code enthalten ist. Es ist wichtig zu beachten, dass sich der linke und der rechte Rand der Navigationsleiste nicht von iOS10 auf iOS11 geändert haben - sie sind immer noch 16 Pixel groß. Ein derart großer Rand macht es schwierig, einen ausreichend großen Klickbereich zu haben.

Die Balkenknöpfe sind jetzt mit UIStackView angelegt. Bei der vorherigen Methode zum Verschieben dieser Schaltflächen näher an den Rand wurden negative feste Leerzeichen verwendet, mit denen diese Stapelansichten nicht umgehen können.

Unterklasse UINavigationBar

FWNavigationBar.h

@interface FWNavigationBar : UINavigationBar

@end

FWNavigationBar.m

#import "FWNavigationBar.h"

@implementation FWNavigationBar

- (void)layoutSubviews {
    [super layoutSubviews];

    if (@available(iOS 11, *)) {
        self.layoutMargins = UIEdgeInsetsZero;

        for (UIView *subview in self.subviews) {
            if ([NSStringFromClass([subview class]) containsString:@"ContentView"]) {
                subview.layoutMargins = UIEdgeInsetsZero;
            }
        }
    }
}

@end

Verwenden des UINavigationControllers

#import "FWNavigationBar.h"

UINavigationController *controller = [UINavigationController initWithNavigationBarClass:[FWNavigationBar class] toolbarClass:nil];
[controller setViewControllers:@[rootViewController] animated:NO];

Erstellen eines UIBarButton

Platzieren Sie diesen Code entweder in einer UIBarButton-Kategorie oder in der Datei, die Sie verwenden möchten, mit der Leistenschaltfläche, die mit einer UIButton-Schaltfläche ein Element mit identischem Aussehen zurückgibt.

+ (UIBarButtonItem *)barButtonWithImage:(UIImage *)image target:(id)target action:(SEL)action {
   UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
   //Note: Only iOS 11 and up supports constraints on custom bar button views
   button.frame = CGRectMake(0, 0, image.size.width, image.size.height);

   button.tintColor = [UIColor lightGrayColor];//Adjust the highlight color

   [button setImage:image forState:UIControlStateNormal];
   //Tint color only applies to this image
   [button setImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateHighlighted];
   [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];

   return [[UIBarButtonItem alloc] initWithCustomView:button];
}

Einstellen der Balkentaste in Ihrem Controller

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationItem.leftBarButtonItem = [UIBarButtonItem barButtonWithImage:[UIImage imageNamed:@"your_button_image"] target:self action:@selector(leftButtonPressed)];
}

Zuletzt würde ich empfehlen, den linken und rechten Rand auf Null zu belassen und nur die Position der Schaltfläche in Ihrem Bild anzupassen. Auf diese Weise können Sie den gesamten anklickbaren Bereich bis zum Bildschirmrand nutzen. Das gleiche gilt für die Höhe Ihres Bildes - stellen Sie sicher, dass die Höhe 44 Punkte beträgt.

7
Josh Bernfeld

Mir ist ein ähnliches Problem aufgefallen.

Ich habe ein Apple Radar für ein ähnliches Problem gemeldet, das wir festgestellt haben Sie erstellen ein Radar.

Ich habe auch einen Thread in Apples Forum erstellt, aber noch kein Feedback: https://forums.developer.Apple.com/message/ 234654

4
Chris Comeau

Ich bin darauf gestoßen: INavigationItem-Margin . Es wirkt wie ein Zauber.

4
zumph

Aus diese Antwort : BarButtons verwenden jetzt Autolayout und erfordern daher Einschränkungen.

if #available(iOS 9.0, *) {
  cButton.widthAnchor.constraint(equalToConstant: customViewButton.width).isActive = true
  cButton.heightAnchor.constraint(equalToConstant: customViewButton.height).isActive = true
}

Ziel c

if (@available(iOS 9, *)) {
  [cButton.widthAnchor constraintEqualToConstant: standardButtonSize.width].active = YES;
  [cButton.heightAnchor constraintEqualToConstant: standardButtonSize.height].active = YES;
}
3
Dane Jordan

Lösung 1: Angesichts der Reaktion von Apple, dass dies das erwartete Verhalten ist, haben wir das Problem umgangen, indem wir die Symbolleiste entfernt und eine benutzerdefinierte Ansicht hinzugefügt haben.

Mir ist klar, dass dies möglicherweise nicht in allen Fällen möglich ist, aber das reguläre UIView lässt sich viel einfacher an das Erscheinungsbild der App anpassen als die Symbolleiste und die Navigationsleiste, in der Apple) die Position der Schaltflächen steuert.

Anstatt unsere benutzerdefinierte Schaltfläche als benutzerdefinierte Ansicht des Schaltflächenobjekts der Benutzeroberfläche festzulegen, haben wir sie als Unteransicht der leeren Benutzeroberfläche-Schaltflächen in der benutzerdefinierten Ansicht festgelegt.

Auf diese Weise konnten wir zum gleichen Aussehen unserer ios 10-App zurückkehren

Lösung 2: Ein bisschen chaotisch, wir haben unsere benutzerdefinierte Ansichtsschaltfläche in einen äußeren UIButton eingebunden, damit die Position des Frames festgelegt werden kann. Dies macht die äußere linke Kante der Schaltfläche leider nicht mehr berührbar, korrigiert jedoch das Aussehen der Schaltflächenposition. Siehe Beispiel:

UIButton* outerButton = [UIButton new]; //the wrapper button
BorderedButton* button = [self initBorderedButton]; //the custom button
[button setTitle:label forState:UIControlStateNormal];
[button setFrame:CGRectMake(-10, 0, [label length] * 4 + 35, 30)];
[button addTarget:controller action:@selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
[outerButton addSubview:button]; //add custom button to outer wrapper button
[outerButton setFrameWidth:button.frameWidth]; //make sure title gives button appropriate space
controller.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:outerButton]; //add wrapper button to the navigation bar
controller.navigationItem.hidesBackButton = YES;

Mit dieser Methode behalten wir unsere Navigationsleiste bei und können die Schaltfläche dort positionieren, wo sie benötigt wird.

Bearbeiten: Wir haben festgestellt, dass Lösung 2 unter iOS 10 nicht funktioniert. Dies betrifft wahrscheinlich nur die winzigen Prozent der Entwickler, die abwärtskompatibel sein müssen.

Lösung

Was uns wirklich mehr Sorgen bereitete, war die Tatsache, dass der Titel der Navigationsleiste in die benutzerdefinierte linke Schaltfläche überging, die Größe der Ränder weniger wichtig war und als Werkzeug zur Platzgewinnung verwendet wurde. Die Lösung besteht darin, den linken Elementen einfach einen Abstandhalter hinzuzufügen.

UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:button];
UIBarButtonItem* spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
controller.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:backButton, spacer, nil];
3
Sashah

Schnelle Version der LayoutSubviews-Methode der benutzerdefinierten UINavigationBar-Unterklasse von Developer3214 ( https://stackoverflow.com/a/46660888/4189037 ):

override func layoutSubviews() {
    super.layoutSubviews();
    if #available(iOS 11, *){
        self.layoutMargins = UIEdgeInsets()
        for subview in self.subviews {
            if String(describing: subview.classForCoder).contains("ContentView") {
                let oldEdges = subview.layoutMargins
                subview.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: oldEdges.right)
            }
        }
    }
}
1

Eine andere Antwort kann helfen.

Meine Lösung: Es funktioniert in ios 9 - 12. Sie sollten fixNavigationItemsMargin (margin:) in function viewDidAppear (aufrufen. _ animated: Bool) und viewDidLayoutSubviews () . fixNavigationItemsMargin (margin:) würde den UINavigationController-Stapel ändern.

sie könnten fixNavigationItemsMargin (margin:) in BaseNavigationController aufrufen und die gemeinsame Arbeit erledigen. Und rufen Sie fixNavigationItemsMargin (margin:) in UIViewController auf, um ein präzises Layout zu erstellen.

// do common initilizer
class BaseNavigationController: UINavigationController {
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    fixNavigationItemsMargin()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    fixNavigationItemsMargin()
}
}

// do precise layout
class ViewController: UIViewController {
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    navigationController?.fixNavigationItemsMargin(40)
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.fixNavigationItemsMargin(40)
}
}

extension UINavigationController {
func fixNavigationItemsMargin(_ margin: CGFloat = 8) {
    let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
    if systemMajorVersion >= 11 {
        // iOS >= 11
        guard let contentView = navigationBar.subviews
            .first(
                where: { sub in
                    String(describing: sub).contains("ContentView")
        }) else { return }

        // refer to: https://www.matrixprojects.net/p/uibarbuttonitem-ios11/
        // if rightBarButtonItems has not any custom views, then margin would be 8(320|375)/12(414)
        // should use customView
        let needAdjustRightItems: Bool
        if let currentVC = viewControllers.last,
            let rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > 0,
            rightItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustRightItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustRightItems = false
        }

        let needAdjustLeftItems: Bool
        if let currentVC = viewControllers.last,
            let leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > 0,
            leftItems.filter({ $0.customView != nil }).count > 0 {
            needAdjustLeftItems = true
        } else {
            print("Use 8(320|375)/12(414), if need precious margin ,use UIBarButtonItem(customView:)!!!")
            needAdjustLeftItems = false
        }

        let layoutMargins: UIEdgeInsets
        if #available(iOS 11.0, *) {
            let directionInsets = contentView.directionalLayoutMargins
            layoutMargins = UIEdgeInsets(
                top: directionInsets.top,
                left: directionInsets.leading,
                bottom: directionInsets.bottom,
                right: directionInsets.trailing)
        } else {
            layoutMargins = contentView.layoutMargins
        }

        contentView.constraints.forEach(
            { cst in

                // iOS 11 the distance between rightest item and NavigationBar should be  margin
                // rightStackView trailing space is -margin / 2
                // rightestItem trailing to rightStackView trailing is -margin / 2
                let rightConstant = -margin / 2

                switch (cst.firstAttribute, cst.secondAttribute) {
                case (.leading, .leading), (.trailing, .trailing):

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.minX < navigationBar.frame.midX {
                        // is leftItems
                        if needAdjustLeftItems {
                            cst.constant = margin - layoutMargins.left
                        }
                    }

                    if let stackView = cst.firstItem as? UIStackView,
                        stackView.frame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    } else if let layoutGuide = cst.firstItem as? UILayoutGuide,
                        layoutGuide.layoutFrame.maxX > navigationBar.frame.midX {
                        // is rightItems
                        if needAdjustRightItems {
                            cst.constant = rightConstant
                        }
                    }

                default: break
                }

        })

        // ensure items space == 8, minispcae
        contentView.subviews.forEach(
            { subsub in
                guard subsub is UIStackView else { return }
                subsub.constraints.forEach(
                    { cst in
                        guard cst.firstAttribute == .width
                            || cst.secondAttribute == .width
                        else { return }
                        cst.constant = 0
                })
        })

    } else {
        // iOS < 11

        let versionItemsCount: Int
        if systemMajorVersion == 10 {
            // iOS 10 navigationItem.rightBarButtonItems == 0
            // space = 16(320|375) / 20(414)
            // should adjust margin
            versionItemsCount = 0
        } else {
            // iOS 9 navigationItem.rightBarButtonItems == 0
            // space = 8(320|375) / 12(414)
            // should not adjust margin
            versionItemsCount = 1
        }

        let spaceProducer = { () -> UIBarButtonItem in
            let spaceItem = UIBarButtonItem(
                barButtonSystemItem: .fixedSpace,
                target: nil,
                action: nil)
            spaceItem.width = margin - 16
            return spaceItem
        }
        if let currentVC = viewControllers.last,
            var rightItems = currentVC.navigationItem.rightBarButtonItems,
            rightItems.count > versionItemsCount,
            let first = rightItems.first {
            // ensure the first BarButtonItem is NOT fixedSpace
            if first.title == nil && first.image == nil && first.customView == nil {
                print("rightBarButtonItems SPACE SETTED!!!  SPACE: ", abs(first.width))

            } else {
                rightItems.insert(spaceProducer(), at: 0)

                // arranged right -> left
                currentVC.navigationItem.rightBarButtonItems = rightItems
            }
        }

        if let currentVC = viewControllers.last,
            var leftItems = currentVC.navigationItem.leftBarButtonItems,
            leftItems.count > versionItemsCount,
            let first = leftItems.first {
            if first.title == nil && first.image == nil && first.customView == nil {
                print("leftBarButtonItems  SPACE SETTED!!!  SPACE: ", abs(first.width))
            } else {
                leftItems.insert(spaceProducer(), at: 0)

                // arranged left -> right
                currentVC.navigationItem.leftBarButtonItems = leftItems
            }
        }
    }
}
}
1
Jules

Ich hatte das gleiche Problem. Ich hatte drei Schaltflächen als rechte Barbutton-Elemente in der Stapelansicht der Navigationsleiste. Daher habe ich die Einfügungen der Unteransichten der Navigationsleiste aktualisiert.

override func viewWillLayoutSubviews() {
                super.viewWillLayoutSubviews()
                for view in (self.navigationController?.navigationBar.subviews)! {
                    view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11)
                }
            }

Hier 11 ist der Platz, den ich brauchte. Es kann alles sein wie pro ihre anforderung. Wenn Sie die Schaltflächen mit 0 Einfügungen möchten, ersetzen Sie view.layoutMargins = UIEdgeInsets.init(top: 0, left: 11, bottom: 0, right: 11) durch view.layoutMargins = UIEdgeInsets.zero.

1
vinny