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.
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.
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).
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)
])
}
}
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: