iOS 11中带有大标题NavigationBar的自定义背景图片

26
如何为iOS 11的大标题NavigationBar设置自定义背景图片?我正在使用一个自定义子类,将其分配给storyboard中的navigationControllers。
这是我创建自定义NavBar的方式:
class CustomNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.navigationBar.tintColor = UIColor(red:1, green:1, blue:1, alpha:0.6)
        self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
        if #available(iOS 11.0, *) {
            self.navigationBar.prefersLargeTitles = true
            self.navigationItem.largeTitleDisplayMode = .automatic
            self.navigationBar.largeTitleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
            self.navigationBar.barTintColor = UIColor.green
        }
        self.navigationBar.isTranslucent = false
        self.navigationBar.setBackgroundImage(#imageLiteral(resourceName: "navigationBarBackground"), for: .default)
        self.navigationBar.shadowImage = #imageLiteral(resourceName: "navigationBarShadow")
    }
}

奇怪的是,setBackgroundImage(image, for: .default) 对于大标题不起作用。它在 iOS 10 上之前有效,并且如果我旋转 iPhone(并激活小 NavBar),则背景会出现?

编辑: backgroundImage 仍然呈现,但某种方式被隐藏。只有当您开始滚动并出现“常规”导航栏时,才能看到 backgroundImage。此时完全忽略 barTintColorscreenshot GIF


嗨@alexkaessner。你找到问题的解决方案了吗? - Tanvir Nayem
可以设置导航栏的背景颜色或者条形颜色,但是很遗憾我无法设置导航栏的背景图片。这是iOS 11的一个bug吗?你知道什么情况吗? - Tanvir Nayem
@OceanBlue 我刚刚添加了一些更多的分析信息,但似乎是个bug。我稍后会向苹果提交报告! - alexkaessner
这个 bug 有什么新进展吗? - Tulleb
2
@Tulleb 完全不是。苹果将其标记为“33345493的副本”,但问题仍然存在。 - alexkaessner
显示剩余2条评论
7个回答

20

我有同样的问题,通过

移除setBackgroundImage并使用带图案的barTint颜色来解决它

let bgimage = imageWithGradient(startColor: UIColor.red, endColor: UIColor.yellow, size: CGSize(width: UIScreen.main.bounds.size.width, height: 1))
self.navigationBar.barTintColor = UIColor(patternImage: bgimage!)

获取带渐变颜色的图像

func imageWithGradient(startColor:UIColor, endColor:UIColor, size:CGSize, horizontally:Bool = true) -> UIImage? {

    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
    gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
    if horizontally {
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
    } else {
        gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
        gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
    }

    UIGraphicsBeginImageContext(gradientLayer.bounds.size)
    gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

我也尝试过这个,但我想要图片被拉伸而不是平铺。我的图片有一个渐变...或者有没有一种方法可以改变patternImage:的属性? - alexkaessner
好的,我现在已经测试过了,问题仍然是平铺。如果我旋转iPhone,渐变太短并且重复。此外,“horizontally: false”没有正确工作。它只是一个橙色条形背景。 - alexkaessner
救命稻草 :-) - gorhal
1
如果我们想要自己的背景图像,没有任何图案或渐变颜色。只是一个简单的图像,我想按原样显示它。你有什么想法导航栏背景图像的大小应该是多少? - Shady Abdou
这适用于普通标题大小和大标题大小,非常感谢。 - Noah Wilder
代码运行完美,但需要注意的是,此代码应放在自定义 NavigationController 中,而不是 ViewController 中。 - 10000RubyPools

8

终于我找到了解决方法!

编辑:适用于 iOS 13 及以上版本。


您可以在视图出现之前使用它,例如在 viewDidLoad() 方法中:

    override func viewDidLoad()
    {
        super.viewDidLoad()

        let largeTitleAppearance = UINavigationBarAppearance() 

        largeTitleAppearance.configureWithOpaqueBackground()
        largeTitleAppearance.backgroundImage = UIImage(named: "BackgroundImage.png")

        self.navigationBar.standardAppearance = largeTitleAppearance
        self.navigationBar.scrollEdgeAppearance = largeTitleAppearance
    }

您所需要的就是:

  1. Create UINavigationBarAppearance instance:

    let largeTitleAppearance = UINavigationBarAppearance() 
    

    Apple documentation:

    UINavigationBarAppearance - An object for customizing the appearance of a navigation bar.


  1. Configure it:

    largeTitleAppearance.configureWithOpaqueBackground()
    

    "Opaque" here because we want to set colorised image (but in practice it doesn't matter, what configure will you set)


  1. Set background image:

    largeTitleAppearance.backgroundImage = UIImage(named: "BackgroundImage.png") // Set here image that you need
    

  1. Assign our largeTitleAppearance object to both standardAppearance and scrollEdgeAppearance navigationBar's fields:

    self.navigationBar.standardAppearance = largeTitleAppearance // For large-navigationBar condition when it is collapsed
    self.navigationBar.scrollEdgeAppearance = largeTitleAppearance // For large-navigationBar condition when it is expanded
    

    Apple documentation:

    .standardAppearance - The appearance settings for a standard-height navigation bar.

    .scrollEdgeAppearance - The appearance settings to use when the edge of any scrollable content reaches the matching edge of the navigation bar.


这对我有所帮助:https://sarunw.com/posts/uinavigationbar-changes-in-ios13/#going-back-to-old-style


这是在iOS 13中的正确解决方案。 UIColor(patternImage:) 的解决方案只是一些hack。 - Sherwin Zadeh
1
顺便提一下,如果您需要更改背景图像的拉伸/定位,请查看backgroundImageContentMode - alexkaessner
@HarryJ 哦,这正是我需要的。你需要使用UIHostingController还是有其他方法? - Jeremy
@Jeremy 把这个放在 View 的 init() 函数里,你就没问题了。 - Harry J
@HarryJ 我尝试过了,但在init()中无法访问self.navigationBar。(你可以访问UINavigationBar.appearance(),但我不想全局设置它)。然后我使用了这种方法(https://dev59.com/C1MI5IYBdhLWcg3wUp02#58427754)来单独设置它,但遇到了一个问题,即自定义对于初始的SceneDelegate不起作用。:( - Jeremy
显示剩余2条评论

6

在iOS 11中,如果您使用大标题,则不再需要设置BackgroundImage(删除其声明)。相反,您需要使用BarTintColor。

class CustomNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.navigationBar.tintColor = UIColor(red:1, green:1, blue:1, alpha:0.6)
        self.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
        if #available(iOS 11.0, *) {
            self.navigationBar.prefersLargeTitles = true
            self.navigationItem.largeTitleDisplayMode = .automatic
            self.navigationBar.largeTitleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
            self.navigationBar.barTintColor = UIColor(red:1, green:1, blue:1, alpha:1)
        }
        else {
            self.navigationBar.setBackgroundImage(#imageLiteral(resourceName: "navigationBarBackground"), for: .default)                
        }
        self.navigationBar.shadowImage = #imageLiteral(resourceName: "navigationBarShadow")
        self.navigationBar.isTranslucent = false
    }
}

1
谢谢您的帖子,但这不是一个真正的解决方案。我还进行了更多的测试和分析,似乎现在我必须坚持传统的导航栏。 - alexkaessner
感谢您的出色回答。您有没有想法导航栏背景图像的大小应该是多少(在您的示例中为navigationBarBackground)? - Shady Abdou

3

尝试使用以下代码(Swift 4.0):

在viewDidLoad()方法中:

self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
if #available(iOS 11.0, *) {
    self.navigationController?.navigationBar.prefersLargeTitles = true
    self.navigationItem.largeTitleDisplayMode = .automatic
    self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black]
} else {
    //iOS <11.0
}
self.title = "Title"
self.navigationController?.navigationBar.barTintColor = UIColor(patternImage: #imageLiteral(resourceName: "nav_bg"))
self.navigationController?.navigationBar.isTranslucent = false

1

借鉴oldrinmendez的答案 - 该解决方案适用于水平渐变。

对于垂直渐变,我能够通过在scrollViewDidScroll中再次调用相同的函数来使用oldrinmendez的函数。这将随着用户滚动不断调整渐变图像的高度。

从oldrinmendez的函数开始:

func imageWithGradient(startColor:UIColor, endColor:UIColor, size:CGSize, horizontally:Bool) -> UIImage? {

        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        gradientLayer.colors = [startColor.cgColor, endColor.cgColor]
        if horizontally {
            gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
        } else {
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
            gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
        }

        UIGraphicsBeginImageContext(gradientLayer.bounds.size)
        gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

创建一个更新函数,使用你想要的选项进行调用:
func updateImageWithGradient() {

        let navBarHeight  = self.navigationController?.navigationBar.frame.size.height
        let statusBarHeight = UIApplication.shared.statusBarFrame.height
        let heightAdjustment: CGFloat = 2

        let gradientHeight = navBarHeight! + statusBarHeight + heightAdjustment

        let bgimage = imageWithGradient(startColor: UIColor.red, endColor: UIColor.orange, size: CGSize(width: UIScreen.main.bounds.size.width, height: gradientHeight), horizontally: false)
        navigationController?.navigationBar.barTintColor = UIColor(patternImage: bgimage!)
    }

最后在scrollViewDidScroll和ViewDidApper中添加更新功能:使用ViewDidAppear以返回正确的导航栏高度。

override func viewDidAppear(_ animated: Bool) {
        updateImageWithGradient()
    }

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
     DispatchQueue.main.async {
        updateImageWithGradient()
       }
    }

0
在 Xamarin 中,它会像这样:
this.NavigationBar.BackgroundColor = UIColor.Clear;

      var gradientLayer = new CAGradientLayer
      {
        Frame = new CGRect(0, 0, UIApplication.SharedApplication.StatusBarFrame.Width,
              UIApplication.SharedApplication.StatusBarFrame.Height + this.NavigationBar.Frame.Height),
        Colors = new CGColor[]
              {Constants.Defaults.Navigation.RealBlueColor.ToCGColor(), Constants.Defaults.Navigation.RealBlueColor.ToCGColor()}
      };

      UIGraphics.BeginImageContext(gradientLayer.Bounds.Size);
      gradientLayer.RenderInContext((UIGraphics.GetCurrentContext()));
      UIImage image = UIGraphics.GetImageFromCurrentImageContext();
      UIGraphics.EndImageContext();

      this.View.Layer.InsertSublayer(gradientLayer, 0);
      this.NavigationBar.BarTintColor = UIColor.FromPatternImage(image);

this.View.Layer.Insert是可选的。当我在NavigationBar上“卷曲”图像时需要它。


0

更改barTint对我没有起作用,所以我更改了navigationBar内部的图层

 navigationBar.layer.backgroundColor = UIColor(patternImage:
        UIImage(named: "BG-Roof1")!.resizableImage(withCapInsets:
            UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0), resizingMode: .stretch)).cgColor

不,对于iOS 12我也是一样的。 - Philipp Otto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接