简体   繁体   中英

Bar Button tint color in iOS 13

In iOS 13, they have changed the way that the nav bar colors operate. Now they use UINavigationBarAppearance along with UIBarButtonItemAppearance to customize the nav bar, along with standardAppearance & scrollEdgeAppearance.

I'm looking for a way to have different nav bar tint colors for standardAppearance & scrollEdgeAppearance. Or the ability to change bar button icon colors for each appearance.

        //set the standard nav bar appearance
        let navBarAppearance = UINavigationBarAppearance()
        navBarAppearance.configureWithOpaqueBackground()
        navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.white]
        navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
        navBarAppearance.backgroundColor = UIColor.mainAppColorForNavBar

        //set bar button appearance
        let buttonAppearance = UIBarButtonItemAppearance()
        buttonAppearance.normal.titleTextAttributes = [.foregroundColor : UIColor.white]
        navBarAppearance.buttonAppearance = buttonAppearance

        UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).standardAppearance = navBarAppearance



        //set the scroll edge nav bar appearance
        let scrollNavBarAppearance = UINavigationBarAppearance()
        scrollNavBarAppearance.configureWithOpaqueBackground()
        scrollNavBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.label]
        scrollNavBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label]

        //set bar button appearance
        let scrollButtonAppearance = UIBarButtonItemAppearance()
        scrollButtonAppearance.normal.titleTextAttributes = [.foregroundColor : UIColor.label]
        scrollNavBarAppearance.buttonAppearance = scrollButtonAppearance

        UINavigationBar.appearance(whenContainedInInstancesOf: [UINavigationController.self]).scrollEdgeAppearance = scrollNavBarAppearance

This will set the nav bar tint color but does not distinguish between standardAppearance & scrollEdgeAppearance.

UINavigationBar.appearance().tintColor = UIColor.white

currently in scrollEdgeAppearance (looks the way I want, no changes needed) 在此处输入图像描述

currently in standardAppearance (the button is lost because it's the same color as the background, I want to change the icon color to white in standardAppearance ) 在此处输入图像描述

Any help is appreciated.

Thanks,

In order to set the UINavigationBar appearance in iOS 13 :

let proxyAppearance = UINavigationBar.appearance()

let scrollEdgeAppearance = UINavigationBarAppearance()
scrollEdgeAppearance.backgroundColor = <your favourite color 1>

let scrollEdgeAppearance = UINavigationBarAppearance()
scrollEdgeAppearance.backgroundColor = <your favourite color 1>

let compactAppearance = UINavigationBarAppearance()
compactAppearance.backgroundColor = <your favourite color 2>

let standardAppearance = UINavigationBarAppearance()
standardAppearance.backgroundColor = <your favourite color 3>

proxyAppearance.scrollEdgeAppearance = scrollEdgeAppearance
proxyAppearance.compactAppearance = compactAppearance
proxyAppearance.standardAppearance = standardAppearance

Include this code in a method marked with @available(iOS 13.0, *) . Hope this will be useful to you. This does the job for me.

Here's how to configure a navigation bar that changes its button text color from green to red as you scroll:

    let bar = self.navigationController!.navigationBar
    let app = UINavigationBarAppearance()
    let b = app.buttonAppearance
    b.normal.titleTextAttributes = [.foregroundColor:UIColor.red]
    bar.standardAppearance = app
    let app2 = UINavigationBarAppearance()
    let b2 = app2.buttonAppearance
    b2.normal.titleTextAttributes = [.foregroundColor:UIColor.green]
    bar.scrollEdgeAppearance = app2

But my sense is that you're not supposed to do that.

I ran into a similar issue for the back button and solved it with setting an image with a different rendering mode for each appearance. This avoids a tintColor being applied.

// demo image
let image = UIImage(systemName: "ellipsis.circle")!

// image with .alwaysOriginal rendering mode to avoid tintColor application
let imageForNavBarAppearance = image
    .withTintColor(.black)
    .withRenderingMode(.alwaysOriginal)
let imageForScrollNavBarAppearance = image
    .withTintColor(.green)
    .withRenderingMode(.alwaysOriginal)

// your UINavigationBarAppearance instances
navBarAppearance.setBackIndicatorImage(imageForNavBarAppearance, transitionMaskImage: imageForNavBarAppearance)
scrollNavBarAppearance.setBackIndicatorImage(imageForScrollNavBarAppearance, transitionMaskImage: imageForScrollNavBarAppearance)

This solves it for back buttons only.

There are two other options to set background images for bar item appearances.

UINavigationBarAppearance.buttonAppearance The appearance configuration with the background image would be applied to all bar items. This should be fine, since you only have one bar item.

UINavigationBarAppearance.doneButtonAppearance If you'd create a done-style bar item for your top-right "+" symbol, this appearance configuration should apply.

let ap = UIBarButtonItemAppearance()
ap.normal.backgroundImage = image.withTintColor(.black).withRenderingMode(.alwaysOriginal)

let scrollAp = UIBarButtonItemAppearance()
scrollAp.normal.backgroundImage = image.withTintColor(.green).withRenderingMode(.alwaysOriginal)

// apply to all bar items
navBarAppearance.buttonAppearance = ap
scrollNavBarAppearance.buttonAppearance = scrollAp

// or apply to done buttons
navBarAppearance.doneButtonAppearance = ap
scrollNavBarAppearance.doneButtonAppearance = scrollAp

After playing around with iOS 13 UINavigationBarAppearance , UIBarButtonItemAppearance etc. I realised that setting a color to navigationBar.tintColor directly apparenty overrides any UIBarButtonItemAppearance configurations.

So here is my workaround:

Given that your UIViewController most likely uses an UICollectionView, UITableView, UIScrollView use it's UIScrollViewDelegate to get scroll updates. Also possible via KVO:

private func observeScrollView() {
    self.observer = scrollView.observe(
        \UIScrollView.contentOffset,
        options: .new
    ) { [weak self] scrollView, change in
        guard let offsetY = change.newValue?.y else { return  }
       self?.handleScroll(offsetY)
    }
}

Now you need to calculate if your LargeTitles are still showing. You can use navigationController?.navigationBar.frame.maxY to access it's height.

private func handleScroll(_ offsetY: CGFloat) {
    let offsetY = -offsetY

    let threshold: CGFloat = navigationController?.navigationBar.frame.maxY ?? 88
    let largeTitlesVisible = offsetY > threshold
    self.largeTitlesVisible = largeTitlesVisible
}

Use property observers to catch toggles.

private var largeTitlesVisible: Bool = true {
    didSet {
        if oldValue != largeTitlesVisible {
            updateNavbar(tranparent: largeTitlesVisible)
       }
    }
}

Now actually change the navbars appearance:

func updateNavbar(transparent: Bool) {
    // overrides UIBarButtonItemAppearance, UINavigationBarAppearance configurations ...
    navigationController.navigationBar.tintColor = transparent ? UIColor.clear : UIColor.systemGreen
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM