UINavigationBar background image with scaleAspectF

2019-02-27 12:39发布

问题:

tl;dr

I'm trying to set a background image for a UINavigationBar. Is there a way to configure the navbar so that it uses scaleAspectFill-like behavior (as a UIImageView with contentMode set to scaleAspectFill)?


The goal

I have an image roughly fitting the navbar dimension of a landscape iPhone (64pt height, about 800pt width; the exact dimension should not matter here though), which I set with navigationBar.setBackgroundImage(img, for: .default). The image is a blurred photo, so no need for pixel-perfection. Btw -- I'm using UINavigationBar.appearance to set the background for all navbars at once.

  1. What I'd like it to do: show the center part of the image when in portrait orientation, and show the full image without cropping when in landscape orientation. If the height doesn't match up exactly (as is to be expected on iPhone X) it should scale up a bit to fill the additional height.

  2. As fallback solution I'd also accept if an image of lesser width is shown centered and fills up the additional horizontal space by stretching the first and last pixel column.

What I tried so far

  • Goal 1: setting the contentMode of UINavigationBar -- seems to be ignored when rendering the image
  • Goal 2: using a smaller image and making it strechable with stretchableImage(withLeftCapWidth:topCapHeight:) -- fills the navbar but the image is pushed to the right bottom (apparently because this method only considers left and top as strechable margins)
  • Goal 2: using a smaller image and making it resizable with resizableImage(withCapInsets:resizingMode:) with resizingMode set to stretch -- in landscape it just stretches the image to full width, ignoring the cap insets (probably this method does not do what I think).

回答1:

Solved by adding an image view to the UINavigationController's view, as described here. The image view is attached to the top, left and right of the nav controller's view and to the bottom of the navbar.

In contrast to the example in the article, I don't use a custom navigation bar; instead I insert the image view behind the existing navbar. Note that the navbar has to be transparent to make this work. Also, the image view has to be clipped, else it strangely extends beyond the navbar's bottom.

Maybe this can be of help to someone else, so here's some code (this should only be applied once to a nav controller, so maybe you are better off putting this code in a subclass than in an extension):

extension UINavigationController {
  func setBackgroundImage(_ image: UIImage) {
    navigationBar.isTranslucent = true
    navigationBar.barStyle = .blackTranslucent

    let logoImageView = UIImageView(image: image)
    logoImageView.contentMode = .scaleAspectFill
    logoImageView.clipsToBounds = true
    logoImageView.translatesAutoresizingMaskIntoConstraints = false

    view.insertSubview(logoImageView, belowSubview: navigationBar)
    NSLayoutConstraint.activate([
      logoImageView.leftAnchor.constraint(equalTo: view.leftAnchor),
      logoImageView.rightAnchor.constraint(equalTo: view.rightAnchor),
      logoImageView.topAnchor.constraint(equalTo: view.topAnchor),
      logoImageView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)
    ])
  }
}


回答2:

You might try adding the UIImageView with contentMode = .scaleAspectFill, directly inside the UINavigationBar, doing so:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let imgView = UIImageView(image: UIImage(named: "desert"))
        imgView.contentMode = .scaleAspectFill
        imgView.frame = self.navigationController!.navigationBar.frame
        self.navigationController?.navigationBar.addSubview(imgView)
        self.navigationController?.navigationBar.sendSubview(toBack: imgView)
    }
}

the result is: