UINavigationBar background image with scaleAspectFill-like behavior - ios

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:

Related

What are the correct dimensions for a custom tab bar item background image?

So I have a .png that's 428x176 px:
I want to use it as a tab bar background image for when an item is in the selected state. The problem is that the image is too big. So now, I'm exploring the different resolution suffixes: #1x, #2x, #3x. But regardless of which I append to the image filename, the image remains too big for the tab bar item's background, although it does get smaller as I increase the resolution factor. How do I determine what the correct dimensions for this image should be?
Keep in mind that this is a background image for when any particular UITabBarItem is in the selected state. It is meant to be the entire width and height of the UITabBarItem. My tab bar will have three items laid across the width of the screen.
Update:
A lot of answers are providing swift based solutions, but what I'm asking for is what dimensions my image should be in pixels, in other words, what sized images should I be including in my bundle, so that the image will fit in the tab bar at all screen sizes. I imagine that since a UITabBar by default expects a UIImage for its selectionIndicatorImage field and not a UIImageView that there must be a solution that doesn't involve hacking around the UITabBar and adding a UIImageView as a subview of it and managing what position the image view should be in, manually, based on which UITabBarItem is currently selected.
Making my answer for this question how to make UITabBar selection indicator image fill the whole space? as reference:
Subclass the UITabBarController
It works for multiple devices even with rotation.
Notes:
Make your images' rendering mode as Original.
Assign this class below to your UITabBarController in your Storyboard or as your base class if you're doing your screen programmatically.
//
// BaseTabBarController.swift
// MyApp
//
// Created by DRC on 1/27/17.
// Copyright © 2017 PrettyITGirl. All rights reserved.
//
import UIKit
class BaseTabBarController: UITabBarController {
let numberOfTabs: CGFloat = 4
let tabBarHeight: CGFloat = 60
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateSelectionIndicatorImage()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
updateSelectionIndicatorImage()
}
func updateSelectionIndicatorImage() {
let width = tabBar.bounds.width
var selectionImage = UIImage(named:"myimage.png")
let tabSize = CGSize(width: width/numberOfTabs, height: tabBarHeight)
UIGraphicsBeginImageContext(tabSize)
selectionImage?.draw(in: CGRect(x: 0, y: 0, width: tabSize.width, height: tabSize.height))
selectionImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tabBar.selectionIndicatorImage = selectionImage
}
}
There's no clear cut answer for this as the tab bar itself isn't of fixed size on various iOS versions and various iOS devices.
Starting iOS 11, tab bar will have smaller height and titles will move to right in landscape mode.
So the idea is to have a stretchable image that can be stretched along all all edges - top/leading/bottom/trailing.
For your reference -
http://macoscope.com/blog/stretchable-images-using-interface-builder/
Here are few images from above blog post to help make it clear -
Hope this helps.
Use the below two lines.
self.tabBarController.tabBar.autoresizesSubviews = NO;
self.tabBarController.tabBar.clipsToBounds = YES;
Xcode works with point, not pixels, so the width will always be 320. In the case of retina display one point is 2x2 pixels and in normal mode it is 1x1.
I think the height for the tab bar should be 320x49 for normal and 640x98 for retina.
the retina image should have the same name as the normal one with the #2x at the end.
Rename your image to tabbarBack#2x.png. This is called pixel doubling for the Retina Display.
Without the #2x iOS doesn't know that it should apply a scale factor and it will be used as it is and though it should be halved.
tabbarBack.png (45 px or so)
tabbarBack#2x.png
[[[self tabBarController] tabBar] setBackgroundImage:[UIImage imageNamed:#"tabbarBack.png"]];
Have you tried with adding imageView as subview of tabbar
var bgView: UIImageView = UIImageView(image: UIImage(named: "background.png"))
bgView.frame = CGRectMake(0, 420, 320, 60)//you might need to modify this frame to your tabbar frame
self.view.addSubview(bgView)
If we talk about height & width of tabbar ,it is 320*49 / 640*98 #2x for standard tabbar. so if you want to keep same dimension try with these width/height ratio.
To add to Tarun Tyagi's answer:
If you add the image to the asset store you can also do visual slicing with the system tools.
Create an asset catalog
Drag & Drop your image into the asset catalog.
Select that image in the asset catalog and click on the "Show Slicing" button on the bottom right
Click on "Start Slicing" and slice that image
Try resizing the image to the tab bar size. Or Add an imageView to the tabBar as subview, and then use the image in that imageView.
Subclass the TabBarController and add imageview there:
class YourTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let backgroundImage = UIImageView(image: UIImage(named: "gray background"))
backgroundImage.frame = backgroundImage.bounds
self.view.addSubview(backgroundImage)
}

Editing navigation bar's shadow line UIImageView having no effect

I'm attempting to alter the Navigation Bar's narrow shadow bar and am using the following code:
if let hairline = findNavigationBarHairline(navigationBar)
{
hairline.bounds.size.height = 5.0
hairline.backgroundColor = UIColor.blueColor()
}
and this:
func findNavigationBarHairline(view:UIView) -> UIImageView?
{
if let hairline = view as? UIImageView where hairline.bounds.size.height <= 1.0
{
return hairline
}
for subview in view.subviews
{
if let imageView = findNavigationBarHairline(subview)
{
return imageView
}
}
return nil
}
This sucessfully finds the UIImageView which is the shadow line but if I try and change anything here it has no effect. Here's a po of the Image View at that point in the code just after its size/color has been set:
(lldb) po hairline
<UIImageView: 0x126d21030; frame = (0 61.75; 320 5); userInteractionEnabled = NO; layer = <CALayer: 0x126d0b470>>
Here it can be seen the height is 5, however it is still displayed with its original size and color. If I use XCode's view hieararchy display and dump the image view from there this is the result:
> Printing description of $124: <UIImageView: 0x126d21030; frame = (0
> 64; 320 0.5); userInteractionEnabled = NO; layer = <CALayer:
> 0x126d0b470>
As can be seen it is the same object, however its height is 0.5 and not 5.
Why is the change to the size and color having no effect? (I have also changed the UIImage, but that is not having any effect either).
First - You should do this by setting an image to the shadowImage property of UINavigationBar. That's what that property is for. Note that you have to set a custom background image via setBackgroundImage(_:forBarMetrics:) for this to work. Using the code below I was able to create a white translucent navbar with no shadow. Note that the white image is just a white pixel, which you could easily enough create in code.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationController!.navigationBar.setBackgroundImage(UIImage(named: "white"), forBarMetrics: .Default)
navigationController!.navigationBar.shadowImage = UIImage()
}
The result was:
Second - Spelunking into the view hierarchy of components provided by the SDK is generally not the best idea. You've got no guarantees that the view hierarchy won't change in future releases (or that what you've got works on older iOS version, for that matter).
Having said, what you are doing is not working most likely because setting a backgroundColor on an image view isn't going to change the image being rendered by that image view. Second, it's very likely that Apple uses Auto Layout to organize the subviews of UINavigationBar, so the frame/bounds is essentially going to be derived data and Auto Layout will just reset it if you change it.

image of UIbutton is stretched in iOS

I have added a right bar button in my navigation bar, and then assign image to the UIButton and set the size to 20px * 20px
the view is good in editor, but when I run the app in simulator, the image is very stretched, can anyone help me please
image is here
Control Click and Drag from Button to View Controller as IBOutlet
override func viewDidLoad(){
yourButtonOutletName.image.contentMode = .ScaleAspectFit
//
}
In your UIButton, set button.imageView.contentMode = UIViewContentModeScaleAspectFit

Change UISearchBar magnify icon color and position

I have a UISearchBar and I would like to change the position of the initial magnify icon (the one that appears in the middle of the UISearchBar) as well as the color or icon.
So far I changed the tint and the icon image.
However, the new icon shows up only if I test the app on a simulator but on an actual device(both run iOS 9.3) it still shows the default icon.
UISearchBar.appearance().setImage(UIImage(named: "SearchbarIcon"), forSearchBarIcon: UISearchBarIcon.Search, state: UIControlState.Normal)
As for the magnify icon position, I want it in the left side, where it shows up if I activate the UISearchBar.
I found quite a lot of answers around here many of the provided solutions don't work on iOS 8.0+ or are in Objective-C and I am having some problems understanding them.
I tried to add a custom background containing the icon but it shows up at the bottom of the search bar while the icon is still there:
The background shows up OK if I change the background offset for Y to -44 (the height of the UISearchBar) but I can no longer type since it seems the entire text field is pushed up. I also tried to change the vertical offset for the SearchText to 44 to compensate but no luck. To be honest, I am not sure what Search Text and the Background offsets are supposed to do but I decided to give them a try.
Is there any way to accomplish this? Or maybe a different approach?
You can adjust the position of the search bar icon using
func positionAdjustmentForSearchBarIcon(_ icon: UISearchBarIcon) -> UIOffset
See https://developer.apple.com/library/ios/documentation/UIKit/Reference/UISearchBar_Class/#//apple_ref/occ/instm/UISearchBar/positionAdjustmentForSearchBarIcon:
You can use:
uiSearchBar.setPositionAdjustment(UIOffset, for: UISearchBar.Icon)
replace UIOffset with your desired offset value UIOffset(horizontal: CGFloatvertical: CGFloat) and UISearchBar.Icon with .search
#Michael - Thanks for all the help.
I managed to grab the UISearchBar's UITextField:
func customizeSearchBar()
{
for subview in srcRegimenSearchBar.subviews
{
for view in subview.subviews
{
if let searchField = view as? UITextField
{
let imageView = UIImageView()
let image = UIImage(named: "SearchBarIcon.png")
imageView.image = image;
imageView.frame = CGRectMake(0, 0, 0, 0)
/*imageView.frame = CGRectMake(100, 0, 20, 19)*/
searchField.leftView = imageView
searchField.leftViewMode = UITextFieldViewMode.Always
}
}
}
}
I wanted to change the position since I have access to the frame but it seems only the the top and height can be modified, at least the way I tried so I set the height and width to 0 (I couldn't find a way to make it nil or remove it completely) and I added an UIImageView with the new icon over in the left side of the UISearchbar and added a custom horizontal offset for the tint.
Not the best solution out there, I'm sure of it, but for now it works.

Setting UIScrollView Background Image

I have a scrollview that takes up the full display size on a view. I want to set the background to a static image. I don't want to tile it, so I don't think I can use backgroundColor. Also, I don't really want it behind the scroll view (setting the scrollview to transparent) because I want bg to scroll with the scroll view.
Any help would be great. Thanks!
Try setting a tillable image as your background color.
scrollView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"tileableImage.png"]];
Just add a UIImageView that is the same size as the scroll view's content size
When we set image to scrollview as a background then that is scrolled with scrollview. So instead of setting image to background , set image to view
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "bg.jpg")!)

Resources